How to include percent calculations in pivot statement - sql

Using a pivot statement, I am able to break down the count in a table of data by title:
select * from (
select * from ta
)
pivot (
COUNT(title)
for title in ( 'worker', 'manager') )
So the result looks like this:
STATUS 'worker' 'manager'
started 3 1
finished 4 5
ready 3 4
What I need to add to it is the percentages, i.e.
STATUS 'worker' percent 'manager' percent
started 3 30% 1 10%
finished 4 40% 5 50%
ready 3 30% 4 40%
Any idea how I can accomplish this within the same statement?
see http://sqlfiddle.com/#!4/e6c04a/1/0

Rather than using conditional aggregation, You may use -
select STATUS
,WORKER
,WORKER/SUM(WORKER) OVER()*100 percent
,MANAGER
,MANAGER/SUM(MANAGER) OVER()*100 percent
from (
select * from ta
)
pivot (
COUNT(title)
for title in ( 'worker' AS WORKER, 'manager' AS MANAGER))
Here is the demo.

Use conditional aggregation and window functions:
select status,
sum(case when title = 'worker' then 1 else 0 end) as worker,
sum(case when title = 'manager' then 1 else 0 end) as manager,
(sum(case when title = 'worker' then 1 else 0 end) /
sum(sum(case when title = 'worker' then 1 else 0 end)) over ()
) as worker,
(sum(case when title = 'manager' then 1 else 0 end) /
sum(sum(case when title = 'manager' then 1 else 0 end)) over ()
) as manager
from ta
group by status;
Here is a sql<>fiddle.

Related

Adding a dummy identifier to data that varies by position and value

I am working on a project in SQL Server with diagnosis codes and a patient can have up to 4 codes but not necessarily more than 1 and a patient cannot repeat a code more than once. However, codes can occur in any order. My goal is to be able to count how many times a Diagnosis code appears in total, as well as how often it appears in a set position.
My data currently resembles the following:
PtKey
Order #
Order Date
Diagnosis1
Diagnosis2
Diagnosis3
Diagnosis 4
345
1527
7/12/20
J44.9
R26.2
NULL
NULL
367
1679
7/12/20
R26.2
H27.2
G47.34
NULL
325
1700
7/12/20
G47.34
NULL
NULL
NULL
327
1710
7/12/20
I26.2
J44.9
G47.34
NULL
I would think the best approach would be to create a dummy column here that would match up the diagnosis by position. For example, Diagnosis 1 with A, and Diagnosis 2 with B, etc.
My current plan is to rollup the diagnosis using an unpivot:
UNPIVOT ( Diag for ColumnALL IN (Diagnosis1, Diagnosis2, Diagnosis3, Diagnosis4)) as unpvt
However, this still doesn’t provide a way to count the diagnoses by position on a sales order.
I want it to look like this:
Diagnosis
Total Count
Diag1 Count
Diag2 Count
Diag3 Count
Diag4 Count
J44.9
2
1
1
0
0
R26.2
1
1
0
0
0
H27.2
1
0
1
0
0
I26.2
1
1
0
0
0
G47.34
3
1
0
2
0
You can unpivot using apply and aggregate:
select v.diagnosis, count(*) as cnt,
sum(case when pos = 1 then 1 else 0 end) as pos_1,
sum(case when pos = 2 then 1 else 0 end) as pos_2,
sum(case when pos = 3 then 1 else 0 end) as pos_3,
sum(case when pos = 4 then 1 else 0 end) as pos_4
from data d cross apply
(values (diagnosis1, 1),
(diagnosis2, 2),
(diagnosis3, 3),
(diagnosis4, 4)
) v(diagnosis, pos)
where diagnosis is not null;
Another way is to use UNPIVOT to transform the columns into groupable entities:
SELECT Diagnosis, [Total Count] = COUNT(*),
[Diag1 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis1' THEN 1 ELSE 0 END),
[Diag2 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis2' THEN 1 ELSE 0 END),
[Diag3 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis3' THEN 1 ELSE 0 END),
[Diag4 Count] = SUM(CASE WHEN DiagGroup = N'Diagnosis4' THEN 1 ELSE 0 END)
FROM
(
SELECT * FROM #x UNPIVOT (Diagnosis FOR DiagGroup IN
([Diagnosis1],[Diagnosis2],[Diagnosis3],[Diagnosis4])) up
) AS x GROUP BY Diagnosis;
Example db<>fiddle
You can also manually unpivot via UNION before doing the conditional aggregation:
SELECT Diagnosis, COUNT(*) As Total Count
, SUM(CASE WHEN Position = 1 THEN 1 ELSE 0 END) As [Diag1 Count]
, SUM(CASE WHEN Position = 2 THEN 1 ELSE 0 END) As [Diag2 Count]
, SUM(CASE WHEN Position = 3 THEN 1 ELSE 0 END) As [Diag3 Count]
, SUM(CASE WHEN Position = 4 THEN 1 ELSE 0 END) As [Diag4 Count]
FROM
(
SELECT PtKey, Diagnosis1 As Diagnosis, 1 As Position
FROM [MyTable]
UNION ALL
SELECT PtKey, Diagnosis2 As Diagnosis, 2 As Position
FROM [MyTable]
WHERE Diagnosis2 IS NOT NULL
UNION ALL
SELECT PtKey, Diagnosis3 As Diagnosis, 3 As Position
FROM [MyTable]
WHERE Diagnosis3 IS NOT NULL
UNION ALL
SELECT PtKey, Diagnosis4 As Diagnosis, 4 As Position
FROM [MyTable]
WHERE Diagnosis4 IS NOT NULL
) d
GROUP BY Diagnosis
Borrowing Aaron's fiddle, to avoid needing to rebuild the schema from scratch, and we get this:
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=d1f7f525e175f0f066dd1749c49cc46d

Simple SQL that I am stuck on

What percent of customers are reward members?
ID Reward Member
1 y
2 y
3 n
4 y
5 n
Count how many are members, divide by total number of customers and multiply by 100.
SELECT sum(case when RewardMember = 'y' then 1 else 0 end)*100.0/count(*) as percentage
FROM Customers
You can use COUNT():
select
1.0 *
count(case when reward_member = 'y' then 1 else 0 end)
/ count(*)
from t
I would use conditional aggregation:
select avg( case when rewardmember = 'y' then 1.0 else 0 end) as ratio
from t;
Some databases allow shorter syntax, such as:
select avg( rewardmember = 'y' )
from t;
or:
select avg( (rewardmember = 'y')::int )
from t;

How to properly return only MAX values for specific/each columns

this is result from my MSSQL Query:
What I would like to achieve is next:
I would like to Select only max numbers for each columns, Kitchen, Bar, Pizzeria and Barbecue.
I know I might solve this with 4 different queryies like this:
SELECT MAX(LastOrdinalNumber), Bar
FROM tblDemoOrdinalNumbers
WHERE Bar = 1
GROUP BY Bar
SELECT MAX(LastOrdinalNumber) as [LastOrdinalNumber], Kitchen
FROM tblDemoOrdinalNumbers
WHERE Kitchen = 1
GROUP BY Kitchen
SELECT MAX(LastOrdinalNumber) as [LastOrdinalNumber], Pizzeria
FROM tblDemoOrdinalNumbers
WHERE Pizzeria = 1
GROUP BY Pizzeria
SELECT MAX(LastOrdinalNumber) as [LastOrdinalNumber], Barbecue
FROM tblDemoOrdinalNumbers
WHERE Barbecue = 1
GROUP BY Barbecue
But is it possible somehow to get in one query results like :
So basically if possible I would get 4 numbers which represents max value for 4 columns (in column 10 in that moment max value for 3 columns was 10 but it doesn't matter, don't let that confuse you.).
Thanks!
You can do this with a separate column for each max value pretty easily:
select max(case when kitchen = 1 then LastOrdinalNumber end) as kitchen,
max(case when bar = 1 then LastOrdinalNumber end) as bar,
max(case when Pizzeria = 1 then LastOrdinalNumber end) as Pizzeria,
max(case when Barbecue = 1 then LastOrdinalNumber end) as Barbecue
from tblDemoOrdinalNumbers ;
If you want the values in rows, you can join back to the original table::
select don.*
from (select max(case when kitchen = 1 then LastOrdinalNumber end) as kitchen,
max(case when bar = 1 then LastOrdinalNumber end) as bar,
max(case when Pizzeria = 1 then LastOrdinalNumber end) as Pizzeria,
max(case when Barbecue = 1 then LastOrdinalNumber end) as Barbecue
from tblDemoOrdinalNumbers
) x join
tblDemoOrdinalNumbers don
on don.LastOrdinalNumber in (kitchen, bar, Pizzeria, Barbecue)

Best way to do a Count in TSQL

I am not so good in TSQL and i want to write a report in this manner:
input: Table A
ID Company Product Flag
1 A Car Y
2 A Van N
3 B Van Y
4 A Part N
Output
Company Y N
A 1 2
B 1 0
if one can assist in TSQL...
You could use conditional aggregation:
SELECT Company
,SUM(CASE WHEN Flag = 'Y' THEN 1 ELSE 0 END) AS Y
,SUM(CASE WHEN Flag = 'N' THEN 1 ELSE 0 END) AS N
FROM tab
GROUP BY Company
You are looking for conditional aggregation:
select company,
sum(case when flag = 'Y' then 1 else 0 end) as num_y,
sum(case when flag = 'N' then 1 else 0 end) as num_n
from t
group by company;
You can use CASE expressions (the people call it "conditional aggregation") to count the flagged products per customer like this (which will ignore a record when the Product column is empty):
SELECT Company
, COUNT(CASE Flag WHEN 'Y' THEN Product END) AS Y
, COUNT(CASE Flag WHEN 'N' THEN Product END) AS N
FROM YourTable
GROUP BY Company;
Or you can use this PIVOT query, which is a short form of writing the above:
SELECT Company, Y, N
FROM (SELECT Company, Product, Flag FROM YourTable) AS src
PIVOT (COUNT(Product) FOR Flag IN (Y, N)) AS pvt;
use case when
select company,
sum(case when flag='Y' then 1 else 0 end) as Y,
sum(case when flag='N' then 1 else 0 end) as N from tabe_data
group by company

AVG and Count from one columns

I Have a table by this Columns :
[Student_ID],[Class_ID],[Techer_ID],[Course_ID],[Marks]
and for range of marks exist name for example:
between 0 to 5 = D
between 6 to 10 = C
between 11 to 15 = B
between 16 to 20 = A
Now i need create T-Sq l Query for Return this result message columns:
Teacher_ID|Course_ID|Count(Marks)|Count(A)| Count(B)|Count(C)|Count(D)
Very thanks for your help
select Teacher_ID
, Course_ID
, count(*)
, sum(case when Marks between 16 and 20 then 1 end) as SumA
, sum(case when Marks between 11 and 15 then 1 end) as SumB
, sum(case when Marks between 6 and 10 then 1 end) as SumC
, sum(case when Marks between 0 and 5 then 1 end) as SumD
from YourTable
group by
Teacher_ID
, Course_ID
I would use the same approach as Andomar, only change sum to count like this:
select Teacher_ID
, Course_ID
, count(*)
, count(case when Marks between 16 and 20 then 1 end) as countA
, count(case when Marks between 11 and 15 then 1 end) as countB
, count(case when Marks between 6 and 10 then 1 end) as countC
, count(case when Marks between 0 and 5 then 1 end) as countD
from YourTable
group by
Teacher_ID
, Course_ID
In my opinion, the query looks more natural this way.
You can do this with PIVOT. (Note also that this formulation of the Marks-to-Letter calculation is a bit safer than one where both ends of each range must be typed.)
with T as (
select
Teacher_ID,
Course_ID,
case
when Marks <= 5 then 'countD'
when Marks <= 10 then 'countC'
when Marks <= 15 then 'countB'
else 'countA' end as Letter
from T
)
select
Teacher_ID,
Course_ID,
countD+countC+countB+countA as countMarks,
countA,
countB,
countC,
countD
from T pivot (
count(Letter) for Letter in ([countA],[countB],[countC],[countD])
) as P