SQL query with join, sum, group by, etc - sql

I'm trying to build a report that will look like this:
jan feb mar apr may jun jul ago sep oct nov dec
food 0 1 1 2 0 0 3 1 0 0 1 1
car 1 0 0 0 1 2 1 0 1 2 3 4
home 0 0 1 2 2 2 5 1 2 4 0 0
other 0 0 0 0 0 0 0 0 0 0 0 0
I have two tables: t_item and t_value. t_item has 2 columns: itemID and itemName. t_value has 3 columns: itemID, value, date.
With the following query I can generate a list with all the itens, even with the empty ones.
SELECT t_item.itemID, ISNULL(SUM(t_value.value), 0) AS value
FROM t_value RIGHT OUTER JOIN t_item ON t_value.itemID = t_item.itemID
GROUP BY t_item.itemID
But, if I try to include a MONTH column (as follows) the result will show only the items with values...
SELECT t_item.itemID, ISNULL(SUM(t_value.value), 0) AS value, MONTH(date) AS date
FROM t_value RIGHT OUTER JOIN t_item ON t_value.itemID = t_item.itemID
GROUP BY t_item.itemID, MONTH(date)
Is it possible to do it? How do I include into the results the itens with no values and group then by month?
TIA,
Bob

WITH calendar(mon) AS
(
SELECT 1
UNION ALL
SELECT mon + 1
FROM calendar
WHERE mon < 12
)
SELECT itemID, mon, SUM(value)
FROM calendar c, t_item i
LEFT OUTER JOIN
t_value v
ON v.itemID = i.itemID
AND MONTH(date) = mon
GROUP BY
i.itemID, mon

For the "holes" in your data you need a filler table. Join this table with a full outer join to the fact table on month.
month
------
month --values jan through dec
For the formating you have a couple options.
In your reporting tool use the cross tab or matrix function.
In SQL use the CASE function.
In SQL use the Pivot function.

Are you using a reporting tool with crosstab like ability?
If not, you can create a sum column for each month. so your resultset would actually look like that report sample.
SELECT t_item.itemID,
--ISNULL(SUM(t_value.value), 0) AS value,
sum(case when MONTH(date) = 1 then t_value.value else 0 end) AS m1_sum,
sum(case when MONTH(date) = 2 then t_value.value else 0 end) AS m2_sum,
sum(case when MONTH(date) = 3 then t_value.value else 0 end) AS m3_sum,
--etc
FROM t_value RIGHT OUTER JOIN t_item ON t_value.itemID = t_item.itemID
GROUP BY t_item.itemID

Here's an example:
create table #months (value int, name varchar(12))
create table #items (value int, name varchar(24))
create table #sales (month int, item int, sales int)
insert into #months values (1, 'jan')
insert into #months values (2, 'feb')
insert into #months values (3, 'mar')
insert into #items values (1, 'apple')
insert into #items values (2, 'pear')
insert into #items values (3, 'nut')
insert into #sales values (1,1,12)
insert into #sales values (2,2,3)
insert into #sales values (2,2,5)
insert into #sales values (3,3,7)
You can query it using a PIVOT table, like:
select *
from (
select
item = #items.name
, month = #months.name
, sales = isnull(sum(#sales.sales),0)
from #months
cross join #items
left join #sales on #months.value = #sales.month
and #items.value = #sales.item
group by #months.name, #items.name
) vw
pivot (sum(sales) for month in ([jan],[feb],[mar])) as PivotTable
Or as an alternative, a regular query:
select
item = #items.name
, jan = sum(case when #sales.month = 1 then sales else 0 end)
, feb = sum(case when #sales.month = 2 then sales else 0 end)
, mar = sum(case when #sales.month = 3 then sales else 0 end)
from #items
left join #sales on #items.value = #sales.item
group by #items.name
Both result in:
item jan feb mar
apple 12 0 0
nut 0 0 7
pear 0 8 0
In the first example, the "cross join" ensures all months and values are present. They're then "left joined", so even the rows with no values are displayed.
The IsNull() is just so that it displays 0 instead of NULL for a month in which that particular item was not sold.

Related

SQL Server Count Distinct records with a specific condition in window functions

I have a table similar to below:
Group
TradeMonth
flag
A
Jan
1
A
Mar
1
A
Mar
0
A
Jun
0
B
Feb
1
B
Apr
1
B
Sep
1
B
Sep
1
I need to have a column that calculates the number of distinct months with non-zero values (flag=1) for each Group. I prefer using window function (not group by) and I know count distinct is not allowed in window functions in sql server, So any solution on how to calculate that with a window function is highly appreciated.
The results should be as below:
Group
TradeMonth
flag
#of flagged_months(Distinct)
A
Jan
1
2
A
Mar
1
2
A
Mar
0
2
A
Jun
0
2
B
Feb
1
3
B
Apr
1
3
B
Sep
1
3
B
Sep
1
3
Unfortunately you can't do COUNT(DISTINC ...) OVER (), but here is one workaround
with
cte as
(
select *,
dr = dense_rank() over (partition by [Group], flag order by [TradeMonth])
from yourtable
)
select [Group], [TradeMonth], flag,
max(case when flag = 1 then dr end) over (partition by [Group])
from cte
dbfiddle demo
Try this
create table #test([Group] varchar(1), TradeMon Varchar(10), Flag int)
insert into #test values ('A', 'Jan', 1),('A', 'Mar', 1),('A', 'Mar', 0),('A', 'Jun', 0),('B', 'Feb', 1),('B', 'Apr', 1),('B', 'Sep', 1),('B', 'Sep', 1)
With distinctCount AS
(
SELECT [group], COUNT(1)DistinctCount
FROM
(
select distinct [group], TradeMon
from #test
where flag=1
)T GROUP BY [group]
)
SELECT T.[GROUP], T.TradeMon, T.Flag, DC.DistinctCount
FROM #test T
INNER JOIN distinctCount DC ON (T.[GROUP] = DC.[Group])
You can actually do this with a single expression:
select t.*,
(dense_rank() over (partition by group
order by (case when flag = 1 then trademonth end)
) +
dense_rank() over (partition by group
order by (case when flag = 1 then trademonth end) desc
) -
(1 + min(flag) over (partition by trademonth))
) as num_unique_flag_1
from t;
What is the logic here? The sum of dense_rank() with an ascending sort and a descending sort is one more than the number of distinct values.
Under normal circumstances (i.e. calculating the number of distinct months), you would just subtract 1.
In this case, though, you treat 0 values as NULL. These still get counted, but there is only one of them. So, you either subtract 1 or 2 depending on the presence of 0 values. Voila! The result is the number of distinct months with a flag of 1.

Can't figure out how to full join two queries with different values in both tables

I have two queries that look like this:
SELECT
sem.Sem_Jahr,
sem.Sem_KW,
COUNT(*) AS Seminars,
bearb.MA_ID
FROM acc_seminar.t_Seminar sem
JOIN acc_seminar.t_Seminar_Thema semth ON sem.Sem_SemTh_ID = semth.SemTh_ID
JOIN acc_ma.t_Mitarbeiter bearb ON sem.Sem_Berb_MA_ID = bearb.MA_ID
WHERE sem.Sem_Sto != 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr
the second query is exactly the same, except the condition is WHERE sem.Sem_Sto != 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372
KW refers to week
I want to show results from both queries, combined. The problem is that the first query may have seminar count value for KW 2, but the second one would have NULL. The problem is, I can't figure out how to join them to get the following desired result:
KW | Seminars from query 1 | Seminars from query 2
----------------------------------------------------
2 | NULL | 5
3 | 8 | NULL
4 | 1 | 4
What I tried:
I tried just putting UNION between these two, but then I only get results from first query.
I also tried to write first query normally and then doing a FULL OUTER JOIN with second query as subquery in JOIN, but then I get results for the first query and results from second query only where the week matches with row from first query.
This whole request seems so banal to me, but I just can't figure it out, it doesn't click in my head on how to join them. Any suggestions?
Alway aim for a minimal, reproducable example. My sample data has way less joins, but should still show your issue and possible solutions.
Sample data
create table data
(
year int,
week int,
flag bit
);
insert into data (year, week, flag) values
(2021, 1, 0),
(2021, 1, 1),
(2021, 1, 1),
(2021, 2, 0),
(2021, 2, 0),
(2021, 2, 0),
(2021, 2, 0),
(2021, 3, 1);
Issue reproduction
Second query as subquery:
select coalesce(f.year, t.year) as year,
coalesce(f.week, t.week) as week,
count(1) as countFalse,
t.countTrue
from data f
full join ( select d.year,
d.week,
count(1) as countTrue
from data d
where d.flag = 1
group by d.year,
d.week ) t
on t.year = f.year
and t.week = f.week
where f.flag = 0 --> issue: week 3 not available for flag = 0, results limited...
group by f.year,
t.year,
f.week,
t.week,
t.countTrue
order by f.year,
f.week;
Result missing week = 3:
year week countFalse countTrue
---- ---- ---------- ---------
2021 1 1 2
2021 2 4 null
Solution 1
Isolate both queries in common table expressions (cte_false, cte_true) and join them without where clause in final select.
with cte_false as
(
select d.year,
d.week,
count(1) as countFalse
from data d
where d.flag = 0
group by d.year,
d.week
),
cte_true as
(
select d.year,
d.week,
count(1) as countTrue
from data d
where d.flag = 1
group by d.year,
d.week
)
select coalesce(f.year, t.year) as year,
coalesce(f.week, t.week) as week,
f.countFalse,
t.countTrue
from cte_false f
full join cte_true t
on t.year = f.year
and t.week = f.week;
Solution 2
Perform all calculations first (cte_count), then use pivot to transform the data.
with cte_count as
(
select d.year,
d.week,
d.flag,
count(1) as countFlag
from data d
group by d.year,
d.week,
d.flag
)
select piv.year,
piv.week,
piv.[0] as countFalse,
piv.[1] as countTrue
from cte_count cc
pivot (max(cc.countFlag) for cc.flag in ([0], [1])) piv;
Result
year week countFalse countTrue
---- ---- ---------- ---------
2021 1 1 2
2021 2 4 null
2021 3 null 1
Fiddle to see things in action.
You can do this using conditional aggregation:
SELECT sem.Sem_Jahr,
sem.Sem_KW,
SUM(CASE WHEN sem.Sem_Sto <> 1 AND semth.SemTh_Typ = 7 AND sem.Sem_Jahr = #Jahr and MA_ID = 372 THEN 1 ELSE 0 END) AS Seminars,
SUM( <whatever the second condition is> THEN 1 ELSE 0 END),
bearb.MA_ID
FROM acc_seminar.t_Seminar sem JOIN
acc_seminar.t_Seminar_Thema semth
ON sem.Sem_SemTh_ID = semth.SemTh_ID JOIN
acc_ma.t_Mitarbeiter bearb
ON sem.Sem_Berb_MA_ID = bearb.MA_ID
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr;
If I speculate that the difference is one of the columns, such as semth.SemTh_Typ = 8, then this can be simplified by moving common conditions to the WHERE clause:
SELECT sem.Sem_Jahr,
sem.Sem_KW,
SUM(CASE WHEN semth.SemTh_Typ = 7 THEN 1 ELSE 0 END) AS Seminars,
SUM(CASE WHEN semth.SemTh_Typ = 8 THEN 1 ELSE 0 END),
bearb.MA_ID
FROM acc_seminar.t_Seminar sem JOIN
acc_seminar.t_Seminar_Thema semth
ON sem.Sem_SemTh_ID = semth.SemTh_ID JOIN
acc_ma.t_Mitarbeiter bearb
ON sem.Sem_Berb_MA_ID = bearb.MA_ID
WHERE sem.Sem_Sto <> 1 AND
semth.SemTh_Typ IN (7, 8) AND
sem.Sem_Jahr = #Jahr AND
MA_ID = 372
GROUP BY bearb.MA_ID, sem.Sem_KW, sem.Sem_Jahr;

Count the cycle, and count that already has been counted

I have my query:
SELECT UserGroupCode,COUNT(UserGroupCode) AS [CountofCycle]
FROM Users.GroupCycles
GROUP BY UserGroupCode;
Which shows me:
UserGroupCode CountofCycles
1 1
4 1
5 1
6 2 (gone into 2nd cycle)
7 1
8 1
9 1
10 1
11 1
12 1
13 1
14 1
15 1
16 1
17 1
18 1
19 1
When i try to count Total UserGroups where countofcycle=1
SELECT Count(t.CountOfCycle) AS 'totalgroups'
FROM
(SELECT CreateDate, COUNT(userGroupCode) AS [CountofCycle]
FROM Users.GroupCycles
GROUP BY CreateDate,UserGroupCode)t
WHERE CountofCycle=1
I get result = 18 which should be 16, if i delete CreateDate from both SELECT And GROUP BY statement i can get correct number of CountofCycles,
and when i change condition to CountofCycle=2 or >1 it shows me 0
What is the problem with showing UserGroups with cycle > 1 ???!??
Here is my query to filter out onCreateDate, in 2nd table that i UNION with 1st one, i cant't use CreateDate, as it disturbs my query results
SELECT Count(t.CountOfCycle) AS 'total groups'
FROM
(SELECT COUNT(userGroupCode) AS [CountofCycle], CreateDate
FROM users.GroupCycles GROUP BY userGroupCode,CreateDate)t
WHERE t.CountOfCycle=1 AND t.CreateDate Between '03/16/2017' AND '04/25/2017'
UNION ALL
SELECT Count(t.CountOfCycle) AS 'group on date2'
FROM
(SELECT COUNT(userGroupCode) AS [CountofCycle] FROM users.GroupCycles GROUP BY userGroupCode)t
WHERE t.CountOfCycle=2
Firstly to address why you are not getting the results you are expecting, and the simple reason is that you are comparing two different queries and expecting the results to be the same.
Consider this very simple example data
UserGroupCode | CreateDate
----------------+----------------
A | 2017-05-10
B | 2017-05-10
B | 2017-05-11
C | 2017-05-10
You have two records where the UserGroupCode is B, so if you run:
DECLARE #T TABLE (UserGroupCode CHAR(1), CreateDate DATE)
INSERT #T (userGroupCode, CreateDate)
VALUES ('A', '2017-05-10'), ('B', '2017-05-10'), ('B', '2017-05-11');
SELECT UserGroupCode, COUNT(*) AS [Count]
FROM #T
GROUP BY UserGroupCode
HAVING COUNT(*) = 2;
This returns:
UserGroupCode Count
-------------------------
B 2
However, if you were to add CreateDate to the grouping, "B" would be split into two groups, each with a count of 1:
DECLARE #T TABLE (UserGroupCode CHAR(1), CreateDate DATE)
INSERT #T (userGroupCode, CreateDate)
VALUES ('A', '2017-05-10'), ('B', '2017-05-10'), ('B', '2017-05-11');
SELECT UserGroupCode, CreateDate, COUNT(*) AS [Count]
FROM #T
WHERE UserGroupCode = 'B'
GROUP BY UserGroupCode, CreateDate;
This returns:
UserGroupCode CreateDate Count
---------------------------------------
B 2017-05-10 1
B 2017-05-11 1
Now, based you your queries you have posted, it looks like you want to know
The number of groups that only have one record in the date range 16th March 2017 to 25th April 2017.
The number of groups that have two records in total.
For this, consider a slightly larger data set:
UserGroupCode | CreateDate
----------------+----------------
A | 2017-04-10
B | 2017-04-10
B | 2017-05-11
C | 2017-01-01
C | 2017-01-02
D | 2017-04-01
D | 2017-04-02
E | 2017-01-02
So here.
Group A has one record in total, and it falls within the date range
Group B has two records in total, on in the date range, one not
Group C has two records, neither in the date range
Group D has two records, both in the date range.
Group E has one record, not in the date range
So for your first requirement:
The number of groups that only have one record in the date range 16th March 2017 to 25th April 2017.
We would expect 2 groups, A and B, because C and E have no records in the date range, and D has two.
and the second we would expect three groups, B, C and D, since A and E only have one record each.
You can do this with a single query by using a conditional aggregate.
DECLARE #T TABLE (UserGroupCode CHAR(1), CreateDate DATE)
INSERT #T (userGroupCode, CreateDate)
VALUES ('A', '2017-04-10'),
('B', '2017-04-10'), ('B', '2017-05-11'),
('C', '2017-01-01'), ('C', '2017-01-02'),
('D', '2017-04-01'), ('D', '2017-04-02'),
('E', '2017-01-02');
SELECT TotalGroups = COUNT(CASE WHEN RecordsInPeriod = 1 THEN 1 END),
GroupOnDate2 = COUNT(CASE WHEN TotalRecords = 2 THEN 1 END)
FROM ( SELECT UserGroupCode,
TotalRecords = COUNT(*),
RecordsInPeriod = COUNT(CASE WHEN CreateDate >= '20170316'
AND CreateDate <= '20170425' THEN 1 END)
FROM #T
GROUP BY UserGroupCode
) AS t;
Which gives:
TotalGroups GroupOnDate2
------------------------------
2 3
I'd expect to see a HAVING clause rather than a WHERE:
SELECT UserGroupCode, COUNT(UserGroupCode) [CountofCycle]
FROM [Users].[GroupCycles]
GROUP BY UserGroupCode
HAVING COUNT(UserGroupCode) > 1;
You could use HAVING, should work (and be more efficient)
select count(*)
from
(
SELECT CreateDate, COUNT(userGroupCode) AS [CountofCycle]
FROM Users.GroupCycles
GROUP BY CreateDate,UserGroupCode
having count(userGroupCode) > 1 -- here is HAVING clause
) x1

In sql divide row1 by row,row2 by row3.... . .and store the output third column

I have this table, Now I need to perform division on amt column and update the data int CalcAmt
month amt CalcAmt
JAN 10000 NULL
FEB 20000 NULL
MAR 30000 NULL
APR 40000 NULL
eg: (FEB/JAN) then store output in CalcAmt of Feb,
(MAR/FEB) then store output in CalcAmt of MAR,
Expected Output:
month amt CalcAmt
JAN 10000 0
FEB 20000 2
MAR 30000 1.5
APR 40000 1.33
This can be easily done through LEAD/LAG, Since you are using Sql Server 2008you cannot use LEAD/LAG function. Instead use can generate row number for each row and self join the same table with row number + 1 to fetch the previous row data.
Note: You need add the remaining months in the order in case statement if you have any.
As a side note you should not use reserved keywords as object name ex: MONTH
;WITH cte
AS (SELECT Rn=CASE [month]
WHEN 'Jan' THEN 1
WHEN 'feb' THEN 2
WHEN 'mar' THEN 3
ELSE 4
END,
*
FROM YOURTABLE)
SELECT A.month,
A.amt,
CalcAmt = A.amt / NULLIF(b.amt ,0)
FROM cte A
LEFT OUTER JOIN cte B
ON A.rn = b.rn + 1
SQLFIDDLE DEMO
If you want to update the table then use this.
;WITH cte
AS (SELECT Rn=CASE [month]
WHEN 'Jan' THEN 1
WHEN 'feb' THEN 2
WHEN 'mar' THEN 3
ELSE 4
END,
*
FROM YOURTABLE)
UPDATE C
SET C.calcamt = D.calcamt
FROM cte C
INNER JOIN (SELECT A.month,
A.amt,
calcamt=A.amt / b.amt
FROM cte A
LEFT OUTER JOIN cte B
ON A.rn = b.rn + 1) D
ON C.[month] = D.[month]

SQL Inner join not working to get change from previous date

I have a table with the following data :
TradeDate Stock BuySell DayClose
--------------------------------------
10-Dec-12 ABC 1 11
10-Dec-12 ABC 2 12
11-Dec-12 ABC 1 11.5
11-Dec-12 ABC 2 12.5
11-Dec-12 DEF 1 15
11-Dec-12 DEF 2 16
and I want to query on it for a particular date 11-Dec-2012 to get the following output :
Stock Buy Sell Mid Change
--------------------------------------
ABC 11.5 12.5 12.0 0.5
DEF 15 16 15.5
Since DEF does not have data for the previous date, change should be blank for it.
I have created the following query :
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum(t1.DayClose - t2.DayClose) as Change
FROM #myTable as t1 inner join #myTable as t2 on
t1.Stock = t2.Stock
where
t1.TradeDate = '2012-12-11' AND
t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < '2012-12-11' ORDER BY TradeDate DESC)
GROUP BY
t1.Stock, t1.buysell, t1.dayclose ) AS P1 GROUP BY stock
I created a temp table #mytable for this purpose :
drop table #mytable
CREATE TABLE #myTable
(
TradeDate datetime,
stock varchar(20),
buysell int,
dayclose decimal(10,2)
)
insert into #mytable values ('10-dec-2012', 'abc' , 1, 11)
insert into #mytable values ('10-dec-2012', 'abc' , 2, 12)
insert into #mytable values ('11-dec-2012', 'abc' , 1, 11.5)
insert into #mytable values ('11-dec-2012', 'abc' , 2, 12.5)
insert into #mytable values ('11-dec-2012', 'def' , 1, 15)
insert into #mytable values ('11-dec-2012', 'def' , 2, 16)
But I am not able to get the required output, rather getting
Stock Buy Sell Mid Change
--------------------------------------------------------------
abc 11.500000 12.500000 12.00000 1.00
Can someone tell me where am I going wrong. I seem to be lost in here.
Thanks,
Monika
Please try:
;WITH T1 as(
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
FROM #mytable a
GROUP BY a.TradeDate, a.stock
)SELECT t.*,
t.Mid - PR.Mid AS Change
FROM T1 t
LEFT JOIN
T1 PR ON
PR.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND PR.stock = t.stock
Try this:
SELECT a.TradeDate
,a.stock
,SUM(CASE WHEN a.BuySell = 1 THEN a.DayClose ELSE 0 END) Buy
,SUM(CASE WHEN a.BuySell = 2 THEN a.DayClose ELSE 0 END) Sell
,SUM(a.DayClose) / 2 AS Mid
INTO #temp
FROM #mytable a
GROUP BY a.TradeDate, a.stock
SELECT t.*,
t.Mid - previousRecord.Mid AS Change
FROM #temp t
LEFT JOIN
#temp previousRecord ON
previousRecord.TradeDate = DATEADD(DAY, -1, t.TradeDate)
AND previousRecord.stock = t.stock
DROP TABLE #temp
All you have to do now is to select the data for a date.
Select Stock,
AVG(CASE BuySell WHEN 1 THEN DayClose END) AS 'Buy',
AVG(CASE BuySell WHEN 2 THEN DayClose END) As 'Sell',
Sum(DayClose/2) as 'Mid',
Sum(Change/2) AS Change
FROM (
select t1.Stock, t1.BuySell, t1.DayClose, Sum( t1.DayClose - t2.DayClose ) as Change
FROM #myTable as t1 left join #myTable as t2 on t2.TradeDate = (SELECT TOP 1 TradeDate FROM #myTable WHERE TradeDate < t1.TradeDate ORDER BY TradeDate DESC)
and t1.Stock = t2.Stock and t1.buysell=t2.buysell
where
t1.TradeDate = '11-12-2012'