Access: Find partial duplicates, show only one and sum a value - sql

I have a database for my invoices. I would like to make a form where I list all purchases from one customer. If the customer bought the same item more than once I'd like to show that item only once and sum the quantity field. I'd like to find an SQL solution, but I'm a bit stuck.
This is the query in my form:
SELECT Order.fkCustID, Order.OrderID, OrderLine.fkProductID, OrderLine.Qty, ProductList.Item
FROM ProductList INNER JOIN
([Order] INNER JOIN
OrderLine
ON Order.OrderID = OrderLine.fkOrderID
) ON ProductList.ProductID = OrderLine.fkProductID
ORDER BY ProductList.Item;
The OrderLine table contains the individual products bought, the Order table links these to my customer table. ProductList contains the product description.
So where fkCustID and fkProductID are the same, I'd like to only show the oldest entry and sum the Qty field of both.

I think you just want an aggregation query, that looks something like this:
SELECT ol.fkProductID, SUM(ol.Qty) as qty, pl.Item
FROM ProductList as pl INNER JOIN
([Order] as o INNER JOIN
OrderLine as ol
ON o.OrderID = ol.fkOrderID
)
ON pl.ProductID = ol.fkProductID
WHERE o.fkCustID = ???
GROUP BY ol.fkProductID, pl.Item
ORDER BY pl.Item;

Related

oracle sql statement help to query against multiple tables

I am struggling with a sql statement. I am hoping a guru can help a beginner out, currently I have multiple select in statements.. but think there is a better way as I have been stuck.
Below are the tables and pertinent columns in each table
country
-country_id
barcodes_banned_in_country
-barcode(varchar)
-country_id
-country_name
orders
-order_id
-country_name
item
-order_id
-item_id
-barcode(varchar)
The goal is to get all orders that are banned based off the barcode banned list.
Any help with this sql statement would be appreciated.
One option uses exists:
select o.*
from orders o
where exists (
select 1
from barcodes_banned_in_country bic
inner join item i on i.barcode = bic.barcode
where i.order_id = o.order_id and bic.country_name = o.country_name
)
This brings all orders whose at least one item have a barcode that is banned in the order's country.
If, on the other hand, you want the list of the banned barcodes per order, then you can join and aggregate:
select o.order_id, o.country_name, listagg(i.barcode, ',') banned_barcodes
from orders o
inner join item i
on i.order_id = o.order_id
inner join barcodes_banned_in_country bic
on i.barcode = bic.barcode
and bic.country_name = o.country_name
group by o.order_id, o.country_name
Note that, as commented by MT0, you should really be storing the id of the country in orders rather than the country name. Accordingly, you wouldn't need the country name in the banned barcodes table.

Need help for writing query for a marketplace in postgres

I have database for a marketplace which looks like this:
I have different suppliers selling the same product at different price points, also some products are more popular than others in a given location. For example we have product A and product B, and product A is more popular that product B based on how many has already been sold in that location, and for product A we have 3 suppliers. I want my query to show product A from the cheapest seller, then product B from the cheapest seller. I can achieve that with this code:
WITH tem_1 AS (SELECT product_id, MIN(price) AS price FROM product_supplier GROUP BY product_id) ,
tem_2 AS (SELECT product_id, SUM(quantity) AS n_orders FROM orders Group by product_id)
SELECT products.product_id, suppliers.supplier_id, products.name, tem_1.price,
COALESCE(tem_2.n_orders,0) AS quant FROM products
INNER JOIN product_supplier ON product_supplier.product_id = products.product_id
INNER JOIN suppliers ON suppliers.supplier_id = product_supplier.supplier_id
INNER JOIN product_code ON product_code.code_id = products.code_id
INNER JOIN product_crop ON product_crop.product_id = products.product_id
INNER JOIN crops ON crops.crops_id = product_crop.crop_id
INNER JOIN product_tags ON product_tags.product_id = products.product_id
INNER JOIN tags ON tags.tag_id = product_tags.tag_id
INNER JOIN tem_1 ON tem_1.price = product_supplier.price AND tem_1.product_id = products.product_id
LEFT JOIN tem_2 ON tem_2.product_id = products.product_id
WHERE crops.crops_id = 1 AND product_supplier.quantity >= 3 AND tags.tag = 'علف کش'
ORDER BY quant DESC
LIMIT 10;
The problem is, if i have two different suppliers from different locations, selling the same product with the same price, the results show that product twice, but i only want the results from the closest supplier to the user, in this case product 101 from supplier 3 and not the supplier 1.
I think i have to use MIN(ST_Distance("geopoint from user", "geopoint from suppliers")) and LATERAL to have a distance filed, but because i'm using aggregate functions, in order to do a GROUP BY to remove the duplicate results, i have to add all the fields product_id, supplier_id, name, price, ... to the GROUP BY and that won't result in removing the duplicates.
Any suggestion on how to achieve that?
Your query is rather hard to follow. But, distinct on solves your problem. I'm not 100% sure what you want to be distinct, but something like this:
select distinct on (product_id, price) . . .
from . . .
where . . .
order by product_id, price, ST_Distance("geopoint from user", "geopoint from suppliers");
This returns one row per product and price, based on the smallest distance.
If you want the data ordered in a different way, then use this as a subquery or CTE and order by again in the outer query.

What's the right kind of join?

I have two tables:
Products
Id Integer
Name Character Varying(200)
Orders
Id Integer
Product_Id integer
Started Timestamp
I need to fetch all orders with the product id and name together.
I've tried using inner join like this:
select orders.id, orders.started, products.id, products.name
from orders inner join
products
on products.id = order.product_id
But it don't show some orders that have no product linked (service orders).
What i'm doing wrong?
When you don't always have elements on the second table, you should use the "left join" -> it will retrive all rows from the left table, even if the right table doesnt have a matching row.
Like this:
select orders.id, orders.started, products.id, products.name from orders
left join products on products.id = order.product.id
You would need to do a LEFT JOIN on your Product table from the Orders table.
SELECT [Your Columns]
FROM Orders o
LEFT OUTER JOIN Products p ON o.ProductID = p.ID
A left join will still retrieve all rows from Orders, and will retrieve the data from the Product table when there is a match. When there is no ProductID in the Products table, the data you're trying to pull from the Products table will simply be NULL.
Using an inner join requires that the column(s) joined on be present in both tables. For example, if you have a Orders.ProductID = 7, Products must have a row with ID = 7 otherwise your query won't return a row for that Order.

SQL Beginner: Getting items from 2 tables (+grouping+ordering)

I have an e-commerce website (using VirtueMart) and I sell products that consist child products. When a product is a parent, it doesn't have ParentID, while it's children refer to it. I know, not the best logic but I didn't create it.
My SQL is very basic and I believe I ask for something quite easy to achieve
Select products that have children.
Sort results by prices (ASC/DSC).
SELECT * FROM Products INNER JOIN Prices ON Products.ProductID = Prices.ProductID ORDER BY Products.Price [ASC/DSC]
Explanation:
SELECT - Select (Get/Retrieve)
* - ALL
FROM Products - Get them from a DB Table named "Products".
INNER JOIN Prices - Selects all rows from both tables as long as there is a match between the columns in both tables. Rather, JOIN DB Table "Products" with DB Table "Prices".
ON - Like WHERE, this defines which rows will be checked for matches.
Products.ProductID = Prices.ProductID - Your match criteria. Get the rows where "ProductID" exists in both DB Tables "Products" and "Prices".
ORDER BY Products.Price [ASC/DSC] - Sorting. Use ASC for Ascending, DSC for Descending.
This table design is subpar for a number of reasons. First, it appears that the value 0 is being used to indicate lack of a parent (as there's no 0 ID for products). Typically this will be a NULL value instead.
If it were a NULL value, the SQL statement to get everything without a parent would be as simple as this:
SELECT * FROM Products WHERE ParentID IS NULL
However, we can't do that. If we make the assumption that 0 = no parent, we can do this:
SELECT * FROM Products WHERE ParentID = 0
However, that's a dangerous assumption to make. Thus, the correct way to do this (given your schema above), would be to compare the two tables and ensure that the parentID exists as a ProductID:
SELECT a.*
FROM Products AS a
WHERE EXISTS (SELECT * FROM Products AS b WHERE a.ID = b.ParentID)
Next, to get the pricing, we have to join those two tables together on a common ID. As the Prices table seems to reference a ProductID, we can use that like so:
SELECT p.ProductID, p.ProductName, pr.Price
FROM Products AS p INNER JOIN Prices AS pr ON p.ProductID = pr.ProductID
WHERE EXISTS (SELECT * FROM Products AS b WHERE p.ID = b.ParentID)
ORDER BY pr.Price
That might be sufficient per the data you've shown, but usually that type of table structure indicates that it's possible to have more than one price associated with a product (we're unable to tell whether this is true based on the quick snapshot).
That should get you close... if you need something more, we'll need more detail.
use the below script if you are using ssms.
SELECT pd.ProductId,ProductName,Price
FROM product pd
LEFT JOIN price pr ON pd.ProductId=pr.ProductID
WHERE EXISTS (SELECT 1 FROM product pd1 WHERE pd.productID=pd1.ParentID)
ORDER BY pr.Price ASC
Note :neither of your parent product have price in price table. If you want the sum of price of their child product use the below script.
SELECT pd.ProductId,pd.ProductName,SUM(ISNULL(pr.Price,0)) SUM_ChildPrice
FROM product pd
LEFT JOIN product pd1 ON pd.productID=pd1.ParentID
LEFT JOIN price pr ON pd1.ProductId=pr.ProductID
GROUP BY pd.ProductId,pd.ProductName
ORDER BY pr.Price ASC
You will have to use self-join:
For example:
SELECT * FROM products parent
JOIN products children ON parent.id = children.parent_id
JOIN prices ON prices.product_id = children.id
ORDER BY prices.price
Because we are using JOIN it will filter out all entries that don't have any children.
I haven't tested it, I hope it would work.

How to select values from two tables that are not contained in the map table?

Lets say I have the following tables:
Customers
Products
CustomerProducts
Is there a way I can do a select from the Customers and Products tables, where the values are NOT in the map table? Basically I need a matched list of Customers and Products they do NOT own.
Another twist: I need to pair one customer per product. So If 5 customers do not have Product A, only the first customer in the query should have Product A. So the results would look something like this:
(Assume that all customers own product B, And more than one customer owns products A, C, and D)
Customer 1, Product A
Customer 2, Product C
Customer 3, Product D
Final twist: I need to run this query as part of an UPDATE statement in SQL Sever. So I need to take the value from the first row:
Customer 1, Product A
and update the Customer record to something like
UPDATE Customers
SET Customers.UnownedProduct = ProductA
WHERE Customers.CustomerID = Customer1ID
But it would be nice if I could do this whole process, in one SQL statement. So I run the query once, and it updates 1 customer with a product they do not own.
Hope that's not too confusing for you! Thanks in advance!
WITH q AS
(
SELECT c.*, p.id AS Unowned,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY c.id) AS rn
FROM Customers c
CROSS JOIN
Products p
LEFT JOIN
CustomerProducts cp
ON cp.customer = c.id
AND cp.product = p.id
WHERE cp.customer IS NULL
)
UPDATE q
SET UnownedProduct = Unowned
WHERE rn = 1
UPDATE statement will update the first customer who doesn't own a certain product.
If you want to select the list, you'll need:
SELECT *
FROM (
SELECT c.*, p.id AS Unowned,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY c.id) AS rn
FROM Customers c
CROSS JOIN
Products p
LEFT JOIN
CustomerProducts cp
ON cp.customer = c.id
AND cp.product = p.id
WHERE cp.customer IS NULL
) cpo
WHERE rn = 1
If you update only one customer at once, you might need to remember which products have been assigned automatically (in CustomerProducts) or have a counter how often a product has been assigned automatically (in Products)
I tried this in oracle (hope it works for you too)
UPDATE customers c
SET unownedProduct =
( SELECT MIN( productid )
FROM products
WHERE productid NOT IN (
SELECT unownedProduct
FROM customers
WHERE unownedProduct IS NOT NULL )
AND productid NOT IN (
SELECT productid
FROM customerProducts cp
WHERE cp.customerId = c.customerid )
)
WHERE customerId = 1
What if the customer doesn't own more than one product? and how are you going to maintain this field as the data changes? I thinkyou really need to do some more thinking about your data structure as it doesn't make sense to store this information in the customer table.