Return row for GROUP BY CASE WHEN IS NULL THEN (...) ELSE (...) even if record does not exist - sql

Let's consider the following scenario.
CREATE TABLE Replicant (Name NVARCHAR(10),Gen INT);
INSERT INTO Replicant VALUES ('tymtam', 2), ('Roy', 6);
SELECT
CASE WHEN Gen < 10 THEN '<10' ELSE '>=10' END as 'Gen',
count(*) as 'Count'
FROM Replicant
GROUP BY CASE WHEN Gen < 10 THEN '<10' ELSE '>=10' END;
The result is a single row:
Gen Count
<10 2
Can I up-sophisticate the query so that I get a zero for the ELSE case?
Gen Count
<10 2
>=10 0
Update 2
My discriminator is 'is null'
SELECT CASE WHEN Gen IS NOT NULL THEN 'Known' ELSE 'Unknown' END as 'Gen', count(*) as 'Count' FROM Replicant
GROUP BY CASE WHEN Gen IS NOT NULL THEN 'Known' ELSE 'Unknown' END;
The result is
Gen Count
Known 2
and I yearn for
Gen Count
Known 2
Unknown 0
Update 1
My context is that I have pairs of queries (metrics) for different generations of replicants:
INSERT INTO [dbo].[Metrics] (...) SELECT
'Metric X for >=10' as 'Name',
COUNT(*) AS 'Count',
(80_char_expression) AS 'Sum',
(80_char_expression) AS 'Min',
(80_char_expression) AS 'Max',
0 AS 'StandardDeviation'
FROM Replicant
WHERE TimestampUtc > DATEADD(WEEK, -1, Current_Timestamp)
AND Gen >= 10
INSERT INTO [dbo].[Metrics] (...) SELECT
'Metric X for <10' as 'Name',
--7 lines repeated from the 1st query
AND Gen < 10
I would prefer to have a single select to insert two rows, even if there are no records.

You can try to use UNOIN ALL make a comparison table for your score then do outer join
Query 1:
SELECT t1.word,
COUNT(Name) 'Count'
FROM
(
SELECT '<10' word,9 maxval,0 minval
UNION ALL
SELECT '>=10' word,2147483646 maxval,10 minval
) t1 LEFT JOIN Replicant on Gen BETWEEN t1.minval AND t1.maxval
GROUP BY t1.word
Results:
| word | Count |
|------|-------|
| <10 | 2 |
| >=10 | 0 |

You can use left join:
SELECT v.Gen, COUNT(r.gen) as cnt
FROM (VALUES (NULL, 10, '<10'),
(10, NULL, '>=10')
) v(lo, hi, gen) LEFT JOIN
Replicant r
ON (r.gen >= v.lo OR v.lo IS NULL) AND
(r.gen < v.hi OR v.hi IS NULL)
GROUP BY v.gen;
You can also use conditional aggregation and unpivoting:
select v.*
from (select sum(case when r.gen < 10 then 1 else 0 end) as gen_1,
sum(case when r.gen >= 10 then 1 else 0 end) as gen_2
from replicant r
) r cross apply
(values (gen_1, '<10'), (gen_2, '>=10')
) v(cnt, gen);

Related

how to merge multiple distinct queries to a single query to see output in multiple rows

Right now i have multiple distinct queries which gives respective results .. But i wanted to run a single query to get all those outputs in multiple rows..
Ex:
select count(distinct message_id)
from dssam.message_metadata
where object_id > 1177 AND workflow_type='3'; --- This gives o/p - 24
select count(distinct message_id)
from dssam.message_metadata
where object_id > 1177 AND workflow_type='4'; --- This gives o/p - 40
select count(distinct message_id)
from dssam.message_metadata
where object_id > 1177 AND flagged='true';--- This gives o/p - 6
But what i am looking for is .. o/p should be as below:
[workflow_type count
============== ====
3 24
4 40
true 6][1]
Can somebody help me on this ..?
You can use UNION ALL and a literal for the workflow_type column.
SELECT '3' workflow_type,
count(DISTINCT message_id) count
FROM dssam.message_metadata
WHERE object_id > 1177
AND workflow_type = '3'
UNION ALL
SELECT '4' workflow_type,
count(DISTINCT message_id) count
FROM dssam.message_metadata
WHERE object_id > 1177
AND workflow_type = '4'
UNION ALL
SELECT 'true' workflow_type,
count(DISTINCT message_id) count
FROM dssam.message_metadata
WHERE object_id > 1177
AND flagged = 'true';
You can avoid 3 scans of the table with some conditional aggregation:
WITH Aggs AS(
SELECT COUNT(DISTINCT CASE mm.workflow_type WHEN 3 THEN message_id END) AS Workflow3,
COUNT(DISTINCT CASE mm.workflow_type WHEN 4 THEN message_id END) AS Workflow4,
COUNT(DISTINCT CASE mm.flagged WHEN 'true' THEN message_id END) AS FlaggedTrue
FROM dssam.message_metadata mm
WHERE [object_id] = 1177)
SELECT V.KPI,
CASE V.KPI WHEN 'Workflow 3' THEN A.Workflow3
WHEN 'Workflow 4' THEN A.Workflow4
WHEN 'Flagged True' THEN A.FlaggedTrue
END AS DistinctCount
FROM Aggs A
CROSS APPLY (VALUES('Workflow 3'),
('Workflow 4'),
('Flagged True')) V(KPI);
A bit shorter version of single scan + unpivot query.
WITH Aggs AS(
SELECT COUNT(DISTINCT CASE mm.workflow_type WHEN 3 THEN message_id END) AS Workflow3,
COUNT(DISTINCT CASE mm.workflow_type WHEN 4 THEN message_id END) AS Workflow4,
COUNT(DISTINCT CASE mm.flagged WHEN 'true' THEN message_id END) AS FlaggedTrue
FROM dssam.message_metadata mm
WHERE [object_id] = 1177)
SELECT V.KPI, V.DistinctCount
FROM Aggs A
CROSS APPLY (VALUES('3', Workflow3),
('4', Workflow4),
('true', FlaggedTrue)
) V(KPI,DistinctCount);
Put the values in columns:
select count(distinct case when workflow_type = 3 then message_id end) as cnt_wt_3,
count(distinct case when workflow_type = 4 then message_id end) as cnt_wt_4,
count(distinct case when flag = 'true' then message_id end) as cnt_true
from dssam.message_metadata m
where object_id > 1177 and
( workflow_type in (3, 4) or flag = 'true' );
(Note: I assume that workflow_type is really an integer.)
You want them in separate columns so you can tell which row corresponds to which value. Otherwise, you have no label on the row. In a table called message_metadata, I am guessing that message_id is unique.
If this is the case, do not use count(distinct). I prefer sum() for this calculation:
select sum(case when workflow_type = 3 then 1 else 0 end) as cnt_wt_3,
count(case when workflow_type = 4 then message_id else 0 end) as cnt_wt_4,
count(case when flag = 'true' then 1 else 0 end) as cnt_true
from dssam.message_metadata m
where object_id > 1177 and
( workflow_type in (3, 4) or flag = 'true' );

SQL Server case when or enum

I have a table something like:
stuff type price
first_stuff 1 43
second_stuff 2 46
third_stuff 3 24
fourth_stuff 2 12
fifth_stuff NULL 90
And for every type of stuff is assigned a description which is not stored in DB
1 = Bad
2 = Good
3 = Excellent
NULL = Not_Assigned
All I want is to return a table which count each type separately, something like:
Description Count
Bad 1
Good 2
Excellent 1
Not_Assigned 1
DECLARE #t TABLE ([type] INT)
INSERT INTO #t ([type])
VALUES (1),(2),(3),(2),(NULL)
SELECT
[Description] =
CASE t.[type]
WHEN 1 THEN 'Bad'
WHEN 2 THEN 'Good'
WHEN 3 THEN 'Excellent'
ELSE 'Not_Assigned'
END, t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM #t
GROUP BY [type]
) t
ORDER BY ISNULL(t.[type], 999)
output -
Description Count
------------ -----------
Bad 1
Good 2
Excellent 1
Not_Assigned 1
;WITH CTE_TYPE
AS (SELECT DESCRIPTION,
VALUE
FROM (VALUES ('BAD',
1),
('GOOD',
2),
('EXCELLENT',
3))V( DESCRIPTION, VALUE )),
CTE_COUNT
AS (SELECT C.DESCRIPTION,
Count(T.TYPE) TYPE_COUNT
FROM YOUR_TABLE T
JOIN CTE_TYPE C
ON T.TYPE = C.VALUE
GROUP BY TYPE,
DESCRIPTION
UNION ALL
SELECT 'NOT_ASSIGNED' AS DESCRIPTION,
Count(*) TYPE_COUNT
FROM YOUR_TABLE
WHERE TYPE IS NULL)
SELECT *
FROM CTE_COUNT
Hope, this helps.
SELECT ISNULL(D.descr, 'Not_Assigned'),
T2.qty
FROM
(SELECT T.type,
COUNT(*) as qty
FROM Table AS T
GROUP BY type) AS T2
LEFT JOIN (SELECT 1 as type, 'Bad' AS descr
UNION ALL
SELECT 2, 'Good'
UNION ALL
SELECT 3, 'Excellent') AS D ON D.type = T2.type
If you are using Sql server 2012+ use this
SELECT
[Description] = coalesce(choose (t.[type],'Bad','Good' ,'Excellent'), 'Not_Assigned'),
t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM yourtable
GROUP BY [type]
) t

Counting if data exists in a row

Hey guys I have the below sample data which i want to query for.
MemberID AGEQ1 AGEQ2 AGEQ2
-----------------------------------------------------------------
1217 2 null null
58458 3 2 null
58459 null null null
58457 null 5 null
299576 6 5 7
What i need to do is to lookup the table and if any AGEx COLUMN contains any data then it counts the number of times there is data for that row in each column
Results example:
for memberID 1217 the count would be 1
for memberID 58458 the count would be 2
for memberID 58459 the count would be 0 or null
for memberID 58457 the count would be 1
for memberID 299576 the count would be 3
This is how it should look like in SQL if i query the entire table
1 Children - 2
2 Children - 1
3 Children - 1
0 Children - 1
So far i have been doing it using the following query which isnt very efficient and does give incorrect tallies as there are multiple combinations that people can answer the AGE question. Also i have to write multiple queries and change the is null to is not null depending on how many children i am looking to count a person has
select COUNT (*) as '1 Children' from Member
where AGEQ1 is not null
and AGEQ2 is null
and AGEQ3 is null
The above query only gives me an answer of 1 but i want to be able to count the other columns for data as well
Hope this is nice and clear and thank you in advance
If all of the columns are integers, you can take advantage of integer math - dividing the column by itself will yield 1, unless the value is NULL, in which case COALESCE can convert the resulting NULL to 0.
SELECT
MemberID,
COALESCE(AGEQ1 / AGEQ1, 0)
+ COALESCE(AGEQ2 / AGEQ2, 0)
+ COALESCE(AGEQ3 / AGEQ3, 0)
+ COALESCE(AGEQ4 / AGEQ4, 0)
+ COALESCE(AGEQ5 / AGEQ5, 0)
+ COALESCE(AGEQ6 / AGEQ6, 0)
FROM dbo.table_name;
To get the number of people with each count of children, then:
;WITH y(y) AS
(
SELECT TOP (7) rn = ROW_NUMBER() OVER
(ORDER BY [object_id]) - 1 FROM sys.objects
),
x AS
(
SELECT
MemberID,
x = COALESCE(AGEQ1 / AGEQ1, 0)
+ COALESCE(AGEQ2 / AGEQ2, 0)
+ COALESCE(AGEQ3 / AGEQ3, 0)
+ COALESCE(AGEQ4 / AGEQ4, 0)
+ COALESCE(AGEQ5 / AGEQ5, 0)
+ COALESCE(AGEQ6 / AGEQ6, 0)
FROM dbo.table_name
)
SELECT
NumberOfChildren = y.y,
NumberOfPeopleWithThatMany = COUNT(x.x)
FROM y LEFT OUTER JOIN x ON y.y = x.x
GROUP BY y.y ORDER BY y.y;
I'd look at using UNPIVOT. That will make your wide column into rows. Since you don't care about what value was in a column, just the presence/absence of value, this will generate a row per not-null column.
The trick then becomes mashing that into the desired output format. It could probably have been done cleaner but I'm a fan of "showing my work" so that others can conform it to their needs.
SQLFiddle
-- Using the above logic
WITH HadAges AS
(
-- Find everyone and determine number of rows
SELECT
UP.MemberID
, count(1) AS rc
FROM
dbo.Member AS M
UNPIVOT
(
ColumnValue for ColumnName in (AGEQ1, AGEQ2, AGEQ3)
) AS UP
GROUP BY
UP.MemberID
)
, NoAge AS
(
-- Account for those that didn't show up
SELECT M.MemberID
FROM
dbo.Member AS M
EXCEPT
SELECT
H.MemberID
FROM
HadAges AS H
)
, NUMBERS AS
(
-- Allowable range is 1-6
SELECT TOP 6
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS TheCount
FROM
sys.all_columns AS SC
)
, COMBINATION AS
(
-- Link those with rows to their count
SELECT
N.TheCount AS ChildCount
, H.MemberID
FROM
NUMBERS AS N
LEFT OUTER JOIN
HadAges AS H
ON H.rc = N.TheCount
UNION ALL
-- Deal with the unlinked
SELECT
0
, NA.MemberID
FROM
NoAge AS NA
)
SELECT
C.ChildCount
, COUNT(C.MemberID) AS Instances
FROM
COMBINATION AS C
GROUP BY
C.ChildCount;
Try this:
select id, a+b+c+d+e+f
from ( select id,
case when age1 is null then 0 else 1 end a,
case when age2 is null then 0 else 1 end b,
case when age3 is null then 0 else 1 end c,
case when age4 is null then 0 else 1 end d,
case when age5 is null then 0 else 1 end e,
case when age6 is null then 0 else 1 end f
from ages
) as t
See here in fiddle http://sqlfiddle.com/#!3/88020/1
To get the quantity of persons with childs
select childs, count(*) as ct
from (
select id, a+b+c+d+e+f childs
from
(
select
id,
case when age1 is null then 0 else 1 end a,
case when age2 is null then 0 else 1 end b,
case when age3 is null then 0 else 1 end c,
case when age4 is null then 0 else 1 end d,
case when age5 is null then 0 else 1 end e,
case when age6 is null then 0 else 1 end f
from ages ) as t
) ct
group by childs
order by 1
See it here at fiddle http://sqlfiddle.com/#!3/88020/24

SQL: SUMming certain items in a column and subtracting it from another figure in that column

Sorry about the title. It might be a bit confusing! The sample table I'm working with is given below:
ID Quantity Type
-----------------------------------------------
1 14 PO
1 2 PO
1 4 MH
1 3 MH
1 2 MH
2 16 PO
2 12 MH
2 9 MH
Here's what I want to do. I want to sum all quantities of ID = 1 and Type = PO (14 + 2) as SUM_IN. I then want to sum all quantities of ID = 1 and Type = MH (4 + 3 + 2) as SUM_OUT. Once I have this done I want to compare the two and return values only where SUM_OUT > SUM_IN. So for ID = 1 would not be returned where as ID = 2 would, because (12 + 9) > 16.
Is there a way to do this in SQL or will I need to use PL/SQL and variables for the task. I have very little experience in PL/SQL, but logically it seems that variables would be the easiest way to solve the problem. I know that select statements can be stored in variables but I'm not sure how to. Here are my two SQL selects anyway
SELECT SUM(QUANTITY) AS SUM_IN
FROM TRANSLOG
WHERE TYPE IN ('PO')
AND ID = '1'
SELECT SUM(QUANTITY) AS SUM_OUT
FROM TRANSLOG
WHERE TYPE IN ('MH')
AND ID = '1'
So if I could set both these to variables, the task shouldn't be too difficult, right???
Thanks in advance for the help.
select ID,
sum ( Quantity * case Type when 'po' then 1 else 0 end ) as SUM_IN,
sum ( Quantity * case Type when 'mh' then 1 else 0 end ) as SUM_OUT
from translog
group by ID
having sum ( Quantity * case Type when 'po' then 1 else -1 end ) < 0
As you have tagged you question with plsql tag I assume that the RDBMS you are goint to execute query against is Oracle. If so, then here is another approach(using DECODE function
) to get the result set you want.
select *
from (select id
, sum(Quantity*decode(tp, 'PO', 1, 0)) as sum_in
, sum(Quantity*decode(tp, 'MH', 1, 0)) as sum_out
from t1
group by id
order by id )
where sum_out > sum_in
Result:
ID SUM_IN SUM_OUT
-----------------------
2 16 21
If you want to display the rest of the columns along with sum_in, sum_out the following query might be in handy:
select id
, quantity
, Tp
, sum_in
, sum_out
from (select id
, quantity
, tp
, sum(Quantity*decode(tp, 'PO', 1, 0)) over(partition by id) as sum_in
, sum(Quantity*decode(tp, 'MH', 1, 0)) over(partition by id) as sum_out
from t1
)
where sum_out > sum_in
Result:
Id Quantity Tp Sum_In Sum_Out
---------------------------------------------
2 16 PO 16 21
2 12 MH 16 21
2 9 MH 16 21
SELECT CASE WHEN b.SUM_OUT > a.SUM_IN then b.SUM_OUT else '' END as SUM_OUT,
CASE WHEN b.SUM_OUT > a.SUM_IN then a.SUM_IN else '' END as SUM_IN
FROM
(SELECT ID,SUM(QUANTITY) AS SUM_IN
FROM TRANSLOG
WHERE TYPE IN ('PO')
AND ID = '1'
GROUP BY ID,Type
) a
INNER JOIN
(SELECT ID,SUM(QUANTITY) AS SUM_OUT
FROM TRANSLOG
WHERE TYPE IN ('MH')
AND ID = '1'
GROUP BY ID,Type
) b
ON a.ID=b.ID

sql query group

SQL query question
I have a query like
select proposal_id, service_id,account_type
from table1
The result is like this:
proposal_id service_id account_type
1 1001 INTERVAL
1 1002 INTERVAL
2 1003 NON INTERVAL
2 1004 NON INTERVAL
3 1005 NON INTERVAL
3 1006 INTERVAL
I want to write a query: for each proposal_id, if all the service have INTERVAL then get 'INTERVAL', if all NON-INTERVAL get 'NON-INTERVAL', if both, get 'Both'
For the example above, it should return
proposal_id account_type
1 INTERVAL
2 NON-INTERVAL
3 BOTH
Data:
declare #table table (id int, sid int, acc nvarchar(20))
insert #table VALUES (1,1001,'INTERVAL'),(1,1002,'INTERVAL'),(2,1003,'NON INTERVAL'),(2,1004,'NON INTERVAL'),
(3,1005,'NON INTERVAL'),(3,1006,'INTERVAL')
Query:
select x.Id
, CASE counter
WHEN 1 THEN x.Account_Type
ELSE 'BOTH'
END AS Account_Type
from (
select Id, Count(DISTINCT(acc)) AS counter, MAX(acc) As Account_Type
from #table
GROUP BY Id
) x
Results
Id Account_Type
----------- --------------------
1 INTERVAL
2 NON INTERVAL
3 BOTH
SELECT
b.proposal_id
,CASE
WHEN s1.proposal_id IS NOT NULL AND s2.proposal_id IS NOT NULL THEN 'BOTH'
WHEN s1.proposal_id IS NOT NULL THEN 'INTERVAL'
WHEN s2.proposal_id IS NOT NULL THEN 'NON-INTERVAL'
ELSE 'UNKNOWN'
END [account_type]
FROM table1 b
LEFT JOIN(
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'INTERVAL'
) s1
ON b.proposal_id = s1.proposal_id
LEFT JOIN (
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'NON-INTERVAL'
)s2
ON b.proposal_id = s2.proposal_id
You could use count distinct to determinate if it is both then use CASE to determinate what to display
SELECT DISTINCT proposal.proposal_id,
CASE cou
WHEN 1 THEN type ELSE 'Both' END as TYPE
FROM proposal
INNER JOIN (SELECT proposal_id, count(distinct type) cou
FROM proposal GROUP BY proposal_id) inn
ON proposal.id = inn.id
select proposal_id,
case when count(distinct account_type) > 1 then 'BOTH'
else max(account_type)
end
from table1
group by proposal_id
You have the fiddler here.