SQL Count columns from other columns - sql

I have this view generated after using LEFT JOIN over 2 tables (simplified example).
Tables:
T1: Id, ...other columns not used
+----+-----+
| Id | ... |
+----+-----+
| 1 | ... |
| 2 | ... |
+----+-----+
T2: Id, NewId (Foreign Key from T1), Status, ...other columns not used
+-----+-------+--------+-----+
| Id | NewId | Status | ... |
+-----+-------+--------+-----+
| 1 | 1 | 1 | ... |
| 2 | 1 | 2 | ... |
| 3 | 1 | 2 | ... |
| 4 | 1 | 3 | ... |
| 5 | 1 | 1 | ... |
| 6 | 1 | 1 | ... |
| 7 | 2 | 0 | ... |
| 8 | 2 | 2 | ... |
| 9 | 2 | 1 | ... |
| 10 | 2 | 2 | ... |
+-----+-------+--------+-----+
Current View:
SELECT
T1.Id,
T2.Status
FROM T1
LEFT JOIN T2 ON T1.Id = T2.NewId;
View: (I got till here)
+----+--------+
| Id | Status |
+----+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 2 |
| 1 | 3 |
| 1 | 1 |
| 1 | 1 |
| 2 | 0 |
| 2 | 2 |
| 2 | 1 |
| 2 | 2 |
+----+--------+
The required view needs to have separate columns for each status value (which are exactly 0, 1, 2 or 3). 0 & 1 are kept in the same column.
Required View: (But I need this)
+----+------------+----------+----------+
| Id | Status 0/1 | Status 2 | Status 3 |
+----+------------+----------+----------+
| 1 | 1 | 1 | 1 |
| 2 | 2 | 2 | 2 |
+----+------------+----------+----------+
I feel like I missing something basic. How can I get this view?
I don't think we need Rank() stuff, and it's a big query over 4 normalized tables (in practice), which is why I need an optimal solution. Subqueries work with inline SELECT queries, but they need JOINs as well. The production requirement has 10 columns for counts over 2 separate columns.

Use conditional aggregation:
select
id,
sum(case when status in (0, 1) then 1 else 0 end) status_0_1,
sum(case when status = 2 then 1 else 0 end) status_2
sum(case when status = 3 then 1 else 0 end) status_3
from mytable
group by id
In your orignal query, this should look like:
select
t1.id,
sum(case when t2.status in (0, 1) then 1 else 0 end) status_0_1,
sum(case when t2.status = 2 then 1 else 0 end) status_2
sum(case when t2.status = 3 then 1 else 0 end) status_3
from t1
left join t2 on t1.id = t2.newid
group by t1.id

Use aggregation:
select id,
sum(case when status in (0, 1) then 1 else 0 end) as status_01,
sum(case when status = 2 then 1 else 0 end) as status_2,
sum(case when status = 3 then 1 else 0 end) as status_3
from t
group by id;
You should be able to build this directly into your left join:
from a left join
b
on . . .
Can be the from clause. Or use your current query as a subquery or CTE.

Related

Oracle SQL count and group by multiple fields

I am able to get the data merging two tables to get the following table.
+------------+------+--------+--------+------------+------------+
| Group Name | Type | Manger | Status | ControlOne | ControlTwo |
+------------+------+--------+--------+------------+------------+
| Group A | 1 | 1 | finish | 2 | 2 |
| Group A | 2 | 1 | open | 0 | 2 |
| Group A | 1 | 1 | finish | 0 | 0 |
| Group A | 1 | 2 | finish | 2 | 0 |
| Group B | 1 | 1 | open | 2 | 0 |
| Group B | 1 | 2 | open | 2 | 2 |
| Group B | 2 | 2 | open | 0 | 2 |
| Group B | 2 | 1 | finish | 0 | 0 |
| Group B | 1 | 1 | open | 2 | 0 |
+------------+------+--------+--------+------------+------------+
Now I need to get the total counts based on GroupName/ Type and Manager to have the output for each group in the following format:
+------------+------+-------------------------------------------------+--------------------------------------------+------------------------------+----------------------------+
| Group Name | Type | Manager1Finish | Manager1Open | Manager2Finish | Manager2Open |
+------------+------+-------------------------------------------------+--------------------------------------------+------------------------------+----------------------------+
| Group A | 1 | 2(count of finish by Group A, manager1, type 1) | 0(count of open Manager1, Type 1, Group A) | 1(count of finish Manager 2) | 0(count of open manager 2) |
| Group A | 2 | 0 | 1 | 0 | 0 |
+------------+------+-------------------------------------------------+--------------------------------------------+------------------------------+----------------------------+
Could you please help to how to achieve this?
Try with CASE WHEN:
SELECT GroupName,
TYPE,
COUNT (CASE
WHEN Manager = 1
AND status = 'Finish'
THEN
1
END)
AS Manager1Finish,
COUNT (CASE
WHEN Manager = 1
AND status = 'Open'
THEN
1
END)
AS Manager1Open,
COUNT (CASE
WHEN Manager = 2
AND status = 'Finish'
THEN
1
END)
AS Manager2Finish,
COUNT (CASE
WHEN Manager = 2
AND status = 'Open'
THEN
2
END)
AS Manager2Open
FROM tablename
GROUP BY GroupName, TYPE
select [group], [type],
sum(case when manager=1 and status='finish' then 1 else 0 end) as m1finish,
sum(case when manager=1 and status='open' then 1 else 0 end) as m1open,
sum(...etc...)
from mytable
group by [group],[type]

SQL Server Pivot table for survey responses

I have a sql server table called surveys with the following data
+------------+--------------+----+----+----+----+
| ModuleCode | SurveyNumber | Q1 | Q2 | Q3 | Q4 |
+------------+--------------+----+----+----+----+
| NME3519 | 1 | 5 | 4 | 5 | 3 |
| NME3519 | 2 | 3 | 3 | 2 | 1 |
| NME3519 | 3 | 4 | 3 | 2 | 1 |
| NME3520 | 1 | 4 | 3 | 2 | 1 |
| NME3519 | 4 | 4 | 2 | 2 | 1 |
+------------+--------------+----+----+----+----+
I'd like to be able to report on one module at a time and for the result to be something like this:
Count of scores
+----------+---+---+---+---+---+
| Question | 1 | 2 | 3 | 4 | 5 |
+----------+---+---+---+---+---+
| Q1 | 0 | 0 | 1 | 2 | 1 |
| Q2 | 0 | 1 | 2 | 1 | 0 |
| Q3 | 0 | 3 | 0 | 0 | 1 |
| Q4 | 3 | 0 | 1 | 0 | 0 |
+----------+---+---+---+---+---+
I'm pretty sure from other examples I need to unpivot and then pivot but I can't get anywhere with my own data.
Many thanks
Richard
Unpivot and aggregate:
select v.question,
sum(case when v.score = 1 then 1 else 0 end) as score_1,
sum(case when v.score = 2 then 1 else 0 end) as score_2,
sum(case when v.score = 3 then 1 else 0 end) as score_3,
sum(case when v.score = 4 then 1 else 0 end) as score_4,
sum(case when v.score = 5 then 1 else 0 end) as score_5
from responses r cross apply
( values ('Q1', r.q1), ('Q2', r.q2), ('Q3', r.q3), ('Q4', r.q4), ('Q5', r.q5)
) v(question, score)
group by v.question;
This version uses a lateral join for unpivoting. I find the syntax simpler and lateral joins more powerful. Why bother learning unpivot when something else does the same thing more concisely, more powerfully, and has the same performance?
As for the pivoting, it uses conditional aggregation. In my experience with SQL Server, this has pretty much the same performance as pivot.

Make a new column based from CASE and GROUP BY result part 2

Previous question : Make a new column based from CASE and GROUP BY result
From the previous question i got the result like below
+------------------------+------+
| id_laporan_rekomendasi | test |
+------------------------+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 0 |
| 5 | 1 |
| 6 | 0 |
+------------------------+------+
The query is just like below (thanks to Gordon Linoff's answer):
SELECT t1.id_laporan_rekomendasi,
MAX( t1.status = 3 ) as test
FROM test t1
GROUP BY t1.id_laporan_rekomendasi;
Now, ihave other problem. on left side of column id_laporan_rekomendasi there is a column callled id_laporan, so the table would looks like this.
+------------+------------------------+------+
| id_laporan | id_laporan_rekomendasi | test |
+------------+------------------------+------+
| 3 | 2 | 0 |
| 3 | 6 | 1 |
| 8 | 3 | 1 |
| 8 | 4 | 1 |
| 7 | 1 | 1 |
| 7 | 5 | 1 |
+------------+------------------------+------+
I need to group by again id_laporan and add a new column called test2 , like if from column test have at least value 0 than the result will be 0, if there is no value other than 0 (ex. 1 ) it will give us value 1 in column test2.
I have tried many possible queries: like
SELECT t2.id_laporan, MAX( t2.test = 1 )
FROM (SELECT t1.id_laporan, t1.id_laporan_rekomendasi, MAX( t1.status = 3 ) as test
FROM test t1
GROUP BY t1.id_laporan_rekomendasi) t2
GROUP BY t2.id_laporan
But it gives me result
+------------+--------------------+
| id_laporan | MAX( t2.test = 1 ) |
+------------+--------------------+
| 3 | 1 |
| 7 | 1 |
| 8 | 1 |
+------------+--------------------+
I changed the max into MAX (t2.test = 0 ) , it given me result like this:
+------------+--------------------+
| id_laporan | MAX( t2.test = 0 ) |
+------------+--------------------+
| 3 | 1 |
| 7 | 0 |
| 8 | 0 |
+------------+--------------------+
I Expect the result would like below:
+------------+--------------------+
| id_laporan | test2 |
+------------+--------------------+
| 3 | 0 |
| 7 | 1 |
| 8 | 1 |
+------------+--------------------+
I finally got the solution,
I just simply change the MAX function, to MIN function, so it would be like
MIN( t2.test = 1 )
FULL QUERY
SELECT t2.id_laporan, MIN( t2.test = 1 )
FROM (SELECT t1.id_laporan, t1.id_laporan_rekomendasi, MAX( t1.status = 3 ) as test
FROM test t1
GROUP BY t1.id_laporan_rekomendasi) t2
GROUP BY t2.id_laporan
With MIN function, it will return 0 when column test had value more lower than 1.

Encapsulated Group by or conditional aggregation in Vertica DB

I have the following table in a Vertica DB:
+---------+-------+
| Readout | Event |
+---------+-------+
| 1 | A |
| 1 | B |
| 1 | A |
| 2 | B |
| 2 | A |
+---------+-------+
I would like to group each readout and count the frequency of the events, resulting in a table like this:
+---------+----------------+----------------+-----------------+
| Readout | Count(Readout) | Count(Event A) | Count (Event B) |
+---------+----------------+----------------+-----------------+
| 1 | 3 | 2 | 1 |
| 2 | 2 | 1 | 1 |
+---------+----------------+----------------+-----------------+
I am sure there is an easy GROUP BY command, but I can't wrap my head around it.
You want conditional aggregation:
select readout, count(*),
sum(case when event = 'A' then 1 else 0 end) as num_a,
sum(case when event = 'B' then 1 else 0 end) as num_b
from t
group by readout;

MySQL: Pivot + Counting

I need help with a SQL that will convert this table:
===================
| Id | FK | Status|
===================
| 1 | A | 100 |
| 2 | A | 101 |
| 3 | B | 100 |
| 4 | B | 101 |
| 5 | C | 100 |
| 6 | C | 101 |
| 7 | A | 102 |
| 8 | A | 102 |
| 9 | B | 102 |
| 10 | B | 102 |
===================
to this:
==========================================
| FK | Count 100 | Count 101 | Count 102 |
==========================================
| A | 1 | 1 | 2 |
| B | 1 | 1 | 2 |
| C | 1 | 1 | 0 |
==========================================
I can so simple counts, etc., but am struggling trying to pivot the table with the information derived. Any help is appreciated.
Use:
SELECT t.fk,
SUM(CASE WHEN t.status = 100 THEN 1 ELSE 0 END) AS count_100,
SUM(CASE WHEN t.status = 101 THEN 1 ELSE 0 END) AS count_101,
SUM(CASE WHEN t.status = 102 THEN 1 ELSE 0 END) AS count_102
FROM TABLE t
GROUP BY t.fk
use:
select * from
(select fk,fk as fk1,statusFK from #t
) as t
pivot
(COUNT(fk1) for statusFK IN ([100],[101],[102])
) AS pt
Just adding a shortcut to #OMG's answer.
You can eliminate CASE statement:
SELECT t.fk,
SUM(t.status = 100) AS count_100,
SUM(t.status = 101) AS count_101,
SUM(t.status = 102) AS count_102
FROM TABLE t
GROUP BY t.fk