SQL Optimized query to get suggested products [duplicate] - sql

I will try now to better explain what is the purpose of the following question and query.
Let's assume we are talking about a ecommerce environment and database. We have, among many others, three tables: products, orders and orders_data. The ORDERS table will handle all placed orders and a sub-table, which we will call ORDERS_DATA will store all products recorded within an order.
Following the tables definition, without those unuseful fields for my question:
ORDERS (*id*, date, totale, ...);
ORDERS_DATA (id_order, id_product, ...);
PRODUCTS (id, name, ...);
The primary key in ORDERS is id, while in ORDERS_DATA the key is (id_order, id_product).
What I would like to do with a query is to retrive, given a PRODUCT ID (while surfing on a product page or cart page as well), suggested products based on ORDERS_DATA table. The query will return all id_product which lives in those orders, where, at least one of these products is the given PRODUCT ID
For the sake of semplicity I will report an example. Let's assume we have these ROWs in ORDERS_DATA:
R1(1, 1); R2(1, 2); R3(1, 3); R4(2, 2), R5(2, 5); R6(3, 3);
I want suggestions for product id = 2. My query will return ids: 1 and 3 (from R1 and R3 - they share the same order id and this order have also product 2 - from R2 - btw its products) and product 5 thanks to the order number 2. Order number 3 will be ignored.
This is the query I wrote, but I'm quite sure it is not the best one in performance and style.
SELECT
A.id_product, COUNT( A.id_product ) AS num
FROM
orders_data A, orders_data B
WHERE
A.id_order = B.id_order
AND B.id_product IN (*IDs-HERE*)
AND A.id_product NOT IN (*IDs-HERE*)
GROUP BY A.id_product
I've already use the INNER JOIN syntax but nothing change in performance.
The query take 0,0022sec with just one product id in the IN clausole. Performance will decrese exponetially with multiple products id (for instance during the cart page, with more products in the basket).
Thanks.

Try replacing the JOIN with an EXISTS test:
SELECT
id_product,
COUNT(1) As num
FROM
orders_data As A
WHERE
id_product NOT IN (*IDs HERE*)
And
Exists
(
SELECT 1
FROM orders_data As B
WHERE B.id_order = A.id_order
And B.id_product IN (*IDs HERE*)
)
GROUP BY
id_product

Related

What's the use of this WHERE clause

this is an answer to the question : We need a list of customer IDs with the total amount they have ordered. Write a SQL statement to return customer ID (cust_id in the Orders table) and total_ordered using a subquery to return the total of orders for each customer. Sort the results by amount spent from greatest to the least. Hint: you’ve used the SUM() to calculate order totals previously.
SELECT prod_name,
(SELECT Sum(quantity)
FROM OrderItems
WHERE Products.prod_id=OrderItems.prod_id) AS quant_sold
FROM Products
;
So there is this simple code up here, and I know that this WHERE clause is comparing two columns in two different tables. But since We are calculating the SUM of that quantity, why do need that WHERE clause exactly. I really couldn't get it. Why the product_id exactly and not any other column ( p.s: the only shared column between those two tables is prod_id column ) I am still a beginner. Thank you!
First you would want to know the sum for each product - so need to adjust the subquery similar to this:
(SELECT prod_id, Sum(quantity) qty
FROM OrderItems
group by prod_id
) AS quant_sold
then once you know how much for each product, then you can link that
SELECT prod_name,
(SELECT prod_id, Sum(quantity) qty
FROM OrderItems
group by prod_id
) AS quant_sold
FROM Products p
WHERE p.prod_id = quant_sold.prod_id
Run it without the where clause and compare the results. You'll learn a lot that way. specifically focus on two different product Ids ensuring they both have order items and quantities.
You have two different tables involved. There are multiple products. You don't want the sum of all orders on each product; which is what you would get without the where clause. So the where clause correlates the two tables ensuring you only SUM the quantity of each order item for each product between the tables. Personally, I'd use a join, sum, and a group by as I find it easier to read and I'm not a fan of sub selects in the select of another query; but that's me.
SELECT prod_name,
(SELECT Sum(quantity)
FROM OrderItems
WHERE Products.prod_id=OrderItems.prod_id) AS quant_sold
FROM Products
Should be the same as:
SELECT prod_name, Sum(coalesce(P.quantity,0))
FROM Products P
LEFT JOIN orderItems OI
on P.prod_id=OI.prod_id
GROUP BY Prod_Name
'Notes
the above is untested.
a left join is needed because all products should be listed and if a product doesn't have an order, the quantity would be zero.
if we use an inner join, the product would be excluded.
We use coalesce because you'd have a "Null" quantity instead of zero for such lines without an order item.
as to which is "right" well it depends and varies on different cases. each has it's own merits and in different cases, one will perform better than another, and in a different case, vice-versa. See --> Join vs. sub-query
As an example:
Say you have Products A & B
"A" has Order Item Quantities of 1 & 2
"B" has order item Quantities of 10 & 20
If we don't have the where clause every result record would have qty 33
If we have the where product "A" would have 3
product "B" would have qty 30.

How can I find all monitor purchases (not with monitor ID value, instead should use the term 'Monitor')

I have two tables,
I need to find all monitor purchases (but not with monitor ID, instead I should use 'Monitor'). I have made code, but it takes the quantity for all products, not only for 'Monitor'.
The code I made:
select name, Price,sum(quantity)
from Products, Orders
where name='Monitor'
How can I fix this problem?
You need to link the rows of both tables. Right now you are creating the cartesian product, containing every row from the first table combined with every row from the second table. With your screenshot, that's going to be 9 rows in total.
SELECT name, Price, SUM(quantity)
FROM Products, Orders
WHERE name='Monitor'
AND Products.ProductId = Orders.ProductId
Or use JOIN directly to separate the filter condition from the join condition:
SELECT name, Price, SUM(quantity)
FROM Products
INNER JOIN Orders
ON Products.ProductId = Orders.ProductId
WHERE Products.name='Monitor'

linking child tables of two related parent-child tables

I have two parent child tables and a couple of supporting tables. I am struggling to figure out the right relationships between them.
Orders, a table that holds a list of Orders including:Order Id, Supplier, Order Date etc..
OrderDetails, a Table holds a list of products that have been ordered, and is a child of Orders, linked on OrderId. Key fields are the ProductId and the Quantity Ordered. Each order containes one or many OrderDetails that outline the products and quantities being ordered.
Shipments, a table that holds a list of Shipments, including Tracking Number and Shipper. Links to Orders through Order Id. An Order may have multiple shipments. For example, I ordered 100 lightbulbs. They were dispatched in 5 shipments.
ShipmentDetails, holds a list of product and shipped quantities and is linked to the Shipments table by ShippingId. Each Shipment may have multiple ShipmentDetails. i.e. One shipment may have 30 Lightbulbs and 10 Door Knobs.
Products is a table that holds a list of Products that need to be both ordered and shipped.
So the logic is that I enter details about the products that are to be ordered in Products. For example, 100 CREE LED Lightbulbs, and 50 Door Handles.
When I place an order, I create an Order in Orders.
i.e. amazon.com, order #45454.
Then I add child rows to that order in OrderDetails. i.e. 30 CREE LED Lightbulbs.
When the order Ships, I create an entry in the Shipments table. i.e. Tracking #46464646464, linked to OrderId in Orders. And then I enter what is in that shipment in ShipmentDetails. For example, only 20 or the 30 CREE LED Lightbulbs may be associated with this Shipping entry.
I am struggling with figuring out how to relate the Shipping Detail records to the Order Detail Table. Lets say the Shipping Detail Table has 4 fields.
ShippingID - a link to the Shipments table parent.
TrackingNum - a field that holds a tracking number.
Product - this should be a drop down, that says, the shipment I belong to is related to a defined Order (because ShipmentDetail is a child of a Shipment record which in turn holds a key to the Orders table, which in turn has OrderDetails that reference products).
Quantity - this should have a default (overridable) value that is the "Quantity Ordered" number from the OrdersDetail record that matches the Order Id and Product Id. Where OrderId is in the Shipments table (linked to the Orders table) and ProductID comes from #3 above. The reason it must be overridable is that a shipment may be a partial shipment.
I am stuck with the preceding #3 and #4. I hope I have explained this in a vaguely understandable way! The pictures below may help!
The ask:
What is the correct join between the ShipmentDetails and the OrderDetail Table
How do I create a field in the ShipmentDetail table with a default value pulled from the Quantities Field of the OrderDetail table, where
Shipments!OrderId = Orders!Id and ShipmentDetail!ProductID = OrderDetails!Product ID
I am working in MS Access 2016 - but I suspect this is a fairly generic SQL question...rather than MS Access specific.
I think the naming is confusing.
What I would rather have is (each first id is autoincrement, forgot how to say this on access):
// Product doesn't contain any information about quantity/price etc ...
create table Products(product_id int, name text, description text ...);
// Orders:
create table Orders(order_id int, client_name text, description text ...);
create table OrderDetails(order_detail_id int, order_id int, product_id int, quantity double, unit_price double, ...);
// Shipments
create table Shipments(shipment_id int, company text, description text ...);
create table ShipmentDetails(shipment_detail_id int, shipment_id int, product_id int, quantity double, price double default 0, ...);
// inserting shipments per product (defaulting to total quantity per product), assuming we have shipment_id SID
insert into ShipmentDetails(shipment_id, order_id, product_id, quantity)
select SID, order_id, product_id, SUM(quantity)
from OrderDetails
group by order_id, product_id;
Then you can of course have some filters (date, customer etc ...).
For the first question, I am not clear what exaclty you want to return.
Here is a comparison of quantities:
select t.order_id, t.product_id, sum(t.quantity) as product_quantity, sum(u.quantity) as shipment_quantity
from OrderDetails t inner join ShipmentDetails u
on t.order_id = u.order_id and t.product_id = u.product_id;

SQL Query - stuck on the following scenario:

Does anyone have an idea of how to deal this:
My scenario is I have a CustomerTable, OrderTable, ItemsTable and DeliveryTable
a Customer can make many orders
an order can contain many items
however this is where the problem is, if i have lets say 1 item, with the quantity of 20.. i receive 15 of these into stock and want to send 15/20 to the customer. currently i can assign the DeliveryID to the individual item but that contains all 20.
So my question is do i need a middle table or is there an obvious approach i am missing here?
A Customer can have multiple Orders
An order can have multiple orderlines (items)
An orderline can have multiple deliverylines
A delivery can have multiple deliverylines(maybe even of different
orders)
You can then lookup if a orderline is completed by fetching all deliverylines.
Select
( select name from items where id = ol.itemid ) as itemname,
ol.quantity as quantity_ordered,
( select sum(quantity) from deliverylines dl where dl.olid = ol.id ) as quantity_delivered
from orderlines ol
where orderid = <id>;

sql select orders with similar items

I need to select those orders in pairs who have the same products in them. ORDER_ITEMS contain the product and a foreign key to reference the parent ORDER row. Order rows need to be different.
I've managed to list out pairs with count how many matching products they have in them, but that's only a similarity count. I need to exclude orders from pairs who have different products in them.
Can have Oracle specific stuff in it.
The two tables are:
Order(order_id, customer_id...)
Order_Item(item_id, order_id FK, product_id,...)
I need tose order_id-s that have all Order_Item childs with matching product_id-s.
Ex. in Orders
{ (ord1, cust1)
(ord2, cust2)}
and in Order_Items
{ (item1, ord1, product_id=3),
(item2, ord1, product_id=6),
(item3, ord2, product_id=3),
(item4, ord2, product_id=6) }
So basically, two people bought exactly the same two things. They are a pair. Those orders whose ordered products don't match exactly are not listed.
You haven't specified db version so I'm assuming 11g - not tested, but I think it will give you the general idea:
SELECT * FROM (
WITH qry AS (
SELECT DISTINCT
order_id
,LISTAGG(product_id,'+')
WITHIN GROUP (ORDER BY product_id)
AS order_signature
FROM order_items
GROUP BY order_id)
SELECT order_id
,order_signature
,COUNT(DISTINCT order_id)
OVER (PARTITION BY order_signature)
count_same
FROM qry
) WHERE count_same > 1;
Limitation: it won't work if some orders are very big, e.g. 100s or 1000s of product IDs.
I'm not sure what your final data set needs to look like, but selecting from Customers with an EXISTS expression in the WHERE clause to look for order matches will get you there.