SQL query with poor performances - sql

I inherited from a SQL query that is quite slow to run (2-2mins20 on average) while the amount of data isn't huge.
I'm not yet much comfortable with SQL but I did attempt to use a temporary table variable in order to have a lookup table to find the region associated with each country. It didn't change anything regarding the total cost of the query (which is 4780, found via an EXPLAIN in DataGrid).
I'm unsure of how I could make it faster besides the creation of the right indexes.
SELECT CASE
WHEN Sites.country IN
('Australia', 'Hong Kong', 'India', 'Korea, Republic of', 'Malaysia', 'New Zealand', 'Philippines',
'Singapore', 'Taiwan', 'Thailand', 'Viet Nam')
THEN 'APAC'
WHEN Sites.country IN ('China')
THEN 'China'
WHEN Sites.country IN
('Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Czech Republic', 'Denmark', 'Estonia', 'Finland',
'France', 'Germany', 'Greece', 'Hungary', 'Ireland', 'Israel', 'Italy', 'Latvia', 'Lebanon',
'Lithuania', 'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania', 'Russian Federation',
'Saudi Arabia', 'Serbia', 'South Africa', 'Spain', 'Sweden', 'Switzerland', 'Turkey', 'Ukraine',
'United Arab Emirates', 'United Kingdom')
THEN 'EMEA'
WHEN Sites.country IN ('Japan')
THEN 'Japan'
WHEN Sites.country IN
('Argentina', 'Brazil', 'Chile', 'Colombia', 'Costa Rica', 'Guatemala', 'Mexico', 'Peru', 'Puerto Rico')
THEN 'Latin America'
WHEN Sites.country IN ('Canada', 'United States')
THEN 'North America'
ELSE '?'
END AS 'Region',
Sites.country AS 'Country',
Protocols.number AS 'Protocol #',
Sites.number AS 'Site #',
VisitReports.finalized AS 'Finalized date',
VisitReports.startDate AS 'Start date',
VisitReports.endDate AS 'End date',
VisitReports.visitMode AS 'Mode',
LEN(REPLACE(VisitReports.remoteVisitDates, ',', '')) / 13 AS '# of remote dates',
VisitReports.onSiteVisitDates AS 'On site dates',
VisitReports.remoteVisitDates AS 'Remote dates',
VisitReports.visitId AS 'Visit ID',
CASE
WHEN R2Answer.data LIKE '%choice_yes%' THEN 'Yes'
WHEN R2Answer.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END AS 'R2',
CASE
WHEN SDR5Answer.data LIKE '%choice_yes%' THEN 'Yes'
WHEN SDR5Answer.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END AS 'SDR5',
CASE
WHEN SDR5_3Answer.data LIKE '%choice_yes%' THEN 'Yes'
WHEN SDR5_3Answer.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END AS 'SDR5_3',
CASE
WHEN SDR6Answer.data LIKE '%choice_yes%' THEN 'Yes'
WHEN SDR6Answer.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END AS 'SDR6',
CASE
WHEN SDR7Answer.data LIKE '%choice_yes%' THEN 'Yes'
WHEN SDR7Answer.data LIKE '%choice_no%' THEN 'No'
WHEN SDR7Answer.data LIKE '%choice_n_a%' THEN 'N/A'
ELSE '-'
END AS 'SDR7',
(SELECT COUNT(*)
FROM [DB].[SCHEMA].ActionItems
WHERE ActionItems.siteId = Sites.id
AND ActionItems.deletedAt IS NULL
AND ActionItems.createdAt > VisitReports.startDate
AND ActionItems.createdAt < VisitReports.finalized) AS '# created ActionItems',
(SELECT COUNT(DISTINCT ActionItems.id)
FROM [DB].[SCHEMA].ActionItemUpdates
JOIN [DB].[SCHEMA].ActionItems
ON ActionItems.id = ActionItemUpdates.actionItemId
AND ActionItems.siteId = Sites.id
WHERE ActionItemUpdates.deletedAt IS NULL
AND ActionItemUpdates.updateType IS NULL
AND ActionItemUpdates.createdAt > VisitReports.startDate
AND ActionItemUpdates.createdAt < VisitReports.finalized) AS '# updated ActionItems',
(SELECT COUNT(*)
FROM [DB].[SCHEMA].ActionItems
WHERE ActionItems.siteId = Sites.id
AND ActionItems.deletedAt IS NULL
AND ActionItems.closed > VisitReports.startDate
AND ActionItems.closed < VisitReports.finalized) AS '# closed ActionItems'
FROM [DB].[SCHEMA].Sites
JOIN [DB].[SCHEMA].Protocols on Protocols.id = Sites.protocolId
JOIN [DB].[SCHEMA].VisitReports ON VisitReports.siteId = Sites.id
AND questionnaireVersion >= 7
AND visitType = 'ongoing'
AND finalized IS NOT NULL
AND VisitReports.endDate > '2022-01-01' AND VisitReports.endDate < '2022-11-11'
LEFT JOIN [DB].[SCHEMA].Answers AS R2Answer ON R2Answer.reportId = VisitReports.id
AND R2Answer.questionId = 'R3'
LEFT JOIN [DB].[SCHEMA].Answers AS SDR5Answer
ON SDR5Answer.reportId = VisitReports.id
AND SDR5Answer.questionId = 'SDR3'
LEFT JOIN [DB].[SCHEMA].Answers AS SDR5_3Answer
ON SDR5_3Answer.reportId = VisitReports.id
AND SDR5_3Answer.questionId = 'SDR3.3'
LEFT JOIN [DB].[SCHEMA].Answers AS SDR6Answer
ON SDR6Answer.reportId = VisitReports.id
AND SDR6Answer.questionId = 'SDR10'
LEFT JOIN [DB].[SCHEMA].Answers AS SDR7Answer
ON SDR7Answer.reportId = VisitReports.id
AND SDR7Answer.questionId = 'SDR4'
ORDER BY [Finalized date]
Thank you in advance for your help!

You can try wth OUTER APPLY and conditional aggregation (CASE WHEN inside the aggregation function).
Something like:
SELECT
...
s.country AS [Country],
p.number AS [Protocol #],
s.number AS [Site #],
vr.finalized AS [Finalized date],
vr.startdate AS [Start date],
vr.enddate AS [End date],
vr.visitmode AS [Mode],
LEN(REPLACE(vr.remotevisitdates, ',', '')) / 13 AS [# of remote dates],
vr.onsitevisitdates AS [On site dates],
vr.remotevisitdates AS [Remote dates],
vr.visitId AS [Visit ID],
ans.[R3] AS [Answer R3],
ans.[SDR3] AS [Answer SDR3],
...
act.created AS [# created ActionItems],
act.updated AS [# updated ActionItems],
act.closed AS [# closed ActionItems]
FROM sites s
JOIN protocols p ON p.id = s.protocolid
JOIN visitreports vr ON vr.siteid = s.id
AND vr.questionnaireversion >= 7
AND vr.visittype = 'ongoing'
AND vr.finalized IS NOT NULL
AND vr.enddate > '2022-01-01'
AND vr.enddate < '2022-11-11'
OUTER APPLY
(
SELECT
COUNT(CASE WHEN ai.deletedat IS NULL AND ai.createdat > vr.startdate AND ai.createdat < vr.finalized THEN 1 END) AS created,
COUNT(CASE WHEN ai.deletedat IS NULL AND ai.closed > vr.startdate AND ai.closed < vr.finalized THEN 1 END) AS closed,
COUNT(CASE WHEN EXISTS
(
SELECT NULL
FROM actionitemupdates aiu
WHERE aiu.actionitemid = ai.id
AND aiu.updateType IS NULL
AND aiu.createdAt > vr.startDate
AND aiu.createdAt < vr.finalized
) THEN 1 END) AS updated
FROM actionitems ai
WHERE ai.siteid = s.id
) act
OUTER APPLY
(
SELECT
MAX(CASE WHEN a.questionId = 'R3' THEN
CASE WHEN a.data LIKE '%choice_yes%' THEN 'Yes'
WHEN a.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END
END) AS [R3],
MAX(CASE WHEN a.questionId = 'SDR3' THEN
CASE WHEN a.data LIKE '%choice_yes%' THEN 'Yes'
WHEN a.data LIKE '%choice_no%' THEN 'No'
ELSE '-'
END
END) AS [SDR3],
...
FROM answers a
WHERE a.reportid = vr.id
) ans
ORDER BY [Finalized date];
In any way you should have indexes on answers(reportid) and on actionitems(siteid) to be able to join the answers and action types quickly.
In order to access the YES/NO/- directly, I suggest you add a computed column to your answers table. That will simplyfy above query.
ALTER TABLE answers ADD yes_or_no AS (CASE WHEN data LIKE '%choice_yes%' THEN 'Yes' WHEN data LIKE '%choice_no%' THEN 'No' ELSE '-' END) PERSISTED;
UPDATE
Due to two shortcomings in SQL Server, the first OUTER APPLY must be split into two steps, otherwise you'll get syntax errors:
OUTER APPLY
(
SELECT
SUM(is_created) AS created,
SUM(is_closed) AS closed,
SUM(is_updated) AS updated
FROM
(
SELECT
CASE WHEN ai.deletedat IS NULL AND ai.createdat > vr.startdate AND ai.createdat < vr.finalized THEN 1 ELSE 0 END AS is_created,
CASE WHEN ai.deletedat IS NULL AND ai.closed > vr.startdate AND ai.closed < vr.finalized THEN 1 ELSE 0 END AS is_closed,
CASE WHEN EXISTS (SELECT NULL
FROM actionitemupdates aiu
WHERE aiu.actionitemid = ai.id
AND aiu.updateType IS NULL
AND aiu.createdAt > vr.startDate
AND aiu.createdAt < vr.finalized
) THEN 1 ELSE 0 END AS is_updated
FROM actionitems ai
WHERE ai.siteid = s.id
) precomputed
) act

As you stated, a Countries region lookup table makes the code cleaner. Besides that, I would look for opportunities to reduce data volume from Answers and ActionItemUpdates if the table size is significant. After that, aggregate Answers to reportId level; ActionItems and ActionItemUpdates to siteId level. Since visitType='ongoing', it seems that it's 1-to-1 between the two results. The last step is to join them together to get a site-level report.
with cte_visitReports as (
select id,
siteId,
finalized,
startDate,
endDate,
visitMode,
visitId,
onSiteVisitDates,
remoteVisitDates
from VisitReports
where endDate > '2022-01-01'
and endDate < '2022-11-11'
and questionnaireVersion >= 7
and visitType = 'ongoing'
and finalized IS NOT NULL),
cte_answers as (
select a.reportId,
max(case
when a.questionId='R3' and a.data like '%choice_yes%' then 'Yes'
when a.questionId='R3' and a.data like '%choice_no%' then 'No'
else '-'
end) as R2,
max(case
when a.questionId='SDR3' and a.data like '%choice_yes%' then 'Yes'
when a.questionId='SDR3' and a.data like '%choice_no%' then 'No'
else '-'
end) as SDR5,
max(case
when a.questionId='SDR3.3' and a.data like '%choice_yes%' then 'Yes'
when a.questionId='SDR3.3' and a.data like '%choice_no%' then 'No'
else '-'
end) as SDR5_3,
max(case
when a.questionId='SDR10' and a.data like '%choice_yes%' then 'Yes'
when a.questionId='SDR10' and a.data like '%choice_no%' then 'No'
else '-'
end) as SDR6,
max(case
when a.questionId='SDR4' and a.data like '%choice_yes%' then 'Yes'
when a.questionId='SDR4' and a.data like '%choice_no%' then 'No'
when a.questionId='SDR4' and a.data like '%choice_n_a%' then 'N/A'
else '-'
end) as SDR7
from Answers a
join cte_visitReports r
on a.reportId = r.id
where a.questionId in ('R3','SDR3','SDR3.3','SDR10','SDR4')
-- additional filters such as createdAt if available to reduce data volume
group by a.reportId),
cte_action_items as (
select ai.siteId,
count(distinct case when ai.deletedAt is null and ai.createdAt > r.startDate and ai.createdAt < r.finalized then ai.id end) as created_action_items,
count(distinct case when ai.deletedAt is null and ai.closed > r.startDate and ai.closed < r.finalized then ai.id end) as closed_action_items,
count(distinct case when iu.deletedAt is null and iu.createdAt > r.startDate and iu.createdAt < r.finalized then ai.id end) as updated_action_items
from ActionItems ai
join cte_visitReports r
on ai.siteId = r.siteId
left
join ActionItemUpdates iu
on ai.id = iu.actionItemId
where 1 = 1
-- additional filters if possible ro reduce data volume
group by ai.siteId)
select coalesce(c.region,'?') as 'Region',
s.country as 'Country',
p.number as 'Protocol #',
s.number as 'Site #',
r.finalized as 'Finalized date',
r.startDate as 'Start date',
r.endDate as 'End Date',
r.visitMode as 'Mode',
len(replace(r.remoteVisitDates, ',', '')) / 13 as '# of remote dates',
r.onSiteVisitDates as 'On site dates',
r.remoteVisitDates as 'Remote dates',
r.visitId as 'Visit ID',
coalesce(a.R2,'-') as 'R2',
coalesce(a.SDR5,'-') as 'SDR5',
coalesce(a.SDR5_3,'-') as 'SDR5_3',
coalesce(a.SDR6,'-') as 'SDR6',
coalesce(a.SDR7,'-') as 'SDR7',
coalesce(ai.created_action_items,0) as '# created ActionItems',
coalesce(ai.updated_action_items,0) as '# updated ActionItems',
coalesce(ai.closed_action_items,0) as '# closed ActionItems'
from Sites s
left
join Countries c
on s.country = c.country
join Protocols p
on s.protocolId = p.id
join cte_visitReports r
on s.id = r.siteId
left
join cte_answers a
on r.id = a.reportId
left
join cte_action_items ai
on s.id = ai.siteId
order by r.finalized;
If the report response time is critical, I would try the following approaches:
Tune the queries
Cache the result (ie. materialized views)
Create a datamart for reports and dashboards

Related

Can I make multiple records display values within the same record

I am using the following query to display records of customers and their disabilites. The query works, but the results show multiple records for the same case and customer where they have multiple disabilities. Is there a way to include all disabilites under one record, so it does not duplicate the entire record for a minor change in the data?
The following is my query in it's current state:
USE MyPersonalSupport_reporting
SELECT
SC.Name AS 'Sub-Contract',
CSCH.Received AS LiveDate,
CS.ServiceEndDate AS ServiceEndDate,
CS.CaseReference,
CONTACT.FirstName AS 'Forename',
CONTACT.LastName AS 'Surname',
CONTACT.DateOfBirth AS DOB,
CONTACT.DateOfDeath,
CONTACT.Age,
CCAV.ConcatenatedAddress AS 'Full Address',
LK1.Value AS Ethnicity,
LK2.Value AS Sex,
LK3.Value AS Religion,
LK4.Value AS Sexuality,
LK5.Value AS Transgender,
LK6.Value AS Nationality,
LK7.Value AS 'First Language',
SO.Name AS ServiceOffering,
LK.Value AS CaseStatus,
DATEDIFF(day, CSCH.Received, CS.serviceenddate) AS 'Days Occupied',
CONCAT (EMP.FirstName, ' ' , EMP.LastName) AS KeyWorker,
CASE WHEN CONTACT.HasDisibility = 1 THEN 'YES' ELSE 'NO' END AS HasDisability,
CASE WHEN DV.value = 'Autistic Spectrum Condition' THEN 'YES' ELSE 'NO' END AS AutisticSpectrumCondition,
CASE WHEN DV.value = 'Hearing Impairment' THEN 'YES' ELSE 'NO' END AS 'Hearing Impairment',
CASE WHEN DV.value = 'Learning Disability' THEN 'YES' ELSE 'NO' END AS 'Learning Disability',
CASE WHEN DV.value = 'Mental Health' THEN 'YES' ELSE 'NO' END AS 'Mental Health',
CASE WHEN DV.value = 'Mobility Disability' THEN 'YES' ELSE 'NO' END AS 'Mobility Disability',
CASE WHEN DV.value = 'Progressive Disability / Chronic Illness' THEN 'YES' ELSE 'NO' END AS 'Progressive Disability / Chronic Illness',
CASE WHEN DV.value = 'Visual Impairment' THEN 'YES' ELSE 'NO' END AS 'Visual Impairment',
CASE WHEN DV.value = 'Other' THEN 'YES' ELSE 'NO' END AS 'Other Disability',
CASE WHEN DV.value = 'Does not wish to disclose' THEN 'YES' ELSE 'NO' END AS 'Does not wish to disclose'
FROM [MyPersonalSupport_reporting].[Mps].[Cases] AS CS
INNER JOIN mps.CaseContracts AS CC ON CS.caseid = CC.caseid
INNER JOIN mps.CaseStatusChangeHistories AS CSCH ON CS.CaseId = CSCH.CaseId
INNER JOIN mps.Contacts AS CONTACT ON CS.CustomerId = CONTACT.ContactId
FULL OUTER JOIN mps.ContactCurrentAddress AS CCAV ON CONTACT.ContactID = CCAV.ContactId
FULL OUTER JOIN mps.LookupItems AS LK ON CSCH.StatusId = LK.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK1 ON CONTACT.EthnicityId = LK1.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK2 ON CONTACT.SexId = LK2.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK3 ON CONTACT.ReligionId = LK3.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK4 ON CONTACT.SexualityId = LK4.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK5 ON CONTACT.TransgenderId = LK5.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK6 ON CONTACT.NationalityId = LK6.LookupItemId
FULL OUTER JOIN mps.LookupItems AS LK7 ON CONTACT.FirstLanguageId = LK7.LookupItemId
FULL OUTER JOIN mps.SubContracts AS SC ON CC.SubContractId = SC.SubContractId
FULL OUTER JOIN mps.ServiceOfferings AS SO ON SC.ServiceOfferingId = SO.ServiceOfferingId
FULL OUTER JOIN mps.Employees AS EMP ON EMP.EmployeeId = CS.KeyWorkerId
FULL OUTER JOIN dbo.disabilitiescrosstab AS DV ON CONTACT.ContactId = DV.EntityID
WHERE
CSCH.Received >= '2000-01-01' AND CS.ServiceEndDate <= GETDATE()
AND CSCH.StatusId = 1392
AND CSCH.Archived = 0
AND CONTACT.Archived = 0
ORDER BY CS.CaseId
Although I don't know your use case exactly but I believe there is no need to do full outer join, you can get away with left join / inner join.
Step 1: You can use STRING_AGG for the "DisibilityName" column. Aggregate all disease for each entity ID
SELECT EntityID, STRING_AGG(DV_VALUE,",")
FROM
dbo.disabilitiescrosstab
GROUP BY EntityID
Step 2: then join aggregated table with your base table
Code(have removed few joins but the idea is same):
SELECT
SC.Name AS 'Sub-Contract',
CSCH.Received AS LiveDate,
CS.ServiceEndDate AS ServiceEndDate,
CS.CaseReference,
CONTACT.FirstName AS 'Forename',
CONTACT.LastName AS 'Surname',
CONTACT.DateOfBirth AS DOB,
CONTACT.DateOfDeath,
CONTACT.Age,
CCAV.ConcatenatedAddress AS 'Full Address',
DV.VALUE AS DisibilityName
FROM [MyPersonalSupport_reporting].[Mps].[Cases] AS CS
LEFT JOIN mps.CaseContracts AS CC ON CS.caseid = CC.caseid
LEFT JOIN mps.CaseStatusChangeHistories AS CSCH ON CS.CaseId = CSCH.CaseId
LEFT JOIN mps.Contacts AS CONTACT ON CS.CustomerId = CONTACT.ContactId
LEFT JOIN mps.ContactCurrentAddress AS CCAV ON CONTACT.ContactID = CCAV.ContactId
LEFT JOIN (SELECT EntityID, STRING_AGG(DV_VALUE,",") FROM dbo.disabilitiescrosstab GROUP BY EntityID) AS DV ON CONTACT.ContactId = DV.EntityID
WHERE
CSCH.Received >= '2000-01-01' AND CS.ServiceEndDate <= GETDATE()
AND CSCH.StatusId = 1392
AND CSCH.Archived = 0
AND CONTACT.Archived = 0
ORDER BY CS.CaseId
It appears that what you need is a two-part query. Once to get each "contact" person with their disabilities already pre-aggregated with all disabilities so the result is a single record per contact. THEN join with the cases. Additionally, you have FULL OUTER JOINS for your lookup tables. Its not like someone would be allowed to have multiple lookup values for ethnicity, sex, religion, sexuality -- would they? But in today's age of associating with things, possible? But I think the intent is that a person can only be classified into one category otherwise you would have multiple records for every combination of ethnicity, sexuality, transgender, etc... So, lets take the lookups as intended as only a single value would ever exist. These should just be INNER JOINS to the corresponding lookup table, not FULL OUTER joins.
Notice this query is nothing but associated with the contact and its corresponding lookup tables / disabilities and grouped by just the contact ID. So it will result in a single record no matter how many disablities per contact.
SELECT
CONTACT.ContactId,
max( CONTACT.FirstName ) 'Forename',
max( CONTACT.LastName ) 'Surname',
max( CONTACT.DateOfBirth ) DOB,
max( CONTACT.DateOfDeath ) DateOfDeath,
max( CONTACT.Age ) Age,
max( CCAV.ConcatenatedAddress ) 'Full Address',
max( LK1.Value ) Ethnicity,
max( LK2.Value ) Sex,
max( LK3.Value ) Religion,
max( LK4.Value ) Sexuality,
max( LK5.Value ) Transgender,
max( LK6.Value ) Nationality,
max( LK7.Value ) 'First Language',
max( CASE WHEN CONTACT.HasDisibility = 1 THEN 'YES' ELSE 'NO' END ) HasDisability,
max( CASE WHEN DV.value = 'Autistic Spectrum Condition' THEN 'YES' ELSE 'NO' END ) AutisticSpectrumCondition,
max( CASE WHEN DV.value = 'Hearing Impairment' THEN 'YES' ELSE 'NO' END ) 'Hearing Impairment',
max( CASE WHEN DV.value = 'Learning Disability' THEN 'YES' ELSE 'NO' END ) 'Learning Disability',
max( CASE WHEN DV.value = 'Mental Health' THEN 'YES' ELSE 'NO' END ) 'Mental Health',
max( CASE WHEN DV.value = 'Mobility Disability' THEN 'YES' ELSE 'NO' END ) 'Mobility Disability',
max( CASE WHEN DV.value = 'Progressive Disability / Chronic Illness' THEN 'YES' ELSE 'NO' END ) 'Progressive Disability / Chronic Illness',
max( CASE WHEN DV.value = 'Visual Impairment' THEN 'YES' ELSE 'NO' END ) 'Visual Impairment',
max( CASE WHEN DV.value = 'Other' THEN 'YES' ELSE 'NO' END ) 'Other Disability',
max( CASE WHEN DV.value = 'Does not wish to disclose' THEN 'YES' ELSE 'NO' END ) 'Does not wish to disclose'
FROM
mps.Contacts AS CONTACT
JOIN dbo.disabilitiescrosstab AS DV
ON CONTACT.ContactId = DV.EntityID
JOIN mps.ContactCurrentAddress AS CCAV
ON CONTACT.ContactID = CCAV.ContactId
JOIN mps.LookupItems AS LK1
ON CONTACT.EthnicityId = LK1.LookupItemId
JOIN mps.LookupItems AS LK2
ON CONTACT.SexId = LK2.LookupItemId
JOIN mps.LookupItems AS LK3
ON CONTACT.ReligionId = LK3.LookupItemId
JOIN mps.LookupItems AS LK4
ON CONTACT.SexualityId = LK4.LookupItemId
JOIN mps.LookupItems AS LK5
ON CONTACT.TransgenderId = LK5.LookupItemId
JOIN mps.LookupItems AS LK6
ON CONTACT.NationalityId = LK6.LookupItemId
JOIN mps.LookupItems AS LK7
ON CONTACT.FirstLanguageId = LK7.LookupItemId
WHERE
-- put the ARCHIVED FILTER HERE so it only includes active vs everyone in the system.
CONTACT.Archived = 0
GROUP BY
CONTACT.ContactId
Now, you can take the above query and use IT as a sub-select query to get its parts in the final
SELECT
SC.Name AS 'Sub-Contract',
CSCH.Received AS LiveDate,
CS.ServiceEndDate AS ServiceEndDate,
CS.CaseReference,
OneContact.Forename,
OneContact.Surname,
OneContact.DOB,
OneContact.DateOfDeath,
OneContact.Age,
OneContact.[Full Address],
OneContact.Ethnicity,
OneContact.Sex,
OneContact.Religion,
OneContact.Sexuality,
OneContact.Transgender,
OneContact.Nationality,
OneContact.[First Language],
SO.Name AS ServiceOffering,
LK.Value AS CaseStatus,
DATEDIFF(day, CSCH.Received, CS.serviceenddate) AS 'Days Occupied',
CONCAT (EMP.FirstName, ' ' , EMP.LastName) AS KeyWorker,
OneContact.HasDisability,
OneContact.AutisticSpectrumCondition,
OneContact.[Hearing Impairment],
OneContact.[Learning Disability],
OneContact.[Mental Health],
OneContact.[Mobility Disability],
OneContact.[Progressive Disability / Chronic Illness],
OneContact.[Visual Impairment],
OneContact.[Other Disability],
OneContact.[Does not wish to disclose]
FROM
Mps.Cases AS CS
INNER JOIN mps.CaseContracts AS CC
ON CS.caseid = CC.caseid
INNER JOIN mps.CaseStatusChangeHistories AS CSCH
ON CS.CaseId = CSCH.CaseId
INNER JOIN
( complete first query above) OneContact
ON CS.CustomerId = OneContact.ContactId
JOIN mps.LookupItems LK
ON CSCH.StatusId = LK.LookupItemId
JOIN mps.SubContracts SC
ON CC.SubContractId = SC.SubContractId
JOIN mps.ServiceOfferings SO
ON SC.ServiceOfferingId = SO.ServiceOfferingId
JOIN mps.Employees EMP
ON EMP.EmployeeId = CS.KeyWorkerId
WHERE
CSCH.Received >= '2000-01-01'
AND CS.ServiceEndDate <= GETDATE()
AND CSCH.StatusId = 1392
AND CSCH.Archived = 0
ORDER BY
CS.CaseId
Now, one suggestion for column names. I do not recommend having final column names with any spaces or special characters such as you have, especially with the ones like [Progressive Disability / Chronic Illness], and [Does not wish to disclose]. They should be normal, no spaces. The formatting / output should be handled by the output process to put in such headers and such. More headache when you need to explicitly tack quotes/brackets around column names, especially if you miss spaces. Instead, have column name as: ProgressiveDisabilityChronicIllness and ChronicIllness. Your output routines can split the column headings.
One additional point. The first query will process ALL contacts regardless of the cases. But the cases will only pull those that it qualifies for. If you find your query is taking too much time because of ALL contacts, you can update that by limiting the contacts by those cases it qualifies for. At least when querying the contacts, I did include the archived = 0 status there.

CASE statement in the ORDER BY CLAUSE

SQL SERVER
I'm attempting to sort records in my ORDER BY clause in an exact manner. So the records should be sorted in the following manner. I think my issue might be the CASE STATEMENT syntax, but I can't seem to find anything telling me that it's wrong, other than the code not running.
od.Status
Firm,
In Process,
Released,
Everything Else
I believed I could assign each type of record a number, and then sort those numbers.
The code below gives me "ORDER BY items must appear in the select list if SELECT DISTINCT is specified"
SELECT DISTINCT
oh.Order_Number AS Order_Number,
oh.Status AS Order_Status,
oh.Customer_Name AS Customer_Name,
vsc.Salesman_Name AS Salesman_Name,
vsc.Email_Address AS Email_Address,
od.Work_Code AS Work_Code,
od.Product_Code AS Product_Code,
CONVERT(char(10),od.Projected_Ship_Date,101) AS Projected_Ship_Date,
CONVERT(char(10),od.Due_Date,101) AS OD_Due_Date,
format(oh.Gross_Amount, '$#,##0.##') AS Gross_Amount,
DATEDIFF(DAY,oh.Order_Date,'{%Current Date%}') AS DIP,
od.Part_Number AS Part_Number,
od.Status AS Status,
CAST(qd.Delivery_Notes AS NVARCHAR(MAX)) AS Delivery_Notes
FROM
dbo.Order_Header oh LEFT OUTER JOIN dbo.Commission_Distribution cd ON oh.Order_Header_ID = cd.Order_Header_ID LEFT OUTER JOIN
dbo.vSalesman_Code vsc ON cd.Salesman_Code = vsc.Salesman_Code JOIN
dbo.Order_Detail od ON od.Order_Header_ID = oh.Order_Header_ID JOIN
dbo.Quotation_Detail qd ON od.Quotation_Detail_ID = qd.Quotation_Detail_ID JOIN
dbo.Quotation_Header qh ON qd.Quotation_Header_ID = qh.Quotation_Header_ID
WHERE
oh.Status = 'Open' AND
cd.Company_Code = 'AIN' AND
oh.Customer_Name NOT IN ( 'A.I. Innovations' , 'AI PROPERTIES Fortville LLC' , 'AI-IN Intercompany' , 'AI-NC Intercompany' ) AND
od.Status <> 'Closed' AND
LEFT(od.Part_Number, 3) <> 'MTS' AND
vsc.Salesman_Name NOT IN ( 'House' , 'House Accounts' ) AND
od.Status <> 'Hold' AND
od.Product_Code NOT LIKE '%PROCES%' AND
od.Product_Code NOT LIKE '%VISTA WARRANT%'
ORDER BY
CASE
WHEN od.Status = 'Firm' THEN 1
WHEN od.Status = 'In Process' THEN 2
WHEN od.Status = 'Released' THEN 3
ELSE 4
END,
vsc.Email_Address ASC,
CONVERT(char(10),od.Projected_Ship_Date,101) ASC
Any help on this would be appreciated. I haven't been able to find very much on this issue. Most issues I've found want to sort one set DESC, and another set ASC, but not in a particular order.
Thanks
Just updated your query:
SELECT DISTINCT
oh.Order_Number AS Order_Number,
oh.Status AS Order_Status,
oh.Customer_Name AS Customer_Name,
vsc.Salesman_Name AS Salesman_Name,
vsc.Email_Address AS Email_Address,
od.Work_Code AS Work_Code,
od.Product_Code AS Product_Code,
CONVERT(char(10),od.Projected_Ship_Date,101) AS Projected_Ship_Date,
CONVERT(char(10),od.Due_Date,101) AS OD_Due_Date,
format(oh.Gross_Amount, '$#,##0.##') AS Gross_Amount,
DATEDIFF(DAY,oh.Order_Date,'{%Current Date%}') AS DIP,
od.Part_Number AS Part_Number,
od.Status AS Status,
CAST(qd.Delivery_Notes AS NVARCHAR(MAX)) AS Delivery_Notes,
CASE
WHEN od.Status = 'Firm' THEN 1
WHEN od.Status = 'In Process' THEN 2
WHEN od.Status = 'Released' THEN 3
ELSE 4
END As StatusOrderId
FROM
dbo.Order_Header oh LEFT OUTER JOIN dbo.Commission_Distribution cd ON
oh.Order_Header_ID = cd.Order_Header_ID LEFT OUTER JOIN
dbo.vSalesman_Code vsc ON cd.Salesman_Code = vsc.Salesman_Code JOIN
dbo.Order_Detail od ON od.Order_Header_ID = oh.Order_Header_ID JOIN
dbo.Quotation_Detail qd ON od.Quotation_Detail_ID = qd.Quotation_Detail_ID JOIN
dbo.Quotation_Header qh ON qd.Quotation_Header_ID = qh.Quotation_Header_ID
WHERE
oh.Status = 'Open' AND
cd.Company_Code = 'AIN' AND
oh.Customer_Name NOT IN ( 'A.I. Innovations' , 'AI PROPERTIES Fortville LLC' , 'AI-IN Intercompany' , 'AI-NC Intercompany' ) AND
od.Status <> 'Closed' AND
LEFT(od.Part_Number, 3) <> 'MTS' AND
vsc.Salesman_Name NOT IN ( 'House' , 'House Accounts' ) AND
od.Status <> 'Hold' AND
od.Product_Code NOT LIKE '%PROCES%' AND
od.Product_Code NOT LIKE '%VISTA WARRANT%'
ORDER BY
CASE
WHEN od.Status = 'Firm' THEN 1
WHEN od.Status = 'In Process' THEN 2
WHEN od.Status = 'Released' THEN 3
ELSE 4
END,
vsc.Email_Address ASC,
CONVERT(char(10),od.Projected_Ship_Date,101) ASC
The problem is that the "case" statement in your order by must actually appear as a column in the select statement because you are doing a select distinct. Here is an example, the first query is invalid, the second works
select distinct
tableName = t.name
from sys.tables t
order by case
when t.name like '%something%' then 1
else 2
end;
select distinct
orderingColumn = case
when t.name like '%something%' then 1
else 2
end,
tableName = t.name
from sys.tables t
order by case
when t.name like '%something%' then 1
else 2
end
The problem here is that the orderingColumn will be returned by the select statement, and it seems like you don't want that, but that's easily fixed by a CTE or subquery:
with MyQuery as
(
select distinct
orderingColumn = case
when t.name like '%something%' then 1
else 2
end,
tableName = t.name
from sys.tables t
)
select tableName from MyQuery order by orderingColumn;

sql join not taking all records from another table

I have a query like this
WITH CTE AS
(
SELECT
U.Name, U.Adluserid AS 'Empid',
MIN(CASE WHEN IOType = 0 THEN Edatetime END) AS 'IN',
MAX(CASE WHEN IOType = 1 THEN Edatetime END) AS 'out',
(CASE
WHEN MAX(E.Status) = 1 THEN 'AL'
WHEN MAX(E.Status) = 2 THEN 'SL'
ELSE 'L'
END) AS leave_status
FROM
Mx_ACSEventTrn
RIGHT JOIN
Mx_UserMst U ON Mx_ACSEventTrn.UsrRefcode = U.UserID
LEFT JOIN
Tbl_Zeo_Empstatus E ON Mx_ACSEventTrn.UsrRefcode = E.Emp_Id
WHERE
CAST(Edatetime AS DATE) BETWEEN '2019-11-03' AND '2019-11-03'
GROUP BY
U.Name, U.Adluserid
)
SELECT
[Name], [Empid], [IN], [OUT],
(CASE
WHEN CAST([IN] AS TIME) IS NULL THEN CAST(leave_status AS NVARCHAR(50))
WHEN CAST([IN] AS TIME) < CAST('08:15' AS TIME) THEN 'P'
ELSE 'L'
END) AS status
FROM
CTE
In my employee master table Mx_UserMst I have 67 employees. But here it is showing only a few employees those who are punched. I want to show all employees from employee master
I believe that the problem is his WHERE clause:
where cast(Edatetime as date) between '2019-11-03' and '2019-11-03'
Why not cast(Edatetime as date) = '2019-11-03'?
I'm not sure in which table the column Edatetime belongs (you should qualify all the columns with the correct table name/alias).
You must move the condition to an ON clause:
WITH CTE AS
(
select U.Name,U.Adluserid as 'Empid',
min(case when IOType=0 then Edatetime end) as 'IN',
max(case when IOType=1 then Edatetime end) as 'out',
case max(E.Status) when 1 then 'AL' when 2 then 'SL' else 'L' end as leave_status
from Mx_UserMst U
left join Mx_ACSEventTrn on Mx_ACSEventTrn.UsrRefcode=U.UserID and (cast(Edatetime as date) between '2019-11-03' and '2019-11-03')
left join Tbl_Zeo_Empstatus E on Mx_ACSEventTrn.UsrRefcode=E.Emp_Id
group by U.Name,U.Adluserid
)
SELECT [Name], [Empid],[IN],[OUT],
case
when cast([IN] as time) is null then cast(leave_status as nvarchar(50))
when cast([IN] as time) < cast('08:15' as time) then 'P'
else 'L'
end as status
FROM CTE
If Edatetime belongs to Tbl_Zeo_Empstatus move the condition to the next join's ON clause.
I also changed the RIGHT to a LEFT join so to make the statement more readable.
If you want to keep everything in a particular table, then that should be the first table in the FROM clause. Subsequent joins should be LEFT JOINs and conditions on subsequent tables should be in the ON clause rather than the WHERE clause.
I would also advise you to use table aliases and to only use single quotes for string and date constants -- NOT column aliases.
The following assumes that IOType and Edatetime are in the table Mx_ACSEventTrn. I should not have to guess. You should qualify all column names in the query.
WITH CTE AS (
SELECT U.Name, U.Adluserid AS Empid,
MIN(CASE WHEN AE.IOType = 0 THEN AE.Edatetime END) AS in_dt,
MAX(CASE WHEN AE.IOType = 1 THEN AE.Edatetime END) AS out_dt,
(CASE WHEN MAX(ES.Status) = 1 THEN 'AL'
WHEN MAX(ES.Status) = 2 THEN 'SL'
ELSE 'L'
END) AS leave_status
FROM Mx_UserMst U LEFT JOIN
Mx_ACSEventTrn AE
ON AE.UsrRefcode = U.UserID AND
CAST(AE.Edatetime AS DATE) BETWEEN '2019-11-03' AND '2019-11-03' LEFT JOIN
Tbl_Zeo_Empstatus ES
ON AE.UsrRefcode = ES.Emp_Id AND
GROUP BY U.Name, U.Adluserid
)
SELECT Name, Empid, IN_DT, OUT_DT,
(CASE WHEN IN_DT IS NULL THEN leave_status
WHEN CAST(IN_DT AS TIME) < CAST('08:15' AS TIME) THEN 'P'
ELSE 'L'
END) AS status
FROM CTE;
Some more points:
Don't name aliases things like IN that are already key words. That is why I gave it the name IN_DT.
There is no reason to cast to a TIME to compare to NULL.
I don't see a reason to cast to NVARCHAR(50) in the outer CASE expression.

Selecting the union part of an sql query

I have this query
SELECT
L.Account,
L.PaidOffDate
FROM
MARS.dbo.vw_Loans L
WHERE
L.isActive = 'False'
AND L.LoanStatus NOT LIKE '%REO%'
AND L.LoanStatus <> 'Trailing Claims'
)
UNION
-- loan numbers for REO sold
(
SELECT
L.Account,
R.SoldDate
FROM
MARS.dbo.vw_REO R
LEFT JOIN
MARS.dbo.vw_Loans L ON L.ConvertedToPropertyID = R.PropertyID
WHERE
R.Active = 0
AND R.Status = 'Sold'
AND L.isActive = 'False'
AND L.LoanStatus LIKE '%REO%'
)
I put this as a common table expression, how do I select R.SoldDate in that case?
Specifically I am trying to run this
CASE
WHEN Inactives.PaidOffDate IS NOT NULL
AND Inactives.PaidOffDate BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, #reportingDate), -1) AND #reportingDate
THEN Inactives.PaidOffDate
ELSE
CASE
WHEN Inactives.SoldDate IS NOT NULL
AND Inactives.SoldDate BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, #reportingDate), -1) AND #reportingDate
THEN Inactives.SoldDate
ELSE ''
END
END AS [CHECKING PIF DATE]
But I cannot select Inactives.SoldDate
I think what you are after here is a computed column, something like this:
SELECT
L.Account,
L.PaidOffDate,
'Payoff Date' AS Type
FROM MARS.dbo.vw_Loans L
WHERE
L.isActive = 'False' AND
L.LoanStatus NOT LIKE '%REO%' AND
L.LoanStatus <> 'Trailing Claims'
UNION ALL
SELECT
L.Account,
R.SoldDate,
'Sale Date'
FROM MARS.dbo.vw_REO R
LEFT JOIN MARS.dbo.vw_Loans L
ON L.ConvertedToPropertyID = R.PropertyID
WHERE
R.Active = 0 AND
R.Status = 'Sold' AND
L.isActive = 'False' AND
L.LoanStatus LIKE '%REO%';
The computed column Type keeps track of the origin of each record, so that you will know this after the union has been peformed. Note that I switched to UNION ALL, assuming that you always wanted to retain all records.
You need to put the values into separate columns rather than separate *rows. I think the logic you want is conditional logic:
SELECT L.Account,
(CASE WHEN L.LoanStatus NOT LIKE '%REO%' AND L.LoanStatus <> 'Trailing Claims'
THEN L.PaidOffDate
END) as PaidOffDate
(CASE WHEN L.LoanStatus LIKE '%REO%'
THEN R.SoldDate
END) as R.SoldDate
FROM MARS.dbo.vw_Loans L LEFT JOIN
MARS.dbo.vw_REO R
ON L.ConvertedToPropertyID = R.PropertyID AND
R.Active = 0 AND R.Status = 'Sold'
WHERE L.isActive = 'False';
The other possibility is that you actually want two separate CTEs, one for each part of the query.

Incorrect results using parameter in SSRS

I have an SSRS report for which I created two datasets for. One for the actual report and one for the parameter.
Dataset 1 is the report
Dataset 2 simply pulls the parameter that I want to report on from the table where it is stored.
When I run the report and select the parameter, it shows me all the values, but when I select one, I still get every record rather than just the records based on the parameter.
Dataset 1
SELECT stk.ItemCode AS ItemCode, stk.warehouse AS warehouse,
stk.Description AS Description, stk.Assortment AS Assortment,
Items.Class_02, stk.ItemGroup AS ItemGroup, stk.ItemStatus AS ItemStatus,
stk.ItemUnit AS ItemUnit, ISNULL((SELECT TOP 1 dbo.bacoSalesPrice(p.ID,
0) FROM staffl p WHERE ({d '2019-05-27'} BETWEEN validfrom AND
ISNULL(validto,{d '9999-12-31'}))
AND prijslijst = 'SALESPRICE' AND Items.ItemCode = artcode ORDER BY
artcode, validfrom desc),0) AS SalesPackagePrice,
ROUND((stk.Stock/stk.SalesPkg),2) AS Stock, ROUND((stk.Stock +
stk.QtyToBeReceived - stk.QtyToBeDelivered)/stk.SalesPkg,2) AS
AvailableStock, stk.SearchCode AS SearchCode FROM Items INNER JOIN (
SELECT
v.magcode AS warehouse,
MAX(i.itemcode) AS ItemCode,
MAX(i.description_0) AS Description,
i.SearchCode AS SearchCode,
MAX(ia.Assortment) AS Assortment,
MAX(ia.Description_0) AS ItemGroup,
MAX(CASE
WHEN i.condition = 'A' THEN 'Active'
WHEN i.condition = 'B' THEN 'Blocked'
WHEN i.condition = 'D' THEN 'Discontinued'
WHEN i.condition = 'E' THEN 'Inactive'
WHEN i.condition = 'F' THEN 'Future'
END) AS ItemStatus,
ISNULL(MAX(a.purchasepackage), '-') AS ItemUnit,
MAX(i.SalesPackagePrice) AS SalesPackagePrice,
MAX(v.bestniv) AS Minimum,
MAX(v.maxvrd) AS Maximum,
MAX(i.lev_crdnr) AS Supplier,
MAX(a.DeliveryTimeInDays) AS DeliveryTime,
MAX(ISNULL(a.SlsPkgsPerPurPkg,1)) AS SalesPkg,
ISNULL(Actual.Quantity,0) AS Stock
,ISNULL(SUM(QtyToReceived),0) AS QtyToBeReceived
,ISNULL(SUM(QtyToBeDelivered),0) AS QtyToBeDelivered
FROM items i
JOIN voorrd v ON i.itemcode=v.artcode
LEFT JOIN itemaccounts a ON i.itemcode=a.itemcode and i.lev_crdnr=a.crdnr
AND i.itemcode IS NOT NULL AND a.itemcode IS NOT NULL AND i.lev_crdnr IS N
NOT NULL AND a.crdnr IS NOT NULL
JOIN ItemAssortment ia ON ia.Assortment = i.Assortment
LEFT OUTER JOIN
dbo.TempToBeReceivedDelivered#3E5BBBE6#FB7B#4309#9CE1#560885B9BF94# budget
ON v.magcode = budget.warehouse AND i.ItemCode = budget.artcode
AND v.magcode IS NOT NULL AND budget.warehouse IS NOT NULL AND i.ItemCode IS NOT NULL AND budget.artcode IS NOT NULL
LEFT OUTER JOIN
(SELECT GX.artcode, GX.warehouse,
GX.Quantity,
GX.AmtActualStock, GX.TotalCnt,
CASE WHEN GX.FreeQuantity > GX.CurrentQuantity THEN (CASE WHEN GX.CurrentQuantity < 0 THEN 0 ELSE GX.CurrentQuantity END) ELSE
(CASE WHEN GX.FreeQuantity < 0 THEN 0 ELSE GX.FreeQuantity END) END As FreeQuantity
FROM (
SELECT sb.ItemCode AS artcode, SUM(CASE WHEN sb.Date <= {d '2019-05-27'} THEN sb.Quantity END) as Quantity,
ROUND(SUM(CASE WHEN sb.Date <= {d '2019-05-27'} THEN sb.StockAmount END),2) as AmtActualStock,
SUM(CASE WHEN sb.Date <= {d '2019-05-27'} THEN sb.Quantity END) as CurrentQuantity,
SUM(CASE WHEN sb.Date <= {d '2019-05-27'} THEN sb.FreeStock END) AS FreeQuantity, sb.Warehouse,
SUM(sb.GbkmutCount) AS TotalCnt
FROM StockBalances sb WITH (NOLOCK) JOIN Items ON sb.ItemCode = Items.ItemCode JOIN ItemAssortment ON Items.Assortment = ItemAssortment.Assortment
--WHERE Items.Class_02 in #Customer
GROUP BY sb.ItemCode, sb.WareHouse, Items.Class_02
HAVING SUM(sb.GbkmutCount) > 0) GX) AS Actual
ON Actual.artcode = i.ItemCode AND Actual.warehouse = v.magcode AND Actual.artcode IS NOT NULL AND i.ItemCode IS NOT NULL AND Actual.warehouse IS NOT NULL AND v.magcode IS NOT NULL
WHERE i.type IN ('S', 'B')
AND i.ItemCode BETWEEN '0030122186' AND 'XS45000'
AND i.Condition IN ('A')
AND ((i.IsSalesItem <> 0)) AND i.Type IN ('S', 'B')
AND 1=1
GROUP BY v.magcode , i.itemcode, i.SearchCode, Actual.Quantity
) stk ON items.ItemCode = stk.ItemCode
INNER JOIN grtbk ON Items.GLAccountDistribution = grtbk.reknr
ORDER BY stk.ItemCode
Dataset 2
SELECT Class_02, ItemCode FROM Items
Parameter in SSRS is set to Get values from Query, Value field: Class_02
Label field: Class_02