SQL Not In sub Query - sql

I have a situation where I need to select prospects (relationship of 'P') from our MEMBERS table for a particular site. However, when I select these people, I need to exclude two groups of those prospects. Each of these groups have entries in a separate table named MEMBERUDFS. Now not all entries in the MEMBERS table will have an entry in the MEMBERUDFS table. Therefore, I tried to exclude those groups from the main query by using two "NOT IN" subqueries but that does not seem to work. I am able to isolate the exclusion group with the following queries:
SELECT MEMBERS_1.memid
FROM MEMBERS AS MEMBERS_1
JOIN SITES AS SITES_1 ON MEMBERS_1.siteid = SITES_1.siteid
LEFT JOIN MEMBERUDFS AS MEMBERUDFS_1 ON MEMBERS_1.memid = MEMBERUDFS_1.memid
JOIN MEMBERUDFSETUPS AS MEMBERUDFSETUPS_1 ON MEMBERUDFS_1.udfid = MEMBERUDFSETUPS_1.udfid
WHERE (MEMBERS_1.relationship = 'P')
AND (MEMBERS_1.email <> '')
AND (MEMBERUDFS_1.udfid = '26')
AND (MEMBERUDFS_1.udfvalue = 'No')
AND (MEMBERS_1.entrydate < DATEADD(DAY, -6, GETDATE()))
AND (MEMBERS_1.siteid = #rvSite)
And This query:
SELECT MEMBERS_2.memid
FROM MEMBERS AS MEMBERS_2
JOIN SITES AS SITES_2 ON MEMBERS_2.siteid = SITES_2.siteid
LEFT JOIN MEMBERUDFS AS MEMBERUDFS_2 ON MEMBERS_2.memid = MEMBERUDFS_2.memid
JOIN MEMBERUDFSETUPS AS MEMBERUDFSETUPS_2 ON MEMBERUDFS_2.udfid = MEMBERUDFSETUPS_2.udfid
WHERE (MEMBERS_2.relationship = 'P')
AND (MEMBERS_2.email <> '')
AND (MEMBERUDFS_2.udfid = '25')
AND (MEMBERUDFS_2.udfvalue = 'Yes')
AND (MEMBERS_2.entrydate < DATEADD(DAY, -21, GETDATE()))
AND (MEMBERS_2.siteid = #rvSite)
SO THIS IS WHAT I HAD PUT TOGETHER, BUT IT DOES NOT ELIMINATE THE TWO SUBQUERY GROUPS:
SELECT MEMBERS.scancode,
MEMBERS.memid,
MEMBERS.fname,
MEMBERS.lname,
MEMBERS.relationship,
MEMBERS.status,
MEMBERS.email,
MEMBERS.entrydate,
SITES.sitename
FROM MEMBERS
JOIN SITES ON MEMBERS.siteid = SITES.siteid
WHERE (MEMBERS.relationship = 'P')
AND (MEMBERS.email <> '')
AND (MEMBERS.siteid = #rvSite)
AND (MEMBERS.memid NOT IN (
SELECT MEMBERS_1.memid
FROM MEMBERS AS MEMBERS_1
JOIN SITES AS SITES_1 ON MEMBERS_1.siteid = SITES_1.siteid
LEFT JOIN MEMBERUDFS AS MEMBERUDFS_1 ON MEMBERS_1.memid = MEMBERUDFS_1.memid
JOIN MEMBERUDFSETUPS AS MEMBERUDFSETUPS_1 ON MEMBERUDFS_1.udfid = MEMBERUDFSETUPS_1.udfid
WHERE (MEMBERS_1.relationship = 'P')
AND (MEMBERS_1.email <> '')
AND (MEMBERUDFS_1.udfid = '26')
AND (MEMBERUDFS_1.udfvalue = 'No')
AND (MEMBERS_1.entrydate < DATEADD(DAY, -6, GETDATE()))
AND (MEMBERS_1.siteid = #rvSite))
)
AND (MEMBERS.memid NOT IN (
SELECT MEMBERS_2.memid
FROM MEMBERS AS MEMBERS_2
JOIN SITES AS SITES_2 ON MEMBERS_2.siteid = SITES_2.siteid
LEFT JOIN MEMBERUDFS AS MEMBERUDFS_2 ON MEMBERS_2.memid = MEMBERUDFS_2.memid
JOIN MEMBERUDFSETUPS AS MEMBERUDFSETUPS_2 ON MEMBERUDFS_2.udfid = MEMBERUDFSETUPS_2.udfid
WHERE (MEMBERS_2.relationship = 'P')
AND (MEMBERS_2.email <> '')
AND (MEMBERUDFS_2.udfid = '25')
AND (MEMBERUDFS_2.udfvalue = 'Yes')
AND (MEMBERS_2.entrydate < DATEADD(DAY, -21, GETDATE()))
AND (MEMBERS_2.siteid = #rvSite))
)
Any help would be appreciated

You just need to play with your where clause...
AND (MEMBERS.memid NOT IN (
SELECT MEMBERS_1.memid
FROM MEMBERS AS MEMBERS_1
JOIN SITES AS SITES_1 ON MEMBERS_1.siteid = SITES_1.siteid
LEFT JOIN MEMBERUDFS AS MEMBERUDFS_1 ON MEMBERS_1.memid = MEMBERUDFS_1.memid
JOIN MEMBERUDFSETUPS AS MEMBERUDFSETUPS_1 ON MEMBERUDFS_1.udfid = MEMBERUDFSETUPS_1.udfid
WHERE (MEMBERS_1.relationship = 'P')
AND (MEMBERS_1.email <> '')
AND (MEMBERS_1.siteid = #rvSite)
AND (((MEMBERUDFS_1.udfid = '26') AND (MEMBERUDFS_1.udfvalue = 'No') AND (MEMBERS_1.entrydate < DATEADD(DAY, -6, GETDATE()))) OR
((MEMBERUDFS_1.udfid = '25') AND (MEMBERUDFS_1.udfvalue = 'Yes') AND (MEMBERS_1.entrydate < DATEADD(DAY, -21, GETDATE()))))

Related

I want to be able to populate cphPayor.payor_name field. I'm getting null values in cphPayor.payor_name. What is wrong with my query below?

SELECT
dbo.leasegis(cm.class, cm.nbr, cm.suffix) AS leasegis,
cm.class, cm.nbr, cm.suffix AS suffix,
dbo.format_cns(cm.class, cm.nbr, cm.suffix) AS label,
CONVERT(VARCHAR(10), cm.begin_dt, 101) AS begindt,
CONVERT(VARCHAR(10), cm.expire_dt, 101) AS expiredt,
aaclc.app_descr, aaclc.app_con_abbrev, aaclc.app_con_lcd,
cla.lseacres, acm.applicant_name AS customer_name, acm.descr,
cpsc.production_status_descr AS production_status,
cphr.document_dt AS document_dt_roy,
cphrm.document_dt AS document_dt_min_roy,
cphrent.document_dt AS document_dt_rent, cphPayor.payor_name
FROM
dbo.app_contr_mast AS acm
INNER JOIN
dbo.contr_master AS cm ON acm.class = cm.class
AND acm.nbr = cm.nbr
AND acm.suffix = cm.suffix
INNER JOIN
dbo.all_app_con_l_cd AS aaclc ON acm.app_con_lcd = aaclc.app_con_lcd
INNER JOIN
dbo.cnsd_lse_acres_vw AS cla ON cm.class = cla.class
AND cm.nbr = cla.nbr
AND cm.suffix = cla.suffix
INNER JOIN
dbo.contr_production_status_cd AS cpsc ON cm.production_status_cd = cpsc.production_status_cd
LEFT OUTER JOIN
(SELECT
class, nbr, suffix, MAX(document_dt) AS document_dt
FROM
dbo.cash_payment_hist
WHERE
(revenue_cd = 1401) AND (document_dt >= DATEADD(m, - 14, GETDATE()))
GROUP BY
class, nbr, suffix, revenue_cd) AS cphr ON cm.class = cphr.class
AND cm.nbr = cphr.nbr
AND cm.suffix = cphr.suffix
LEFT OUTER JOIN
(SELECT
class, nbr, suffix, MAX(document_dt) AS document_dt
FROM
dbo.cash_payment_hist AS cash_payment_hist_1
WHERE
(revenue_cd = 1404) AND (document_dt >= DATEADD(m, - 18, GETDATE()))
GROUP BY
class, nbr, suffix, revenue_cd) AS cphrm ON cm.class = cphrm.class
AND cm.nbr = cphrm.nbr
AND cm.suffix = cphrm.suffix
LEFT OUTER JOIN
(SELECT
class, nbr, suffix, MAX(document_dt) AS document_dt
FROM
dbo.cash_payment_hist AS cash_payment_hist_2
WHERE
(revenue_cd = 1301) AND (document_dt >= DATEADD(m, - 18, GETDATE()))
GROUP BY
class, nbr, suffix, revenue_cd) AS cphrent ON cm.class = cphrent.class
AND cm.nbr = cphrent.nbr
AND cm.suffix = cphrent.suffix
LEFT OUTER JOIN
(SELECT TOP (1) class, nbr, suffix, payor_name
FROM dbo.cash_payment_hist AS cash_payment_hist_3
WHERE (revenue_cd IN (1301, 1401, 1404))
ORDER BY document_dt DESC) AS cphPayor ON cm.class = cphrent.class
AND cm.nbr = cphrent.nbr
AND cm.suffix = cphrent.suffix
WHERE
(cm.record_status = 1)
My first guess would be that you don't have any records in cash_payment_hist with a revenue_cd value of 1301, 1401, or 1404, combined with the fact that the On clause of that Left Outer Join that creates cphPayor references the alias cphrent, not cphPayor.

SQL Server 2008 Specified column was specified multiple times

The below code throws the following exception. How can I fix this?
Msg 8156, Level 16, State 1, Line 17
The column 'id' was specified multiple times for 'QISproduct'.
Query:
SELECT
g.artcode
, sum(g.aantal)
, i.class_01
, i.Isstockitem
FROM
gbkmut AS g
INNER JOIN
items AS i ON i.itemcode = g.artcode
INNER JOIN
(SELECT
QISP.id
, QISprocess.nml
, QISeventlog.id
, QISeventlog.dtsample
, QISproduct.nms
, QISbatchlog.nm
, QIStestlog.idvariable
, QIStestlog.no
, QISshortnote.ds
, gewicht = CASE QIStestlog.IDvariable
WHEN '139'
THEN QIStestlog.no
END
, aantal = CASE QIStestlog.IDvariable
WHEN '234'
THEN QIStestlog.no
END
, siloleeg = CASE QIStestlog.idvariable
WHEN '23'
THEN CASE QIStestlog.no
WHEN '10'
THEN 'Ja'
ELSE 'Nee'
END
END
, QISvariable.nml
, gl.nm
, QISprocess.id
FROM
QIC.Vobra_new2.dbo.production AS QISP
INNER JOIN
QIC.Vobra_new2.dbo.process AS QISprocess ON QISP.idprocess = QISProcess.id
INNER JOIN
QIC.Vobra_new2.dbo.product AS QISproduct ON QISP.idproduct = QISproduct.id
INNER JOIN
QIC.Vobra_new2.dbo.batchlog AS QISbatchlog ON QISP.idbatch = QISbatchlog.id
INNER JOIN
QIC.Vobra_new2.dbo.eventlog AS QISeventlog ON QISeventlog.idproduction = QISP.id
AND QISeventlog.idbatch = QISbatchlog.id
INNER JOIN
QIC.Vobra_new2.dbo.testlog AS QIStestlog ON QIStestlog.idevent = QISeventlog.id
LEFT OUTER JOIN
QIC.Vobra_new2.dbo.shortnote AS QISshortnote ON QISshortnote.id = QIStestlog.no
AND QIStestlog.idvariable = '144'
INNER JOIN
QIC.Vobra_new2.dbo.variable AS QISvariable ON QISvariable.id = QIStestlog.idvariable
LEFT OUTER JOIN
QIC.Vobra_new2.dbo.vvarxproc AS vvp ON vvp.idvariable = QISvariable.id
AND vvp.idprocess = QISP.idprocess
LEFT OUTER JOIN
QIC.Vobra_new2.dbo.attribute AS QISattribute ON QISattribute.id = vvp.idattribute
LEFT OUTER JOIN
QIC.Vobra_new2.dbo.grade AS QISgrade ON QISgrade.id = QISattribute.idgrade
LEFT OUTER JOIN
QIC.Vobra_new2.dbo.gradelevel AS gl ON gl.idgrade = QISattribute.idgrade
AND gl.nlevel = QIStestlog.no
WHERE
QISbatchlog.nm NOT LIKE 'V%'
AND QISP.dtstart > '2017-01-01'
AND QISP.dtstart < '2017-01-19'
AND QISP.idprocess IN ('12', '13', '14', '15', '16', '17', '18', '41')
AND QIStestlog.idvariable IN ('234', '139', '128')
) QISproduct ON g.artcode = QISproduct.nms
WHERE
g.bkjrcode > '2015'
AND g.reknr IN (3000, 3010, 3020)
AND g.aantal > 0
AND g.warehouse IN ('1', '9')
AND g.datum >= '2017-01-01'
AND g.oorsprong = 'R'
AND g.kstplcode <> 'VPR'
GROUP BY
g.artcode, i.Class_01, i.IsStockItem
The computed query aliased to QISProduct contains id column from two tables i.e. QISEventLog and QISProcess. So rename those columns to different names. Updated query
SELECT g.artcode
,sum(g.aantal)
,i.class_01
,i.Isstockitem
FROM gbkmut AS g
INNER JOIN items AS i ON i.itemcode = g.artcode
INNER JOIN (
SELECT QISP.id
,QISprocess.nml AS Processnml
,QISeventlog.id AS EventLogId
,QISeventlog.dtsample
,QISproduct.nms
,QISbatchlog.nm AS batchnm
,QIStestlog.idvariable
,QIStestlog.no
,QISshortnote.ds
,gewicht = CASE QIStestlog.IDvariable
WHEN '139'
THEN QIStestlog.no
END
,aantal = CASE QIStestlog.IDvariable
WHEN '234'
THEN QIStestlog.no
END
,siloleeg = CASE QIStestlog.idvariable
WHEN '23'
THEN CASE QIStestlog.no
WHEN '10'
THEN 'Ja'
ELSE 'Nee'
END
END
,QISvariable.nml variablenml
,gl.nm AS glnm
,QISprocess.id AS ProcessId
FROM QIC.Vobra_new2.dbo.production AS QISP
INNER JOIN QIC.Vobra_new2.dbo.process AS QISprocess ON QISP.idprocess = QISProcess.id
INNER JOIN QIC.Vobra_new2.dbo.product AS QISproduct ON QISP.idproduct = QISproduct.id
INNER JOIN QIC.Vobra_new2.dbo.batchlog AS QISbatchlog ON QISP.idbatch = QISbatchlog.id
INNER JOIN QIC.Vobra_new2.dbo.eventlog AS QISeventlog ON QISeventlog.idproduction = QISP.id
AND QISeventlog.idbatch = QISbatchlog.id
INNER JOIN QIC.Vobra_new2.dbo.testlog AS QIStestlog ON QIStestlog.idevent = QISeventlog.id
LEFT JOIN QIC.Vobra_new2.dbo.shortnote AS QISshortnote ON QISshortnote.id = QIStestlog.no
AND QIStestlog.idvariable = '144'
INNER JOIN QIC.Vobra_new2.dbo.variable AS QISvariable ON QISvariable.id = QIStestlog.idvariable
LEFT JOIN QIC.Vobra_new2.dbo.vvarxproc AS vvp ON vvp.idvariable = QISvariable.id
AND vvp.idprocess = QISP.idprocess
LEFT JOIN QIC.Vobra_new2.dbo.attribute AS QISattribute ON QISattribute.id = vvp.idattribute
LEFT JOIN QIC.Vobra_new2.dbo.grade AS QISgrade ON QISgrade.id = QISattribute.idgrade
LEFT JOIN QIC.Vobra_new2.dbo.gradelevel AS gl ON gl.idgrade = QISattribute.idgrade
AND gl.nlevel = QIStestlog.no
WHERE QISbatchlog.nm NOT LIKE 'V%'
AND QISP.dtstart > '2017-01-01'
AND QISP.dtstart < '2017-01-19'
AND QISP.idprocess IN (
'12'
,'13'
,'14'
,'15'
,'16'
,'17'
,'18'
,'41'
)
AND QIStestlog.idvariable IN (
'234'
,'139'
,'128'
)
) QISproduct ON g.artcode = QISproduct.nms
WHERE g.bkjrcode > '2015'
AND g.reknr IN (
3000
,3010
,3020
)
AND g.aantal > 0
AND g.warehouse IN (
'1'
,'9'
)
AND g.datum >= '2017-01-01'
AND g.oorsprong = 'R'
AND g.kstplcode <> 'VPR'
GROUP BY g.artcode
,i.Class_01
,i.IsStockItem
, QISbatchlog.nm
And
, gl.nm
Have same column name
You can add as to change colum name
, gl.nm as col1
You have multiple issues in your query. You are populating following columns with same column name in your inner query. Use Unique name using AS alias in inner query.
QISP.id
QISeventlog.id
QISprocess.id
gl.nm
QISbatchlog.nm
QISprocess.nml
QISvariable.nml
It is mandatory to have unique column name return by select list in sql.

NULL Values not Being Returned

I have a situation where I am running a query again tables MEMBERS, MEMBERUDFS, and MEMERUDFSETUPS. Where MEMBER UDFs are User Defined Fields that are created by the system admin to store data about customers in customizable fields. The UDFSETUPS table defines the configurable fields and MEMBERUDFS stores the values of those fields if they exist.
The issue I am running into is when I pull data back, I am not getting the rows for entries that have a UDF with an ID of 17 (MEMBERUDFS.udfid = '17') that do not have an entry or are null. I tried Left Joins but it did not seem to help.
SELECT MEMBERS.scancode, MEMBERS.fname, MEMBERS.lname, MEMBERS.datejoin, MEMBERS.entrydate
, MEMBERS.relationship, MEMBERUDFS.udfvalue
FROM MEMBERS
LEFT OUTER JOINMEMBERUDFS
ON MEMBERS.memid = MEMBERUDFS.memid
INNER JOIN MEMBERUDFSETUPS
ON MEMBERUDFS.udfid = MEMBERUDFSETUPS.udfid
WHERE (MEMBERS.siteid = #rvSite)
AND (MEMBERS.status = 'A')
AND (MEMBERS.relationship = 'M')
AND (MEMBERUDFS.udfid = '17')
AND (MEMBERS.datejoin BETWEEN #rvStartDate AND #rvEndDate)
AND (MEMBERS.mtypeid NOT IN (5, 6, 7, 11, 14, 31))
OR (MEMBERS.siteid = #rvSite)
AND (MEMBERS.relationship = 'P')
AND (MEMBERUDFS.udfid = '17')
AND (MEMBERS.entrydate BETWEEN #rvStartDate AND #rvEndDate)
Move the limit on the JoinMemberUDFS to the join or your nulls get excluded.
Preferred method move filter to join criteria
SELECT MEMBERS.scancode, MEMBERS.fname, MEMBERS.lname, MEMBERS.datejoin, MEMBERS.entrydate
, MEMBERS.relationship, MEMBERUDFS.udfvalue
FROM MEMBERS
LEFT JOIN MEMBERUDFS
ON MEMBERS.memid = MEMBERUDFS.memid
AND MEMBERUDFS.udfid = '17'
LEFT JOIN MEMBERUDFSETUPS
ON MEMBERUDFS.udfid = MEMBERUDFSETUPS.udfid
WHERE (MEMBERS.siteid = #rvSite
AND MEMBERS.status = 'A'
AND MEMBERS.relationship = 'M'
AND MEMBERS.datejoin BETWEEN #rvStartDate AND #rvEndDate
AND MEMBERS.mtypeid NOT IN (5, 6, 7, 11, 14, 31))
OR (MEMBERS.siteid = #rvSite
AND MEMBERS.relationship = 'P'
AND MEMBERS.entrydate BETWEEN #rvStartDate AND #rvEndDate)
Not preferred handle when it's null and ensure it returns true for the evaluation
SELECT MEMBERS.scancode, MEMBERS.fname, MEMBERS.lname, MEMBERS.datejoin, MEMBERS.entrydate
, MEMBERS.relationship, MEMBERUDFS.udfvalue
FROM MEMBERS
LEFT OUTER JOINMEMBERUDFS
ON MEMBERS.memid = MEMBERUDFS.memid
INNER JOIN MEMBERUDFSETUPS
ON MEMBERUDFS.udfid = MEMBERUDFSETUPS.udfid
WHERE (MEMBERS.siteid = #rvSite
AND MEMBERS.status = 'A'
AND MEMBERS.relationship = 'M'
AND MEMBERS.datejoin BETWEEN #rvStartDate AND #rvEndDate
AND MEMBERS.mtypeid NOT IN (5, 6, 7, 11, 14, 31)
AND (MEMBERUDFS.udfid = '17' or MEMBERUDFS.udfid is null)
OR (MEMBERS.siteid = #rvSite
AND MEMBERS.relationship = 'P'
AND MEMBERS.entrydate BETWEEN #rvStartDate AND #rvEndDate)
AND (MEMBERUDFS.udfid = '17' or MEMBERUDFS.udfid is null)

SQL Query Optimization with Union Select

I have this query, it returns 570 rows, but runs 2m 35s. In SQL the query execute, but in my solution, it gives a timeout. How can I optimize this to run under 1m, pref 30s.
SELECT [Region] = Region.FirstName,
[Patient] = Patient.Name,
[PatientStatus] = AccountRating.Name,
[MedicalAid] = AccountType.Name,
[QuoteAmount] = ( SELECT TOP 1 A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5966
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.SubscriberID = 240
AND Q.AccountID = Patient.AccountID
),
[InvoiceAmount] = ( SELECT TOP 1 A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5969
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.SubscriberID = 240
AND Q.AccountID = Patient.AccountID
),
[DateSubmitted] = ( SELECT TOP 1 A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5965
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.SubscriberID = 240
AND Q.AccountID = Patient.AccountID
),
[DateApprovedDeclined] = ( SELECT TOP 1 A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5968
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.SubscriberID = 240
AND Q.AccountID = Patient.AccountID
),
[IntAdmFormCreatedDate]= Q.DateCreated,
[HasAdminForm] = 'Yes',
[CreatedByUser] = PatientCreatedBy.Name
FROM dbo.Account AS Patient
JOIN dbo.AccountRating
ON Patient.AccountRatingID = AccountRating.AccountRatingID
JOIN dbo.AccountType
ON Patient.AccountTypeID = AccountType.AccountTypeID
JOIN dbo.[User] Region
ON Patient.UserID = Region.UserID
JOIN dbo.[User] PatientCreatedBy
ON Patient.CreatedBy = PatientCreatedBy.UserID
JOIN dbo.Questionnaire Q
ON Patient.AccountID = Q.AccountID
WHERE Patient.SubscriberID = 240
AND (Q.DateCreated < DATEADD(D, 26, DATEADD(MONTH, DATEDIFF(MONTH, CONVERT(DATETIME, '1900-01-01 00:00:00', 102), GETDATE()), CONVERT(DATETIME, '1900-01-01 00:00:00', 102))))
AND Q.QuestionnaireDefinitionID = 235
AND Q.IsActive = 1
AND Region.FirstName <> 'Rubbish'
UNION SELECT [Region] = Region.FirstName,
[Patient] = Patient.Name,
[PatientStatus] = AccountRating.Name,
[MedicalAid] = AccountType.Name,
[QuoteAmount] = '0',
[InvoiceAmount] = '0',
[DateSubmitted] = '',
[DateApprovedDeclined] = '',
[IntAdmFormCreatedDate] = '',
[HasAdminForm] = 'No',
[CreatedByUser] = PatientCreatedBy.Name
FROM dbo.Account AS Patient
JOIN dbo.AccountRating
ON Patient.AccountRatingID = AccountRating.AccountRatingID
JOIN dbo.AccountType
ON Patient.AccountTypeID = AccountType.AccountTypeID
JOIN dbo.[User] AS Region
ON Patient.UserID = Region.UserID
JOIN dbo.[User] AS PatientCreatedBy
ON Patient.CreatedBy = PatientCreatedBy.UserID
WHERE NOT EXISTS( SELECT *
FROM Questionnaire AS Q
WHERE Patient.AccountID = Q.AccountID
AND Q.QuestionnaireDefinitionID = 235
AND Patient.SubscriberID = 240
AND Q.SubscriberID = 240
)
AND Patient.SubscriberID = 240
AND Patient.DateCreated < DATEADD(D, 26, DATEADD(MONTH, DATEDIFF(MONTH, CONVERT(DATETIME, '1900-01-01 00:00:00', 102), GETDATE()), CONVERT(DATETIME, '1900-01-01 00:00:00', 102)))
AND Region.FirstName <> 'Rubbish'
Here is another version of the query I tried, but also runs the same time.
SELECT [Region] = Region.FirstName,
[Patient] = Patient.Name,
[PatientStatus] = AccountRating.Name,
[MedicalAid] = AccountType.Name,
[QuoteAmount] = Q1.Response,
[InvoiceAmount] = Q2.Response,
[DateSubmitted] = Q3.Response,
[DateApprovedDeclined] = Q4.Response,
[IntAdmFormCreatedDate]= Q.DateCreated,
[HasAdminForm] = 'Yes',
[CreatedByUser] = PatientCreatedBy.Name
FROM dbo.Account AS Patient
JOIN dbo.AccountRating
ON Patient.AccountRatingID = AccountRating.AccountRatingID
JOIN dbo.AccountType
ON Patient.AccountTypeID = AccountType.AccountTypeID
JOIN dbo.[User] Region
ON Patient.UserID = Region.UserID
JOIN dbo.[User] PatientCreatedBy
ON Patient.CreatedBy = PatientCreatedBy.UserID
JOIN dbo.Questionnaire Q
ON Patient.AccountID = Q.AccountID
OUTER APPLY
(
SELECT TOP 1 Q.AccountID,
A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5966
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
Q.SubscriberID = 240
AND Q.AccountID = Patient.AccountID
) Q1
OUTER APPLY
(
SELECT TOP 1 Q.AccountID,
A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5969
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.AccountID = Patient.AccountID
) Q2
OUTER APPLY
(
SELECT TOP 1 Q.AccountID,
A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5965
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.AccountID = Patient.AccountID
) Q3
OUTER APPLY
(
SELECT TOP 1 Q.AccountID,
A.Response
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.Name = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
AND QD.QuestionDefinitionID = 5968
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.AccountID = Patient.AccountID
) Q4
WHERE Patient.SubscriberID = 240
AND (Q.DateCreated < DATEADD(D, 26, DATEADD(MONTH, DATEDIFF(MONTH, CONVERT(DATETIME, '1900-01-01 00:00:00', 102), GETDATE()), CONVERT(DATETIME, '1900-01-01 00:00:00', 102))))
AND Q.QuestionnaireDefinitionID = 235
AND Q.IsActive = 1
AND Region.FirstName <> 'Rubbish'
UNION SELECT [Region] = Region.FirstName,
[Patient] = Patient.Name,
[PatientStatus] = AccountRating.Name,
[MedicalAid] = AccountType.Name,
[QuoteAmount] = '0',
[InvoiceAmount] = '0',
[DateSubmitted] = '',
[DateApprovedDeclined] = '',
[IntAdmFormCreatedDate] = '',
[HasAdminForm] = 'No',
[CreatedByUser] = PatientCreatedBy.Name
FROM dbo.Account AS Patient
JOIN dbo.AccountRating
ON Patient.AccountRatingID = AccountRating.AccountRatingID
JOIN dbo.AccountType
ON Patient.AccountTypeID = AccountType.AccountTypeID
JOIN dbo.[User] AS Region
ON Patient.UserID = Region.UserID
JOIN dbo.[User] AS PatientCreatedBy
ON Patient.CreatedBy = PatientCreatedBy.UserID
WHERE NOT EXISTS( SELECT *
FROM Questionnaire AS Q
WHERE Patient.AccountID = Q.AccountID
AND Q.QuestionnaireDefinitionID = 235
AND Patient.SubscriberID = 240
AND Q.SubscriberID = 240
)
AND Patient.SubscriberID = 240
AND Patient.DateCreated < DATEADD(D, 26, DATEADD(MONTH, DATEDIFF(MONTH, CONVERT(DATETIME, '1900-01-01 00:00:00', 102), GETDATE()), CONVERT(DATETIME, '1900-01-01 00:00:00', 102)))
AND Region.FirstName <> 'Rubbish'
Your Scalar Subqueries all share the same joins, just the QD.QuestionDefinitionID differs.
You can rewrite those 4 TOPs with a single Derived Table and join to it instead:
...
LEFT JOIN
(
SELECT
Q.AccountID,
MAX(CASE WHEN QD.QuestionDefinitionID = 5966 THEN A.Response END) AS [DateSubmitted]
MAX(CASE WHEN QD.QuestionDefinitionID = 5968 THEN A.Response END) AS [DateApprovedDeclined]
...
FROM dbo.Questionnaire Q
JOIN dbo.QuestionnaireDefinition QRD
ON QRD.QuestionnaireDefinitionID = Q.QuestionnaireDefinitionID
AND QRD.NAME = 'Internal Admin'
LEFT JOIN QuestionDefinition QD
ON Q.QuestionnaireDefinitionID = QD.QuestionnaireDefinitionID
LEFT OUTER JOIN Answer A
ON A.QuestionnaireID = Q.QuestionnaireID
AND A.QuestionDefinitionID = QD.QuestionDefinitionID
WHERE Q.IsActive = 1
AND Q.SubscriberID = 240
GROUP BY Q.AccountID
) AS Q
ON Q.AccountID = Patient.AccountID
I used MAX because you didn't have ORDER BY in you subqueries, so the exact value either doesn't matter or there's only a single row per value.

My slow T-SQL query needs a complete rethink

Good afternoon all. I'm going to post the stored procedure in it's entire glory. Feel free to rip it to shreds. The author won't mind.
DECLARE #itemTypeID INT
SELECT #itemTypeID=ItemTypeID FROM dbo.ItemTypes WHERE ItemTypeName = 'Advert'
BEGIN
SELECT a.Active,
a.ParentClass,
a.Classification,
a.Variant,
FV."Full Views",
PV."Print Views",
EE."Email Enquiries",
a.ItemRef,
a.SiteID
FROM
(
SELECT DISTINCT i.ItemID,
i.ItemRef,
i.SiteID,
i.ParentClass,
i.Classification,
i.Summary AS "Variant",
i.Active
FROM Items i
JOIN Actions a
ON a.ItemID = i.ItemID
JOIN ActionTypes at
ON a.ActionTypeID = at.ActionTypeID
WHERE i.ItemTypeID = 1
AND a.DateAndTime BETWEEN #startDate AND #endDate
AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
AND ((#siteID = -1) OR (i.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
) a LEFT JOIN
(
SELECT i.ItemID,
COUNT(*) AS "Full Views"
FROM CustomerSites cs JOIN Items i
ON cs.SiteID = i.SiteID
JOIN Actions a
ON a.ItemID = i.ItemID
JOIN ActionTypes at
ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s
ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Full view'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY
i.ItemID
) FV
ON a.ItemID = FV.ItemID
LEFT JOIN
(
SELECT i.ItemID,
COUNT(*) AS "Print Views"
FROM CustomerSites cs JOIN Items i
ON cs.SiteID = i.SiteID
JOIN Actions a
ON a.ItemID = i.ItemID
JOIN ActionTypes at
ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s
ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Print view'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY
i.ItemID
) PV
ON a.ItemID = PV.ItemID
LEFT JOIN
(
SELECT i.ItemID,
COUNT(*) AS "Email Enquiries"
FROM CustomerSites cs JOIN Items i
ON cs.SiteID = i.SiteID
JOIN Actions a
ON a.ItemID = i.ItemID
JOIN ActionTypes at
ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s
ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Email enquiry'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY
i.ItemID
) EE
ON a.ItemID = EE.ItemID
UNION
SELECT '0','','','','','','','',''
Now ultimately all this does is return some records and the number of times a particular action has occured against them.
A small subset would look like.
Item Description Views Printed Emails
Item1 Desc1 12 NULL 1
Item2 Desc2 NULL NULL 3
Item3 Desc3 5 6 2
Hopefully you can see what's going on.
I want a list of items who have had actions against them for a particular date range for a particular customer for a particular site and the query should be filterable on parent class and classification. Nice
The first select returns all distinct items that fall within the selection criteria.
The other 3 queries all simply returning counts of 1 type of action against each item. The query is pants slow even against a small amount of data. This is never going to go live it just won't work.
Hopefully you can see the error of the 'authors' ways and correct him/her.
here is the original query with my formatting style:
SELECT
a.Active
,a.ParentClass
,a.Classification
,a.Variant
,FV."Full Views"
,PV."Print Views"
,EE."Email Enquiries"
,a.ItemRef
,a.SiteID
FROM (SELECT DISTINCT
i.ItemID,
,i.ItemRef
,i.SiteID
,i.ParentClass
,i.Classification
,i.Summary AS "Variant"
,i.Active
FROM Items i
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
WHERE i.ItemTypeID = 1
AND a.DateAndTime BETWEEN #startDate AND #endDate
AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
AND ((#siteID = -1) OR (i.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
) a
LEFT JOIN (SELECT
i.ItemID
,COUNT(*) AS "Full Views"
FROM CustomerSites cs
JOIN Items i ON cs.SiteID = i.SiteID
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Full view'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY i.ItemID
) FV ON a.ItemID = FV.ItemID
LEFT JOIN (SELECT
i.ItemID
,COUNT(*) AS "Print Views"
FROM CustomerSites cs
JOIN Items i ON cs.SiteID = i.SiteID
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Print view'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY i.ItemID
) PV ON a.ItemID = PV.ItemID
LEFT JOIN (SELECT
i.ItemID
,COUNT(*) AS "Email Enquiries"
FROM CustomerSites cs
JOIN Items i ON cs.SiteID = i.SiteID
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName = 'Email enquiry'
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY i.ItemID
) EE ON a.ItemID = EE.ItemID
UNION
SELECT '0','','','','','','','',''
this should help a little:
;WITH CustomerSitesCounts AS
(
SELECT
at.ActionTypeName
,i.ItemID
,COUNT(*) AS "Print Views"
FROM CustomerSites cs
JOIN Items i ON cs.SiteID = i.SiteID
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
JOIN Sites s ON cs.SiteID = s.SiteID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND i.ItemTypeID = #itemTypeID
AND at.ActionTypeName IN ( 'Print view','Full view','Email enquiry')
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
GROUP BY at.ActionTypeName,i.ItemID
)
SELECT
a.Active
,a.ParentClass
,a.Classification
,a.Variant
,FV."Full Views"
,PV."Print Views"
,EE."Email Enquiries"
,a.ItemRef
,a.SiteID
FROM (SELECT DISTINCT
i.ItemID,
,i.ItemRef
,i.SiteID
,i.ParentClass
,i.Classification
,i.Summary AS "Variant"
,i.Active
FROM Items i
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
WHERE i.ItemTypeID = 1
AND a.DateAndTime BETWEEN #startDate AND #endDate
AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
AND ((#siteID = -1) OR (i.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
) a
LEFT JOIN CustomerSitesCounts FV ON a.ItemID = FV.ItemID AND FV.ActionTypeName='Full view'
LEFT JOIN CustomerSitesCounts PV ON a.ItemID = PV.ItemID AND PV.ActionTypeName='Print view'
LEFT JOIN CustomerSitesCounts EE ON a.ItemID = EE.ItemID AND EE.ActionTypeName='Email enquiry'
UNION
SELECT '0','','','','','','','',''
Problems:
"catch-all" type queries cannot be optimized. Solution: parametrized dynamic SQL.
UNION forces sorting. Solution: use UNION ALL instead (unless you really need the implicit DISTINCT that it forces).
This should be the fastest solution:
DECLARE #sql AS VARCHAR(MAX)
SELECT #sql = '
;WITH cteCommon as (
SELECT
i.ItemID,
,i.ItemRef
,i.SiteID
,i.ParentClass
,i.Classification
,i.Summary
,i.Active
,at.ActionTypeName
FROM Items i
JOIN Actions a ON a.ItemID = i.ItemID
JOIN ActionTypes at ON a.ActionTypeID = at.ActionTypeID
WHERE at.ActionTypeName IN (''Full view'', ''Print view'', ''Email enquiry'')
--NOTE: if you max-out this date range, then you mant want to exclude it also, RBarryYoung
AND a.DateAndTime BETWEEN #startDate AND #endDate
'
+ CASE #siteid WHEN -1 THEN '' ELSE 'AND (i.SiteID = #siteID)
' END
+ CASE #parentClass WHEN '%' THEN '' ELSE 'AND (i.ParentClass = #parentClass)
' END
+ CASE #class WHEN '%' THEN '' ELSE 'AND (i.classification = #class)
' END
+ '
)
, cteA as (
SELECT DISTINCT
i.ItemID,
,i.ItemRef
,i.SiteID
,i.ParentClass
,i.Classification
,i.Summary AS "Variant"
,i.Active
FROM cteA as i
WHERE i.ItemTypeID = 1
)
, cteCountViews AS (
SELECT
i.ItemID
,i.ActionType
,COUNT(*) AS "ViewCount"
FROM cteCommon i
JOIN CustomerSites cs ON cs.SiteID = i.SiteID
JOIN Sites s ON cs.SiteID = s.SiteID
WHERE i.ItemTypeID = #itemTypeID
'
+ CASE WHEN #customerid IS NULL THEN '' ELSE '(cs.CustomerID = #customerID)' END
+ '
GROUP BY i.ItemID
,i.ActionType
)
SELECT
a.Active
,a.ParentClass
,a.Classification
,a.Variant
,FV."Full Views"
,PV."Print Views"
,EE."Email Enquiries"
,a.ItemRef
,a.SiteID
FROM cteA AS a
LEFT JOIN (
SELECT i.ItemID, ViewCount AS "Full Views"
FROM cteCountViews i
WHERE i.ActionTypeName = ''Full view''
) FV ON a.ItemID = FV.ItemID
LEFT JOIN (
SELECT i.ItemID, ViewCount AS "Print Views"
FROM cteCountViews i
WHERE i.ActionTypeName = ''Print view''
) PV ON a.ItemID = PV.ItemID
LEFT JOIN (
SELECT i.ItemID, ViewCount AS "Email Enquiries"
FROM cteCountViews i
WHERE i.ActionTypeName = ''Email enquiry''
) EE ON a.ItemID = EE.ItemID
UNION ALL
SELECT ''0'','''','''','''','''','''','''','''',''''
'
EXEC sp_ExecuteSQL #sql
,'#startdate DATETIME,#enddate DATETIME,#siteid INT,#parentclass VARCHAR(MAX),#class VARCHAR(MAX),#itemtypeid INT,#customerid INT'
, #startdate, #enddate, #siteid, #parentclass, #class, #itemtypeid, #customerid
Note: your use of wildcards on some of thes (class, sites, etc.) with JOINs is likely to cause cross-multiplication of your source rows and enormous result sets.
Another option is to setup either table variable or a temporary #Table to hold the results. Add a unique constraint on the ItemID.
Break apart the sub-equeries into separate steps. First INSERT the records you want to count into this table. Run separate UPDATEs on the data for each view type-- UPDATE the Print View count, UPDATE the Full View count, UPDATE the Email Enquiry count. Then return the results. If necessary, split apart the OR conditions into separate queries.
This approach runs through your data several times, but it avoids LEFT JOINs and sub-queries that aren't indexed.
In our app, having multiple steps seems to perform better than one very complex query. Your results may vary.
First of all, you can avoid having a copy of 3 absolutely the same sub-queries buy having a generic one and use WITH statement so that you can reuse it.
Then, why doing a subselect when not really required.
Then again, remove some joins (and therefore DISTINCTs) that you do not need.
And you get something along these lines (not tested, naturally):
DECLARE #itemTypeID INT
SELECT #itemTypeID=ItemTypeID FROM dbo.ItemTypes WHERE ItemTypeName = 'Advert'
BEGIN
WITH ItemTypeSummary
AS (SELECT i.ItemID,
at.ActionTypeName,
COUNT(*) AS CNT
FROM Items i
JOIN Actions a
ON a.ItemID = i.ItemID
JOIN ActionTypes at
ON a.ActionTypeID = at.ActionTypeID
AND at.ActionTypeName IN ('Full view', 'Print view', 'Email enquiry')
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
--// not sure you need those below as they are all part of Items filter anyways in the main query
/*
AND i.ItemTypeID = #itemTypeID
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (cs.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
*/
GROUP BY i.ItemID,
at.ActionTypeName
)
SELECT DISTINCT i.ItemID,
i.ItemRef,
i.SiteID,
i.ParentClass,
i.Classification,
i.Summary AS "Variant",
i.Active,
FV.CNT AS "Full views",
PV.CNT AS "Print views",
EE.CNT AS "Email enquiries"
FROM Items i
JOIN CustomerSites cs
ON cs.SiteID = i.SiteID
LEFT JOIN ItemTypeSummary FV
ON i.ItemID = FV.ItemID
AND FV.ActionTypeName = 'Full view'
LEFT JOIN ItemTypeSummary PV
ON i.ItemID = PV.ItemID
AND PV.ActionTypeName = 'Print view'
LEFT JOIN ItemTypeSummary EE
ON i.ItemID = EE.ItemID
AND EE.ActionTypeName = 'Email enquiry'
WHERE i.ItemTypeID = #itemTypeID
AND ((#customerID IS NULL) OR (cs.CustomerID = #customerID))
AND ((#siteID = -1) OR (i.SiteID = #siteID))
AND ((#parentClass = '%') OR (i.ParentClass = #parentClass))
AND ((#class = '%') OR (i.classification = #class))
END
Also keep in mind that doing a trick of filter ALL or specific item that you have with those OR statements is not really cool if you can avoid them, because SQLServer will not be able to generate optimal execution plan.
Rewritten:
WITH base AS (
SELECT i.ItemID,
i.ItemRef,
i.SiteID,
i.ParentClass,
i.Classification,
i.Summary,
i.Active,
i.ItemTypeID,
at.ActionTypeName
FROM ITEMS i
JOIN ACTIONS a ON a.ItemID = i.ItemID
JOIN ACTIONTYPES at ON at.ActionTypeID = a.ActionTypeID
WHERE a.DateAndTime BETWEEN #startDate AND #endDate
AND (#siteID = -1 OR i.SiteID = #siteID)
AND (#parentClass = '%' OR i.ParentClass = #parentClass)
AND (#class = '%' OR i.classification = #class)),
items AS (
SELECT b.ItemID,
b.ItemRef,
b.SiteID,
b.ParentClass,
b.Classification,
b.Summary AS "Variant",
b.Active
FROM base b
WHERE b.itemtypeid = 1
AND b.actiontypename IN ('Full view', 'Print view', 'Email enquiry')
GROUP BY i.ItemID, i.ItemRef, i.SiteID, i.ParentClass, i.Classification, i.Summary, i.Active),
full_views AS (
SELECT b.ItemID,
COUNT(*) AS num_full_Views
FROM base b
JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
JOIN SITES s ON s.siteid = b.siteid
WHERE b.itemtypeid = #itemTypeID
AND b.ActionTypeName = 'Full view'
AND (#customerID IS NULL OR cs.CustomerID = #customerID)
GROUP BY b.itemid),
print_views AS (
SELECT b.ItemID,
COUNT(*) AS num_print_views
FROM base b
JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
JOIN SITES s ON s.siteid = b.siteid
WHERE b.itemtypeid = #itemTypeID
AND b.ActionTypeName = 'Print view'
AND (#customerID IS NULL OR cs.CustomerID = #customerID)
GROUP BY b.itemid),
email_queries AS (
SELECT b.ItemID,
COUNT(*) AS num_email_enquiries
FROM base b
JOIN CUSTOMERSITES cs ON cs.siteid = b.siteid
JOIN SITES s ON s.siteid = b.siteid
WHERE b.itemtypeid = #itemTypeID
AND b.ActionTypeName = 'Email enquiry'
AND (#customerID IS NULL OR cs.CustomerID = #customerID)
GROUP BY b.itemid)
SELECT a.Active,
a.ParentClass,
a.Classification,
a.Variant,
ISNULL(fv.num_full_Views, 0) AS "Full Views",
ISNULL(pv.num_print_views, 0) AS "Print Views",
ISNULL(ee.num_email_enquiries, 0) AS "Email Enquiries",
a.ItemRef,
a.SiteID
FROM items a
LEFT JOIN full_views fv ON fv.itemid = a.itemid
LEFT JOIN print_views pv ON pv.itemid = a.itemid
LEFT JOIN email_queries ee ON ee.itemid = a.itemid
To get better performance, I'd convert this to dynamic SQL in order to remove the parameter checks like these:
AND (#siteID = -1 OR i.SiteID = #siteID)
...because of the negative impact on sargability.