Connecting two tables so that results are shown in multiple columns - sql

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

Related

How to write a SQL query that generate values based on data in the table

I have a table that looks like the table below and I need to make a query that shows the previous price of the Stock
Stock_ID | Stock_Code | Price | From_date | To_date
-----------+------------+-------+------------+-----------
1 | XYZ | 71 | 2013-01-05 | 2013-01-06
1 | XYZ | 72 | 2013-01-07 | 2013-01-08
1 | XYZ | 74 | 2013-01-09 | 2013-01-10
2 | QWE | 24 | 2013-01-05 | 2013-01-06
2 | QWE | 22 | 2013-01-07 | 2013-01-08
2 | QWE | 30 | 2013-02-09 | 2013-01-10
The query should resulted in something like this:
Stock_ID | Stock_Code | Price | From_date | To_date | Previous_Price
-----------+------------+-----+--------------+---------------------------
1 | XYZ | 71 | 2013-01-05 | 2013-01-06| null
1 | XYZ | 72 | 2013-01-07 | 2013-01-08| 71
1 | XYZ | 74 | 2013-01-09 | 2013-01-10| 72
2 | QWE | 24 | 2013-01-05 | 2013-01-06| null
2 | QWE | 22 | 2013-01-07 | 2013-01-08| 24
2 | QWE | 30 | 2013-02-09 | 2013-01-10| 22
what I tried:
SELECT *,
(SELECT Price
WHERE To_date in (SELECT DATEADD(day, -1, From_date) from StockTable))
FROM StockTable
However, the order is incorrect and the null is showed in the bottom.
I suspect this is because it's a totally separate query and the second query does not directly uses the date from the SELECT * table.
I included Order By as well as suggested by one of the comments, but it still doesn't work.
Is it possible to do so without creating a new table and use join? What can I do to obtain the result?
Perhaps the window function lag() over() would be a good fit here
Select *
,Prev_Price = lag(Price) over (partition by Stock_ID order by From_date)
from YourTable
Results
A first principles approach is to simply use an inline query to select the previous record. It not a generally efficient solution but if the dates are contiguous and there are not many records for each Stock_Code then the performance might be acceptable:
SELECT s.Stock_ID, s.Stock_Code, s.Price, s.From_date, s.To_date
, (SELECT Price
FROM StockTable lst
WHERE lst.Stock_ID = s.Stock_ID
AND lst.Stock_Code = s.Stock_Code
AND lst.To_date = (
SELECT MAX(To_date)
FROM StockTable mx
WHERE mx.Stock_ID = s.Stock_ID
AND mx.Stock_Code = s.Stock_Code
AND mx.To_date < s.To_Date
)
) as Previous_Price
FROM StockTable s
You could however simplify this with a simple LAG window query:
SELECT Stock_ID, Stock_Code, Price, From_date, To_date
, LAG (Price, 1, 0)
OVER (PARTITION BY Stock_ID, Stock_Code
ORDER BY To_Date)
as Previous_Price
FROM StockTable s
Try this fiddle for proof: http://sqlfiddle.com/#!18/a4372/2
I'm not sure that is the best solution, but:
create table #temp(
Stock_ID INT,
Stock_Code NChar(5),
Price INT,
From_Date DATE,
To_Date DATE
)
INSERT INTO #temp VALUES
(1, 'XYZ', 71, '2013-01-05', '2013-01-06'),
(1, 'XYZ', 72, '2013-01-07', '2013-01-08'),
(1, 'XYZ', 74, '2013-01-09', '2013-01-10'),
(2, 'QWE', 24, '2013-01-05', '2013-01-06'),
(2, 'QWE', 22, '2013-01-07', '2013-01-08'),
(2, 'QWE', 30, '2013-01-09', '2013-01-10')
SELECT
t1.Stock_ID,
t1.Stock_Code,
t1.Price,
t1.From_Date,
t1.To_Date,
(SELECT
t3.Price [Previouse_Price]
FROM
#temp t3
INNER JOIN
(SELECT
MAX(t2.From_Date) [Previouse_Date]
FROM
#temp t2
WHERE
t1.Stock_ID = t2.Stock_ID
AND
t1.Stock_Code = t2.Stock_Code
AND
t1.From_Date > t2.From_Date
) A ON A.Previouse_Date = t3.From_Date
WHERE
t1.Stock_ID = t3.Stock_ID
AND
t1.Stock_Code = t3.Stock_Code)
FROM #temp t1

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);

Joining tables on dates, but aligning one table by an extra day

I have two tables below:
table A
+------------+-------+
| Date | Value |
+------------+-------+
| 11-08-2018 | 2.3 |
| 11-09-2018 | 4.3 |
| 11-12-2018 | 2.9 |
| 11-13-2018 | 3.9 |
+------------+-------+
table B
+------------+---------+
| Date | Value |
+------------+---------+
| 11-07-2018 | -6.99 |
| 11-08-2018 | 12.3333 |
| 11-09-2018 | 14.22 |
| 11-12-2018 | 3.66 |
+------------+---------+
I need to join them on the Date field however I want to join them so I get a result like below (i.e. I want to add a day to the date field in table B also ignoring weekend)
+------------+---------+---------+
| Date | Value A | Value B |
+------------+---------+---------+
| 11-08-2018 | 2.3 | -6.99 |
| 11-09-2018 | 4.3 | 12.333 |
| 11-12-2018 | 2.9 | 14.22 |
| 11-13-2018 | 3.9 | 3.66 |
+------------+---------+---------+
How best to achieve this?
Assuming you want to join Monday with previous Friday:
SELECT *
FROM tablea
INNER JOIN tableb ON DATEADD(DAY, IIF(DATENAME(WEEKDAY, tablea.Date) = 'Monday', -3, -1), tablea.Date) = tableb.Date
Define a function that adds days to a date skipping the weekend days:
create function dbo.udf_AddWorkingDays(#dateToIncrement as date, #daysToAdd as int)
returns datetime
as
begin
SET #dateToIncrement = dateadd(d, #daysToAdd, #dateToIncrement)
--skip sundays
IF datename(DW, #dateToIncrement) = 'sunday'
SET #dateToIncrement = dateadd(d, 1, #dateToIncrement)
--skip saturdays
IF datename(DW, #dateToIncrement) = 'saturday'
SET #dateToIncrement = dateadd(d, 2, #dateToIncrement)
return cast(#dateToIncrement AS datetime)
end
Use the functions in your join condition:
declare #tableA table ([Date] date , [Value] decimal(10,2))
declare #tableB table ([Date] date , [Value] decimal(10,2))
insert into #tableA values
('2018-11-08', 2.3)
,('2018-11-09', 4.3)
,('2018-11-12', 2.9)
,('2018-11-13', 3.9)
insert into #tableB values
('2018-11-07', -6.99 )
,('2018-11-08', 12.3333)
,('2018-11-09', 14.22 )
,('2018-11-12', 3.66 )
select A.[Date], A.[Value], B.[Value]
from #tableA A
inner join #tableB B on A.[Date] = dbo.udf_AddWorkingDays(B.[Date], 1)
Result:
You can modify your join condition accordingly
SELECT TableA.Date, TableA.Value as [Value A], TableB.Value as [Value B]
FROM TableB
JOIN TableA
ON TableA.Date = DATEADD(day, 1, TableA.Date)

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 |
+-----------+--------+-------+---------+

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