doing a simple pivot on year - sql

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

Related

SQL COUNT by each date with multiple date fields

I have the following table and query which is not giving the correct result.
JOB table have Open, Finished and Closed dates.
Now I need to pull the count of Open, Finished and Closed Jobs between on the selected dates group by each dates and Location.
Please help me to get the result like in below expected result
+-------+-----------+------------+-----------+----------+
| JOB_id| DateOpen | DateFinish | DateClose | Location |
+-------+-----------+------------+-----------+----------+
| 100 | 16-Dec-18 | 18-Dec-18 | 19-Dec-18 | A |
| 101 | 16-Dec-18 | 18-Dec-18 | 19-Dec-18 | A |
| 102 | 17-Dec-18 | 19-Dec-18 | 20-Dec-18 | C |
| 103 | 10-Dec-18 | 11-Dec-18 | 16-Dec-18 | D |
| 104 | 17-Dec-18 | 19-Dec-18 | 18-Dec-18 | E |
+-------+-----------+------------+-----------+----------+
Query:
SELECT count(DateOpen) as Opened,
count(DateFinish) as Finised,
count(DateClose) as Closed,
(DateOpen) as Date
FROM JOBS
WHERE DateOpen BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/17/2018')
group by DateOpen
Expected Result:
+-----------+------+----------+--------+----------+
| Date | Open | Finished | Closed | Location |
+-----------+------+----------+--------+----------+
| 16-Dec-18 | 2 | 0 | 0 | A |
| 16-Dec-18 | 0 | 0 | 1 | D |
| 17-Dec-18 | 1 | 0 | 0 | C |
| 17-Dec-18 | 1 | 0 | 0 | E |
+-----------+------+----------+--------+----------+
You could pull all open, finish and closed dates in a single column and left join your jobs table with it:
DECLARE #date1 AS DATE = '2018-12-16';
DECLARE #date2 AS DATE = '2018-12-17';
WITH dates(date) AS (
SELECT DateOpen FROM jobs
UNION
SELECT DateFinish FROM jobs
UNION
SELECT DateClose FROM jobs
)
SELECT dates.date
, Location
, COUNT(CASE WHEN dates.date = DateOpen THEN 1 END) AS Opened
, COUNT(CASE WHEN dates.date = DateFinish THEN 1 END) AS Finished
, COUNT(CASE WHEN dates.date = DateClose THEN 1 END) AS Closed
FROM dates
LEFT JOIN jobs ON dates.date IN (DateOpen, DateFinish, DateClose)
WHERE dates.date BETWEEN #date1 AND #date2
GROUP BY dates.date
, Location
Result:
| date | Location | Opened | Finished | Closed |
|------------|----------|--------|----------|--------|
| 16/12/2018 | A | 2 | 0 | 0 |
| 16/12/2018 | D | 0 | 0 | 1 |
| 17/12/2018 | C | 1 | 0 | 0 |
| 17/12/2018 | E | 1 | 0 | 0 |
Demo on DB Fiddle
You can use below query for the desired result set.
select coalesce(t1.date, t2.date, t3.date) as date, t1.Opened, t2.Finished, t3.Closed,
coalesce(t1.location, t2.location, t3.location) as location
from
(SELECT Convert(date,DateOpen) as Date ,count(JobID) as Opened, location
FROM JOBS
WHERE DateOpen BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/17/2018')
group by Convert(date,DateOpen), location
) t1
Full join
(SELECT Convert(date,DateFinish) as Date ,count(JobID) as Finished, location
FROM JOBS
WHERE DateFinish BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/17/2018')
group by Convert(date,DateFinish), location
) t2 ON(t1.date = t2.date and t1.location = t2.location)
FULL JOIN
(SELECT Convert(date,DateClose) as Date ,count(JobID) as Closed, location
FROM JOBS
WHERE DateClose BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/17/2018')
group by Convert(date,DateClose), location
) t3 ON(t2.date = t3.date and t2.location = t3.location)
First, I would recommend you to stop storing dates as text and use proper data types.
To do what you need, make a list of all dates and locations by selecting each of the dates columns and location and union them into one result (first cte - allDates). Then we need a distinct list (second cte - aggregated) to select from, and count the number of rows from your table that match the current date/location values.
Here is the whole solution:
declare #JOBS table(JOB_ID int, DateOpen varchar(10), DateFinish varchar(10), DateClose varchar(10), Location varchar(5))
insert into #JOBS values
( 100, '16-Dec-18', '18-Dec-18', '19-Dec-18', 'A'),
( 101, '16-Dec-18', '18-Dec-18', '19-Dec-18', 'A'),
( 102, '17-Dec-18', '19-Dec-18', '20-Dec-18', 'C'),
( 103, '10-Dec-18', '11-Dec-18', '16-Dec-18', 'D'),
( 104, '17-Dec-18', '19-Dec-18', '18-Dec-18', 'E')
;with allDates as (
select convert(date, DateOpen) as [Date], Location from #JOBS
union
select convert(date, DateFinish), Location from #JOBS
union
select convert(date, DateClose), Location from #JOBS
),
aggregated as (
select [Date], Location
from allDates
group by [Date], Location
)
select
a.Date
, (select count(*) from #JOBS where a.[Date] = DateOpen and a.Location = Location) [Open]
, (select count(*) from #JOBS where a.[Date] = DateFinish and a.Location = Location) Finished
, (select count(*) from #JOBS where a.[Date] = DateClose and a.Location = Location) Closed
, Location
from aggregated a
where a.Date between '20181216' and '20181217'
You can use a case statement with sum,
SELECT Convert(date,DateOpen) as Date ,
sum(case when DateOpen =DateOpen then 1 else 0 end) as Opened,
sum(case when DateFinish=DateOpen then 1 else 0 end) as Finised,
sum(case when DateClose=DateOpen then 1 else 0 end) as Closed,
Location
FROM JOBS
WHERE DateOpen BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/17/2018')
group by Convert(date,DateOpen),Location
UNION
SELECT Convert(date,DateClose) as Date ,
sum(case when DateOpen =DateClose then 1 else 0 end) as Opened,
sum(case when DateFinish=DateClose then 1 else 0 end) as Finised,
sum(case when DateClose=DateClose then 1 else 0 end) as Closed,
Location
FROM JOBS
WHERE DateClose BETWEEN '12/16/2018' AND DATEADD(DAY, 1, '12/16/2018')
group by Convert(date,DateClose),Location
DECLARE #startDate DATETIME = '12/16/2018'
DECLARE #endDate DATETIME = '12/17/2018'
SELECT
count(CASE when DateOpen BETWEEN #startDate AND #endDate THEN 1 end) as Opened,
count(CASE when DateFinish BETWEEN #startDate AND #endDate THEN 1 end) as Finised,
count(CASE when DateClose BETWEEN #startDate AND #endDate THEN 1 end) as Closed,
DateOpen as Date,
Location
FROM JOBS
WHERE DateOpen BETWEEN #startDate AND #endDate
group by DateOpen, Location
You can try the following code, using SUM function that sums the state of processes on inputed dates:
SELECT Convert(date1,DateOpen) as Date,
sum(case
when DateOpen = Convert(date1,DateOpen) then 1
else 0
end) as Open,
sum(case
when DateFinish = Convert(date1,DateOpen) then 1
else 0
end) as Finished,
sum(case
when DateClose = Convert(date1,DateOpen) then 1
else 0
end) as Closed,
Location
FROM JOBS
group by Location
UNION ALL
SELECT Convert(date2,DateOpen) as Date,
sum(case
when DateOpen = Convert(date2,DateOpen) then 1
else 0
end) as Open,
sum(case
when DateFinish = Convert(date2,DateOpen) then 1
else 0
end) as Finished,
sum(case
when DateClose = Convert(date2,DateOpen) then 1
else 0
end) as Closed,
Location
FROM JOBS
group by Location;
EDITED: date1 and date2 are input parameters.
You can give it a try using unpivot to see how the performance is
with cols_to_rows
as (
select *
from t
unpivot(col for val in (dateopen,datefinish,dateclose))m
)
select col
,location
,count(case when val='dateopen' then 1 end) as open1
,count(case when val='datefinish' then 1 end) as finish
,count(case when val='dateclose' then 1 end) as close1
from cols_to_rows
where col between cast('2018-12-16' as date)
and cast('2018-12-17' as date)
group by col,location,val
order by col,location
| 10/12/2018 00:00:00 | D | 1 | 0 | 0 |
| 11/12/2018 00:00:00 | D | 0 | 1 | 0 |
| 16/12/2018 00:00:00 | A | 2 | 0 | 0 |
| 16/12/2018 00:00:00 | D | 0 | 0 | 1 |
| 17/12/2018 00:00:00 | C | 1 | 0 | 0 |
| 17/12/2018 00:00:00 | E | 1 | 0 | 0 |
| 18/12/2018 00:00:00 | A | 0 | 2 | 0 |
| 18/12/2018 00:00:00 | E | 0 | 0 | 1 |
| 19/12/2018 00:00:00 | A | 0 | 0 | 2 |
| 19/12/2018 00:00:00 | C | 0 | 1 | 0 |
| 19/12/2018 00:00:00 | E | 0 | 1 | 0 |
| 20/12/2018 00:00:00 | C | 0 | 0 | 1 |
Here is a dbfiddle link
https://dbfiddle.uk/?rdbms=sqlserver_2012&fiddle=1ca0c0180a4d31d6e112e9f3b1b99715
Try this:
DECLARE #MinDate DATE = '12/16/2018',
#MaxDate DATE = '12/17/2018'
DECLARE #DateTable TABLE (DateOpen DATETIME)
INSERT INTO #DateTable
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
;with cte
As
(
SELECT DISTINCT d.DateOpen,
SUM(CASE WHEN j.DateOpen =d.DateOpen THEN 1 ELSE 0 END) OVER(partition by d.DateOpen,Location) As [Open]
,SUM(CASE WHEN j.DateFinish =d.DateOpen THEN 1 ELSE 0 END) OVER(partition by d.DateOpen,Location) As [Finished]
,SUM(CASE WHEN j.DateClose =d.DateOpen THEN 1 ELSE 0 END) OVER(partition by d.DateOpen,Location) As [Closed]
,Location
FROM #DateTable d
CROSS JOIN JOBS j
)
Select * from cte where [Open]>0 or Finished>0 or Closed>0
Order by DateOpen,Location
Try this one
WITH CTE AS
(
SELECT *
FROM
(
SELECT Location
FROM T
GROUP BY Location
) L CROSS APPLY
(
SELECT DateOpen
FROM T
WHERE DateOpen BETWEEN '2018-12-16' AND '2018-12-18'
) D
GROUP BY Location, DateOpen
),
F AS
(
SELECT *,
(SELECT COUNT(1) FROM T WHERE Location = CTE.Location AND DateOpen = CTE.DateOpen) [Open],
(SELECT COUNT(1) FROM T WHERE Location = CTE.Location AND DateFinish = CTE.DateOpen)[Finish],
(SELECT COUNT(1) FROM T WHERE Location = CTE.Location AND DateClose = CTE.DateOpen) [Close]
FROM CTE
)
SELECT DateOpen,
[Open],
[Finish],
[Close],
Location
FROM F
WHERE [Open] > 0
OR
[Finish] > 0
OR
[Close] > 0
ORDER BY DateOpen
Returns:
+---------------------+------+--------+-------+----------+
| DateOpen | Open | Finish | Close | Location |
+---------------------+------+--------+-------+----------+
| 16/12/2018 00:00:00 | 2 | 0 | 0 | A |
| 16/12/2018 00:00:00 | 0 | 0 | 1 | D |
| 17/12/2018 00:00:00 | 1 | 0 | 0 | C |
| 17/12/2018 00:00:00 | 1 | 0 | 0 | E |
+---------------------+------+--------+-------+----------+
Demo

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

How to merge two different rows(how to assign different value is zero)

I am trying to use union for merging two output but these rows value are different.I need different rows value are zero.like output(third) table.I was struggle with pass two days please help me.
Select t1.round,
t1.SC,
t1.ST,
t1.OTHERS,
t2.round_up,
t2.SC_up,
t2.ST_up,
t2.OTHERS_up
From
(Select round as round,
Sum (non_slsc_qty) as SC,
Sum (non_slst_qty) as ST,
Sum (non_slot_qty) as OTHERS
FROM vhn_issue
where (date between '2015-08-01' and '2015-08-31')AND
dvn_cd='15' AND phc_cd='012' AND hsc_cd='05' GROUP BY round) t1
,
(Select round as round_up,
Sum (non_slsc_qty) as SC_up,
Sum (non_slst_qty) as ST_up,
Sum (non_slot_qty) as OTHERS_up,
FROM vhn_issue
where (date between '2015-04-01' and '2015-08-31')AND
dvn_cd='15' AND phc_cd='012' AND hsc_cd='05' GROUP BY round) t2
This first table result
+-----------------------------------+------------+--------+--------
| round | SC | ST | OTHERS |
+-----------------------------------+------------+--------+--------
| 1 | 20 | 30 | 50 |
| | | | |
| | | | |
+-----------------------------------+------------+--------+--------+
This is second table result
+-----------------------------------+------------+--------+----------
| round_up | SC_up | ST_up | OTHERS_up |
+-----------------------------------+------------+--------+-----------
| 1 | 21 | 31 | 51 |
| 3 | 10 | 5 | 2 |
| | | | |
+-----------------------------------+------------+--------+--------+---
I need output like this
+------------+--------+----------------------------------------------
| round_up | SC | ST |OTHERS | SC_up | ST_up |OTHERS_up |
+------------+--------+-----------------------------------------------
| 1 | 20 | 30 | 50 | 21 | 31 | 51 |
| | | | | | | |
| 3 | 0 | 0 | 0 | 10 | 5 | 2 |
+------------+--------+--------+---------------------------------------
You can use WITH Queries (Common Table Expressions) to wrap the two selects and use RIGHT JOIN to get the desired output,COALESCE is used to print 0 instead of NULL.
WITH a
AS (
SELECT round AS round
,Sum(non_slsc_qty) AS SC
,Sum(non_slst_qty) AS ST
,Sum(non_slot_qty) AS OTHERS
FROM vhn_issue
WHERE (
DATE BETWEEN '2015-08-01'
AND '2015-08-31'
)
AND dvn_cd = '15'
AND phc_cd = '012'
AND hsc_cd = '05'
GROUP BY round
)
,b
AS (
SELECT round AS round_up
,Sum(non_slsc_qty) AS SC_up
,Sum(non_slst_qty) AS ST_up
,Sum(non_slot_qty) AS OTHERS_up
,
FROM vhn_issue
WHERE (
DATE BETWEEN '2015-04-01'
AND '2015-08-31'
)
AND dvn_cd = '15'
AND phc_cd = '012'
AND hsc_cd = '05'
GROUP BY round
)
SELECT coalesce(b.round_up, 0) round_up
,coalesce(a.sc, 0) sc
,coalesce(a.st, 0) st
,coalesce(a.others, 0) others
,coalesce(b.sc_up, 0) sc_up
,coalesce(b.st_up, 0) st_up
,coalesce(b.others_up, 0) others_up
FROM a
RIGHT JOIN b ON a.round = b.round_up
WITH Results_CTE AS
(
Select t1.round as round_up ,
t1.SC as SC,
t1.ST as ST,
t1.OTHERS as OTHERS,
0 as SC_up,
0 as ST_up,
0 as OTHERS_up
from round t1
union all
t2.round_up as round_up ,
0 as SC,
0 as ST,
0 as OTHERS,
t2.SC_up,
t2.ST_up,
t2.OTHERS_up from round t2
)
select round_up , sum(SC) as SC,sum (ST) as ST, sum(OTHERS) as OTHERS, sum(SC_up) as SC_up, sum(ST_up) as ST_up, sum(OTHERS_up) as OTHERS_ up
from Results_CTE group by round_up

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