Add flag for row existence in another table with group by - sql

For below query I want to have a flag called isHold that will evaluate to 0
if there is no billNo from the view viewBills exists in onHold table and
1 otherwise
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when exists (select 1 from OnHold oh
where oh.billNo = billNo) then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
where cb.statusid <> 3
group by cb.id
I got this error when executing
Cannot perform an aggregate function on an expression
containing an aggregate or a subquery.
It's reasonable but how can achieve that?

You can use outer apply or a left join to move the logic to the FROM clause:
select max(t.id) as TrackingID , max(vb.billNo) as billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) as isMultiple,
max(case when t.TrackingID = 31 then 1 else 0 end) as IsCancelled,
max(case when oh.billNo is not null then 1 else 0 end) as IsHold
from viewBills vb join
tracking t
on vb.billNo = t.billNo join
customerBills cb
on vb.billNo = cb.billNo join
customerPieces cp
on cb.id = cp.customerBillId outer apply
(select top (1) oh.*
from OnHold oh
where oh.billNo = cb.billNo
) oh
where cw.statusid <> 3
group by cb.id;

You can go for LEFT OUTER JOIN and do the aggregation as given below:
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when oh.billNo IS NOT NULL then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
LEFT OUTER JOIN OnHold oh ON oh.billNo = vb.billNo
where cb.statusid <> 3
group by cb.id

Related

SQL ANY as a function instead of an operator

I need to count users that match certain conditions. To do that I need to join some tables and check if any of the grouping combination match the condition.
The way I implemented that now is by having a nested select that counts original matches and then counting the rows that have at least one result.
SELECT
COUNT(case when NestedCount1 > 0 then 1 else null end) as Count1,
COUNT(case when NestedCount2 > 0 then 1 else null end) as Count2,
COUNT(case when NestedCount3 > 0 then 1 else null end) as Count3
FROM
(SELECT
COUNT(case when Type = 1 then 1 else null end) as NestedCount1,
COUNT(case when Type = 2 then 1 else null end) as NestedCount2,
COUNT(case when Type = 2 AND Condition = 1 then 1 else null end) as NestedCount3
FROM [User]
LEFT JOIN [UserGroup] ON [User].Id = [UserGroup].UserId
LEFT JOIN [Group] ON [UserGroup].GroupId = [Group].Id
GROUP BY [User].Id) nested
What irks me is that the counts from the nested select are only used to check existence. However since ANY in SQL is only an operator I cannot think of a cleaner way on how to rewrite this.
The query returns correct results as is.
I'm wondering if there is any way to rewrite this that would avoid having intermediate results that are only used to check existence condition?
Sample imput User.csv Group.csv UserGroup.csv
Expected results: 483, 272, 121
It might be possible to simplify that query.
I think that the group on the UserId can be avoided.
By using distinct conditional counts on the user id.
Then there's no need for a sub-query.
SELECT
COUNT(DISTINCT case when [User].[Type] = 1 then [User].Id end) as Count1,
COUNT(DISTINCT case when [User].[Type] = 2 then [User].Id end) as Count2,
COUNT(DISTINCT case when [User].[Type] = 2 AND Condition = 1 then [User].Id end) as Count3
FROM [User]
LEFT JOIN [UserGroup] ON [UserGroup].UserId = [User].Id
LEFT JOIN [Group] ON [Group].Id = [UserGroup].GroupId;
SELECT
SUM(case when NestedCount1 > 0 then 1 else 0 end) as Count1,
SUM(case when NestedCount2 > 0 then 1 else 0 end) as Count2,
SUM(case when NestedCount3 > 0 then 1 else 0 end) as Count3
FROM
(
SELECT
[User].Id,
COUNT(case when Type = 1 then 1 else 0 end) as NestedCount1,
COUNT(case when Type = 2 then 1 else 0 end) as NestedCount2,
COUNT(case when Type = 2 AND Condition = 1 then 1 else 0 end) as NestedCount3
FROM [User]
LEFT JOIN [UserGroup] ON [UserGroup].UserId = [User].Id
LEFT JOIN [Group] ON [Group].Id = [UserGroup].GroupId
GROUP BY [User].Id
) nested

Get COUNT with a condition from a joined table

I have a table SyncHistory:
SyncHistoryId SyncType SyncDateTime
-----------------------------------------------------
55 1 2017-11-28 09:30:51.810
56 1 2017-11-28 10:30:32.123
And then another table SyncDetails:
SyndDetailId SyncHistoryId ItemId ItemCreated ItemChanged
---------------------------------------------------------------------------
98 55 12345 1 0
99 55 23183 1 0
100 55 87687 0 1
101 55 23234 0 0
102 55 23222 0 0
103 56 9928 1 0
What I'm trying to do is create a query that gives me this:
Sync Data New Existing & Changed Existing & Not Changed
---------------------------------------------------------------------------
11/28/2017 9:30am 2 1 2
11/28/2017 10:30am 1 0 0
This is what I'm trying:
SELECT
sh.SyncHistoryId
, sh.SyncDateTime
, count(sd1.SyncDetailId) AS Created
, count(sd2.SyncDetailId) AS ExistingChanged
, count(sd3.SyncDetailId) AS ExistingNotChanged
FROM
SyncHistory sh
LEFT JOIN SyncDetails sd1 ON sh.SyncHistoryId = sd1.SyncHistoryId AND sd1.ItemCreated = 1 AND sd1.ItemChanged = 0
LEFT JOIN SyncDetails sd2 ON sh.SyncHistoryId = sd2.SyncHistoryId AND sd2.ItemCreated = 0 AND sd2.ItemChanged = 1
LEFT JOIN SyncDetails sd3 ON sh.SyncHistoryId = sd3.SyncHistoryId AND sd3.ItemCreated = 0 AND sd3.ItemChanged = 0
WHERE
sh.SyncType = 1
GROUP BY
sh.SyncHistoryId
, sh.SyncDateTime
ORDER BY
sh.SyncDateTime DESC
But, none of the resulting counts are accurate. I'm doing something wrong, but not sure what.
SELECT h.SyncDateTime,
SUM(case when d.ItemCreated = 1 then 1 else 0 end) as New,
SUM(case when d.ItemChanged = 1 then 1 else 0 end) as [Existing & Changed],
SUM(case when d.ItemCreated = 0 and d.ItemChanged = 0 then 1 else 0 end) as [Existing & Not Changed]
FROM SyncHistory h
INNER JOIN SyncDetails d ON h.SyncHistoryId = d.SyncHistoryId
GROUP BY h.SyncDateTime
You only need to JOIN to the details table once. You can get your counts from that through aggregation:
SELECT
CONVERT(VARCHAR(16), SH.SyncDateTime, 120) AS SyncTime,
SUM(CASE WHEN SD.ItemCreated = 1 AND SD.ItemChanged = 0 THEN 1 ELSE 0 END) AS New,
SUM(CASE WHEN SD.ItemCreated = 0 AND SD.ItemChanged = 1 THEN 1 ELSE 0 END) AS ExistingAndChanged,
SUM(CASE WHEN SD.ItemCreated = 0 AND SD.ItemChanged = 0 THEN 1 ELSE 0 END) AS ExistingAndNotChanged
FROM
SyncHistory SH
LEFT OUTER JOIN SyncDetails SD ON SD.SyncHistoryID = SH.SyncHistoryID
GROUP BY
CONVERT(VARCHAR(16), SH.SyncDateTime, 120)
You weren't clear on how the grouping/datetime should be determined. What I have is by the minute. If it's supposed to be by the hour on the 1/2 hour mark or something else then you'll need to change that part of the query in the GROUP BY and the first column of the SELECT.
Another solution. I hope it will work - no CASE, no subquery:
SELECT
sh.SyncHistoryId
,sh.SyncDateTime
,COUNT( NULLIF( sd.ItemCreated, 0 ) ) AS Created
,COUNT( NULLIF( sd.ItemCreated, 1 ) + NULLIF( sd1.ItemChanged, 0 ) ) AS ExistingChanged
,COUNT( NULLIF( sd.ItemCreated, 1 ) + NULLIF( sd1.ItemChanged, 1 ) ) AS ExistingNotChanged
FROM
SyncHistory sh JOIN SyncDetails sd ON sh.SyncHistoryId = sd.SyncHistoryId
WHERE
sh.SyncType = 1
GROUP BY
sh.SyncHistoryId
,sh.SyncDateTime
ORDER BY
sh.SyncDateTime DESC
I hope subquery is not forbidden:
SELECT
sh.SyncHistoryId
,sh.SyncDateTime
,(SELECT COUNT(*) FROM SyncDetails sd WHERE sh.SyncHistoryId = sd.SyncHistoryId AND sd.ItemCreated = 1 AND sd1.ItemChanged = 0) AS Created
,(SELECT COUNT(*) FROM SyncDetails sd WHERE sh.SyncHistoryId = sd.SyncHistoryId AND sd.ItemCreated = 0 AND sd1.ItemChanged = 1) AS ExistingChanged
,(SELECT COUNT(*) FROM SyncDetails sd WHERE sh.SyncHistoryId = sd.SyncHistoryId AND sd.ItemCreated = 0 AND sd1.ItemChanged = 0) AS ExistingNotChanged
FROM
SyncHistory sh
WHERE
sh.SyncType = 1
ORDER BY
sh.SyncDateTime DESC

JOIN always the default value, else join the match value

I have the following SQL Server Query
select r.isactive,r.workingyear,r.startperiod,r.endperiod,r.anniversary
from setup_holiday_policy t cross apply
(select data
from dbo.Split(t.scheduleapplication, ',')
) di cross apply
(select max(case when did.id = 1 then did.data end) as isactive,
max(case when did.id = 2 then did.data end) as workingyear,
max(case when did.id = 3 then did.data end) as anniversary,
max(case when did.id = 4 then did.data end) as startperiod,
max(case when did.id = 5 then did.data end) as endperiod
from dbo.Split(di.data,':') did
) r
WHERE r.workingyear = #employeeworkingyears
The policy table can have a 0 value in the workingyear field. Meaning that when this field has 0 then is the default record I should return.
setup_holiday_policy
So, if #employeeworkingyears = 2 and there is no workingyears = 2 in setup_holiday_policy I should return the default row that has the 0 value in workingyears field.
This is a sample of the rows returned.
Any clue how to achieve this?
If only one row is going to be returned (as suggested by the sample data), you can do this using top:
select top 1 r.isactive,r.workingyear,r.startperiod,r.endperiod,r.anniversary
from setup_holiday_policy t cross apply
(select data
from dbo.Split(t.scheduleapplication, ',')
) di cross apply
(select max(case when did.id = 1 then did.data end) as isactive,
max(case when did.id = 2 then did.data end) as workingyear,
max(case when did.id = 3 then did.data end) as anniversary,
max(case when did.id = 4 then did.data end) as startperiod,
max(case when did.id = 5 then did.data end) as endperiod
from dbo.Split(di.data,':') did
) r left outer join
(select #employeeworkingyears as employeeworkingyears
) e
on
WHERE r.workingyear in (#employeeworkingyears, 0)
order by r.workingyear desc;

SQL query rewrite for prettification and or performance improvement

I have a query that essentially amounts to:
Select query 1
Union
Select query 2
where rowid not in query 1 rowids
Is there a prettier / more performant way to do this? I'm assuming the results of query 1 would be cached and thus utilized in the union... but it's also kinda oogly.
Update with the original query:
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and FruitType NOT IN
(
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
)
Thanks!
You don't need the second case statement in the NOT in clause. And not Exists is often faster in SQL Server.
SELECT FruitType
, count(CASE WHEN Status = 0 THEN 1 ELSE 0 END) AS Fresh
, count(CASE WHEN Status = 1 THEN 1 ELSE 0 END) AS Ripe
, count(CASE WHEN Status = 2 THEN 1 ELSE 0 END) AS Moldy
FROM FruitTypes FT1
LEfT JOIN Fruits F on F.FTID = FT1.ID
where
Fruit.IsHighPriced = 0
GROUP BY FruitType
Union ALL
select FruitType, 0 as Fresh, 0 as Ripe, 0 as Moldy
FROM FruitTypes ft3
where
ft3.StoreID = #PassedInStoreID
and NOT EXISTS
(
SELECT *
FROM FruitTypes FT2
LEfT JOIN Fruits F on F.FTID = FT2.ID
where
Fruit.IsHighPriced = 0
and ft3.FruitType = FT2.FruitType
)
The prettiest way of writing would probably be by turning query #1 into a view or a function, then using that view or function to call the repetitious code.
Performance could possibly be improved by using query #1 to fill a temp table or table variable, then using that temp table in place of the repititious code.

Combining unrelated queries into one query to produce counts

I would like a stored procedure to run daily that produces a report of counts.
For example, the .csv would look something like this:
Daily,1
Deaths,0
In-House EKG,4
In-House Xray,2
Suicidal Patients,12
HIV,0
Their individual queries look something like this:
-- Daily and Death Counts
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay',
from
organizer_tasks
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
-- HIV
select
count(distinct(pid)) as 'HIV'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
As you can see, each set of data comes from a different table, and has no relation. How can I accomplish my desired result set?
Thanks.
-- Daily and Death Counts
select * from (
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily',
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
) tmp unpivot (Number for Type in ([Daily], [Deaths])) t
union all
-- In-House Tasks
select * from (
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG',
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
) tmp unpivot (Number for Type in ([In-House EKG], [In-House XRay])) t
union all
-- Suicidal Patients
select 'Suicidal Inmates',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'
union all
-- HIV
select 'HIV',
count(distinct(pid))
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
inner join patient_data pd on pr.pid = pd.pid
where
pr.status = 'open'
and pl.title like '%hiv%'
Try this with Union which is form in one Query :
select
SUM(CASE WHEN location != '[OUT]' THEN 1 ELSE 0 END) as 'Daily'
from
patient_data
UNION ALL
select
SUM(CASE WHEN death = 1 THEN 1 ELSE 0 END) as 'Deaths'
from
patient_data
UNION ALL
-- In-House Tasks
select
SUM(CASE WHEN cat_id = 72 THEN 1 ELSE 0 END) as 'In-House EKG'
from
organizer_tasks
UNION ALL
select
SUM(CASE WHEN cat_id = 73 THEN 1 ELSE 0 END) as 'In-House XRay'
from
organizer_tasks
UNION ALL
-- Suicidal Patients
select
count(distinct(pid)) as 'Suicidal Inmates'
from
problems pr
inner join problem_list pl on pl.id = pr.problem_list_id
where
pr.status = 'open'
and pl.title like '%suicide%'