Converting multiple rows to multiple columns in SQL - sql

I have a data something like this:
Team Month Count_of_Stores
a 4 10
b 4 4
c 4 6
a 5 8
b 5 14
e 5 9
a 6 7
b 6 3
f 6 1
I working to get an output something like this converting the rows to columns:
Team Month Count_of_Stores Team Month Count_of_Stores Team Month Count_of_Stores
a 4 10 a 5 8 a 6 7
b 4 4 b 5 14 b 6 3
c 4 6 e 5 9 f 6 1
I am sure pivot should be of great help here, but confused in the appropriate usage here. any help is much appreciated.

I'm inferring team association based on the data provided. Something like the below could work for you (demo: http://rextester.com/GQHPV34978)
CREATE TABLE temp
(
[teamID] INT,
[month] INT,
[count_of_stores] INT
)
INSERT INTO temp
VALUES
(1,4,10),
(2,4,4),
(3,4,6),
(1,5,8),
(2,5,14),
(3,5,9),
(1,6,7),
(2,6,3),
(3,6,1)
SELECT [teamID],
MAX(CASE WHEN MONTH = 4 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 4 THEN count_of_stores END ) AS Count_of_Stores,
MAX(CASE WHEN MONTH = 5 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 5 THEN count_of_stores END ) AS Count_of_Stores ,
MAX(CASE WHEN MONTH = 6 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 6 THEN count_of_stores END ) AS Count_of_Stores
FROM temp
GROUP BY teamID
Updating with the following based on new information (demo:http://rextester.com/JIZQX61960)
create TABLE #temp
(
[teamID] varchar,
[month] INT,
[count_of_stores] INT
)
INSERT INTO #temp
VALUES
('a',4,10),
('b',4,4),
('c',4,6),
('a',5,8),
('b',5,14),
('e',5,9),
('a',6,7),
('b',6,3),
('f',6,1);
WITH monthGrouping AS
(
SELECT row_number() over (partition by month order by month) as rn, [teamID], [month],[count_of_stores] FROM #temp
)
SELECT
MAX(CASE WHEN MONTH = 4 THEN [teamID] END )AS [teamID],
MAX(CASE WHEN MONTH = 4 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 4 THEN count_of_stores END ) AS Count_of_Stores,
MAX(CASE WHEN MONTH = 5 THEN [teamID] END )AS [teamID],
MAX(CASE WHEN MONTH = 5 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 5 THEN count_of_stores END ) AS Count_of_Stores ,
MAX(CASE WHEN MONTH = 6 THEN [teamID] END )AS [teamID],
MAX(CASE WHEN MONTH = 6 THEN MONTH END )AS Month,
MAX(CASE WHEN MONTH = 6 THEN count_of_stores END ) AS Count_of_Stores
FROM monthGrouping
GROUP BY rn

Related

TSQL - dates overlapping - number of days

I have the following table on SQL Server:
ID
FROM
TO
OFFER NUMBER
1
2022.01.02
9999.12.31
1
1
2022.01.02
2022.02.10
2
2
2022.01.05
2022.02.15
1
3
2022.01.02
9999.12.31
1
3
2022.01.15
2022.02.20
2
3
2022.02.03
2022.02.25
3
4
2022.01.16
2022.02.05
1
5
2022.01.17
2022.02.13
1
5
2022.02.05
2022.02.13
2
The range includes the start date but excludes the end date.
The date 9999.12.31 is given (comes from another system), but we could use the last day of the current quarter instead.
I need to find a way to determine the number of days when the customer sees exactly one, two, or three offers. The following picture shows the method upon id 3:
The expected results should be like (without using the last day of the quarter):
ID
# of days when the customer sees only 1 offer
# of days when the customer sees 2 offers
# of days when the customer sees 3 offers
1
2913863
39
0
2
41
0
0
3
2913861
24
17
4
20
0
0
5
19
8
0
I've found this article but it did not enlighten me.
Also I have limited privileges that is I am not able to declare a variable for example so I need to use "basic" TSQL.
Please provide a detailed explanation besides the code.
Thanks in advance!
The following will (for each ID) extract all distinct dates, construct non-overlapping date ranges to test, and will count up the number of offers per range. The final step is to sum and format.
The fact that the start dates are inclusive and the end dates are exclusive while sometimes non-intuitive for the human, actually works well in algorithms like this.
DECLARE #Data TABLE (Id INT, FromDate DATETIME, ToDate DATETIME, OfferNumber INT)
INSERT #Data
VALUES
(1, '2022-01-02', '9999-12-31', 1),
(1, '2022-01-02', '2022-02-10', 2),
(2, '2022-01-05', '2022-02-15', 1),
(3, '2022-01-02', '9999-12-31', 1),
(3, '2022-01-15', '2022-02-20', 2),
(3, '2022-02-03', '2022-02-25', 3),
(4, '2022-01-16', '2022-02-05', 1),
(5, '2022-01-17', '2022-02-13', 1),
(5, '2022-02-05', '2022-02-13', 2)
;
WITH Dates AS ( -- Gather distinct dates
SELECT Id, Date = FromDate FROM #Data
UNION --(distinct)
SELECT Id, Date = ToDate FROM #Data
),
Ranges AS ( --Construct non-overlapping ranges (The ToDate = NULL case will be ignored later)
SELECT ID, FromDate = Date, ToDate = LEAD(Date) OVER(PARTITION BY Id ORDER BY Date)
FROM Dates
),
Counts AS ( -- Calculate days and count offers per date range
SELECT R.Id, R.FromDate, R.ToDate,
Days = DATEDIFF(DAY, R.FromDate, R.ToDate),
Offers = COUNT(*)
FROM Ranges R
JOIN #Data D ON D.Id = R.Id
AND D.FromDate <= R.FromDate
AND D.ToDate >= R.ToDate
GROUP BY R.Id, R.FromDate, R.ToDate
)
SELECT Id
,[Days with 1 Offer] = SUM(CASE WHEN Offers = 1 THEN Days ELSE 0 END)
,[Days with 2 Offers] = SUM(CASE WHEN Offers = 2 THEN Days ELSE 0 END)
,[Days with 3 Offers] = SUM(CASE WHEN Offers = 3 THEN Days ELSE 0 END)
FROM Counts
GROUP BY Id
The WITH clause introduces Common Table Expressions (CTEs) which progressively build up intermediate results until a final select can be made.
Results:
Id
Days with 1 Offer
Days with 2 Offers
Days with 3 Offers
1
2913863
39
0
2
41
0
0
3
2913861
24
17
4
20
0
0
5
19
8
0
Alternately, the final select could use a pivot. Something like:
SELECT Id,
[Days with 1 Offer] = ISNULL([1], 0),
[Days with 2 Offers] = ISNULL([2], 0),
[Days with 3 Offers] = ISNULL([3], 0)
FROM (SELECT Id, Offers, Days FROM Counts) C
PIVOT (SUM(Days) FOR Offers IN ([1], [2], [3])) PVT
ORDER BY Id
See This db<>fiddle for a working example.
Find all date points for each ID. For each date point, find the number of overlapping.
Refer to comments within query
with
dates as
(
-- get all date points
select ID, theDate = FromDate from offers
union -- union to exclude any duplicate
select ID, theDate = ToDate from offers
),
cte as
(
select ID = d.ID,
Date_Start = d.theDate,
Date_End = LEAD(d.theDate) OVER (PARTITION BY ID ORDER BY theDate),
TheCount = c.cnt
from dates d
cross apply
(
-- Count no of overlapping
select cnt = count(*)
from offers x
where x.ID = d.ID
and x.FromDate <= d.theDate
and x.ToDate > d.theDate
) c
)
select ID, TheCount, days = sum(datediff(day, Date_Start, Date_End))
from cte
where Date_End is not null
group by ID, TheCount
order by ID, TheCount
Result :
ID
TheCount
days
1
1
2913863
1
2
39
2
1
41
3
1
2913861
3
2
29
3
3
12
4
1
20
5
1
19
5
2
8
To get to the required format, use PIVOT
dbfiddle demo

How to make pivot table in SQL Server in my case?

I wants to display the record by same column. I don't know how describe the question also.
I have a table called SoldQtyTable
ItemNo Weeks Years QtySold AsOfWeekOnHand
----------------------------------------------------
1 1 2017 5 3
2 1 2017 2 5
3 1 2017 66 70
1 2 2017 4 33
I wants to display like below
ItemNo Years [1QtySold] [1_OnHand] [2QtySold] [2_OnHand]
-----------------------------------------------------------------------
1 2017 5 3 4 33
2 2017 2 5
3 2017 66 70
I tried in this way. But It doesn't work
select
PVT1.ItemID,
PVT1.StoreID,
PVT1.Years,
isnull([1],0) as [1QtySold], isnull([2],0) as [2QtySold],
isnull([1_OnHand],0) as [1_OnHand], isnull([2_OnHand],0) as [2_OnHand]
from
(
SELECT
ItemID,
StoreID,
Years,
Weeks,
AsOfWeekOnHand
FROM
SoldQtyTable
) L
PIVOT
(
SUM(AsOfWeekOnHand)
FOR Weeks IN ( [1_OnHand], [2_OnHand])
) AS PVT1
LEFT JOIN
(
SELECT
ItemID,
StoreID,
Years,
Weeks,
QtySold
FROM
SoldQtyTable
) L
PIVOT
(
SUM(QtySold)
FOR Weeks IN ( [1soldQty], [2soldQty]
) AS PVT2 on PVT2.ItemID = PVT1.ItemID and PVT1.Years = PVT2.Years
where
PVT1.years = 2017
I find conditional aggregation so much simpler:
SELECT ItemID, Years,
SUM(CASE WHEN weeks = 1 THEN QtySold END) as QtySold_1,
SUM(CASE WHEN weeks = 1 THEN AsOfWeekOnHand END) as AsOfWeekOnHand_1,
SUM(CASE WHEN weeks = 2 THEN QtySold END) as QtySold_2,
SUM(CASE WHEN weeks = 3 THEN AsOfWeekOnHand END) as AsOfWeekOnHand_2
FROM SoldQtyTable
GROUP BY ItemID, Years
ORDER BY ItemID, Years;
If you want to PIVOT multiple columns, you can achieve that by doing UNPIVOT first and then doing PIVOT on just one value column as described in this answer.
SELECT ItemID,
StoreID,
Years,
[1_QtySold],
[1_AsOfWeekOnHand] AS [1_OnHand],
[2_QtySold],
[2_AsOfWeekOnHand] AS [2_OnHand]
FROM
(
SELECT
ItemID,
StoreID,
Years,
Weeks + '_' + col AS col,
[value]
FROM
(
SELECT
ItemID,
StoreID,
Years,
CAST(Weeks as varchar) Weeks,
AsOfWeekOnHand,
QtySold
FROM
SoldQtyTable
WHERE Years = 2017 -- your original filter PVT1.years = 2017
) src
UNPIVOT
(
VALUE
FOR col in (AsOfWeekOnHand, QtySold)
) unpiv
) s
PIVOT
(
SUM([value])
FOR col IN ([1_AsOfWeekOnHand], [1_QtySold], [2_AsOfWeekOnHand], [2_QtySold])
) unpiv
ORDER BY StoreID
Here is the SQL Fiddle.

Return zero value for all the month in series with count zero

This is my query:
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
ORDER BY MonthNumber
Which produces result set as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
MikeCasey January 10 1
MikeCasey February 4 2
PrecisionCareSupport February 0 2
MikeCasey March 4 3
PrecisionCareSupport March 0 3
MikeCasey April 8 4
PrecisionCareSupport April 0 4
MikeCasey May 16 5
MikeCasey July 4 7
PrecisionCareSupport July 1 7
PrecisionCareSupport August 0 8
MikeCasey September 10 9
MikeCasey October 12 10
I am generating a chart and would like to generate series for that chart but the series should be formed in a way that each series label must have all the tick values(zero if missing respective month). In Simple words,I want resultset as:
Name Month ServiceDeliveryTime MonthNumber
----------------------------------------------------------------------------
mb January 52 1
mb February 0 2
mb March 0 3
mb April 0 4
- - 0 5
Upto December then series will continue for Client MikeCasey upto December and so on...for all the series Labels.If any of the tick is missing for that client there will be value zero for that month.
How Can I produce this result set ? I want some uniform solution because there can be number of such queries for different charts.
Mr Shaw, try this
;WITH
(SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
) AS mytable
SELECT
myTableName.Name
,mytableMonth.Month
,ISNULL(mytable.ServiceDeliveryTime,0)
,mutableMonth.MonthNumber
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month,MonthNumber FROM mytable) mytableMonth
LEFT INNER JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month = mytable.Month AND mytableMonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.MonthNumber
I have taken all distinct months and names from your data and done a cross join.
;WITH mytable(Name,Month,ServiceDeliveryTime,MonthNumber) AS
(
SELECT STAFF.stf_first_name + '' + STAFF.stf_last_name As Name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date) As Month,
SUM(case when RES_HAB_DATA.reshabdata_duration > 0
then (RES_HAB_DATA.reshabdata_duration/15) else 0 end) As ServiceDeliveryTime,
MONTH(RES_HAB_DATA.reshabdata_data_date) As MonthNumber
FROM RES_HAB_DATA
JOIN RES_HAB ON RES_HAB_DATA.reshab_id = RES_HAB.reshab_id
JOIN STAFF ON RES_HAB_DATA.staff_id = STAFF.stf_id
WHERE RES_HAB.serv_id = 30
AND RES_HAB_DATA.reshabdata_data_date >= '1/1/2015'
GROUP BY STAFF.stf_last_name,
STAFF.stf_first_name,
DATENAME(month,RES_HAB_DATA.reshabdata_data_date),
MONTH(RES_HAB_DATA.reshabdata_data_date)
)
SELECT
myTableName.Name
,mytableMonth.Month_Name
,ISNULL(mytable.ServiceDeliveryTime,0) as ServiceDeliveryTime
,mytableMonth.id
FROM
(SELECT DISTINCT Name from mytable) mytableName
CROSS JOIN (SELECT DISTINCT Month_Name,id FROM MyMonths) mytableMonth
LEFT JOIN mytable ON mytableName.Name = mytable.Name AND mytableMonth.Month_Name = mytable.Month AND mytable.MonthNumber = mytable.MonthNumber
ORDER BY mytableName.Name, mytableMonth.id
MyMonths table is already created table with id as MonthNumber and Month_Name as Month.
Cheers!

Select Count usage divided by month

I do have a table license_Usage where which works like a log of the usage of licenses in a day
ID User license date
1 1 A 22/1/2015
2 1 A 23/1/2015
3 1 B 23/1/2015
4 1 A 24/1/2015
5 2 A 22/2/2015
6 2 A 23/2/2015
7 1 B 23/2/2015
Where I want to Count how many licenses a user used in a month, the result should look like:
User Jan Feb
1 2 1 ...
2 0 2
How can I manage to do that???
You need a PIVOT or cross tab query. e.g.
SELECT [User],
COUNT(CASE WHEN Month = 1 THEN 1 END) AS Jan,
COUNT(CASE WHEN Month = 2 THEN 1 END) AS Feb,
COUNT(CASE WHEN Month = 3 THEN 1 END) AS Mar
/*TODO - Fill in other 9 months using above pattern*/
FROM [license]
CROSS APPLY (SELECT MONTH([date])) AS CA(Month)
WHERE [date] >= '20150101'
AND [date] < '20160101'
AND [license] = 'A'
GROUP BY [User]
SQL Fiddle

How to convert Dynamic 7 day rows into columns with t-sql

Background Info
I have a large table 400M+ rows that changes daily (one days data drops out an a new days data drops in) The table is partitioned on a 'day' field so there are 31 paritions.
Each row in the table has data similar to this:
ID, Postcode, DeliveryPoint, Quantity, Day, Month
1 SN1 1BG A1 6 29 1
2 SN1 1BG A1 1 28 1
3 SN1 1BG A2 2 27 1
4 SN1 1BG A1 3 28 1
5 SN2 1AQ B1 1 29 12
6 SN1 1BG A1 2 26 12
I need to pull out 7 days of data in the format:
Postcode, Deliverypoint, 7dayAverage, Day1,day2,Day3,Day4,Day5,Day6,Day7
SN1 1BG A1 2 0 1 2 1 3 4 0
I can easily extract the data for the 7 day period but need to create a columnar version as shown above.
I have something like this:
select postcode,deliverypoint,
sum (case day when 23 then quantity else 0 end) as day1,
sum (case day when 24 then quantity else 0 end) as day2,
sum(case day when 25 then quantity else 0 end) as day3,
sum(case day when 26 then quantity else 0 end) as day4,
sum(case day when 27 then quantity else 0 end) as day5,
sum(case day when 28 then quantity else 0 end) as day6,
sum(case day when 29 then quantity else 0 end) as day7,
sum(quantity)*1.0/#daysinweek as wkavg
into #allweekdp
from maintable dp with (nolock)
where day in (select day from #days)
group by postcode,deliverypoint
where #days has the day numbers in the 7 day period.
But as you can see, I've hard-coded the day numbers into the query, I want to get them out of my temporary table #days but can't see a way of doing it (an array would be perfect here)
Or a I going about this in completely the wrong way ?
Kind Regards
Steve
If I understand correctly, what I would do is:
Convert the day and month columns into datetime values,
Get the first day of the week and day of the weekday (1-7) for each date, and
Pivot the data and group by the first day of the week
see here: sqlfiddle
As utexaspunk suggested, Pivot might be the way to go. I've never been comfortable with pivot and have preferred to pivot it manually so I control how everything looks, so I'm using a similar solution to how you did your script to solve the issue. No idea how the performance between my way and utexaspunk's will compare.
Declare #Min_Day Integer = Select MIN(day) as Min_Day From #days;
With Day_Coding_CTE as (
Select Distinct day
, day - #Min_Day + 1 as Day_Label
From #days
)
, Non_Columnar_CTE as (
Select dp.postcode
, dp.deliverypoint
, d.day
, c.Day_Label
, SUM(quantity) as Quantity
From maintable dp with (nolock)
Left Outer Join #days d
on dp.day = d.day --It also seems like you'll need more criteria here, but you'll have to figure out what those should be
Left Outer Join Day_Coding_CTE c
on d.day = c.day
)
Select postcode
, deliverypoint
, SUM(Case
When Day_Label = 1
Then Quantity
Else 0
End) as Day1
, SUM(Case
When Day_Label = 2
Then Quantity
Else 0
End) as Day2
, SUM(Case
When Day_Label = 3
Then Quantity
Else 0
End) as Day3
, SUM(Case
When Day_Label = 4
Then Quantity
Else 0
End) as Day4
, SUM(Case
When Day_Label = 5
Then Quantity
Else 0
End) as Day5
, SUM(Case
When Day_Label = 6
Then Quantity
Else 0
End) as Day6
, SUM(Case
When Day_Label = 7
Then Quantity
Else 0
End) as Day7
, SUM(Quantity)/#daysinweek as wkavg
From Non_Columnar_CTE
Group by postcode
deliverypoint