SQL Server Slow query using subqueries - sql

I have a very slooowwwww query.
It selects customer records, with combined criteria, kinda like this:
A table has Customers, another table has CustomerCars, another table has CustomerMotorcycles.
A customer can have one or more cars. If the customer is a commercial business and any one of the customer's cars is a Ford, then we want to exclude that customer from our selection.
A customer can also have one or more motorcycles, and if the customer is a retail business and any one of its motorcycles is a Harley, then we want to exclude that customer.
So I have a statement like:
SELECT *
FROM CUST
WHERE
(CUST.CUSTTYPE = 'COMM'
AND CUST.CUSTID NOT IN (SELECT CUSTCARS.CUSTID
FROM CUSTCARS
WHERE CUSTCARS.CAR = 'FORD'))
OR
(CUST.CUSTTYPE = 'RETAIL'
AND CUST.CUSTID NOT IN (SELECT CUSTCYCLES.CUSTID
FROM CUSTCYCLES
WHERE CUSTCYCLES.CYCLE = 'HARLEY'))
This runs crazy slow.
This is currently being run as a bunch of separate queries that dump data into temporary tables, then several other queries are run to delete the records we don't want, but it's quite clumsy.
Any suggestions? Thanks for any help!

Try "left excluding joins", where we left join the data that matches the conditions we do not want, and then exclude the matching rows through the where clause:
SELECT
CUST.*
FROM CUST
LEFT JOIN CUSTCARS ON CUST.CUSTID = CUSTCARS.CUSTID
AND CUSTCARS.CAR = 'FORD'
AND CUST.CUSTTYPE = 'COMM'
LEFT JOIN CUSTCYCLES ON CUST.CUSTID = CUSTCYCLES.CUSTID
AND CUSTCYCLES.CYCLE = 'HARLEY'
AND CUST.CUSTTYPE = 'RETAIL'
WHERE CUSTCARS.CUSTID IS NULL
OR CUSTCYCLES.CUSTID IS NULL
;
Whilst I'm here, it might be the OR in your existing query that causes excessive slowness (maybe) so perhaps combining the 2 subqueries to one list would help:
SELECT
*
FROM CUST
WHERE CUSTID NOT IN (
SELECT
CUSTCARS.CUSTID
FROM CUSTCARS
INNER JOIN CUST ON CUSTCARS.CUSTID = CUST.CUSTID
WHERE CUSTCARS.CAR = 'FORD'
AND CUST.CUSTTYPE = 'COMM'
UNION ALL
SELECT
CUSTCYCLES.CUSTID
FROM CUSTCYCLES
INNER JOIN CUST ON CUSTCYCLES.CUSTID = CUST.CUSTID
WHERE CUSTCYCLES.CYCLE = 'HARLEY'
AND CUST.CUSTTYPE = 'RETAIL'
)
;

Two things come to mind. First, make sure there are indexes for CUSTCARS.CAR and CUSTCYCLES.CYCLE. Second, you might try NOT EXISTS instead of NOT IN.
SELECT * FROM CUST
WHERE
(CUST.CUSTTYPE = 'COMM'
AND NOT EXISTS(SELECT 1 FROM CUSTCARS WHERE CUSTCARS.CAR = 'FORD'
AND CUSTCARS.CUSTID=CUST.CUSTID))
OR
(CUST.CUSTTYPE = 'RETAIL'
AND NOT EXISTS(SELECT 1 FROM CUSTCYCLES WHERE CUSTCYCLES.CYCLE = 'HARLEY'
AND CUSTCYCLES.CUSTID=CUST.CUSTID))

I would suggest starting with not exists:
select c.*
from cust c
where not (c.custtype = 'COMM' and
exists (select 1
from custcars cc
where cc.custid =c.custid and cc.car = 'FORD'
)
) and
not (c.custtype = 'RETAIL' and
exists (select 1
from custcycles cc
where cc.custid = c.custid and cc.cycle = 'HARLEY'
)
) ;
Then, you want to be sure you have indexes on custcar(custid, car) and custcycles(custid, cycle).

Related

Multiple Joins less cost way

Below query has 3 tables where I have to do 2 joins to get a column information, It is very slow, is there any effective way to run this query?
SELECT DISTINCT
st.status_c1
FROM
schemaname.tablea st
INNER JOIN (
SELECT
lic.SpecId AS applicationid,
lic.comData AS combusappid,
lic.ageId,
lic.licId,
lic.licid,
lic.appid,
com.nybe_bustbl_id AS busid
FROM
schemaname.tableb lic
INNER JOIN tablec com ON lic.comData = com.comData
WHERE
lic.ageId = '12'
) rt ON
st.ageId = rt.ageId
AND
st.licId = rt.licId
AND
st.licid = rt.licid
AND
st.appid = rt.appid
WHERE
status_id = 3;
Your current query will create extra rows when the JOIN condition is met for multiple entries in either table and then DISTINCT will filter these duplicates out. You could try to cut down the amount of work filtering duplicates by using EXISTS:
SELECT DISTINCT
st.status_c1
FROM schemaname.tablea st
WHERE status_id = 3
AND EXISTS (
SELECT 1
FROM schemaname.tableb lic
WHERE lic.ageId = '12'
AND st.ageId = lic.ageId
AND st.licId = lic.licId
AND st.appid = lic.appid
AND EXISTS(
SELECT 1 FROM tablec com WHERE lic.comData = com.comData
)
);
There is a bunch of redundancy in the query (licid is in the SELECT and ON twice) and you don't need to use subqueries for this. I think this will work:
SELECT DISTINCT st.status_c1
FROM tablea st
INNER JOIN tableb lic ON st.ageId = lic.ageId
AND st.licId = lic.licId
AND st.appid = lic.appid
INNER JOIN tablec com ON lic.comData = com.comData
WHERE status_id = 3
and lic.ageId = '12'
How frequently are you going to run this query, how much time is it taking now and what is the explectation. Are statistcs run on all tha tables.
There are many things which we can think of, but to start with if possible could you plese give ue the like the table structure and explain plan of the query.
Also may be an index on status_c1 table tablea help. As pointed out try removing the join condition which is twice AND st.licid = rt.licid
SELECT DISTINCT st.status_c1
FROM schemaname.tablea st
INNER JOIN (
SELECT
lic.SpecId AS applicationid, lic.comData AS combusappid, lic.ageId, lic.licId, lic.licid,
lic.appid, com.nybe_bustbl_id AS busid
FROM schemaname.tableb lic
INNER JOIN tablec com ON lic.comData = com.comData
WHERE lic.ageId = '12'
) rt ON st.ageId = rt.ageId AND st.licId = rt.licId AND st.licid = rt.licid AND st.appid = rt.appid
WHERE status_id = 3;

Rewrite a where in / intersect query to a join

Is there a way to rewrite my query into a join one.
The question I have to solve is: List the names of green items sold by no department on the first floor. Do not show duplicates.
select distinct itemname from xsale where deptname in (
select deptname from xdept where deptfloor <> 1
)
intersect (
select itemname from xitem where itemcolor= 'green'
)
I have been stuck at this exercise a couple of days now because the join statements don't make much sense to me even after reading about it. I hope someone can help me.
I think the following query will help you about how to use join statment for your query;
EDITED
select distinct xs.itemname from xsale xs
inner join xdept xp on xs.deptname=xp.deptname
where xp.deptfloor <>1
intersect
(select xi.itemname from xitem xi where xi.itemcolor= 'green')
Check if this works for you
Select Distinct a.itemname from xsale a
INNER JOIN xdept b on a.deptName = b.deptName
INNER JOIN xitem c on a.itemName = c.itemname
Where b.deptfloor <> 1 and c.itemcolor = 'green'
I think you can express the logic as an EXIST and NOT EXIST query:
SELECT itemname
FROM xitem
WHERE itemcolor = 'green' -- all green items
AND EXISTS (
-- exists a sale for that item
SELECT 1
FROM xsale
WHERE xsale.itemname = xitem.itemname
AND NOT EXISTS (
-- not exists a department in those sales with floor = 1
SELECT 1
FROM xdept
WHERE xdept.deptname = xsale.deptname AND xdept.deptfloor = 1
)
)
It is often best to build your queries up step by step. So if we break the problem down:
List the names of green items sold
SELECT i.ItemName
FROM xitem AS i
WHERE i.ItemColor = 'Green';
Items sold by a department on the first floor
SELECT s.ItemName
FROM xsale AS s
INNER JOIN xdept AS d
ON d.DeptName = s.DeptName
WHERE d.DeptFloor = 1;
So now, you want all the items output by the first query, except for those that appear in the 2nd:
SELECT i.ItemName
FROM xitem AS i
WHERE i.ItemColor = 'Green'
EXCEPT
SELECT s.ItemName
FROM xsale AS s
INNER JOIN xdept AS d
ON d.DeptName = s.DeptName
WHERE d.DeptFloor = 1;
Then the final part:
Do not show duplicates:
SELECT DISTINCT i.ItemName
FROM xitem AS i
WHERE i.ItemColor = 'Green'
EXCEPT
SELECT s.ItemName
FROM xsale AS s
INNER JOIN xdept AS d
ON d.DeptName = s.DeptName
WHERE d.DeptFloor = 1;
An alertanative to EXCEPT would be NOT EXISTS, they will almost always result in the same execution plan, but I find NOT EXISTS is more flexible (you don't need the same columns in both queries):
SELECT i.ItemName
FROM xitem AS i
WHERE i.ItemColor = 'Green'
AND NOT EXISTS
( SELECT 1
FROM xsale AS s
INNER JOIN xdept AS d
ON d.DeptName = s.DeptName
WHERE d.DeptFloor = 1
AND s.ItemName = i.ItemName
)
GROUP BY i.ItemName;
Again to show an alternative, I have used GROUP BY rather than DISTINCT. In most cases these are semantically equivalent, but there are scenarios where GROUP BY will perform better (namely when a scalar function is involved - GROUP BY will remove duplciates first, and then execute the funciton on all remaining values, DISTINCT will execute the function first and remove duplicate results).
Examples on DB Fiddle

Display Y/N column if record found in detail table

I'm trying to create a query so that I can have a column show Y/N if a particular item was ordered for a group of orders. The item I'm looking for would be OLI.id = '538'.
So my results would be:
Order#, Customer#, FreightPaid
12345, 00112233, Y
12346, 00112233, N
I cannot figure out if I need to use a subquery or the where exists function ?
Here's my current query:
SELECT distinct
OrderID,
Accountuid as Customerno
FROM [SMILEWEB_live].[dbo].[OrderLog] OL
inner join Orderlog_item OLI on OLI.orderlogkey = OL.[key]
inner join Account A on A.uid = OL.Accountuid
where A.GroupId = 'X9955'
and OL.CreateDate >= GETDATE() - 60
I would suggest an exists clause instead of a join:
select ol.OrderID, ol.Accountuid as Customerno,
(case when exists (select 1
from Orderlog_item OLI join
Account A
on A.uid = OL.Accountuid
where OLI.orderlogkey = OL.[key] and A.GroupId = 'X9955'
)
then 1 else 0
end) as flag
from [SMILEWEB_live].[dbo].[OrderLog] OL
where OL.CreateDate >= GETDATE() - 60;
This prevents a couple of problems. First, duplicate rows which are caused when there are multiple matching rows (and select distinct add unnecessary overhead). Second, missing rows, which happen when you use inner join instead of an outer join.

How can I join on multiple columns within the same table that contain the same type of info?

I am currently joining two tables based on Claim_Number and Customer_Number.
SELECT
A.*,
B.*,
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbp.Compound_Info AS B ON A.Claim_Number = B.Claim_Number AND A.Customer_Number = B.Customer_Number
WHERE A.Filled_YearMonth = '201312' AND A.Compound_Ind = 'Y'
This returns exactly the data I'm looking for. The problem is that I now need to join to another table to get information based on a Product_ID. This would be easy if there was only one Product_ID in the Compound_Info table for each record. However, there are 10. So basically I need to SELECT 10 additional columns for Product_Name based on each of those Product_ID's that are being selected already. How can do that? This is what I was thinking in my head, but is not working right.
SELECT
A.*,
B.*,
PD_Info_1.Product_Name,
PD_Info_2.Product_Name,
....etc {Up to 10 Product Names}
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbo.Compound_Info AS B ON A.Claim_Number = B.Claim_Number AND A.Customer_Number = B.Customer_Number
LEFT JOIN Company.dbo.Product_Info AS PD_Info_1 ON B.Product_ID_1 = PD_Info_1.Product_ID
LEFT JOIN Company.dbo.Product_Info AS PD_Info_2 ON B.Product_ID_2 = PD_Info_2.Product_ID
.... {Up to 10 LEFT JOIN's}
WHERE A.Filled_YearMonth = '201312' AND A.Compound_Ind = 'Y'
This query not only doesn't return the correct results, it also takes forever to run. My actual SQL is a lot longer and I've changed table names, etc but I hope that you can get the idea. If it matters, I will be creating a view based on this query.
Please advise on how to select multiple columns from the same table correctly and efficiently. Thanks!
I found put my extra stuff into CTE and add ROW_NUMBER to insure that I get only 1 row that I care about. it would look something like this. I only did for first 2 product info.
WITH PD_Info
AS ( SELECT Product_ID
,Product_Name
,Effective_Date
,ROW_NUMBER() OVER ( PARTITION BY Product_ID, Product_Name ORDER BY Effective_Date DESC ) AS RowNum
FROM Company.dbo.Product_Info)
SELECT A.*
,B.*
,PD_Info_1.Product_Name
,PD_Info_2.Product_Name
FROM Company.dbo.Company_Master AS A
LEFT JOIN Company.dbo.Compound_Info AS B
ON A.Claim_Number = B.Claim_Number
AND A.Customer_Number = B.Customer_Number
LEFT JOIN PD_Info AS PD_Info_1
ON B.Product_ID_1 = PD_Info_1.Product_ID
AND B.Fill_Date >= PD_Info_1.Effective_Date
AND PD_Info_2.RowNum = 1
LEFT JOIN PD_Info AS PD_Info_2
ON B.Product_ID_2 = PD_Info_2.Product_ID
AND B.Fill_Date >= PD_Info_2.Effective_Date
AND PD_Info_2.RowNum = 1

SQL SHOW one more column name FROM operator in

I need to show columns from IN
operator
My query is :
SELECT prizes.type,prizes.name
FROM lottery_payment_prizes payment
JOIN lottery_prizes prizes ON
prizes.id = payment.prize_id
WHERE payment.payment_id IN
(( SELECT id FROM lottery_payments
WHERE lottery_id = (SELECT id FROM lotteries WHERE abbr = 'TR' AND group_id = '3' )))
Result is:
My question : How can show two columns from lottery_payments ,
lottery_payments returns only ID to IN operator
function.I need to show column name "place" from lottery_payemnts table.
Thanks all.
If you need to columns from lottery_payments, you need to add a join to it:
SELECT payments.place, payments.id, prizes.type, prizes.name
FROM lottery_payment_prizes payment
JOIN lottery_prizes prizes ON
prizes.id = payment.prize_id
JOIN lottery_payments payments ON
payment.payment_id = payments.id
WHERE lottery_id = (SELECT id FROM lotteries WHERE abbr = 'TR' AND group_id = '3' )
Instead of using IN You should use join
SELECT prizes.type,prizes.name
FROM
lottery_payment_prizes payment
JOIN
lottery_prizes prizes ON prizes.id = payment.prize_id
JOIN lottery_payments lp ON payment.payment_id = lp.id
WHERE lp.lottery_id = (SELECT id FROM lotteries WHERE abbr = 'TR' AND group_id = '3' )))