Sum (or count) multiple case statement - sql

I have the following table:
Month | Item | Events | Party | Spirit | Faith |
May | 123 | 1 | 1 | 0 | 0 |
June |123 | 1 | 0 | 1 | 1 |
it is basically 1 for yes 0 for no. I need to know how many different categories each item is in each month
I need the following results:
Month | Item | Counts |
May | 123 | 2 |
June| 123 | 3 |
This is NOT working:
select Month, Item,
sum(case when EVENTS = 1 then 1 when PARTY = 1 then 1 when SPIRIT = 1 then 1 when FAITH = 1 then 1 else 0 end) as Counts
from TABLE
group by 1,2
Please help, thanks!

You don't need aggregation:
select Month, Item,
(events + party + spirit + faith) as counts
from t;

CREATE TABLE #T
(
Month varchar(10), Item int, Events bit, Party bit, Spirit bit , Faith bit
)
insert into #T
SELECT 'May' , 123 , 1 , 1 , 0 , 0 union
SELECT 'June' ,123 , 1 , 0 , 1 , 1
select Month, Item, CAST(Events AS INT) + CAST(Party AS INT)+ CAST(Spirit AS
INT) +CAST(Faith AS INT) from #T
Aggregation is not needed. Since the events, party, spirit and faith are bit columns, we need to cast it to int and then add it.

Related

How to perform group by in SQL Server for specific output

I have a table with few records, I want to get month wise data along with count on one of the column. The output should contain Month and count of Isregistered flag.
Table structure
| Inserted On | IsRegistered |
+-------------+--------------+
| 10-01-2020 | 1 |
| 15-01-2020 | 1 |
| 17-01-2020 | null |
| 17-02-2020 | 1 |
| 21-02-2020 | null |
| 04-04-2020 | null |
| 18-04-2020 | null |
| 19-04-2020 | 1 |
Excepted output
| Inserted On | Registered | Not Registered
+-------------+------------+---------------
| Jan | 2 | 1
| Feb | 1 | 1
| Apr | 1 | 2
I tried by performing normal group by but didn't got desired output
SELECT
DATENAME(MONTH, dateinserted) AS [MonthName], COUNT(ISRegistered)
FROM
tablename
GROUP BY
(DATENAME(MONTH, dateinserted))
Note: here null is treated as not registered
You can use aggregation. I would include the year and use the month number rather than name, so:
select year(inserted_on), month(inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by year(inserted_on), month(inserted_on)
order by year(inserted_on), month(inserted_on);
Note: If you really want the monthname and want to combine data from different years (which seems unlikely, but . . . ), then you can use:
select datename(month, inserted_on),
coalesce(sum(is_registered), 0) as num_registered,
sum(case when is_registered is null then 1 else 0 end) as num_not_registered
from tablename
group by datename(month, inserted_on)
order by month(min(inserted_on));
The GROUP BY should include both the year and month (so there's no overlapping) as well as the DATENAME (for display). Something like this
drop table if exists #tablename;
go
create table #tablename(dateinserted date, ISRegistered int);
insert #tablename values
('2020-12-01', 0),
('2020-11-02', 1),
('2020-11-03', 1),
('2020-12-01', 1),
('2020-12-03', 1),
('2020-11-02', 0);
select year(dateinserted) yr,
datename(month, dateinserted) AS [MonthName],
sum(ISRegistered) Registered ,
sum(1-ISRegistered) [Not Registered]
from #tablename
group by year(dateinserted), month(dateinserted), datename(month, dateinserted)
order by year(dateinserted), month(dateinserted);
yr MonthName Registered Not Registered
2020 November 2 1
2020 December 2 1

Count who paid group by 1, 2 or 3+

I have a payment table like the example below and I need a query that gives me how many IDs paid (AMOUNT > 0) 1 time, 2 times, 3 or more times. Example:
+----+--------+
| ID | AMOUNT |
+----+--------+
| 1 | 50 |
| 1 | 0 |
| 2 | 10 |
| 2 | 20 |
| 2 | 15 |
| 2 | 10 |
| 3 | 80 |
+----+--------+
I expect the result:
+-----------+------------+-------------+
| 1 payment | 2 payments | 3+ payments |
+-----------+------------+-------------+
| 2 | 0 | 1 |
+-----------+------------+-------------+
ID 1: Paid 1 time (50). The other payment is 0, so I did not count. So, 1 person paid 1 time.
ID 2: Paid 3 times (10,20,15). So, 1 person paid 3 or more time.
ID 3: Paid 1 time (80). So, 2 persons paid 1 time.
I'm doing manually on excel right now but I'm pretty sure there is a more practical solution. Any ideas?
A little sub-query will do the trick
Declare #YOurTable table (ID int, AMOUNT int)
Insert into #YourTable values
( 1 , 50 ),
( 1 , 0) ,
( 2 , 10) ,
( 2 , 20) ,
( 2 , 15) ,
( 2 , 10) ,
( 3 , 80)
Select [1_Payment] = sum(case when Cnt=1 then 1 else 0 end)
,[2_Payment] = sum(case when Cnt=2 then 1 else 0 end)
,[3_Payment] = sum(case when Cnt>2 then 1 else 0 end)
From (
Select id
,Cnt=count(*)
From #YourTable
Where Amount<>0
Group By ID
) A
Returns
1_Payment 2_Payment 3_Payment
2 0 1
To get the output you want try using a table to form the data and then SELECT from that:
with c as (
select count(*) count from mytable where amount > 0 group by id)
select
sum(case count when 1 then 1 else 0 end) "1 Payment"
, sum(case count when 2 then 1 else 0 end) "2 Payments"
, sum(case when count > 2 then 1 else 0 end) "3 Payments"
from c
Here is an example you can play with to see how the query is working.

SQL Server - Insert lines with null values when month doesn't exist

I have a table like this one:
Yr | Mnth | W_ID | X_ID | Y_ID | Z_ID | Purchases | Sales | Returns |
2015 | 10 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2015 | 12 | 1 | 5210 | 1402 | 2 | 12000.00 | etc | etc |
2016 | 1 | 1 | 5210 | 1402 | 2 | 1000.00 | etc | etc |
2016 | 3 | 1 | 5210 | 1402 | 2 | etc | etc | etc |
2014 | 3 | 9 | 880 | 2 | 7 | etc | etc | etc |
2014 | 12 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 5 | 9 | 880 | 2 | 7 | etc | etc | etc |
2015 | 7 | 9 | 880 | 2 | 7 | etc | etc | etc |
For each combination of (W, X, Y, Z) I would like to insert the months that don't appear in the table and are between the first and last month.
In this example, for combination (W=1, X=5210, Y=1402, Z=2), I would like to have additional rows for 2015/11 and 2016/02, where Purchases, Sales and Returns are NULL. For combination (W=9, X=880, Y=2, Z=7) I would like to have additional rows for months between 2014/4 and 2014/11, 2015/01 and 2015/04, 2016/06.
I hope I have explained myself correctly.
Thank you in advance for any help you can provide.
The process is rather cumbersome in this case, but quite possible. One method uses a recursive CTE. Another uses a numbers table. I'm going to use the latter.
The idea is:
Find the minimum and maximum values for the year/month combination for each set of ids. For this, the values will be turned into months since time 0 using the formula year*12 + month.
Generate a bunch of numbers.
Generate all rows between the two values for each combination of ids.
For each generated row, use arithmetic to re-extract the year and month.
Use left join to bring in the original data.
The query looks like:
with n as (
select row_number() over (order by (select null)) - 1 as n -- start at 0
from master.spt_values
),
minmax as (
select w_id, x_id, y_id, z_id, min(yr*12 + mnth) as minyyyymm,
max(yr*12 + mnth) as maxyyyymm
from t
group by w_id, x_id, y_id, z_id
),
wxyz as (
select minmax.*, minmax.minyyyymm + n.n,
(minmax.minyyyymm + n.n) / 12 as yyyy,
((minmax.minyyyymm + n.n) % 12) + 1 as mm
from minmax join
n
on minmax.minyyyymm + n.n <= minmax.maxyyyymm
)
select wxyz.yyyy, wxyz.mm, wxyz.w_id, wxyz.x_id, wxyz.y_id, wxyz.z_id,
<columns from t here>
from wxyz left join
t
on wxyz.w_id = t.w_id and wxyz.x_id = t.x_id and wxyz.y_id = t.y_id and
wxyz.z_id = t.z_id and wxyz.yyyy = t.yr and wxyz.mm = t.mnth;
Thank you for your help.
Your solution works, but I noticed it is not very good in terms of performance, but meanwhile I have managed to get a solution for my problem.
DECLARE #start_date DATE, #end_date DATE;
SET #start_date = (SELECT MIN(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
SET #end_date = (SELECT MAX(EOMONTH(DATEFROMPARTS(Yr , Mnth, 1))) FROM Table_Input);
DECLARE #tdates TABLE (Period DATE, Yr INT, Mnth INT);
WHILE #start_date <= #end_date
BEGIN
INSERT INTO #tdates(PEriod, Yr, Mnth) VALUES(#start_date, YEAR(#start_date), MONTH(#start_date));
SET #start_date = EOMONTH(DATEADD(mm,1,DATEFROMPARTS(YEAR(#start_date), MONTH(#start_date), 1)));
END
DECLARE #pks TABLE (W_ID NVARCHAR(50), X_ID NVARCHAR(50)
, Y_ID NVARCHAR(50), Z_ID NVARCHAR(50)
, PerMin DATE, PerMax DATE);
INSERT INTO #pks (W_ID, X_ID, Y_ID, Z_ID, PerMin, PerMax)
SELECT W_ID, X_ID, Y_ID, Z_ID
, MIN(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMin
, MAX(EOMONTH(DATEFROMPARTS(Ano, Mes, 1))) AS PerMax
FROM Table1
GROUP BY W_ID, X_ID, Y_ID, Z_ID;
INSERT INTO Table_Output(W_ID, X_ID, Y_ID, Z_ID
, ComprasLiquidas, RTV, DevManuais, ComprasBrutas, Vendas, Stock, ReceitasComerciais)
SELECT TP.DB, TP.Ano, TP.Mes, TP.Supplier_Code, TP.Depart_Code, TP.BizUnit_Code
, TA.ComprasLiquidas, TA.RTV, TA.DevManuais, TA.ComprasBrutas, TA.Vendas, TA.Stock, TA.ReceitasComerciais
FROM
(
SELECT W_ID, X_ID, Y_ID, Z_ID
FROM #tdatas CROSS JOIN #pks
WHERE Period BETWEEN PerMin And PerMax
) AS TP
LEFT JOIN Table_Input AS TA
ON TP.W_ID = TA.W_ID AND TP.X_ID = TA.X_ID AND TP.Y_ID = TA.Y_ID
AND TP.Z_ID = TA.Z_ID
AND TP.Yr = TA.Yr
AND TP.Mnth = TA.Mnth
ORDER BY TP.W_ID, TP.X_ID, TP.Y_ID, TP.Z_ID, TP.Yr, TP.Mnth;
I do the following:
Get the Min and Max date of the entire table - #start_date and #end_date variables;
Create an auxiliary table with all dates between Min and Max - #tdates table;
Get all the combinations of (W_ID, X_ID, Y_ID, Z_ID) along with the min and max dates of that combination - #pks table;
Create the cartesian product between #tdates and #pks, and in the WHERE clause I filter the results between the Min and Max of the combination;
Compute a LEFT JOIN of the cartesian product table with the input data table.

Need a query that switches columns

I have this table:
__________________________
| id1 | id2 | count | time |
|-----|-----|-------|------|
| abc | def | 10 | 3 |
| abc | def | 5 | 1 |
| ghi | jkl | 2 | 3 |
+--------------------------+
id1 and id2 are varchar, count is int and time is int.
id1 and id2 together make the primary key.
Time can be 1,2,3,4 or 5 depending on when an item was added (NOT UNIQUE).
I want to write a query that gives me this output instead:
_________________________________________
| id1 | id2 | 1 | 2 | 3 | 4 | 5 |
|-----|-----|-----|-----|-----|-----|-----|
| abc | def | 5 | 0 | 10 | 0 | 0 |
| ghi | jkl | 0 | 0 | 2 | 0 | 0 |
+-----------------------------------------+
Is that possible? I'm sittin here scratching my head but I cant figure it out!
You're in luck. The rule for a pivot is that you still need to know the number and names of the columns in the result set without having to look them up at the time you run the query. As long as you know that, you're okay, and in this case your columns are restricted to the range 1 through 5.
There are a few ways to pivot like this. I still prefer the sum(case) method:
select id1, id2,
sum(case when time = 1 then [count] else 0 end) "1",
sum(case when time = 2 then [count] else 0 end) "2",
sum(case when time = 3 then [count] else 0 end) "3",
sum(case when time = 4 then [count] else 0 end) "4",
sum(case when time = 5 then [count] else 0 end) "5"
from [table]
group by id1, id2
Another optioin is the PIVOT keyword:
select id1,id2,[1],[2],[3],[4],[5]
from [table]
PIVOT ( SUM([count]) FOR time IN ([1],[2],[3],[4],[5]) ) As Times
Something like this:
select ID1, ID2,
sum(f1) as '1',
sum(f2) as '2',
sum(f3) as '3',
sum(f4) as '4',
sum(f5) as '5'
from ( select ID1, ID2,
case when time =1 then time else 0 end as 'f1',
case when time =2 then time else 0 end as 'f2',
case when time =3 then time else 0 end as 'f3',
case when time =4 then time else 0 end as 'f4',
case when time =5 then time else 0 end as 'f5'
from dbo._Test
) as v
group by ID1, ID2
The inner query gives you columns for each time value, the outer query sums the values so you don't get two rows for the 'abc' + 'def' row.

Putting stuff into date ranges in SQL Server 2005

I have a table with week ranges (week number,start date, end date) and a table with tutorial dates (for writing tutors (tutor ID, tutorial_date, tutorial type(A or B).
I want to create two query that shows the week ranges (week 1, week 2) across the top with the tutor names on the side with count of tutorials (of type "A") in that week's date range in each block for that week.
The result should look like this:
Counts of Tutorials of Type "A"
Tutor|Week One|Week Two|Week Three|Week Four|Total
Joe | 3 | 5 | 7 | 8 | 23
Sam | 2 | 4 | 3 | 8 | 17
Meaning that Joe completed 3 tutorials in week one, five in week two, 7 in week three, and 8 in week 4.
The second query should show totals for tutorial type "A" and type "B"
Tutor|Week One|Week Two|Week Three|Week Four|Total |
Joe | 3/1 | 5/3 | 7/2 | 8/2 | 23/8 |
Sam | 2/3 | 4/4 | 3/2 | 8/3 | 17/12 |
Here, in Week One, Joe has done 3 tutorials of type A and 1 of type B.
Sample table data for tutorials (week one)
Tutor | Tutorial_ID | Tutorial Date |Type|
------------------------------------------
Joe | 1 | 2011-01-01 | A |
Joe | 2 | 2011-01-02 | A |
Joe | 3 | 2011-01-03 | A |
Joe | 4 | 2011-01-03 | B |
Sam | 5 | 2011-01-01 | A |
Sam | 6 | 2011-01-02 | A |
Sam | 7 | 2011-01-03 | B |
The week table looks like this:
weekNumber |startDate |endDate
1 |2011-01-01|2011-01-15
I'd like to gen this in SQL Server 2005
There are a few ways to do this.
For query one, where you only need to PIVOT on type 'A' then you can do just a PIVOT
select *
from
(
select w1.tutor
, w1.type
, wk.weeknumber
from w1
inner join wk
on w1.tutorialdate between wk.startdate and wk.enddate
where w1.type = 'a'
) x
pivot
(
count(type)
for weeknumber in ([1])
)p
See SQL Fiddle with Demo
Or you can use a Count() with a CASE statement.
select w1.tutor
, COUNT(CASE WHEN w1.type = 'A' THEN 1 ELSE null END) [Week One]
from w1
inner join wk
on w1.tutorialdate between wk.startdate and wk.enddate
group by w1.tutor
See SQL Fiddle with Demo
But for the second query, I would just use a Count() with a CASE
select w1.tutor
, Cast(COUNT(CASE WHEN w1.type = 'A' AND wk.weeknumber = 1 THEN 1 ELSE null END) as varchar(10))
+ ' / '
+ Cast(COUNT(CASE WHEN w1.type = 'B' AND wk.weeknumber = 1 THEN 1 ELSE null END) as varchar(10)) [Week One]
, Cast(COUNT(CASE WHEN w1.type = 'A' AND wk.weeknumber = 2 THEN 1 ELSE null END) as varchar(10))
+ ' / '
+ Cast(COUNT(CASE WHEN w1.type = 'B' AND wk.weeknumber = 2 THEN 1 ELSE null END) as varchar(10)) [Week Two]
from w1
inner join wk
on w1.tutorialdate between wk.startdate and wk.enddate
group by w1.tutor
See SQL Fiddle with Demo
Edit as AndriyM pointed out the second could be done with a PIVOT here is a solution for the Second query:
SELECT *
FROM
(
select distinct w1.tutor
, wk.weeknumber
, left(total, len(total)-1) Totals
FROM w1
inner join wk
on w1.tutorialdate between wk.startdate and wk.enddate
CROSS APPLY
(
SELECT cast(count(w2.type) as varchar(max)) + ' / '
from w1 w2
inner join wk wk2
on w2.tutorialdate between wk2.startdate and wk2.enddate
WHERE w2.tutor = w1.tutor
AND wk2.weeknumber = wk.weeknumber
group by w2.tutor, wk2.weeknumber, w2.type
FOR XML PATH('')
) D ( total )
) x
PIVOT
(
min(totals)
for weeknumber in ([1], [2])
) p
See SQL Fiddle with Demo