Can this oracle query be tuned anymore? - sql

I'm trying to minimize the cost of this query as much as possible without creating any indexes.
This is the original query with a cost of 599:
SELECT DISTINCT OL.PRODUCT_ID
FROM ORDERS O JOIN ORDER_LINES OL ON (O.ORDER_ID = OL.ORDER_ID)
JOIN PRODUCTS P ON (OL.PRODUCT_ID = P.PRODUCT_ID)
JOIN CUSTOMERS C ON (C.CUSTOMER_ID = O.CUSTOMER_ID)
WHERE C.CUSTOMER_ID = 474871
OR UPPER(C.FIRST_NAME) = 'EDGAR';
This is what I've done so far. The cost is now 344:
SELECT OL.PRODUCT_ID
FROM ORDER_LINES OL
WHERE EXISTS
(SELECT ORDER_ID
FROM ORDERS
WHERE CUSTOMER_ID = 474871
AND ORDER_ID = OL.ORDER_ID)
OR EXISTS
(SELECT ORDER_ID
FROM ORDERS
WHERE CUSTOMER_ID IN
(SELECT CUSTOMER_ID
FROM CUSTOMERS
WHERE UPPER(FIRST_NAME) = 'EDGAR')
AND ORDER_ID = OL.ORDER_ID);
Is there anything that stands out that I may try to drive down the cost more?
Here is a screen shot of the explain plan:
Screenshot of ERD:

Looking at the cost is misleading and may lead you to make changes that aren't actually beneficial. To quote Tom Kyte, "You cannot compare the cost of 2 queries with each other. ... they might as well be random numbers."
The best way to check query performance is to actually time the query, ideally with realistic data. You should also be wary of premature optimization. Your first query is pretty straight-forward; I would stick with it unless a performance issue manifests.

SELECT OL.PRODUCT_ID
FROM ORDER_LINES OL
WHERE OL.ORDER_ID IN
(SELECT ORDER_ID FROM ORDERS
WHERE CUSTOMER_ID IN (SELECT CUSTOMER_ID FROM CUSTOMERS
WHERE CUSTOMER_ID = 474871 OR UPPER(FIRST_NAME) = 'EDGAR')
);
I suppose there is an index on OL.ORDER_ID (now you have FULL SCAN of ORDER_LINES)

I'm curious if the engine is smart enough to apply the where clause before the joins... If it's doing it after the join, then the results it has to scan are larger than they need to be... What happens if you move the limiting criteria to the join so it HAS to be evaluated before the join occurs. (fully expect this to be 599 or less. just don't know if it will be less...
SELECT OL.PRODUCT_ID
FROM CUSTOMERS C
INNER JOIN ORDERS O
ON (C.CUSTOMER_ID = O.CUSTOMER_ID)
AND (C.customer_ID = 47871 OR upper(C.First_name) = 'EDGAR')
INNER JOIN ORDER_LINES OL ON (O.ORDER_ID = OL.ORDER_ID)
INNER JOIN PRODUCTS P ON (OL.PRODUCT_ID = P.PRODUCT_ID)
GROUP BY OL.Product_ID
I wonder if the OR is causing the problem....
if you run it w/o the or how much cost is reduced
and then what happens if you union the two sets instead of using an or.
SELECT OL.PRODUCT_ID
FROM CUSTOMERS C
INNER JOIN ORDERS O
ON (C.CUSTOMER_ID = O.CUSTOMER_ID)
INNER JOIN ORDER_LINES OL ON (O.ORDER_ID = OL.ORDER_ID)
INNER JOIN PRODUCTS P ON (OL.PRODUCT_ID = P.PRODUCT_ID)
WHERE C.customer_ID = 47871
UNION
SELECT OL.PRODUCT_ID
FROM CUSTOMERS C
INNER JOIN ORDERS O
ON (C.CUSTOMER_ID = O.CUSTOMER_ID)
INNER JOIN ORDER_LINES OL ON (O.ORDER_ID = OL.ORDER_ID)
INNER JOIN PRODUCTS P ON (OL.PRODUCT_ID = P.PRODUCT_ID)
WHERE upper(C.First_name) = 'EDGAR')

Related

Return only one data from multiple row

I have 4 tables called orders, order_details, products, and storages. Every products can have multiple thumbnail image that saved on storages table.
I want to return specific orders by id which return multiple row of order_details where every order_details have only one product. In every product, I want to get only one thumbnail image from storages table.
Here's what comes in my mind if I want to get a row where order_id = 1,
SELECT *
FROM orders o
JOIN order_details od ON o.id = od.order_id
JOIN products p ON p.id = od.product_id
JOIN storages s ON s.product_id = p.id --> i haven't figured out how to return only one image for every product in order_details
WHERE o.id = 1
Can somebody give me a help, I've tried to figured out this for days but still not getting it right :(
Thank you in advance.
A simple method is to use row_number():
SELECT *
FROM orders o JOIN
order_details od
ON o.id = od.order_id JOIN
products p
ON p.id = od.product_id JOIN
(SELECT s.*,
ROW_NUMBER() OVER (PARTITION BY product_id ORDER by random()) as seqnum
FROM storages s
) s
ON s.product_id = p.id
WHERE o.id = 1 AND seqnum = 1;
This returns a random image. You can replace the ORDER BY to get any image you want -- the oldest, newest, biggest, smallest or whatever.
I haven't figured out how to return only one image for every product in order_details
In Postgres, I would recommend distinct on:
select distinct on (o.id, od.product_id) *
from orders o
join order_details od on o.id = od.order_id
join products p on p.id = od.product_id
join storages s on s.product_id = p.id
order by o.id, od.product_id, s.id
This guarantees just one rows per order and product, with the storage that has the smallest id. You can filter on a given order id with a where clause, if you like.
Or maybe you want to use the primary key of the order details instead of the product (this allows twice the same product in two different order details of the same order).
select distinct on (o.id, od.id) *
from orders o
join order_details od on o.id = od.order_id
join products p on p.id = od.product_id
join storages s on s.product_id = p.id
order by o.id, od.id, s.id

How do we find which customers placed orders with items made in USA refer to image

Which customers placed orders with items made inside the USA?
SELECT DISTINCT, WHERE, Temporary Table, Subquery
tables to refer
I would use exists with a correlated subquery that follows the relationships like customer > order > order_item > product > supplier and filters on US suppliers:
select c.*
from customer c
where exists (
select 1
from order o
inner join order_item oi on oi.order_id = o.id
inner join product p on p.id = oi.product_id
inner join supplier s on s.id = p.supplier_id
where o.customer_id = c.id and s.country = 'USA'
)

SQL Oracle (using AND clause)

when I use below code, I get the data of customers who ordered "Planned" or 'obsolete' products, but I want to get data of the customers who ordered both type, changing 'or' to 'and' does not work... please help
SELECT DISTINCT customers.CUST_EMAIL
,ORDERS.ORDER_ID
,PRODUCT_INFORMATION.PRODUCT_NAME
,PRODUCT_INFORMATION.PRODUCT_STATUS
FROM PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
OR PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
ORDER BY CUSTOMERS.CUST_EMAIL;
I'm guessing that you want the following. If you want to get correct answer, rather than guesses, please provide a good representative set of sample data and your expected result based on that sample data.
First part of the query returns Customers that ordered planned products, second part of the query returns Customers that ordered obsolete products. INTERSECT operator returns only those that have ordered both planned and obsolete products.
You don't need explicit DISTINCT any more, because INTERSECT would do it anyway.
I've removed PRODUCT_INFORMATION.PRODUCT_STATUS from the list of returned columns, because with it the result set would be always empty.
I removed ORDERS.ORDER_ID and PRODUCT_INFORMATION.PRODUCT_NAME from result as well. I don't know what should be the correct query, but it is likely that INTERSECT should be done just on CUSTOMER_ID and then, once you get the list of IDs, you can join other tables to it fetching other related details if needed.
The performance of this method is beyond the scope of the question.
SELECT
CUSTOMERS.CUSTOMER_ID
,customers.CUST_EMAIL
FROM
PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
INTERSECT
SELECT
CUSTOMERS.CUSTOMER_ID
,customers.CUST_EMAIL
FROM
PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
ORDER BY CUST_EMAIL
without the script for you tables it's difficult to build a test case and a working query; i'll try with this step:
select order_id from (
SELECT customers.CUSTOMER_ID
,sum(decode(PRODUCT_INFORMATION.PRODUCT_STATUS, 'obsolete', 1, 0)) obsolete
,sum(decode(PRODUCT_INFORMATION.PRODUCT_STATUS, 'planned', 1, 0)) planned
FROM PRODUCT_INFORMATION
INNER JOIN ORDER_ITEMS ON PRODUCT_INFORMATION.PRODUCT_ID = ORDER_ITEMS.PRODUCT_ID
INNER JOIN ORDERS ON ORDER_ITEMS.ORDER_ID = ORDERS.ORDER_ID
INNER JOIN CUSTOMERS ON CUSTOMERS.CUSTOMER_ID = ORDERS.CUSTOMER_ID
WHERE PRODUCT_INFORMATION.PRODUCT_STATUS = 'planned'
OR PRODUCT_INFORMATION.PRODUCT_STATUS = 'obsolete'
group by customers.CUSTOMER_ID)
where obsolete>1 and planned>1
This query should return all the customer id that have items in orders with both the product status (the different product status may be in different orders), if you want to retrieve orders that have products with both status you must change the query removing customer.customer_id and adding orders.order_id. If you provide some script with sample data we can provide a better answer

SQL Query Multiple Products on One Order

Let's say I have 2 tables:
tOrder tOrderLine
------ ----------
OrderID OrderID
Other Fields ProductID
Other Fields
I want to get back all orders that have a certain 2 products on the same order. Currently, I'm doing:
select o.OrderID
from tOrder o
inner join tOrderLine ol on o.OrderID = ol.OrderID
inner join tOrderLine ol2 on o.OrderID = ol2.OrderID
where ol.ProductID = 67
and ol2.ProductID = 68
This works and gives me the results I need, but it seems a little hokey. Also, if I wanted to get all orders that have a set of 10 items, the query is going to get monstrous.
Is there a better way to do what I'm looking for?
There are 2 simple ways I can think to do it:
Inner join to tOrderLine twice. Exactly how you did it. I actually would have never thought of that. It was an interesting solution when I saw it.
EXISTS clauses. For each tOrder row, check if there exists a row in tOrderLine corresponding to it and one of the products.
For example:
select tOrder.OrderID
from tOrder o
where exists (select 1 from tOrderLine ol where ol.OrderID = o.OrderID and ol.ProductID = 67)
and exists (select 1 from tOrderLine ol where ol.OrderID = o.OrderID and ol.ProductID = 68)
This is how I would write it, simply because it's more obvious what the query is doing. On the other hand, yours seems easier for the parser to optimize.
I'd be curious to know if you look at the execution plan of either one if they are the same. The correlated sub-queries could be rewritten internally to be inner joins.
You could try to solve it using a distinct count on a having:
select t.OrderId
, count(*)
from tOrder t
join tOrderLine o
on t.OrderID = o.OrderID
where o.ProductId in (67, 68)
group
by t.OrderId
having count(distinct o.ProductId) = 2
Try this
Select Distinct a.*
From tOrder a
inner join tOrderLine b on a.orderId = b.orderId
Where a.orderId in
(
Select orderId
From tOrderLine
Where
productId in (68,69)
--replace with set of products you want
Group by orderId
Having Count(Distinct productId) = 2
---replace with 10 if you want to return orders with 10 items
)
SQL DEMO

SQL Query on SQL Server 2008

I'm trying to get only customers that ordered both a "Gas Range" and a "Washer". I'm getting Customers who ordered a "Gas Range" and not a "Washer" and customers with both. I need the customer that meets both conditions. I'm close but a little stuck. Below is the query that I have so far. Please let me know if you need more information.
My Tables - CUSTOMER(CUST_NUM, CUST_NAME), ORDER_LINE(ORDER_NUM, PART_NUM), ORDERS(ORDER_NUM, CUST_NUM), PART(PART_NUM, PART_DESCRIPTION)
SELECT C.CUST_NAME AS [Customer(s) that ordered a Gas Range and Washer]
FROM CUSTOMER C
INNER JOIN ORDERS O
ON C.CUST_NUM = O.CUST_NUM
INNER JOIN ORDER_LINE OL
ON O.ORDER_NUM = OL.ORDER_NUM
INNER JOIN PART P
ON OL.PART_NUM = P.PART_NUM
WHERE P.PART_DESCRIPTION IN ('GasRange','Washer')
GROUP BY C.CUST_NAME
try the following
SELECT C.CUST_NAME AS [Customer(s) that ordered a Gas Range and Washer]
FROM CUSTOMER C
INNER JOIN ORDERS O
ON C.CUST_NUM = O.CUST_NUM
INNER JOIN ORDER_LINE OL
ON O.ORDER_NUM = OL.ORDER_NUM
INNER JOIN PART P
ON OL.PART_NUM = P.PART_NUM
INNER JOIN ORDERS O2
ON C.CUST_NUM = O2.CUST_NUM
INNER JOIN ORDER_LINE OL2
ON O2.ORDER_NUM = OL2.ORDER_NUM
INNER JOIN PART P2
ON OL2.PART_NUM = P2.PART_NUM
WHERE P.PART_DESCRIPTION IN ('GasRange') and P2.PART_DESCRIPTION IN ('Washer')
GROUP BY C.CUST_NAME
EDIT: Had a further look and I'm afraid that this can't be simplified in any other way than using WITH and complicated aggregate functions, which I would say would be more complicated than this - I think the other solution suggested using WITH won't work - it joins incorrectly. You definitely can't remove order line, and you have to use the order twice as well - if it was used once, it will cover only when the customer ordered it within one order, which is not what you wanted ;)
Try this...
So basically you need to join your Parts table again to ensure the same customer ordered a "Gas Range" and a "Washer". An IN, like in your current query functions as an OR therefore you are not getting the expected result.
WITH CTE AS (
SELECT DISTINCT O.CUST_NUM FROM ORDERS O
INNER JOIN ORDER_LINE OL
ON O.ORDER_NUM = OL.ORDER_NUM
INNER JOIN PART P
ON OL.PART_NUM = P.PART_NUM
INNER JOIN PART P2
ON OL.PART_NUM = P2.PART_NUM
WHERE P.PART_DESCRIPTION IN ('GasRange')
AND P2.PART_DESCRIPTION IN ('Washer')
)
SELECT C.CUST_NAME AS [Customer(s) that ordered a Gas Range and Washer]
FROM CUSTOMER C
INNER JOIN CTE O
ON C.CUST_NUM = O.CUST_NUM