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

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' );

Related

Single SQL query for getting count based of 2 condition in same table

I have data like this
Now I need a single query to get count of id where Info is 'Yes' and count of id which are in both 'yes' and 'no'
Single query for:
SELECT COUNT(id) FROM table WHERE info = 'yes'
and
SELECT COUNT(id) FROM table WHERE info = 'yes' AND info = 'No'
Since
Id having Yes are 7 (1,2,3,4,5,6,7)
and Id having and Yes and No both vaules are only 3 (1,4, 6)
it should give me id_as_yes = 7 and id_as_yes_no = 3
You can do it with aggregation and window functions:
SELECT DISTINCT
SUM(MAX(CASE WHEN info = 'yes' THEN 1 ELSE 0 END)) OVER () id_as_yes,
COUNT(CASE WHEN COUNT(DISTINCT info) = 2 THEN 1 END) OVER () id_as_yes_no
FROM tablename
GROUP BY id
See the demo.
Results:
> id_as_yes | id_as_yes_no
> --------: | -----------:
> 7 | 3
You need conditional aggregation.
Select id,
Count(case when info = 'y' then 1 end) as y_count,
Count(case when info = 'y' and has_n = 1 then 1 end) as yn_count
From (SELECT id, info,
Max(case when info = 'no' then 1 end) over (partirion by id) as has_n
From your_table) t
You can do this without a subquery. This relies on the observation that the number of ids that are "no" only is:
count(distinct id) - count(distinct case when info = 'yes' then id end)
And similarly for the number of yeses. So, the number that have both is the number of ids minus the number of no only minus the number of yes only:
select count(distinct case when info = 'yes' then id end) as num_yeses,
(count(distinct id) -
(count(distinct id) - count(distinct case when info = 'yes' then id end)) -
(count(distinct id) - count(distinct case when info = 'no' then id end))
)
from t;
This should do the trick...it's definitely not efficient or elegant, but no null value aggregate warnings
dbFiddle link
Select
(select count(distinct id) from mytest where info = 'yes') as yeses
,(select count(distinct id) from mytest where info = 'no' and id in (select distinct id from mytest where info = 'yes' )) as [yes and no]

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

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);

SQL MAX value of two sub queries

I have two queries and I want to get the maximum value of the two of them.
MAX((SELECT COUNT(p.[ItemID]) FROM [dbo].[Table] p WHERE HasHuman=0),
(SELECT COUNT(p.[ItemID]) FROM [dbo].[Table] p WHERE HasHuman=1))
You can calculate both result in a single query and then apply TOP:
select top 1
HasHuman,
COUNT(p.[ItemID]) as cnt
from [dbo].[Table]
group by HasHuman
order by cnt desc
You could even do this in a single query:
SELECT
CASE WHEN SUM(CASE WHEN HasHuman=0 THEN 1 ELSE 0 END) >
SUM(CASE WHEN HasHuman=1 THEN 1 ELSE 0 END)
THEN SUM(CASE WHEN HasHuman=0 THEN 1 ELSE 0 END)
ELSE SUM(CASE WHEN HasHuman=1 THEN 1 ELSE 0 END) END
FROM [dbo].[Table]
WHERE ItemID IS NOT NULL -- you were not counting NULLs
SELECT MAX(RC)
FROM (SELECT COUNT(p.ItemID) AS RC FROM dbo.[Table]
WHERE HasHuman=0
UNION
SELECT COUNT(p.ItemID) AS RC FROM dbo.[Table]
WHERE HasHuman=1
) A

SQL Server - count how many names have 'A' and how many have 'E'

I have problem with SQL query.
I have names in column Name in Table_Name, for example:
'Mila', 'Adrianna' 'Emma', 'Edward', 'Adam', 'Piter'
I would like to count how many names contain the letter 'A' and how many contain the letter 'E'.
The output should be:
letter_A ( 5 )| letter_E (3)
I tried to do this:
SELECT Name,
letter_A = CHARINDEX('A', Name),
letter_E = CHARINDEX('E', Name)
FROM Table_Name
GROUP BY Name
HAVING ( CHARINDEX('A', Nazwisko) != 0
OR ( CHARINDEX('E', Nazwisko) ) != 0 )
My query only shows if 'A' or 'E' is in Name :/
Can anyone help? :)
You can use conditional aggregation:
select sum(case when Nazwisko like '%A%' then 1 else 0 end) as A_cnt,
sum(case when Nazwisko like '%E%' then 1 else 0 end) as E_cnt
from table_name
where Nazwisko like '%A%' or Nazwisko like '%E%';
You just need to aggregate if you only need the counts.
select
sum(case when charindex('a',name) <> 0 then 1 else 0 end) as a_count
,sum(case when charindex('e',name) <> 0 then 1 else 0 end) as e_count
from table_name
;WITH CTE
AS (SELECT NAME
FROM (VALUES ('MILA'),
('ADRIANNA'),
('EMMA'),
('EDWARD'),
('ADAM'),
('PITER'))V(NAME)),
CTE_NAME
AS (SELECT COUNT(NAME_A) NAME_A,
COUNT(NAME_E) NAME_E
FROM (SELECT CASE
WHEN NAME LIKE '%A%' THEN NAME
END NAME_A,
CASE
WHEN NAME LIKE '%E%' THEN NAME
END NAME_E
FROM CTE
GROUP BY NAME)A)
SELECT *
FROM CTE_NAME

Looping in select query

I want to do something like this:
select id,
count(*) as total,
FOR temp IN SELECT DISTINCT somerow FROM mytable ORDER BY somerow LOOP
sum(case when somerow = temp then 1 else 0 end) temp,
END LOOP;
from mytable
group by id
order by id
I created working select:
select id,
count(*) as total,
sum(case when somerow = 'a' then 1 else 0 end) somerow_a,
sum(case when somerow = 'b' then 1 else 0 end) somerow_b,
sum(case when somerow = 'c' then 1 else 0 end) somerow_c,
sum(case when somerow = 'd' then 1 else 0 end) somerow_d,
sum(case when somerow = 'e' then 1 else 0 end) somerow_e,
sum(case when somerow = 'f' then 1 else 0 end) somerow_f,
sum(case when somerow = 'g' then 1 else 0 end) somerow_g,
sum(case when somerow = 'h' then 1 else 0 end) somerow_h,
sum(case when somerow = 'i' then 1 else 0 end) somerow_i,
sum(case when somerow = 'j' then 1 else 0 end) somerow_j,
sum(case when somerow = 'k' then 1 else 0 end) somerow_k
from mytable
group by id
order by id
this works, but it is 'static' - if some new value will be added to 'somerow' I will have to change sql manually to get all the values from somerow column, and that is why I'm wondering if it is possible to do something with for loop.
So what I want to get is this:
id somerow_a somerow_b ....
0 3 2 ....
1 2 10 ....
2 19 3 ....
. ... ...
. ... ...
. ... ...
So what I'd like to do is to count all the rows which has some specific letter in it and group it by id (this id isn't primary key, but it is repeating - for id there are about 80 different values possible).
http://sqlfiddle.com/#!15/18feb/2
Are arrays good for you? (SQL Fiddle)
select
id,
sum(totalcol) as total,
array_agg(somecol) as somecol,
array_agg(totalcol) as totalcol
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
;
id | total | somecol | totalcol
----+-------+---------+----------
1 | 6 | {b,a,c} | {2,1,3}
2 | 5 | {d,f} | {2,3}
In 9.2 it is possible to have a set of JSON objects (Fiddle)
select row_to_json(s)
from (
select
id,
sum(totalcol) as total,
array_agg(somecol) as somecol,
array_agg(totalcol) as totalcol
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
) s
;
row_to_json
---------------------------------------------------------------
{"id":1,"total":6,"somecol":["b","a","c"],"totalcol":[2,1,3]}
{"id":2,"total":5,"somecol":["d","f"],"totalcol":[2,3]}
In 9.3, with the addition of lateral, a single object (Fiddle)
select to_json(format('{%s}', (string_agg(j, ','))))
from (
select format('%s:%s', to_json(id), to_json(c)) as j
from
(
select
id,
sum(totalcol) as total_sum,
array_agg(somecol) as somecol_array,
array_agg(totalcol) as totalcol_array
from (
select id, somecol, count(*) as totalcol
from mytable
group by id, somecol
) s
group by id
) s
cross join lateral
(
select
total_sum as total,
somecol_array as somecol,
totalcol_array as totalcol
) c
) s
;
to_json
---------------------------------------------------------------------------------------------------------------------------------------
"{1:{\"total\":6,\"somecol\":[\"b\",\"a\",\"c\"],\"totalcol\":[2,1,3]},2:{\"total\":5,\"somecol\":[\"d\",\"f\"],\"totalcol\":[2,3]}}"
In 9.2 it is also possible to have a single object in a more convoluted way using subqueries in instead of lateral
SQL is very rigid about the return type. It demands to know what to return beforehand.
For a completely dynamic number of resulting values, you can only use arrays like #Clodoaldo posted. Effectively a static return type, you do not get individual columns for each value.
If you know the number of columns at call time ("semi-dynamic"), you can create a function taking (and returning) polymorphic parameters. Closely related answer with lots of details:
Dynamic alternative to pivot with CASE and GROUP BY
(You also find a related answer with arrays from #Clodoaldo there.)
Your remaining option is to use two round-trips to the server. The first to determine the the actual query with the actual return type. The second to execute the query based on the first call.
Else, you have to go with a static query. While doing that, I see two nicer options for what you have right now:
1. Simpler expression
select id
, count(*) AS total
, count(somecol = 'a' OR NULL) AS somerow_a
, count(somecol = 'b' OR NULL) AS somerow_b
, ...
from mytable
group by id
order by id;
How does it work?
Compute percents from SUM() in the same SELECT sql query
SQL Fiddle.
2. crosstab()
crosstab() is more complex at first, but written in C, optimized for the task and shorter for long lists. You need the additional module tablefunc installed. Read the basics here if you are not familiar:
PostgreSQL Crosstab Query
SELECT * FROM crosstab(
$$
SELECT id
, count(*) OVER (PARTITION BY id)::int AS total
, somecol
, count(*)::int AS ct -- casting to int, don't think you need bigint?
FROM mytable
GROUP BY 1,3
ORDER BY 1,3
$$
,
$$SELECT unnest('{a,b,c,d}'::text[])$$
) AS f (id int, total int, a int, b int, c int, d int);