SQL Union Count to Sum Data - sql-server

I have a bit of a complex query .... I need to do an update statement on the summation of two union-ed SQL queries (problem is the data in the queries isn't numeric so i'm counting rows instead of summing values) but I then need to sum those rows.
UPDATE #LT_Actuals_TEMP
SET pCount = h.countPerfs
FROM (
select count(distinct c.perf_description) as countPerfs, b.program, b.Prog_id
from #LT_Actuals_TEMP TableP
where a.Performances = 'Y' and a.current_inactive = 0
group by b.Program, b.Prog_id
union
select distinct count(p.perf_code) as countPerfs, x.value, b.Prog_id
from T_PERF p
where x.content_type = 23
group by x.value, b.Prog_id
) h where h.Prog_id = #LT_Actuals_TEMP.program_id
the first query data comes back as such
countPerfs program Prog_id
7 Name 31
and second query comes back as
countPerfs program Prog_id
1 Name 31
what I need pCount to be set to at the end of the day is 8
Expected results
when I do select * from #LT_Actuals_TEMP
I see the value
8 for the Program Name, Id 31

You can solve it by adding another level in the from part where you sum up the data returned from the union.
Your query seems to be missing some source tables (as there are aliases used that don't point to anything) so I guess you're removed some parts, but in general it should look something like this:
UPDATE #LT_Actuals_TEMP
SET pCount = h.sum_of_countperfs
FROM (
select program, prog_id, sum(countPerfs) as sum_of_countperfs
from (
select count(distinct c.perf_description) as countPerfs, b.program, b.Prog_id
from #LT_Actuals_TEMP TableP
where a.Performances = 'Y' and a.current_inactive = 0
group by b.Program, b.Prog_id
union all
select distinct count(p.perf_code) as countPerfs, x.value, b.Prog_id
from T_PERF p
where x.content_type = 23
group by x.value, b.Prog_id
) as sub_q group by program, prog_id
) h where h.Prog_id = #LT_Actuals_TEMP.program_id
Also, you probably want to use union all so that duplicates are not removed.

Related

SQL - Finding Gaps in Coverage

I am running this problem on SQL server
Here is my problem.
have something like this
Dataset A
FK_ID StartDate EndDate Type
1 10/1/2018 11/30/2018 M
1 12/1/2018 2/28/2019 N
1 3/1/2019 10/31/2019 M
I have a second data source I have no control over with data something like this:
Dataset B
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/15/2018 M
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
What I am trying to accomplish is to check to make sure every date within each TYPE M record in Dataset A has at least 1 record in Dataset B.
For example record 1 in Dataset A does NOT have coverage from 10/26/2018 through 11/30/2018. I really only care about when the coverage ends, in this case I want to return 10/26/2018 because it is the first date where the span has no coverage from Dataset B.
I've written a function that does this but it is pretty slow because it is cycling through each date within each M record and counting the number of records in Dataset B. It exits the loop when it finds the first one but I would really like to make this more efficient. I am sure I am not thinking about this properly so any suggestions anyone can offer would be helpful.
This is the section of code I'm currently running
else if #SpanType = 'M'
begin
set #CurrDate = #SpanStart
set #UncovDays = 0
while #CurrDate <= #SpanEnd
Begin
if (SELECT count(*)
FROM eligiblecoverage ec join eligibilityplan ep on ec.plandescription = ep.planname
WHERE ec.masterindividualid = #IndID
and ec.planbegindate <= #CurrDate and ec.planenddate >= #CurrDate
and ec.sourcecreateddate = #MaxDate
and ep.medicaidcoverage = 1) = 0
begin
SET #Result = concat('NON Starting ',format(#currdate, 'M/d/yyyy'))
BREAK
end
set #CurrDate = #CurrDate + 1
end
end
I am not married to having a function it just could not find a way to do this in queries that wasn't very very slow.
EDIT: Dataset B will never have any TYPEs except M so that is not a consideration
EDIT 2: The code offered by DonPablo does de-overlap the data but only in cases where there is an overlap at all. It reduces dataset B to:
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
instead of
FK_ID SpanStart SpanEnd Type
1 10/1/2018 10/25/2018 M
1 2/15/2019 4/30/2019 M
1 5/1/2019 10/31/2019 M
I am still futzing around with it but it's a start.
I would approach this by focusing on B. My assumption is that any absent record would follow span_end in the table. So here is the idea:
Unpivot the dates in B (adding "1" to the end dates)
Add a flag if they are present with type "M".
Check to see if any not-present records are in the span for A.
Check the first and last dates as well.
So, this looks like:
with bdates as (
select v.dte,
(case when exists (select 1
from b b2
where v.dte between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1 else 0
end) as in_b
from b cross apply
(values (spanstart), (dateadd(day, 1, spanend)
) v(dte)
where b.type = 'M' -- all we care about
group by v.dte -- no need for duplicates
)
select a.*,
(case when not exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 0
when not exists (select 1
from b b2
where a.enddate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
when exists (select 1
from bdates bd
where bd.dte between a.startdate and a.enddate and
bd.in_b = 0
)
then 0
when exists (select 1
from b b2
where a.startdate between b2.spanstart and b2.spanend and
b2.type = 'M'
)
then 1
else 0
end)
from a;
What is this doing? Four validity checks:
Is the starttime valid?
Is the endtime valid?
Are any intermediate dates invalid?
Is there at least one valid record?
Start by framing the problem in smaller pieces, in a sequence of actions like I did in the comment.
See George Polya "How To Solve It" 1945
Then Google is your friend -- look at==> sql de-overlap date ranges into one record (over a million results)
UPDATED--I picked Merge overlapping dates in SQL Server
and updated it for our table and column names.
Also look at theory from 1983 Allen's Interval Algebra https://www.ics.uci.edu/~alspaugh/cls/shr/allen.html
Or from 2014 https://stewashton.wordpress.com/2014/03/11/sql-for-date-ranges-gaps-and-overlaps/
This is a primer on how to setup test data for this problem.
Finally determine what counts via Ranking the various pairs of A vs B --
bypass those totally Within, then work with earliest PartialOverlaps, lastly do the Precede/Follow items.
--from Merge overlapping dates in SQL Server
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
)
Select * from DeOverlapped_B
Now we have something to feed into the next steps, and we can use the above as a CTE
======================================
with SpanStarts as
(
select distinct FK_ID, SpanStart
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanStart < t1.SpanStart
and t2.SpanEnd >= t1.SpanStart)
),
SpanEnds as
(
select distinct FK_ID, SpanEnd
from Coverage_B as t1
where not exists
(select * from Coverage_B as t2
where t2.FK_ID = t1.FK_ID
and t2.SpanEnd > t1.SpanEnd
and t2.SpanStart <= t1.SpanEnd)
),
DeOverlapped_B as
(
Select FK_ID, SpanStart,
(select min(SpanEnd) from SpanEnds as e
where e.FK_ID = s.FK_ID
and SpanEnd >= SpanStart) as SpanEnd
from SpanStarts as s
),
-- find A row's coverage
ACoverage as (
Select
a.*, b.SpanEnd, b.SpanStart,
Case
When SpanStart <= StartDate And StartDate <= SpanEnd
And SpanStart <= EndDate And EndDate <= SpanEnd
Then '1within' -- starts, equals, during, finishes
When EndDate < SpanStart
Or SpanEnd < StartDate
Then '3beforeAfter' -- preceeds, meets, preceeded, met
Else '2overlap' -- one or two ends hang over spanStart/End
End as relation
From Coverage_A a
Left Join DeOverlapped_B b
On a.FK_ID = b.FK_ID
Where a.Type = 'M'
)
Select
*
,Case
When relation1 = '2' And StartDate < SpanStart Then StartDate
When relation1 = '2' Then DateAdd(d, 1, SpanEnd)
When relation1 = '3' Then StartDate
End as UnCoveredBeginning
From (
Select
*
,SUBSTRING(relation,1,1) as relation1
,ROW_NUMBER() Over (Partition by A_ID Order by relation, SpanStart) as Rownum
from ACoverage
) aRNO
Where Rownum = 1
And relation1 <> '1'

Why is this SQL case statement behaving like an OR statement?

Consider the following query:
declare #RentalId int = 1
SELECT
r.RentalId
,r.[Name]
,rt.TypeId
FROM dbo.Rental r
LEFT JOIN dbo.RentalRateType rt ON (
r.RentalId = rt.RentalId
AND rt.TypeId = (
case when rt.TypeId = 6 and coalesce(rt.[Max], rt.[Min]) is not null then 6
when rt.TypeId = 1 and coalesce(rt.[Max], rt.[Min] is not null then 1
else -1 end
))
WHERE r.RentalId = #RentalId
I'm attempting to return a single record/row. The particular rental in question has 2 records in the dbo.RentalRateType table, and when I run the above query, I get 2 results, but I want it to short circuit on the first match in the case where.
Basically, the end user can fill in multiple rate types, more than what you see in this example, and each of those types has a priority. 6 is the highest priority in the example.
So I'm getting this result:
RentalId | Name | TypeId
----------------------------
1 Super Car 6
1 Super Car 1
But if the type (6) exists, I would expect only the first row above returned.
I must be missing something silly. This works as expected:
case when 1=2 then 6
when 1=1 then 1
else -1 end
While I'm here, I'm open to a more efficient manner of handling this if exists.
Use an apply instead, these are an efficient way to get "top n" queries:
SELECT
r.RentalId
, r.[Name]
, oa.TypeId
FROM dbo.Rental r
OUTER APPLY (
SELECT TOP (1)
rt.TypeId
FROM dbo.RentalRateType rt
WHERE r.RentalId = rt.RentalId
ORDER BY
rt.TypeId DESC
) oa
WHERE r.RentalId = #RentalId

SQL query to show good records as well as null records

My query works perfectly well to find records with real values, however, I also need my query to show records with null values. So far my attempts at recreating this query to also show null values has resulted in losing at least 1 of my columns of results so now I'm looking for help.
This is my query so far:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[TransactionSummary] t
INNER JOIN [log].[dbo].[Customer] c
on t.CustNo = c.CustNo
and t.City = c.City
and t.subno = c.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
This is currently what my query results show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
This is what I want my query results to show:
CustName City InProcTotal TransactionCountTotal Complete Total
Cust 1 City(a) 23 7 30
Cust 2 City(b) 74 2 76
Cust 3 City(c) 54 4 58
Cust 4 City(d) 0 0 0
Cust 5 City(e) 0 0 0
I suggest you use RIGHT JOIN in the place of INNER JOIN. You should then retain the rows from Customer that don't have matching rows in TransactionSummary.
You may also want to refactor the query like this so you use LEFT JOIN. The next person to work on the query will thank you; LEFT JOIN operations are more common.
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on t.CustNo = c.CustNo
and t.City = c.City
jwabsolution, your issue stems from grabbing all transactions instead of all customers. My mind works in this way: you want to select all of the customers & find all transaction states. Therefore, you should be selecting from the customer table. Also, you shouldn't use the INNER JOIN or you will ignore any customers that don't have transactions. Instead, use left join the transactions table. In this manner, you will retrieve all customers (even those with no transactions). Here is a good visual for SQL joins: http://www.codeproject.com/KB/database/Visual_SQL_Joins/Visual_SQL_JOINS_orig.jpg
So your query should look like this:
SELECT sq.*, sq.TransactionCountTotal - sq.CompleteTotal as InProcTotal from
(
select
c.CustName,
t.[City],
sum (t.TransactionCount) as TransactionCountTotal
sum (
case
when (
[format] in (23,25,38)
or [format] between 400 and 499
or format between 800 and 899
)
then t.TransactionCount
else 0
end
) as CompleteTotal
FROM [log].[dbo].[Customer] c
LEFT JOIN [log].[dbo].[TransactionSummary] t
on c.CustNo = t.CustNo
and c.City = t.City
and c.subno = t.subno
where t.transactiondate between '7/1/16' and '7/11/16'
group by c.CustName,t.City
) sq
Fixed it. Needed to use coalesce to get the values to show up properly.
Also added a "where" option if I want to query individual customers
SELECT sq.* ,sq.TransactionCountTotal - sq.CompleteTotal as [InProcTotal]
from
(
select
c.custname
,c.port
,sum(coalesce(t.transactioncount,0)) as TransactionCountTotal
,sum(
case when (
[format]in(23,25,38)
or[format]between 400 and 499
or[format]between 800 and 899)
then t.TransactionCount
else 0
end) as CompleteTotal
from log.dbo.customer c
left join log.dbo.TransactionSummary t
on c.custNo=t.custno
and c.subno=t.subno
and c.city=t.city
and t.transactiondate between '7/1/16' and '7/12/16'
/*where c.custname=''*/
group by c.custname,c.city
) sq

TSQL while loop

UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(code) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
The above code gets all users for every house (Houses table). SUMs the code column (Users table) for all users. And finally updates the result in lstatus column of the houses table.
My question is:
I need to rewrite the query which is NOT to sum the code column. Rather I want to create case statements. for example:
tempvar = 0 //local variable might be required
For each user {
If code == 1 then tempvar += 5
else if code == 2 then tempvar += 10
etc
tempvar = 0;
}
Once we have looped through all the users for each house we can now set lStatus = tempvar.
The tempvar should then be reset to 0 for the next house.
You should try to avoid loops and other procedural constructs when coding SQL. A relational database can't easily optimize such things and they often perform orders of magnitude slower than their declarative counterparts. In this case, it seems simple enough to replace your SUM(code) with the CASE statement that you describe:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT ref, SUM(CASE code WHEN 1 THEN 5 WHEN 2 THEN 10 ELSE 0 END) AS codesum
FROM Users
GROUP BY ref) AS U ON U.ref = H.ref
In this way, SUM can still handle the duty that you imagine your temp variable would be doing.
Also, if you have many cases, you might think about putting those in a table and simply joining on that to get your sum. This might be better to maintain. I'm using a table variable here, but it could look like the following:
DECLARE #codes TABLE (
code INT NOT NULL PRIMARY KEY,
value INT NOT NULL
)
INSERT INTO #codes SELECT 1, 5
INSERT INTO #codes SELECT 2, 10
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (SELECT a.ref, SUM(b.value) AS codesum
FROM Users a
JOIN #codes b on a.code = b.code -- Here we get the values dynamically
GROUP BY a.ref) AS U ON U.ref = H.ref
Try this:
UPDATE Houses
SET lStatus = U.codesum
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
END
) AS codesum
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref
Try this :
UPDATE Houses
SET lStatus = U.code
FROM Houses H
JOIN (
SELECT ref, SUM(
CASE
WHEN Code = 1
THEN 5
WHEN Code = 2
THEN 10
ELSE 0
END
) AS code
FROM Users
GROUP BY ref
) AS U ON U.ref = H.ref

SQL Query need to show blank records

I have a query where i'm selecting 50 ids from the DB and grouping them by the id.
The only problem is that only 34 exists in the DB so it only shows 34 results.
My desired result is to show 50 results with the ones that don't exists in.
Is this possible?
Thanks
My query
SELECT no, count(no) as visits FROM DB
WHERE (no = '1' or no = '2' or no = '3' or no = '4' or no = '5')
GROUP BY no
This example will only give me 3 records even though i'm selecting 5 records
with noTable
as
( select 1 as no
union
select 2
union
select 3
union
select 4
union
select 5
)
select noTable.no,count(*)
from noTable inner join DB
on noTable.no = DB.no group by noTable.no
union
select noTable.no,0
from noTable left join DB
on noTable.no = DB.no
where DB.no is null;
Might not be elegant if there're 50 id...
You could add a tally table using WITH and LEFT JOIN your original table with the tally table.
;with q (no) AS (
SELECT 1
UNION ALL
SELECT no + 1
FROM q
WHERE no < 50
)
SELECT q.no
, CASE WHEN db.no IS NOT NULL
THEN COUNT(q.no)
ELSE 0
END AS Visits
FROM q
LEFT JOIN db ON db.no = q.no
GROUP BY
no

Resources