Need to speed up the results of this SQL statement. Any advice? - sql

I've got the following SQL Statement that needs some major speed up. The problem is I need to search on two fields, where each of them is calling several sub-selects. Is there a way to join the two fields together so I call the sub-selects only once?
SELECT billyr, billno, propacct, vinid, taxpaid, duedate, datepif, propdesc
FROM trcdba.billspaid
WHERE date(datepif) > '01/06/2009'
AND date(datepif) <= '01/06/2010'
AND custno in
(select custno from cwdba.txpytaxid where taxpayerno in
(select taxpayerno from cwdba.txpyaccts where accountno in
(select accountno from rtadba.reasacct where controlno = 1234567)))
OR custno2 in
(select custno from cwdba.txpytaxid where taxpayerno in
(select taxpayerno from cwdba.txpyaccts where accountno in
(select accountno from rtadba.reasacct where controlno = 1234567)))

I would use joins instead of the embedded sub-queries.

when you use a function on the column:
date(datepif) > '01/06/2009'
AND date(datepif) <= '01/06/2010'
an index will NOT be used. Try something like this
datepif > someconversionhere('01/06/2009')
AND datepif <= someconversionhere('01/06/2010')
Use inner joins too. There isn't any info in the question to indicate table size or if there is an index or not, so this is a guess and should work best if there are many more rows in billspaid for the date range vs rows that match the joining tables for r.controlno = 1234567, which I suspect is the case:
SELECT
COALESCE(b1.billyr,b2.billyr) AS billyr
,COALESCE(b1.billno,b2.billno) AS billno
,COALESCE(b1.propacct,b2.propacct) AS propacct
,COALESCE(b1.vinid,b2.vinid) AS vinid
,COALESCE(b1.taxpaid,b2.taxpaid) AS taxpaid
,COALESCE(b1.duedate,b2.duedate) AS duedate
,COALESCE(b1.datepif,b2.datepif) AS datepif
,COALESCE(b1.propdesc,b2.propdesc) AS propdesc
FROM rtadba.reasacct r
INNER JOIN cwdba.txpyaccts a ON r.accountno=t.accountno
INNER JOIN cwdba.txpytaxid t ON a.taxpayerno=t.taxpayerno
LEFT OUTER JOIN trcdba.billspaid b1 ON t.custno=b1.custno AND b1.datepif > someconversionhere('01/06/2009') AND b1.datepif <= someconversionhere('01/06/2010')
LEFT OUTER JOIN trcdba.billspaid b2 ON t.custno2=b2.custno AND b2.datepif > someconversionhere('01/06/2009') AND b2.datepif <= someconversionhere('01/06/2010')
WHERE r.controlno = 1234567
AND COALESCE(b1.custno,b2.custno) IS NOT NULL
create an index for each of these:
rtadba.reasacct.controlno and cover on accountno
cwdba.txpyaccts.accountno and cover on taxpayerno
cwdba.txpytaxid.taxpayerno and cover on custno
trcdba.billspaid.custno +datepif
trcdba.billspaid.custno2 +datepif

Here's the same thing using JOIN instead of sub queries.
SELECT billyr, billno, propacct, vinid, taxpaid, duedate, datepif, propdesc
FROM billspaid
INNER JOIN txpytaxid
ON txpytaxid.custno = billspaid.custno OR txpytaxid.custno = billspaid.custno2
INNER JOIN txpyaccts
ON txpyaccts.taxpayerno = txpytaxid.taxpayerno
INNER JOIN reasacct
ON reasacct.accountno = txpyaccts.accountno AND reasacct.controlno = 1234567
WHERE date(datepif) > '01/06/2009'
AND date(datepif) <= '01/06/2010'
However, if the OR in the JOIN is giving you performance problems, you can always try using a union:
(SELECT billyr, billno, propacct, vinid, taxpaid, duedate, datepif, propdesc
FROM billspaid
INNER JOIN txpytaxid
ON txpytaxid.custno = billspaid.custno
INNER JOIN txpyaccts
ON txpyaccts.taxpayerno = txpytaxid.taxpayerno
INNER JOIN reasacct
ON reasacct.accountno = txpyaccts.accountno AND reasacct.controlno = 1234567
WHERE date(datepif) > '01/06/2009'
AND date(datepif) <= '01/06/2010')
UNION
(SELECT billyr, billno, propacct, vinid, taxpaid, duedate, datepif, propdesc
FROM billspaid
INNER JOIN txpytaxid
ON txpytaxid.custno = billspaid.custno2
INNER JOIN txpyaccts
ON txpyaccts.taxpayerno = txpytaxid.taxpayerno
INNER JOIN reasacct
ON reasacct.accountno = txpyaccts.accountno AND reasacct.controlno = 1234567
WHERE date(datepif) > '01/06/2009'
AND date(datepif) <= '01/06/2010')

Use EXISTS instead of IN ( unless the result set of the IN subquery is very small).
If you do UNION instead of OR ( which should be functionally equivalent ) use UNION ALL instead.

Related

SQL Error when using LIKE CASE WHEN with subquery

I'm attempting to write an SQL statement that use Excel parameters to insert data via a drop down menu. This normally works great, but I think because I'm using a subquery in this case I'm getting the error "The multi-part identifier could not be bound." The subquery is used to create a sum of the quantity of an item purchased within the past week. Here's the code:
SELECT DISTINCT ITMMASTER.ITMREF_0 AS ITEMCODE, ITMMASTER.ITMDES1_0 AS ITEMDESC, Sum(ITMMVT.PHYSTO_0) AS AVAILSTOCK, Sum(ITMMVT.ORDSTO_0) AS ONORDER, Sum(ITMMVT.PHYALL_0) AS ALLOCATED, Sum(ITMFACILIT.MAXSTO_0) AS MAX,
INVQTY = (SELECT Sum(SINVOICED.QTY_0) AS INVQTY
FROM x3v11.PROD.SINVOICED SINVOICED
WHERE SINVOICED.INVDAT_0 BETWEEN DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0))
AND DATEADD(DAY,4,DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0)))
AND SINVOICED.ITMREF_0 = ITMMASTER.ITMREF_0
),
ITMMASTER.ITMWEI_0 AS WEIGHT, ITMCOST.CSTTOT_0 AS COST, ITMBPS.BPSNUM_0 AS VENDOR, BPSUPPLIER.BPSNAM_0 AS VENDNAME, ITMMASTER.TSICOD_2 AS CAT
FROM x3v11.PROD.ITMMASTER ITMMASTER
LEFT OUTER JOIN x3v11.PROD.ITMMVT ITMMVT ON ITMMASTER.ITMREF_0 = ITMMVT.ITMREF_0
LEFT OUTER JOIN x3v11.PROD.ITMFACILIT ITMFACILIT ON ITMMVT.ITMREF_0 = ITMFACILIT.ITMREF_0 AND ITMMVT.STOFCY_0 = ITMFACILIT.STOFCY_0
LEFT OUTER JOIN x3v11.PROD.ITMCOST ITMCOST ON ITMMVT.ITMREF_0 = ITMCOST.ITMREF_0 AND ITMMVT.STOFCY_0 = ITMCOST.STOFCY_0
LEFT OUTER JOIN x3v11.PROD.ITMBPS ITMBPS ON ITMMASTER.ITMREF_0 = ITMBPS.ITMREF_0
LEFT OUTER JOIN x3v11.PROD.BPSUPPLIER BPSUPPLIER ON ITMBPS.BPSNUM_0 = BPSUPPLIER.BPSNUM_0
WHERE ITMCOST.STOFCY_0 <> '115'
AND ITMFACILIT.MAXSTO_0 <> 0
AND ITMBPS.PIO_0 <> 99
AND ITMBPS.BPSNUM_0 LIKE CASE WHEN ? IS NOT NULL THEN ? ELSE '%' END
GROUP BY ITMMASTER.ITMREF_0, ITMMASTER.ITMDES1_0, ITMMASTER.ITMWEI_0, ITMCOST.CSTTOT_0, ITMBPS.BPSNUM_0, BPSUPPLIER.BPSNAM_0, ITMMASTER.TSICOD_2
ORDER BY ITMMASTER.ITMREF_0
I think you have specified the syntax for the subquery incorrectly:
INVQTY = (SELECT Sum(SINVOICED.QTY_0) AS INVQTY
FROM x3v11.PROD.SINVOICED SINVOICED
WHERE SINVOICED.INVDAT_0 BETWEEN
DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0))
AND DATEADD(DAY,4,DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0)))
AND SINVOICED.ITMREF_0 = ITMMASTER.ITMREF_0
),
I think it should be:
(SELECT Sum(SINVOICED.QTY_0)
FROM x3v11.PROD.SINVOICED SINVOICED
WHERE SINVOICED.INVDAT_0 BETWEEN
DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0))
AND DATEADD(DAY,4,DATEADD(WEEK,-1,DATEADD(WEEK,DATEDIFF(WEEK,0,GETDATE()),0)))
AND SINVOICED.ITMREF_0 = ITMMASTER.ITMREF_0
) AS INVQTY,

How could I join these queries together?

I have 2 queries. One includes a subquery and the other is a pretty basic query. I can't figure out how to combine them to return a table with name, workdate, minutes_inactive, and hoursworked.
I have the code below for what I have tried. The simple query is lines 1,2, and the last 5 lines. I also added a join clause (join punchclock p on p.servrepID = l.repid) to it.
Both these queries ran on their own so this is solely just the problem of combining them.
select
sr.sr_name as liaison, cast(date_created as date) workdate,
(count(date_created) * 4) as minutes_inactive,
(select
sr_name, cast(punchintime as date) as workdate,
round(sum(cast(datediff(minute,punchintime, punchouttime) as real) / 60), 2) as hoursworked,
count(*) as punches
from
(select
sr_name, punchintime = punchdatetime,
punchouttime = isnull((select top 1 pc2.punchdatetime
from punchclock pc2
where pc2.punchdatetime > pc.punchdatetime
and pc.servrepid = pc2.servrepid
and pc2.inout = 0
order by pc2.punchdatetime), getdate())
from
punchclock pc
join
servicereps sr on pc.servrepid = sr.servrepid
where
punchyear >= 2017 and pc.inout = 1
group by
sr_name, cast(punchintime as date)))
from
tbl_liga_popup_log l
join
servicereps sr on sr.servrepID = l.repid
join
punchclock p on p.servrepID = l.repid collate latin1_general_bin
group by
cast(l.date_created as date), sr.sr_name
I get this error:
Msg 102, Level 15, State 1, Line 19
Incorrect syntax near ')'
I keep getting this error but there are more errors if I adjust that part.
I don't know that we'll fix everything here, but there are a few issues with your query.
You have to alias your sub-query (technically a derived table, but whatever)
You have two froms in your outer query.
You have to join to the derived table.
Here's an crude example:
select
<some stuff>
from
(select col1 from table1) t1
inner join t2
on t1.col1 = t2.col2
The large error here is that you are placing queries in the select section (before the from). You can only do this if the query returns a single value. Else, you have to put your query in a parenthesis (you have done this) in the from section, give it an alias, and join it accordingly.
You also seem to be using group bys that are not needed anywhere. I can't see aggregation functions like sum().
My best bet is that you are looking for the following query:
select
sr_name as liaison
,cast(date_created as date) workdate
,count(distinct date_created) * 4 as minutes_inactive
,cast(punchintime as date) as workdate
,round(sum(cast(datediff(minute,punchintime,isnull(pc2_query.punchouttime,getdate())) as real) / 60), 2) as hoursworked
,count(*) as punches
from
punchclock pc
inner join servicereps sr on pc.servrepid = sr.servrepid
cross apply
(
select top 1 pc2.punchdatetime as punchouttime
from punchclock pc2
where pc2.punchdatetime > pc.punchdatetime
and pc.servrepid = pc2.servrepid
and pc2.inout = 0
order by pc2.punchdatetime
)q1
inner join tbl_liga_popup_log l on sr.servrepID = l.repid
where punchyear >= 2017 and pc.inout = 1

returning only the most recent record in dataset

I have a query returning data for several dates.
I want to return only record with the most recent date in the field SAPOD (the date is in fact CYYMMDD where C=0 before 2000 and 1 after 2000 and I can show this as YYYYMMDD using SAPOD=Case when LEFT(SAPOD,1)=1 then '20' else '19' end + SUBSTRING(cast(sapod as nvarchar(7)),2,7))
here is my query:
SELECT GFCUS, Ne.NEEAN, SCDLE, SAPOD, SATCD,CUS.GFCUN, BGCFN1,
BGCFN2, BGCFN3, SV.SVCSA, SV.SVNA1, SV.SVNA2, SV.SVNA3,
SV.SVNA4, SV.SVNA5, SV.SVPZIP, SV.NONUK
FROM SCPF ACC
INNER JOIN GFPF CUS ON GFCPNC = SCAN
LEFT OUTER JOIN SXPF SEQ ON SXCUS = GFCUS AND SXPRIM = ''
LEFT OUTER JOIN SVPFClean SV ON SVSEQ = SXSEQ
LEFT OUTER JOIN BGPF ON BGCUS = GFCUS AND BGCLC = GFCLC
LEFT OUTER JOIN NEPF NE ON SCAB=NE.NEAB and SCAN=ne.NEAN and SCAS=ne.NEAS
LEFT OUTER JOIN SAPF SA ON SCAB=SAAB and SCAN=SAAN and SCAS=SAAS
WHERE
(SATCD>500 and
scsac='IV' and
scbal = 0 and
scai30<>'Y' and
scai14<>'Y' and
not exists(select * from v5pf where v5and=scan and v5bal<>0))
GROUP BY GFCUS, Ne.NEEAN, SCDLE, SAPOD, SATCD,
CUS.GFCUN, BGCFN1, BGCFN2, BGCFN3, SV.SVCSA,
SV.SVNA1, SV.SVNA2, SV.SVNA3, SV.SVNA4, SV.SVNA5, SV.SVPZIP, SV.NONUK
ORDER BY MAX(SCAN) ASC, SAPOD DESC
I am getting results like the below where there are several transactions by a customer, and we only want to show the data of the most recent transaction:
So how can I show just the most recent transaction? Is this a case where I should use an OUTER APPLY or CROSS APPY?
EDIT:
Sorry I should clarify that I need the most recent date for each of the unique records in the field NEEAN which is the Account number
You can use ROW_NUMBER() as follows:
SELECT
ROW_NUMBER() OVER (PARTITION BY Ne.NEEAN ORDER BY SAPOD DESC) AS [Row],
GFCUS, Ne.NEEAN, SCDLE, SAPOD, SATCD,CUS.GFCUN, BGCFN1,
BGCFN2, BGCFN3, SV.SVCSA, SV.SVNA1, SV.SVNA2, SV.SVNA3,
SV.SVNA4, SV.SVNA5, SV.SVPZIP, SV.NONUK
FROM SCPF ACC
INNER JOIN GFPF CUS ON GFCPNC = SCAN
LEFT OUTER JOIN SXPF SEQ ON SXCUS = GFCUS AND SXPRIM = ''
LEFT OUTER JOIN SVPFClean SV ON SVSEQ = SXSEQ
LEFT OUTER JOIN BGPF ON BGCUS = GFCUS AND BGCLC = GFCLC
LEFT OUTER JOIN NEPF NE ON SCAB=NE.NEAB and SCAN=ne.NEAN and SCAS=ne.NEAS
LEFT OUTER JOIN SAPF SA ON SCAB=SAAB and SCAN=SAAN and SCAS=SAAS
WHERE
(SATCD>500 and
scsac='IV' and
scbal = 0 and
scai30<>'Y' and
scai14<>'Y' and
not exists(select * from v5pf where v5and=scan and v5bal<>0)) and
[Row] = 1
GROUP BY GFCUS, Ne.NEEAN, SCDLE, SAPOD, SATCD,
CUS.GFCUN, BGCFN1, BGCFN2, BGCFN3, SV.SVCSA,
SV.SVNA1, SV.SVNA2, SV.SVNA3, SV.SVNA4, SV.SVNA5, SV.SVPZIP, SV.NONUK
ORDER BY MAX(SCAN) ASC
You could encapsulate this within a subquery if you don't want to return the [Row] column.
you can user row_number to get top 1 row per customer
In the where clause need to return values with pos value as 1
sample query
row_number() over ( partition by GFCUS order by SAPOD desc) as pos

SQL - Derived tables issue

I have the following SQL query:
SELECT VehicleRegistrations.ID, VehicleRegistrations.VehicleReg,
VehicleRegistrations.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations
INNER JOIN VehicleType ON VehicleRegistrations.VehicleType = VehicleType.ID
LEFT OUTER JOIN (SELECT TOP (1) ID, VehicleRegID, DateEffective, IVehHire,
FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts
WHERE (DateEffective <= GETDATE())
ORDER BY DateEffective DESC) AS dt
ON dt.VehicleRegID = VehicleRegistrations.ID
What I basically want to do is always select the top 1 record from the 'VehicleFixedCosts' table, where the VehicleRegID matches the one in the main query. What is happening here is that it's selecting the top row before the join, so if the vehicle registration of the top row doesn't match the one we're joining to it returns nothing.
Any ideas? I really don't want to have use subselects for each of the columns I need to return
Try this:
SELECT vr.ID, vr.VehicleReg,
vr.Phone, VehicleType.VehicleTypeDescription,
dt.ID AS 'CostID', dt.IVehHire, dt.FixedCostPerYear, dt.VehicleParts,
dt.MaintenancePerMile, dt.DateEffective
FROM VehicleRegistrations vr
INNER JOIN VehicleType ON vr.VehicleType = VehicleType.ID
LEFT OUTER JOIN (
SELECT ID, VehicleRegID, DateEffective, IVehHire, FixedCostPerYear, VehicleParts, MaintenancePerMile
FROM VehicleFixedCosts vfc
JOIN (
select VehicleRegID, max(DateEffective) as DateEffective
from VehicleFixedCosts
where DateEffective <= getdate()
group by VehicleRegID
) t ON vfc.VehicleRegID = t.VehicleRegID and vfc.DateEffective = t.DateEffective
) AS dt
ON dt.VehicleRegID = vr.ID
Subquery underneath dt might need some grouping but without schema (and maybe sample data) it's hard to say which column should be involved in that.

SQL Union Query

SELECT pv.PropertyID, COUNT(pv.VisitID) AS InitialVisit
FROM tblPAppointments pa INNER JOIN tblPropertyVisit pv ON pv.AppID = pa.AppID
WHERE pv.Status = 0
GROUP BY pv.PropertyID
UNION ALL
SELECT jv.PropertyID, COUNT(jv.JobVistID) AS JobVisit
FROM tblPAppointments pa INNER JOIN tblJobVisits jv ON jv.AppID = pa.AppID
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
I need to get InitialVisit count and JobVisit count in two separate columns.above query returns just two columns (PropertyID,InitialVisit).
Use a NULL as a placeholder for the column that there won't be any output for:
SELECT pv.PropertyID,
COUNT(pv.VisitID) AS InitialVisit,
NULL AS jobvisit
FROM tblPAppointments pa
JOIN tblPropertyVisit pv ON pv.AppID = pa.AppID
WHERE pv.Status = 0
GROUP BY pv.PropertyID
UNION ALL
SELECT jv.PropertyID,
NULL AS initialvisit,
COUNT(jv.JobVistID) AS JobVisit
FROM tblPAppointments pa
JOIN tblJobVisits jv ON jv.AppID = pa.AppID
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
This will return three columns. The column alias is necessary in the first query, but not in the second -- I aliased both to make it clear what is happening.
Be aware that using NULL like this in SQL Server will require you to use CAST/CONVERT on the NULL for data types other than INT because SQL Server defaults the NULL to an INT data type (as odd as that is).
An alternate query that doesn't use UNION:
SELECT x.propertyid,
COUNT(y.visitid) AS initialvisit,
COUNT(z.jobvisitid) AS jobvisit
FROM (SELECT pv.propertyid
FROM TBLPROPERTYVISIT pv
WHERE EXISTS (SELECT NULL
FROM TBLAPPOINTMENTS a
WHERE a.appid = pv.appid)
UNION
SELECT jv.propertyid
FROM TBLJOBVISIT jv
WHERE EXISTS (SELECT NULL
FROM TBLAPPOINTMENTS a
WHERE a.appid = jv.appid)) x
LEFT JOIN TBLPROPERTYVISIT y ON y.propertyid = x.propertyid
LEFT JOIN TBLJOBVISIT z ON z.propertyid = x.propertyid
GROUP BY x.propertyid
No need for a UNION at all. And you don't use tblPAppointments either
Edited to allow for no rows in one of the tables. Still one row output though
SELECT
ISNULL(pv2.PropertyID, jv2.PropertyID),
ISNULL(pv2.InitialVisit, 0),
ISNULL(jv2.JobVisit, 0)
FROM
(
SELECT pv.PropertyID, COUNT(pv.VisitID) AS InitialVisit
FROM tblPropertyVisit pv
WHERE pv.Status = 0
GROUP BY pv.PropertyID
) pv2
FULL OUTER JOIN
(
SELECT jv.PropertyID, COUNT(jv.JobVistID) AS JobVisit
FROM tblJobVisits jv
WHERE jv.VisitStatus = 1
GROUP BY jv.PropertyID
) jv2 ON pv2.PropertyID = jv2.PropertyID