Listing number sequence for financial periods - sql-server

In SQL 2016, I need to create a list using financial periods but only have the from/to available - it's formatted similar to dates but are 0mmyyyy, so the first 3 numbers are the month/period and the last 4 digits the year.
e.g. period_from is '0102017' and period_to '0032018', but trying to bring back a list that includes the ones in between as well?
0102017,
0112017,
0122017,
0012018,
0022018
Also, the first three characters can go to 012 or 013, so need to be able to easily alter the code for other databases.

I am not entirely sure what you are wanting to use this list for, but you can get all your period values with the help of a tally table and some common table expressions.
-- Test data
declare #p table(PeriodFrom nvarchar(10),PeriodTo nvarchar(10));
insert into #p values('0102017','0032018'),('0052018','0112018');
-- Specify the additional periods you want to include, use 31st December for correct sorting
declare #e table(ExtraPeriodDate date
,ExtraPeriodText nvarchar(10)
);
insert into #e values('20171231','0132017');
-- Convert start and end of periods to dates
with m as (select cast(min(right(PeriodFrom,4) + substring(PeriodFrom,2,2)) + '01' as date) as MinPeriod
,cast(max(right(PeriodTo,4) + substring(PeriodTo,2,2)) + '01' as date) as MaxPeriod
from #p
) -- Built a tally table of dates to join from
,t(t) as (select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1 union all select 1)
,d(d) as (select top (select datediff(month,MinPeriod,MaxPeriod)+1 from m) dateadd(m,row_number() over (order by (select null))-1,m.MinPeriod) from m, t t1, t t2, t t3, t t4, t t5)
-- Use the tally table to convert back to your date period text format
,p as (select d.d as PeriodDate
,'0' + right('00' + cast(month(d) as nvarchar(2)),2) + cast(year(d) as nvarchar(4)) as PeriodText
from d
union all -- and add in any of the addition '13th' month periods you specified previously
select ExtraPeriodDate
,ExtraPeriodText
from #e
)
select PeriodText
from p
order by PeriodDate;
Output:
+------------+
| PeriodText |
+------------+
| 0102017 |
| 0112017 |
| 0122017 |
| 0132017 |
| 0012018 |
| 0022018 |
| 0032018 |
| 0042018 |
| 0052018 |
| 0062018 |
| 0072018 |
| 0082018 |
| 0092018 |
| 0102018 |
| 0112018 |
+------------+
If this isn't what you require exactly it should put you on the right path to generating these values either as the result of a function or concatenated together into a list as per your comment by using for xml on the result by changing the final select statement to:
select stuff((select ', ' + PeriodText
from p
order by PeriodDate
for xml path('')
)
,1,2,'') as PeriodTexts;
Which outputs:
+---------------------------------------------------------------------------------------------------------------------------------------+
| PeriodTexts |
+---------------------------------------------------------------------------------------------------------------------------------------+
| 0102017, 0112017, 0122017, 0132017, 0012018, 0022018, 0032018, 0042018, 0052018, 0062018, 0072018, 0082018, 0092018, 0102018, 0112018 |
+---------------------------------------------------------------------------------------------------------------------------------------+

This is going to be a little complicated. To start, I have a user defined table value function that outputs a calendar table based on a start and end date. You'll want to create that first...
CREATE FUNCTION dbo.udf_calendar (#datestart smalldatetime, #dateend smalldatetime)
RETURNS #calendar TABLE (
[day] int,
[date] smalldatetime
)
AS
BEGIN
DECLARE #rows int
DECLARE #i int = 1
SELECT
#rows = DATEDIFF(DAY, #datestart, #dateend)
WHILE (#i <= #rows)
BEGIN
INSERT INTO #calendar ([day])
VALUES (#i)
SET #i = #i + 1
END
UPDATE a
SET [date] = DATEADD(DAY, [day] - 1, #datestart)
--select *, DATEADD(day,id-1,#datestart)
FROM #calendar a
RETURN
END
Then, the following will give you the output that I THINK you are looking for. I've commented to try and explain how I got there, but it still might be a bit difficult to follow...
--Create temp table example with your period from and to.
IF (SELECT
OBJECT_ID('tempdb..#example'))
IS NOT NULL
DROP TABLE #example
SELECT
'0102017' periodfrom,
'0032018' periodto INTO #example
/*
This is the difficult part. Basically you're inner joining the calendar
to the temp table where the dates are between the manipulated period from and to.
I've added an extra column formatted to allow ordering correctly by period.
*/
SELECT DISTINCT
periodfrom,
periodto,
RIGHT('00' + CAST(DATEPART(MONTH, [date]) AS varchar(50)), 3) + CAST(DATEPART(YEAR, [date]) AS varchar(50)) datefill,
CAST(DATEPART(YEAR, [date]) AS varchar(50)) + RIGHT('00' + CAST(DATEPART(MONTH, [date]) AS varchar(50)), 3) datefill2
FROM dbo.udf_calendar('2015-01-01', '2018-12-31') a
INNER JOIN #example b
ON a.[date] BETWEEN SUBSTRING(periodfrom, 2, 2) + '-01-' + SUBSTRING(periodfrom, 4, 4) AND SUBSTRING(periodto, 2, 2) + '-01-' + SUBSTRING(periodto, 4, 4)
ORDER BY datefill2

Related

T-SQL create multiply records from one records

I have a cost record and I would like to create N records from it.
The children records have some different parameters.
For example:
The parents record:
date | amount | duration
20170201 | 5000 | 5 months
The children records:
date | amount | duration
20170301 | 1000 | 1 months
20170401 | 1000 | 1 months
20170501 | 1000 | 1 months
20170601 | 1000 | 1 months
20170701 | 1000 | 1 months
How can I do this without iteration? Without cursor or while?
Following SQL CTE query could be used based on Abdul's solution
/*
Create Table PARENT (PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO PARENT SELECT '20170201',5000 ,5
INSERT INTO PARENT SELECT '20180601',120 ,3
*/
;WITH CTE_CHILD
AS (
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Parent_Date) as Child_Date,
Parent_Amount/Parent_Month AS Child_Amount,
1 AS Child_Duration
FROM Parent
UNION ALL
SELECT
Parent_Date,
Parent_Amount,
Parent_Month,
DateAdd(Month, 1, Child_Date) as Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
WHERE
DateAdd(Month, 1, Child_Date) <= DateAdd(Month, Parent_Month, Parent_Date)
)
SELECT
Child_Date,
Child_Amount,
Child_Duration
FROM CTE_CHILD
assuming you have a table like below:
create table tblRecords ( date int, amount money, duration int);
insert into tblRecords values
(20170201,5000,5),
(20180101,9000,3);
you can use a query like below:
select
date= date + r*100
,amount= amount/duration
,duration =1
from tblRecords
cross apply
(
select top (select duration)
r= row_number() over(order by (select null))
from
sys.objects s1
cross join
sys.objects s2
) h
see working demo
One method is CTE.
DECLARE #PARENT AS TABLE
(PARENT_DATE DATE, PARENT_AMOUNT DECIMAL(18,2),PARENT_MONTH INT)
INSERT INTO #PARENT
SELECT '20170201',5000 ,5
;WITH CTE_CHILD
AS (
SELECT DATEADD(MONTH,1,PARENT_DATE) AS CHILD_DATE
,PARENT_AMOUNT/PARENT_MONTH AS CHILD_AMOUNT
,1 AS CHILD_DURATION
FROM #PARENT
WHERE DATEADD(MONTH,1,PARENT_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
UNION ALL
SELECT DATEADD(MONTH,1,CHILD_DATE)
,PARENT_AMOUNT/PARENT_MONTH
,1
FROM CTE_CHILD
INNER JOIN #PARENT ON DATEADD(MONTH,1,CHILD_DATE) <= DATEADD(MONTH,PARENT_MONTH,PARENT_DATE)
)
SELECT * FROM CTE_CHILD
option (maxrecursion 0)
Output:-
CHILD_DATE CHILD_AMOUNT CHILD_DURATION
2017-03-01 1000.0000000000000 1
2017-04-01 1000.0000000000000 1
2017-05-01 1000.0000000000000 1
2017-06-01 1000.0000000000000 1
2017-07-01 1000.0000000000000 1

Converting rows to columns

I have a query with the columns 'Name', 'Amount', and 'ReasonId'. I want to sum the amount and put the reasons on one row to keep every name to a single line. There are about 50 distinct ReasonId's so I do not want to name the column the name of the ReasonId's. Instead, I would like to name the columns 'Reason1', 'Reason2', 'Reason3', and 'Reason4'. One single name can have up to 4 different reasons.
I have this:
Name Amount ReasonId
-------------------------
Bob $5 7
Bob $8 6
John $2 8
John $5 9
John $3 9
John $8 4
I want to produce the following:
Name Amount Reason1 Reason2 Reason3 Reason4
-----------------------------------------------------
Bob $13 7 6 NULL NULL
John $18 8 9 4 NULL
One way to do this is to use the dense_rank window function to number the rows, and then use conditional aggregation to put the reason in the correct columns.
I can't see anything that would give the specific order of the reason columns though, maybe there is some column missing that provides the order?
with cte as (
select
name,
reasonid,
amount,
dense_rank() over (partition by name order by reasonid) rn
from your_table
)
select
name,
sum(amount) amount,
max(case when rn = 1 then reasonid end) reason1,
max(case when rn = 2 then reasonid end) reason2,
max(case when rn = 3 then reasonid end) reason3,
max(case when rn = 4 then reasonid end) reason4
from cte
group by name
If you have some column that gives the order you want then change the order by clause used in the dense_rank function.
Sample SQL Fiddle (using PG as MSSQL seems to be offline).
The output from the query above would be:
| name | amount | reason1 | reason2 | reason3 | reason4 |
|------|--------|---------|---------|---------|---------|
| Bob | 13 | 6 | 7 | (null) | (null) |
| John | 18 | 4 | 8 | 9 | (null) |
You could also use a pivot to achieve this; if you know the columns you can enter them in the script, but if not, you can use dynamic sql (there are reasons why you might want to avoid the dynamic solution).
The advantage of this route is that you can enter the column list in a table and then changes to that table will result in changes to your output with change to the script involved. The disadvantages are all those associated with dynamic SQL.
In the interests of variation, here is a dynamic SQL solution using temp tables to hold your data, since a different possibility has been provided:
-- set up your data
CREATE TABLE #MyTab (Name VARCHAR(4), Amount INT, ReasonId INT)
CREATE TABLE #AllPossibleReasons (Id INT,Label VARCHAR(10))
INSERT #AllPossibleReasons
VALUES
(1,'Reason1')
,(2,'Reason2')
,(3,'Reason3')
,(4,'Reason4')
,(5,'Reason5')
,(6,'Reason6')
,(7,'Reason7')
,(8,'Reason8')
,(9,'Reason9')
INSERT #MyTab
VALUES
('Bob',7,7)
,('Bob',8,6)
,('John',2,8)
,('John',5,9)
,('John',3,9)
,('John',8,4)
-----------------------------------------------------------------------------
-- The actual query
DECLARE #ReasonList VARCHAR(MAX) = ''
DECLARE #SQL VARCHAR(MAX)
SELECT #ReasonList = #ReasonList + ',' + QUOTENAME(Label)
FROM #AllPossibleReasons
SET #ReasonList = SUBSTRING(#ReasonList,2,LEN(#ReasonList))
SET #SQL =
'SELECT Name,Value,' + #ReasonList + ' FROM
(SELECT
M.Name,SUM(Amount) AS This, Label, SUM(Total.Value) AS Value
FROM
#MyTab AS M
INNER JOIN #AllPossibleReasons AS Reason ON M.ReasonId = Reason.Id
INNER JOIN(SELECT T.Name, SUM(Amount)Value
FROM #MyTab T GROUP BY T.Name) AS Total ON M.Name = Total.Name
GROUP BY M.Name, Reason.Label) AS Up
PIVOT (SUM(THis) FOR Label IN (' + #ReasonList + ')) AS Pvt'
EXEC (#SQL)
DROP TABLE #AllPossibleReasons
DROP TABLE #MyTab
Working from the information in ListAGG in SQLSERVER, I came up with this somewhat ugly example:
with tbl1 as (
-- Set up initial data set
select 'Bob' name, 5 amount, 7 ReasonId
union all select 'Bob' , 3, 4
union all select 'Bob', 2, 1
union all select 'Brian', 8, 2
union all select 'Bob', 6, 4
union all select 'Brian', 1, 3
union all select 'Tim', 2, 2)
, TBL2 AS ( -- Add a blank to separate the concatenation
SELECT NAME
, AMOUNT
, CAST(ReasonId as varchar) + ' ' ReasonId from tbl1
)
select ta.name
, Total
, ReasonIds from (
(select distinct name, stuff((select distinct '' + t2.ReasonId from tbl2 t2
where t1.name = t2.name
for xml path(''), type).value('.','NVARCHAR(MAX)'),1,0,' ') ReasonIds from tbl2 t1) ta
inner join ( select name, sum(amount) Total from tbl1 group by name) tb on ta.name = tb.name) ;
This converts TBL1 to the following:
name Total ReasonIds
Bob 16 1 4 7
Brian 9 2 3
Tim 2 2

Stop sum when value reached

Assume Table1:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
| P4 | 700|
| P5 | 5500|
| P6 | 1900|
If I want to sum of CashAmount to be 'at least' 9000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
| P3 | 8000|
If I want to sum of CashAmount to be 'at least' 4000. PaymentID order should be the same.
Expected Result:
|PaymentID|CashAmount|
----------------------
| P1 | 3000|
| P2 | 5000|
I had a look at limiting the rows to where the sum a column equals a certain value in MySQL. But the accepted answer is not working with MSSQL and is not exactly what I'm looking for. Most of the answers there I've tested and they return only rows that the total amount is less than, not at least specific value.
SQL Server 2005 and Later
SELECT *
FROM TableName t
CROSS APPLY (SELECT SUM(Amount)
FROM TableName
WHERE [Date] <= t.[DATE]) c(AmtSum)
WHERE AmtSum <= 13
SQL Server 2012 and Later
SELECT *
FROM (
SELECT *
,SUM(Amount) OVER (ORDER BY [Date], Amount) AmtSum
FROM TableName
)t
WHERE AmtSum <= 13
According to your new input I changed my approach slightly. Hope this is what you need...
EDIT: Here's the version with SUM(x) OVER(...):
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=9000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx
,SUM(CashAmount) OVER(ORDER BY PaymentID) AS Summa
FROM #payment
)
SELECT * FROM SortedPayment
WHERE inx<=(SELECT TOP 1 x.inx
FROM SortedPayment AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
And that's the old version for SQL-Server < 2012
DECLARE #payment TABLE(PaymentID VARCHAR(10),CashAmount INT);
INSERT INTO #payment VALUES
('P1',3000)
,('P2',5000)
,('P3',8000)
,('P4',700)
,('P5',5500)
,('P6',1900);
DECLARE #myMinToReach INT=4000;
WITH SortedPayment AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PaymentID) AS inx,*
FROM #payment
)
,Accumulated AS
(
SELECT tbl.*
FROM
(
SELECT SortedPayment.*
,Accumulated.Summa
FROM SortedPayment
CROSS APPLY
(
SELECT SUM(ps2.CashAmount) AS Summa
FROM SortedPayment AS ps2
WHERE ps2.inx<=SortedPayment.inx
) AS Accumulated
) AS tbl
)
SELECT * FROM Accumulated
WHERE inx<=(SELECT TOP 1 x.inx
FROM Accumulated AS x
WHERE Summa>#myMinToReach
ORDER BY Summa ASC);
declare #s int;
update table set rc=row_count() over (order by date)
declare #i int;
set #i=1;
while #s<=12 or #i<100000
set #s=#s+(select amount from table where rc=#i+1);
set #i=#i+1;
end
// #s has at least 12

Create query with dynamic columns from different tables

I'm trying to create a query with dynamic columns, based on data from three tables.
This is the database structure:
STUDENT
studentID int,
studentNumber int,
studentName nvarchar(100).
EXAM:
examID int,
examName varchar(100),
examenDate datetime,
EXAM_REGISTRATION:
studentID int,
examID int,
A record is added to the EXAM_REGISTRATION table when a student has registered for an exam.
What I'm trying to get is a list of all the exams and all the students in a pivot table to see which students have registered for which exams, like this:
Quite frankly I don't know where to start.
I can query everything individually and put it all together but how can I combine it into one query?
I've been researching pivot tables, but every example seems to query only from one table and uses numbers and functions like MIN, AVG etc.
Can someone help me along?
ok lets go
some data to play with
create table #student
(studentID int, studentNumber int, studentName nvarchar(100))
create table #exam
(examID int, examName nvarchar(100), examDate datetime)
create table #examReg
(studentID int, examID int)
insert into #student
values (1, 787878, 'pierwszy')
,(2, 89898, 'drugi')
,(3, 343434, 'trzeci')
,(4, 121212, 'czwarty')
insert into #exam
values (1, 'exPierwszy', GETDATE())
,(2, 'exDrugi', GETDATE())
,(3, 'exTrzeci', GETDATE())
insert into #examReg
values (1,2),(1,3)
, (2,2),(2,3)
,(3,1),(3,2)
,(4,1),(4,2),(4,3)
and now the main part, and explanation
first of all you have to get pivot query
select examName, examDate , min([1]) , min([2]), min([3]) ,min([4])--studentID as studentID, examDate --,studentNumber
from
(select a.studentID , studentNumber, examDate, examName
from #student a
join #examReg b on a.studentID = b.studentID
join #exam c on c.examID = b.examID ) as m
pivot
(min(studentNumber) FOR studentID in ([1],[2],[3],[4])) as t
group by examName, examDate
as you have it , just change it select statement and studentID list in pivot declaration, you have to generate those parts dynamicly , so we just copy previously written query and replace columns with our token
declare #sqlTemplate nvarchar(max) =
'select examName, examDate ##sqlColumnList##
from
(select a.studentID , studentNumber, examDate, examName
from #student a
join #examReg b on a.studentID = b.studentID
join #exam c on c.examID = b.examID ) as m
pivot
(min(studentNumber) FOR studentID in (##sqlStudentIDList##)) as t
group by examName, examDate
'
after that you generate column list and studentID list by concatenting strings in tsql
declare #sqlColumnList nvarchar(max) = ''
select #sqlColumnList += ',min([' + cast(studentID as nvarchar(10)) + ']) as [' + studentName +'(' + cast(studentNumber as nvarchar(10)) + ')]'
from #student
declare #sqlStudentIDList nvarchar(max) = ''
select #sqlStudentIDList += '[' + CAST(studentID as nvarchar(10)) + '],'
from #student
set #sqlStudentIDList = SUBSTRING(#sqlStudentIDList, 0, LEN(#sqlStudentIDList))
select #sqlStudentIDList
once you have it , all you have to do is to replace tokens in previous template
set #sqlTemplate = REPLACE(#sqlTemplate, '##sqlColumnList##', #sqlColumnList)
set #sqlTemplate = REPLACE(#sqlTemplate, '##sqlStudentIDList##', #sqlStudentIDList)
select #sqlTemplate
exec sp_executesql #sqlTemplate
and thats it
if you want to read more about pivot go for msdn
if you want to read about dynamic go for this link
edit: to adjust the query for the question from comment you would have to change #sqlColumnList like that
select #sqlColumnList += ',min(' + QUOTENAME(studentID) + ') as Student' + CAST(studentID as nvarchar(10)) + '_REG,
'''+ studentName + ''' as Student' + cast(studentID as nvarchar(10)) + '_NAME,
'+ cast(studentID as nvarchar(10)) + ' as Student' + cast(studentID as nvarchar(10)) + '_ID'
from #student
This is a pivot of the data. I would perform this slightly different than the other answer. If you know all of the values, then you can hard-code the values.
A static version will be:
select examname,
examendate,
IsNull([Smith, John (14323)], 'false') [Smith, John (14323)],
IsNull([Craft, Peter (14334)], 'false') [Craft, Peter (14334)],
IsNull([Davis, Alan (13432)], 'false') [Davis, Alan (13432)],
IsNull([Newman, Ted (133123)], 'false') [Newman, Ted (133123)]
from
(
select e.examname,
e.examenDate,
s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')' studentNameNum,
'true ' as Flag
from exam e
left join exam_registration er
on e.examid = er.examid
right join student s
on er.studentid = s.studentid
) src
pivot
(
max(flag)
for studentNameNum in ([Smith, John (14323)], [Craft, Peter (14334)],
[Davis, Alan (13432)], [Newman, Ted (133123)])
) piv
See SQL Fiddle with Demo
If your values are unknown then the query will be:
DECLARE #cols AS NVARCHAR(MAX),
#colsNull AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')')
from student s
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #colsNull = STUFF((SELECT distinct ',IsNull(' + QUOTENAME(s.studentName + ' ('+cast(s.studentnumber as varchar(50))+')')+', ''false'')'+' as '+QUOTENAME(s.studentName+' ('+cast(s.studentnumber as varchar(50))+')')
from student s
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT examname,
examenDate,' + #colsNull + ' from
(
select e.examname,
e.examenDate,
s.studentName + '' (''+cast(s.studentnumber as varchar(50))+'')'' studentNameNum,
''true '' as Flag
from exam e
left join exam_registration er
on e.examid = er.examid
right join student s
on er.studentid = s.studentid
) x
pivot
(
max(flag)
for studentNameNum in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
The result will be:
| EXAMNAME | EXAMENDATE | CRAFT, PETER (14334) | DAVIS, ALAN (13432) | NEWMAN, TED (133123) | SMITH, JOHN (14323) |
----------------------------------------------------------------------------------------------------------------------------
| Exam 1 | 2013-01-01 12:00:00 | false | false | true | false |
| Exam 2 | 2013-01-01 14:00:00 | true | false | false | true |
| Exam 3 | 2013-01-02 12:00:00 | true | true | false | false |
| Exam 4 | 2013-01-02 14:00:00 | false | false | true | false |
| Exam 5 | 2013-01-03 12:00:00 | false | false | false | true |

SQL Server Pivots: Displaying row values to column headers

I have a table (items) which is in the following format:
ITEMNO | WEEKNO | VALUE
A1234 | 1 | 805
A2345 | 2 | 14.50
A3547 | 2 | 1396.70
A2208 | 1 | 17.65
A4326 | 6 | 19.99
It's a table which shows the value of sales for items in a given week.
The results or what I want to display in a table format is the item number in a row followed by columns for each week containing the values, e.g.
ITEMNO | WK1 | WK2 | WK3 | WK4 | WK5 ...etc up to 52
A1234 | 805 | 345 | 234 | 12 | 10 ...etc up to 52
A2345 | 23 | 12 | 456 | 34 | 99 ...etc up to 52
A3456 | 234 | 123 | 34 | 25 | 190 ...etc up to 52
Although I've 52...so I've only data for up to week9 but that will increase with time.
So basically what it is I'm looking to display is the week number value as a column header.
Is this possible...although I'm tempted to just grab the data and display properly through code/(asp.net) but I was wondering if there was away to display it like this in SQL?
Does anyone know or think that that this might be the best way?
There are two ways of doing this with static SQL and dynamic SQL:
Static Pivot:
SELECT P.ItemNo, IsNull(P.[1], 0) as Wk1, IsNull(P.[2], 0) as Wk2
, IsNull(P.[3], 0) as Wk3, IsNull(P.[4], 0) as Wk4
, IsNull(P.[5], 0) as Wk5, IsNull(P.[6], 0) as Wk6
, IsNull(P.[7], 0) as Wk7, IsNull(P.[8], 0) as Wk8
, IsNull(P.[9], 0) as Wk9
FROM
(
SELECT ItemNo, WeekNo, [Value]
FROM dbo.Items
) I
PIVOT
(
SUM([Value])
FOR WeekNo IN ([1], [2], [3], [4], [5], [6], [7], [8], [9])
) as P
Dynamic Pivot:
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX);
-- Construct the column list for the IN clause
SET #cols = STUFF(
(SELECT N',' + QUOTENAME(w) AS [text()]
FROM (SELECT DISTINCT WeekNo AS W FROM dbo.Items) AS W
ORDER BY W
FOR XML PATH('')),
1, 1, N'');
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT ItemNo, WeekNo, Value
FROM dbo.Items) AS I
PIVOT(SUM(Value) FOR WeekNo IN(' + #cols + N')) AS P;';
EXEC sp_executesql #sql;
GO
Maybe something like this:
Test data
CREATE TABLE #tbl
(
ITEMNO VARCHAR(100),
WEEKNO INT,
VALUE FLOAT
)
INSERT INTO #tbl
VALUES
('A1234',1,805),
('A2345',2,14.50),
('A3547',2,1396.70),
('A2208',1,17.65),
('A4326',6,19.99)
Week columns
DECLARE #cols VARCHAR(MAX)
;WITH Nbrs ( n ) AS (
SELECT 1 UNION ALL
SELECT 1 + n FROM Nbrs WHERE n < 52 )
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(n AS VARCHAR(2))),
QUOTENAME('WK'+CAST(n AS VARCHAR(2))))
FROM
Nbrs
Just the included weeks
DECLARE #cols VARCHAR(MAX)
;WITH CTE
AS
(
SELECT
ROW_NUMBER() OVER(PARTITION BY WEEKNO ORDER BY WEEKNO) AS RowNbr,
WEEKNO
FROM
#tbl
)
SELECT #cols = COALESCE(#cols + ','+QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))),
QUOTENAME('WK'+CAST(WEEKNO AS VARCHAR(2))))
FROM
CTE
WHERE
CTE.RowNbr=1
Dynamic pivot
DECLARE #query NVARCHAR(4000)=
N'SELECT
*
FROM
(
SELECT
tbl.ITEMNO,
''WK''+CAST(tbl.WEEKNO AS VARCHAR(2)) AS WEEKNO,
tbl.VALUE
FROM
#tbl as tbl
) AS p
PIVOT
(
SUM(VALUE)
FOR WEEKNO IN ('+#cols+')
) AS pvt'
EXECUTE(#query)
Drop the temp table
DROP TABLE #tbl
Use Pivot, although quite a bit of code..
If you create report in reporting services, can use matrix..
Follow the below walkthrogh which explains it clearly
http://www.tsqltutorials.com/pivot.php
You can use PIVOT if you want to do this in sql directly.
It can be more efficient to use the SQL Server to do this as opposed to the client depending upon the size of the data and the aggregation.

Resources