tsql select with all variations of joins - sql

For an export I have to select all stats for all years and groups, even if the combination has no amount to export.
Here an example and expected result:
DECLARE #years TABLE (yr INT)
INSERT #years VALUES (2020),(2021),(2022)
DECLARE #grps TABLE (grp INT)
INSERT #grps VALUES (1),(2),(3)
DECLARE #stats TABLE (item INT, yr INT, grp INT, amount INT)
INSERT #stats VALUES
(1,2021,1,344)
,(1,2021,2,34)
,(1,2021,3,44)
,(1,2020,1,249)
,(1,2020,3,70)
,(2,2021,1,850)
,(2,2021,2,1260)
,(2,2020,1,799)
/* EXPECTED RESULT */
/*
|item|year|grp |amout|
|1 |2020|1 | 249 |
|1 |2020|2 | 0 |
|1 |2020|3 | 70 |
|1 |2021|1 | 344 |
|1 |2021|2 | 34 |
|1 |2021|3 | 44 |
|1 |2022|1 | 0 |
|1 |2022|2 | 0 |
|1 |2022|3 | 0 |
|2 |2020|1 | 799 |
|2 |2020|2 | 0 |
|2 |2020|3 | 0 |
|2 |2021|1 | 850 |
|2 |2021|2 |1260 |
|2 |2021|3 | 0 |
|2 |2022|1 | 0 |
|2 |2022|2 | 0 |
|2 |2022|3 | 0 |
*/
I get id done with this query:
SELECT item.item
,yr.yr
,grp.grp
,ISNULL(st.amount,0) [amount]
FROM (SELECT DISTINCT item FROM #stats) item
CROSS APPLY (SELECT yr FROM #years) yr
CROSS APPLY (SELECT grp FROM #grps) grp
LEFT JOIN #stats st
ON st.item = item.item
AND st.yr = yr.yr
AND st.grp = grp.grp
ORDER BY 1,2,3
Is there no simpler solution to this?

It's debatable if it's a simpler solution.
But you can cross join in a CTE to get all expected combos.
Then left join the CTE to the existing data.
DECLARE #years TABLE (yr INT);
INSERT #years VALUES (2020),(2021),(2022);
DECLARE #grps TABLE (grp INT);
INSERT #grps VALUES (1),(2),(3);
DECLARE #stats TABLE (item INT, yr INT, grp INT, amount INT)
INSERT #stats VALUES
(1,2021,1,344)
,(1,2021,2,34)
,(1,2021,3,44)
,(1,2020,1,249)
,(1,2020,3,70)
,(2,2021,1,850)
,(2,2021,2,1260)
,(2,2020,1,799);
;WITH CTE_YEARS AS (
SELECT DISTINCT yr FROM #years
)
, CTE_GROUPS AS (
SELECT DISTINCT grp FROM #grps
)
, CTE_ITEMS AS (
SELECT DISTINCT item FROM #stats
)
, CTE_ALL_YEAR_GROUP_ITEMS AS (
SELECT y.yr, g.grp, i.item
FROM CTE_YEARS y
CROSS JOIN CTE_GROUPS g
CROSS JOIN CTE_ITEMS i
)
SELECT
cte.item
, cte.yr
, cte.grp
, ISNULL(stat.amount, 0) AS amount
FROM CTE_ALL_YEAR_GROUP_ITEMS cte
LEFT JOIN #stats stat
ON stat.item = cte.item
AND stat.yr = cte.yr
AND stat.grp = cte.grp
ORDER BY 1,2,3
item
yr
grp
amount
1
2020
1
249
1
2020
2
0
1
2020
3
70
1
2021
1
344
1
2021
2
34
1
2021
3
44
1
2022
1
0
1
2022
2
0
1
2022
3
0
2
2020
1
799
2
2020
2
0
2
2020
3
0
2
2021
1
850
2
2021
2
1260
2
2021
3
0
2
2022
1
0
2
2022
2
0
2
2022
3
0
Test on db<>fiddle here

Related

SQL join table to itself to get data for previous year

SQL. How can I join table to itself to get desired results just as shown in the table below. The logic is that I want to have Units for the same product and corresponding month of previous year.
The simple left join on source table to itself on key a.[year]=b.[year]+1 (and of course month to month and product to product) would cause the loss of the data where we had values in the previous year and do not have now.
A full join should be sufficient
select distinct
coalesce(a.year, b.year+1) as year
, coalesce(a.month, b.month) as month
, coalesce(a.product, b.product) as product
, a.units as units
, b.units as units_prev
from yourtable a
full join yourtable b on a.[year] = b.[year]+1 and a.[month] = b.[month] and a.product = b.product
Your expected results though are slightly off from the description 2018, month 2, product 2 does not exist with a prior value of 2933.
DB Fiddle : https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=d01dc5bd626854b083be0864f2d5b0e4
Result :
year month product units units_prev
2017 1 1 1721
2017 2 1 4915
2017 4 2 2933
2017 5 1 5230
2018 1 1 1721
2018 1 2 7672
2018 2 1 5216 4915
2018 3 1 8911
2018 4 2 2933
2018 5 1 5230
2019 1 2 7672
2019 2 1 5216
2019 3 1 8911
If you need to filter out futures like that, then you can add an additional where predicate, something like :
where coalesce(a.year, b.year+1) <= year(getdate())
year month
Use cross join to generate the rows, left join to bring in the data and then lag() to get the "previous" value:
select y.year, m.month, p.product, t.units,
lag(t.units) over (partition by p.product, m.month order by y.year) as prev_units
from (select distinct year from t) y cross join
(select distinct month from t) m cross join
(select distinct product from t) p left join
t
on t.year = y.year and t.month = m.month and t.product = p.producct;
I would go with LAG, and a calendar table.
SELECT C.[Year],
C.[Month],
YPT.product,
YST.units,
YST.LAG(YST.units) OVER (PARTITION BY YTP.[product],C.[month] ORDER BY C.[year]) AS UnitsPrev
FROM CalendarTable C
CROSS JOIN YourProductTable YPT
LEFT JOIN YourSourceTable YST ON C.[Year] YST.[Year]
AND C.[Month] = YST.[Month]
AND YPT.Product = YST.Product
WHERE C.[day] = 1
AND C.[date] BETWEEN {SomeStartDate} AND {SomeEndDate];
This guessing a little on your design (it assumes you have a product table).
You could generate all possible combinations for year, month and product in your data using CROSS JOIN. A simple LEFT JOIN will give you the value or NULL if data for a specific combination exists.
DECLARE #t TABLE (year int, month int, product int, unit int);
INSERT INTO #t VALUES
(2017, 1, 1, 1721),
(2017, 2, 1, 4915),
(2017, 5, 1, 5230),
(2018, 2, 1, 5216),
(2018, 3, 1, 8911),
(2017, 4, 2, 2933),
(2018, 1, 2, 7672);
SELECT ally.year, allm.month, allp.product, curr.units, prev.units AS units_prev
FROM (SELECT DISTINCT year FROM #t) AS ally
CROSS JOIN (SELECT DISTINCT product FROM #t) AS allp
CROSS JOIN (SELECT DISTINCT month FROM #t) AS allm
LEFT JOIN #t AS curr ON curr.year = ally.year AND curr.product = allp.product AND curr.month = allm.month
LEFT JOIN #t AS prev ON prev.year = ally.year - 1 AND prev.product = allp.product AND prev.month = allm.month
Result:
| year | month | product | units | units_prev |
|------|-------|---------|-------|------------|
| 2017 | 1 | 1 | 1721 | NULL |
| 2017 | 2 | 1 | 4915 | NULL |
| 2017 | 3 | 1 | NULL | NULL |
| 2017 | 4 | 1 | NULL | NULL |
| 2017 | 5 | 1 | 5230 | NULL |
| 2017 | 1 | 2 | NULL | NULL |
| 2017 | 2 | 2 | NULL | NULL |
| 2017 | 3 | 2 | NULL | NULL |
| 2017 | 4 | 2 | 2933 | NULL |
| 2017 | 5 | 2 | NULL | NULL |
| 2018 | 1 | 1 | NULL | 1721 |
| 2018 | 2 | 1 | 5216 | 4915 |
| 2018 | 3 | 1 | 8911 | NULL |
| 2018 | 4 | 1 | NULL | NULL |
| 2018 | 5 | 1 | NULL | 5230 |
| 2018 | 1 | 2 | 7672 | NULL |
| 2018 | 2 | 2 | NULL | NULL |
| 2018 | 3 | 2 | NULL | NULL |
| 2018 | 4 | 2 | NULL | 2933 |
| 2018 | 5 | 2 | NULL | NULL |
If you want the rows where nothing was sold in both 2017 and 2018 as in your desired results for March 2017 as well you need to generate out the month, year and join in the product to get the null values.
This query does it for the month and year, hopefully you should be able to add the Product as well if required
DECLARE #startMonth INT=1
DECLARE #endMonth INT=12
DECLARE #startYear INT=2017
DECLARE #endYear INT=2018
;
WITH months AS (
SELECT #startMonth AS m
UNION ALL
SELECT m+1 FROM months WHERE m+1<=#endMonth
),
years AS (
SELECT #startYear AS y
UNION ALL
SELECT y+1 FROM years WHERE y+1<=#endYear
),
monthYears AS (
SELECT m, y
FROM months, years
)
SELECT thisYear.[Year], thisYear.[Month], thisYear.[Product], thisYear.[Units], prevYear.[Units] as units_prev
FROM
(SELECT [Product], my.y as [Year], my.m as [Month], [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as thisYear
LEFT OUTER JOIN
(SELECT [Product], my.y as [Year], my.m as [Month], my.y + 1 as NextYear, [Units]
FROM monthYears my
LEFT JOIN sales on my.m = [Month] and my.y = [Year]) as prevYear
on thisYear.Product = prevYear.Product
and (thisYEAR.[Year]) = prevYear.[NextYear]
and thisYEAR.[Month] = prevYear.[Month]
ORDER BY thisYear.[Year], thisYear.[Month], thisYear.[Product]
option (maxrecursion 12);

Connecting two tables so that results are shown in multiple columns

Let's assume we have table with data like this
+----------------+------------+-------+
| Company_number | Date | Value |
+----------------+------------+-------+
| 123 | 2017-01-01 | 5 |
| 123 | 2017-02-01 | 10 |
| 123 | 2018-01-01 | 15 |
| 456 | 2018-01-05 | 33 |
+----------------+------------+-------+
What should I do to receive data in format
+----------------+------+------------+------------+
| Company_number | Mont | Value 2017 | Value 2018 |
+----------------+------+------------+------------+
| 123 | 01 | 5 | 15 |
| 123 | 02 | 10 | |
| 456 | 01 | 33 | |
+----------------+------+------------+------------+
I have no idea how to select all data from 2017 and connect it using the company number and month with data from 2018.
I have taken all of the records from 2017, put it into another table, and tried to use this select, but it doesn't show records when there are no common months (there is no record for February).
select
s.company_number
,datepart(month,s.date) as Month
,s.Value as Value_2017
,r.Value as Value_2018
from table1 as s
left join table2 as r on concat(r.company_number,datepart(month,r.date))=concat(s.company_number,datepart(month,s.date))
where datepart(year,s.date)='2018'
select results (with no February)
+----------------+-------+------------+------------+
| company_number | Month | Value_2017 | Value_2018 |
+----------------+-------+------------+------------+
| 123 | 1 | 15 | 5 |
| 456 | 1 | 33 | NULL |
+----------------+-------+------------+------------+
Try this out:
SELECT
COALESCE([t1].[company_number], [t2].[company_number]) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
[table1] AS [t1]
FULL OUTER JOIN
[table2] as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
Just tried it with (can be copy/pasted in editor and executed) and it works fine:
DECLARE #t1 TABLE (Company_number INT, [Date] Date, Value INT)
DECLARE #t2 TABLE (Company_number INT, [Date] Date, Value INT)
INSERT INTO #t1 VALUES (123, '2017-01-01', 5), (123, '2017-02-01', 10)
INSERT INTO #t2 VALUES (123, '2018-01-01', 15), (456, '2018-01-05', 33)
SELECT
COALESCE([t1].Company_number, [t2].Company_number) AS [Company_Number],
MONTH(COALESCE([t1].[date], [t2].[date])) AS [Month],
[t1].[value] AS [2018 Value],
[t2].[value] AS [2017 Value]
FROM
#t1 AS [t1]
FULL OUTER JOIN
#t2 as [t2] on [t1].[company_number] = [t2].[company_number]
AND MONTH([t1].[date]) = MONTH ([t2].[date])
This is using CTE on the same table1 NOT TWO TABLES for 2017 and 2018.
With tmp as (Select company_id,
Datepart(year, dte) as year,
Datepart(month, dte) as month,
Sum(value) as value
from tbl
Group by company_id,
Datepart(year, dte),
Datepart(month, dte))
Select coalesce(m.company_id,n.company_id) as company_id,
coalesce(m.month,n.month) as month,
m.value as value_2017,
n.value as value_2018
From (select * from tmp
Where year=2017) m
Full outer join (select * from tmp
Where year=2018) n
On m.company_id=n.company_id
and m.month=n.month
Result:
company_number month Value_2017 Value_2018
123 1 5 5
123 2 10 (null)
456 1 (null) 23

trying to get more rows from the table than there actually exist

I was not sure about a descriptive Title of this question so I put there what i thought to be the best sentence.
I have a table like this:
StudentID, Month, Year, Present
-------------------------------
1, 1, 2016, P
1, 12, 2016, P
1, 4, 2017, P
I want to write SQL statement, without using cursor, to return a resultset like the below one:
Required output:
1, 1, 2016, P -- <-- it is 'P' in the table
1, 2, 2016, A
1, 3, 2016, A
1, 4, 2016, A
1, 5, 2016, A
1, 6, 2016, A
1, 7, 2016, A
1, 8, 2016, A
1, 9, 2016, A
1, 10, 2016, A
1, 11, 2016, A
1, 12, 2016, P -- <-- it is 'P' in the table
1, 1, 2017, A
1, 2, 2017, A
1, 3, 2017, A
1, 4, 2017, P -- <-- it is 'P' in the table
1, 5, 2017, A
1, 6, 2017, A
1, 7, 2017, A
1, 8, 2017, A
1, 9, 2017, A
1, 10, 2017, A
1, 11, 2017, A
1, 12, 2017, A
is this possible through one SQL statement, without using cursors.
I am working on SQL Server 2000 database.
You can generate numbers 1 to 12 using tally table and then do cross apply as below:
;with cte1 as
( select * from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) a(n) ) --number generation
,Cte_numbers as ( select num = Row_number() over (order by (SELECT NULL)) from cte1 c1, cte1 c2, cte1 c3)
Select s.StudentId, n.num as [Month], s.[Year], Coalesce(s1.Present,'A') as Present from (
Select distinct StudentId, [YEAR] from #student ) s
cross apply ( Select top (12) num from Cte_numbers ) n
left join #student s1
on s.StudentId = s1.StudentId
and s.[Year] = s1.[Year]
and n.num = s1.[Month]
order by s.StudentId, s.[Year]
You'll need to create tables for the missing entities. For instance, a Month entity with all months and Year with all years:
CREATE TABLE Month_Table (
Month_ID int NOT NULL,
PRIMARY KEY (Month_ID)
);
CREATE TABLE Year_Table (
Year_ID int NOT NULL,
PRIMARY KEY (Year_ID)
);
Fill Month_Table with numbers from 1 to 12, and Year_Table with numbers from 2016 to 2017.
Then use LEFT OUTER joins to fill the missing values with the ones provided by those tables. Use COALESCE to use the values from the auxiliary tables if your table doesn't contain them, and 'A' when your table doesn't contain the data:
SELECT T.StudentID,
COALESCE(T.Month, Month_Table.Month_ID) Month,
COALESCE(T.Year, Year_Table.Year_ID) Year,
COALESCE(T.Present, 'A') Present
FROM YOUR_TABLE T
LEFT OUTER JOIN Month_Table ON Month_Table.Month_ID = YOUR_TABLE
LEFT OUTER JOIN Year_Table ON Year_Table.Year_ID = YOUR_TABLE.Year;
Here's two examples, one that uses CTEs, recursion, etc. just to show how easy this is in SQL Server 2005 onwards, then an example that should work in SQL Server 2000.
Like all the other answers this is basically using a numbers or tally table to do the heavy lifting. There's a good article on how to generate numbers here: https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
DECLARE #MyData TABLE (StudentId INT, [Month] INT, [Year] INT, Present CHAR(1));
INSERT INTO #MyData SELECT 1, 1, 2016, 'P';
INSERT INTO #MyData SELECT 1, 12, 2016, 'P';
INSERT INTO #MyData SELECT 1, 4, 2017, 'P';
--Recursive CTE to load numbers table dynamically
WITH Numbers AS (
SELECT 1 AS [Month], 2016 AS [Year]
UNION ALL
SELECT CASE WHEN [Month] = 12 THEN 1 ELSE [Month] + 1 END, CASE WHEN [Month] = 12 THEN [Year] + 1 ELSE [Year] END
FROM Numbers
WHERE [Year] < 2017 OR [Month] < 12),
Students AS (
SELECT DISTINCT StudentId FROM #MyData)
SELECT
s.StudentId,
n.[Year],
n.[Month],
ISNULL(m.Present, 'A') AS Present
FROM
Students s
CROSS JOIN Numbers n
LEFT JOIN #MyData m ON m.StudentId = s.StudentId AND m.[Year] = n.[Year] AND m.[Month] = n.[Month]
OPTION (MAXRECURSION 0);
--SQL Server 2000 doesn't have CTEs so we have to use a different method
DECLARE #Numbers TABLE ([Year] INT, [Month] INT);
DECLARE #Year INT = 2016;
WHILE #Year < 2018
BEGIN
DECLARE #Month INT = 1;
WHILE #Month < 13
BEGIN
INSERT INTO #Numbers SELECT #Year, #Month;
SELECT #Month = #Month + 1;
END;
SELECT #Year = #Year + 1;
END;
--SQL Server 2000 query
SELECT
s.StudentId,
n.[Year],
n.[Month],
ISNULL(m.Present, 'A') AS Present
FROM
(SELECT DISTINCT StudentId FROM #MyData) s
CROSS JOIN #Numbers n
LEFT JOIN #MyData m ON m.StudentId = s.StudentId AND m.[Year] = n.[Year] AND m.[Month] = n.[Month];
I think you can do it, by creating a table (tally) with all year-month you'll need.
Sample data:
CREATE TABLE TD1 (YYEAR SMALLINT, MMONTH SmALLINT);
INSERT INTO TD1 VALUES (2016,1);
INSERT INTO TD1 VALUES (2016,2);
INSERT INTO TD1 VALUES (2016,3);
INSERT INTO TD1 VALUES (2016,4);
INSERT INTO TD1 VALUES (2016,5);
INSERT INTO TD1 VALUES (2016,6);
INSERT INTO TD1 VALUES (2016,7);
INSERT INTO TD1 VALUES (2016,8);
INSERT INTO TD1 VALUES (2016,9);
INSERT INTO TD1 VALUES (2016,10);
INSERT INTO TD1 VALUES (2016,11);
INSERT INTO TD1 VALUES (2016,12);
INSERT INTO TD1 VALUES (2017,1);
INSERT INTO TD1 VALUES (2017,2);
INSERT INTO TD1 VALUES (2017,3);
INSERT INTO TD1 VALUES (2017,4);
INSERT INTO TD1 VALUES (2017,5);
INSERT INTO TD1 VALUES (2017,6);
INSERT INTO TD1 VALUES (2017,7);
INSERT INTO TD1 VALUES (2017,8);
INSERT INTO TD1 VALUES (2017,9);
INSERT INTO TD1 VALUES (2017,10);
INSERT INTO TD1 VALUES (2017,11);
INSERT INTO TD1 VALUES (2017,12);
CREATE TABLE TS1 (StudentId INT, MMONTH SMALLINT, YYEAR SMALLINT, PRESENT CHAR(1))
INSERT INTO TS1 VALUES (1,1,2016,'P');
INSERT INTO TS1 VALUES (1,12,2016,'P');
INSERT INTO TS1 VALUES (1,4,2017,'P');
INSERT INTO TS1 VALUES (2,3,2017,'P');
Query:
SELECT A.STUDENTID, TD1.MMONTH, TD1.YYEAR, COALESCE(TS1.PRESENT, 'A') AS PRESENT
FROM TD1
CROSS JOIN (SELECT DISTINCT STUDENTID FROM TS1) A
LEFT JOIN TS1 On TD1.MMONTH=TS1.MMONTH AND TD1.YYEAR=TS1.YYEAR AND A.STUDENTID = TS1.STUDENTID
ORDER BY A.STUDENTID, TD1.YYEAR, TD1.MMONTH;
Output:
+-----------+--------+-------+---------+
| STUDENTID | MMONTH | YYEAR | PRESENT |
+-----------+--------+-------+---------+
| 1 | 1 | 2016 | P |
| 1 | 2 | 2016 | A |
| 1 | 3 | 2016 | A |
| 1 | 4 | 2016 | A |
| 1 | 5 | 2016 | A |
| 1 | 6 | 2016 | A |
| 1 | 7 | 2016 | A |
| 1 | 8 | 2016 | A |
| 1 | 9 | 2016 | A |
| 1 | 10 | 2016 | A |
| 1 | 11 | 2016 | A |
| 1 | 12 | 2016 | P |
| 1 | 1 | 2017 | A |
| 1 | 2 | 2017 | A |
| 1 | 3 | 2017 | A |
| 1 | 4 | 2017 | P |
| 1 | 5 | 2017 | A |
| 1 | 6 | 2017 | A |
| 1 | 7 | 2017 | A |
| 1 | 8 | 2017 | A |
| 1 | 9 | 2017 | A |
| 1 | 10 | 2017 | A |
| 1 | 11 | 2017 | A |
| 1 | 12 | 2017 | A |
| 2 | 1 | 2016 | A |
| 2 | 2 | 2016 | A |
| 2 | 3 | 2016 | A |
| 2 | 4 | 2016 | A |
| 2 | 5 | 2016 | A |
| 2 | 6 | 2016 | A |
| 2 | 7 | 2016 | A |
| 2 | 8 | 2016 | A |
| 2 | 9 | 2016 | A |
| 2 | 10 | 2016 | A |
| 2 | 11 | 2016 | A |
| 2 | 12 | 2016 | A |
| 2 | 1 | 2017 | A |
| 2 | 2 | 2017 | A |
| 2 | 3 | 2017 | P |
| 2 | 4 | 2017 | A |
| 2 | 5 | 2017 | A |
| 2 | 6 | 2017 | A |
| 2 | 7 | 2017 | A |
| 2 | 8 | 2017 | A |
| 2 | 9 | 2017 | A |
| 2 | 10 | 2017 | A |
| 2 | 11 | 2017 | A |
| 2 | 12 | 2017 | A |
+-----------+--------+-------+---------+

doing a simple pivot on year

I have a table:
+----+-------+------+
| id | times | year |
+----+-------+------+
| 5 | 2 | 2008 |
| 6 | 76 | 2008 |
| 2 | 43 | 2009 |
| 4 | 5 | 2009 |
| 1 | 3 | 2010 |
| 9 | 6 | 2010 |
| 7 | 444 | 2011 |
| 8 | 3 | 2011 |
| 3 | 65 | 2012 |
+----+-------+------+
I would like to create a pivot out of this table which buckets times per year :
+--------+------+------+------+------+------+
| | 2008 | 2009 | 2010 | 2011 | 2012 |
+--------+------+------+------+------+------+
| 0 | | | | | |
| 1-30 | 1 | 1 | 2 | 1 | |
| 31-60 | | 1 | | | |
| 61-90 | 1 | | | | 1 |
| 91-120 | | | | | |
| 121+ | | | | 1 | |
+--------+------+------+------+------+------+
how do i start to tackle this challenge with sql? thank you so much for your guidance.
You can use the sql server PIVOT function for this. If you know the all of the values for the years as well as the buckets then you can hard-code the query:
select *
from
(
select
case
when times = 0 then '0'
when times >= 1 and times <=30 then '1-30'
when times >= 31 and times <=60 then '31-60'
when times >= 61 and times <=90 then '61-90'
when times >= 91 and times <=120 then '91-120'
else '121+' end bucket,
year
from yourtable
) src
pivot
(
count(year)
for year in ([2008], [2009], [2010], [2011], [2012])
) piv;
See SQL Fiddle with Demo
If you don't have access to the PIVOT function then you can use an aggregate function with a CASE:
select bucket,
sum(case when year = 2008 then 1 else 0 end) [2008],
sum(case when year = 2009 then 1 else 0 end) [2009],
sum(case when year = 2010 then 1 else 0 end) [2010],
sum(case when year = 2011 then 1 else 0 end) [2011],
sum(case when year = 2012 then 1 else 0 end) [2012]
from
(
select
case
when times = 0 then '0'
when times >= 1 and times <=30 then '1-30'
when times >= 31 and times <=60 then '31-60'
when times >= 61 and times <=90 then '61-90'
when times >= 91 and times <=120 then '91-120'
else '121+' end bucket,
year
from yourtable
) src
group by bucket
See SQL Fiddle with Demo
If you need all of the buckets to be listed, then you will want to have the bucket ranges stored in either a table or using a CTE query, then you can use the following:
;with buckets(startbucket, endbucket, rnk) as
(
select 0, 0, 1
union all
select 1, 30, 2
union all
select 31, 60, 3
union all
select 61, 90, 4
union all
select 91, 120, 5
union all
select 121, null, 6
)
select
case when startbucket = 0 then '0'
when endbucket is null then cast(startbucket as varchar(50)) + '+'
else cast(startbucket as varchar(50)) + '-'+cast(endbucket as varchar(50)) end buckets,
[2008], [2009], [2010], [2011], [2012]
from
(
select rnk,
year,
startbucket,
endbucket
from buckets b
left join yourtable t
on t.times >= b.startbucket and t.times <= coalesce(b.endbucket, 100000)
) src
pivot
(
count(year)
for year in ([2008], [2009], [2010], [2011], [2012])
) piv;
See SQL Fiddle with Demo
Result:
| BUCKETS | 2008 | 2009 | 2010 | 2011 | 2012 |
----------------------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 |
| 1-30 | 1 | 1 | 2 | 1 | 0 |
| 31-60 | 0 | 1 | 0 | 0 | 0 |
| 61-90 | 1 | 0 | 0 | 0 | 1 |
| 91-120 | 0 | 0 | 0 | 0 | 0 |
| 121+ | 0 | 0 | 0 | 1 | 0 |
The above will work great if you have a known number of values (years) that you need to transpose. If you have an unknown number then you will want to implement dynamic sql, similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(year)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'with buckets(startbucket, endbucket, rnk) as
(
select 0, 0, 1
union all
select 1, 30, 2
union all
select 31, 60, 3
union all
select 61, 90, 4
union all
select 91, 120, 5
union all
select 121, null, 6
)
select
case when startbucket = 0 then ''0''
when endbucket is null then cast(startbucket as varchar(50)) + ''+''
else cast(startbucket as varchar(50)) + ''-''+cast(endbucket as varchar(50)) end buckets,
'+#cols+'
from
(
select rnk,
year,
startbucket, endbucket
from buckets b
left join yourtable t
on t.times >= b.startbucket and t.times <= coalesce(b.endbucket, 100000)
) src
pivot
(
count(year)
for year in ('+#cols+')
) piv;'
execute(#query)
See SQL Fiddle with Demo
The result will be the same for both the static (hard-coded) version and the dynamic version.
Darn it! Bluefeet beat me to it. My attempt is similar but uses a table to configure the buckets.
CREATE TABLE Bucket
(
id int,
minbound int,
maxbound int
)
INSERT INTO Bucket VALUES(1, 0, 30)
,(2, 31, 60)
,(3, 61, 90)
,(4, 91, 120)
,(5, 121, null)
Then one can calculate the bucket for each record in a CTE like so....
;WITH RecordBucket
AS
(
SELECT
r.*,
b.id as bucketid
FROM
Record r
INNER JOIN Bucket b ON r.times BETWEEN b.minbound and ISNULL(b.maxbound, 20000000)
)
...and outer join back to the buckets for the final query to allow ordering and empty buckets to be included:
select
b.id as BucketId,
CASE
WHEN b.maxbound IS NULL THEN CONVERT(VARCHAR(16), b.minbound) + '+'
ELSE CONVERT(VARCHAR(16), b.minbound) + ' - ' + CONVERT(VARCHAR(16), b.maxbound)
END as BucketName,
[2008],[2009],[2010],[2011]
from
Bucket b
LEFT JOIN
(
SELECT
bucketid,
times,
year
from
RecordBucket
) rb
pivot (count(times) for year in ([2008],[2009],[2010],[2011]))
as pvt ON b.id = pvt.bucketid
order by
bucketid

SQL Query Group By Mount And Year

I have this table, called history (dates are in DD-MM-YYYY):
====================
| Buy | Qty |
====================
| 01-01-2012 | 1 |
| 01-01-2012 | 1 |
| 01-02-2012 | 1 |
| 01-03-2012 | 1 |
| 01-05-2012 | 1 |
| 01-07-2012 | 1 |
| 01-12-2012 | 1 |
====================
NOTE: There was no purchase in months 4, 6, 8, 9, 10, 11.
If I run :
SELECT MONTH(buy) AS day, YEAR(buy) as year, SUM(qty)
FROM history
GROUP BY MONTH(buy),YEAR(buy)
I get this result:
======================
| Month | Year | Qty |
======================
| 01 | 2012 | 2 |
| 02 | 2012 | 1 |
| 03 | 2012 | 1 |
| 05 | 2012 | 1 |
| 07 | 2012 | 1 |
| 12 | 2012 | 1 |
======================
I want months 4, 6, 8, 9, 10, 11 to show too but have Qty of 0 (zero), like this:
======================
| Month | Year | Qty |
======================
| 01 | 2012 | 2 |
| 02 | 2012 | 1 |
| 03 | 2012 | 1 |
| 04 | 2012 | 0 |
| 05 | 2012 | 1 |
| 06 | 2012 | 0 |
| 07 | 2012 | 1 |
| 08 | 2012 | 0 |
| 09 | 2012 | 0 |
| 10 | 2012 | 0 |
| 11 | 2012 | 0 |
| 12 | 2012 | 1 |
======================
How can I do this?
Try this :
Declare #Sample table
(Buy datetime ,Qty int)
Insert into #Sample values
( '01-01-2012' ,1),
('01-01-2012',1 ),
('01-02-2012',1 ),
('01-03-2012',1 ),
('01-05-2012',1 ),
('01-07-2012',1 ),
('01-12-2012',1 )
;with cte as
(
select top 12 row_number() over(order by t1.number) as N
from master..spt_values t1
cross join master..spt_values t2
)
select t.N as month,
isnull(datepart(year,y.buy),'2012') as Year,
sum(isnull(y.qty,0)) as Quantity
from cte t
left join #Sample y
on month(convert(varchar(20),buy,103)) = t.N
group by y.buy,t.N
Create a Month table to store the value from 1 to 12 .Instead of master..spt_values you can also use sys.all_objects
select row_number() over (order by object_id) as months
from sys.all_objects
or use a recursive cte to generate the month table
;with cte(N) as
(
Select 1
union all
Select 1+N from cte where N<12
)
Select * from cte
and then use Left join to compare the value from the month table with your table and use isnull function to handle the null values.
You could use a look-up table of integers.
What I use in this situation is a function/procedure that generates the number via a CTE (Example below)
The rest would be to create dates using the resulting number as the month - and then join your results from above.
declare #min int
declare #max int
set #min = 1
set #max = 12
;
with numbers(n) as
(
select #min as n
union all
select n + 1
from numbers
where n + 1 <= #max
)
select row_number() over (order by n) [row], n
from numbers option(maxrecursion 0);
Below code will helps you, you can also filter data by year
SELECT MonthYearTable.Month , MonthYearTable.Year, ISNULL(SUM(qty),0)
FROM history
Right JOIN (SELECT Month, Year FROM (SELECT 1 AS Month
Union SELECT 2
Union SELECT 3
Union SELECT 4
Union SELECT 5
Union SELECT 6
Union SELECT 7
Union SELECT 8
Union SELECT 9
Union SELECT 10
Union SELECT 11
Union SELECT 12) AS MonthTable
INNER JOIN (SELECT DISTINCT YEAR(buy) AS year FROM history) YearTable ON 1=1
) AS MonthYearTable ON Month(history.buy) = MonthYearTable.Month AND Year(history.buy) = MonthYearTable.Year
GROUP BY MonthYearTable.Month, MonthYearTable.Year
ORDER BY MonthYearTable.Year, MonthYearTable.Month
Try This:
CREATE TABLE history(Buy datetime,Qty int)
INSERT INTO history
VALUES('01-01-2012',1),
('01-01-2012',1),
('01-02-2012',1),
('01-03-2012',1),
('01-05-2012',1),
('01-07-2012',1),
('01-12-2012',1)
Declare #mindate datetime
Declare #maxdate datetime
select #mindate=MIN(convert(datetime,buy,103)),#maxdate=MAX(convert(datetime,buy,103)) from history
select datepart(month,a.dt) as month,datepart(year,a.dt) as year,isnull(SUM(qty),0) as Quantity
from
(select DATEADD(mm,number,convert(varchar(10),#mindate,103)) as dt from master..spt_values
where type='p' and DATEADD(mm,number,convert(varchar(10),#mindate,103)) <= convert(varchar(10),#maxdate,103)
) a left join history h
on DATEPART(month,a.dt) = DATEPART(month,convert(varchar(10),h.Buy ,103))
group by a.dt
order by a.dt