Selecting data for columns based on a range of dates - sql

I have a table that has a week_id and net_sales for that week (as well as a lot of other columns).
style_number, week_id, net_sales
ABCD, 1, 100.00
ABCD, 2, 125.00
EFGH, 1, 50.00
EFGH, 2, 75.00
I am trying to write a statement that will list the
style_number, net_sales
for the
MAX(week_id), net_sales for the MAX(week_id)-1 .... , MAX(week_id) - n
So that the results look like:
ABCD, 125.00, 100.00
EFGH, 75.00, 50.00
What is the best way to approach this, especially when n can be rather large (i.e. looking back 52 weeks)?
I hope this makes sense! I am using SQL Server 2008 R2. Thanks a lot in advance!

You can use PIVOT and dynamic SQL to deal with your large number of weeks
DECLARE #cols NVARCHAR(MAX), #sql NVARCHAR(MAX)
SET #cols = STUFF((SELECT DISTINCT ',' + QUOTENAME(week_id)
FROM sales
ORDER BY 1 DESC
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #sql = 'SELECT style_number, ' + #cols +
' FROM
(
SELECT style_number, week_id, net_sales
FROM sales
) x
PIVOT
(
MAX(net_sales) FOR week_id IN (' + #cols + ')
) p
ORDER BY style_number'
EXECUTE(#sql)
Here is SQLFiddle demo.

If you know the number of weeks, since you are using SQL Server 2008, you can use the PIVOT command, or you can use MAX with CASE:
Here's an example using MAX with CASE:
select
style_number,
max(case when week_id = 2 then net_sales end) week2sales,
max(case when week_id = 1 then net_sales end) week1sales
from yourtable
group by style_number
SQL Fiddle Demo
If you do not know the number of weeks, you'll need to look into using dynamic SQL. Just do a search, lots of posts on SO on it.

You might consider using the PIVOT command: http://msdn.microsoft.com/en-us/library/ms177410(v=sql.105).aspx
OR
If you are okay with the result being a comma separated list, you could use the STUFF and FOR XML commands like so.
SELECT DISTINCT
style_name,
STUFF(
(SELECT ',' + CAST(net_sales AS VARCHAR(20))
FROM MyTable AS SubTable
WHERE SubTableUser.style = MyTable.style_name
ORDER BY week_id DESC --DESC will get your max ID to be first
FOR XML PATH('')), 1, 1, '') AS net_sales_list
FROM MyTable
ORDER BY style_name
This will provide you with:
style_name | net_sales_list
---------------------------
ABCD | 100.00,125.00
EFGH | 75.00,50.00

Related

SQL dynamic pivot after CTE

I hope this is more specific? sorry if I am unclear, kind of new to this. Thank you for the help!!
I'm trying to get a dynamic pivot to work on a CTE. I have looked around a bit and I have a couple of problems. For what I fount, it seems that something like the following post is pretty standard for a dynamic sql:
Pivot Table and Concatenate Columns
I have the following columns in my table with trades:
Date | product | time | price | volume |
I want to get the average price for each quarter of the day, so I want to pivot the time column after rounding it down to the nearest quarter time. and taking the Weighted average price per product and date.
so I use one CTE to create the pivot list:
DECLARE #pivot_list as varchar(max)
;with startquarter(starttradequarter)
AS
(
SELECT cast(DATEadd(mi,(datediff(mi,0,Time))/15*15,0)as varchar)
from [table]
where date > '2014-04-15'
),
PIVOT_CODES(PIVOT_CODE)
AS
(
SELECT DISTINCT starttradequarter AS PIVOT_CODE
from startquarter
)
SELECT #pivot_list = COALESCE(#pivot_list + ',[' + PIVOT_CODE + ']','[' + PIVOT_CODE + ']')
FROM PIVOT_CODES
then I want to use this variable in a pivot of the table:
;With productselector(Date,startquarter,product,volume,price)
as
(
SELECT [Date]
,cast(DATEadd(mi,(datediff(mi,0,Time))/15*15,0)as varchar) as startquarter
,[product]
,[Volume]
,[Price]
FROM [table]
where DelDate = '2014-01-06' and product = 'x'
),
WAPricequarter(startquarter,date,sumvolume,WAPq,product)
AS
(
SELECT startquarter
,Date
,sum(volume) as sumvolume
,round(sum(volume*price)/sum(volume),2) as WAPq
,product
from productselector
group by date, startquarter, product
)
SELECT date, product, + #pivot_list
from WAPricequarter
PIVOT (
SUM([sumvolume])
FOR startquarter IN (#pivot_list)
) AS pvt
So I see in all dynamic pivots the second statement first put in a variable and then executed, is this necessary?
If not how do I get the pivot to work on the columns in the #pivot_list, it now gives an incorrect syntax error that I can't get to work.
If it is necessary to put it in a variable and then execute, how can I then still filter for product or date inside that variable since I have to use '' around it.

Dynamic SELECT statement, generate columns based on present and future values

Currently building a SELECT statement in SQL Server 2008 but would like to make this SELECT statement dynamic, so the columns can be defined based on values in a table. I heard about pivot table and cursors, but seems kind of hard to understand at my current level, here is the code;
DECLARE #date DATE = null
IF #date is null
set # date = GETDATE() as DATE
SELECT
Name,
value1,
value2,
value3,
value4
FROM ref_Table a
FULL OUTER JOIN (
SELECT
PK_ID ID,
sum(case when FK_ContainerType_ID = 1 then 1 else null) Box,
sum(case when FK_ContainerType_ID = 2 then 1 else null) Pallet,
sum(case when FK_ContainerType_ID = 3 then 1 else null) Bag,
sum(case when FK_ContainerType_ID = 4 then 1 else null) Drum
from
Packages
WHERE
#date between PackageStart AND PackageEnd
group by PK_ID ) b on a.Name = b.ID
where
Group = 0
The following works great for me , but PK_Type_ID and the name of the column(PackageNameX,..) are hard coded, I need to be dynamic and it can build itself based on present or futures values in the Package table.
Any help or guidance on the right direction would be greatly appreciated...,
As requested
ref_Table (PK_ID, Name)
1, John
2, Mary
3, Albert
4, Jane
Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
1 , 1, 4, 1JAN2014, 30JAN2014
2 , 2, 3, 1JAN2014, 30JAN2014
3 , 3, 2, 1JAN2014, 30JAN2014
4 , 4, 1, 1JAN2014, 30JAN2014
ContainerType (PK_ID, Type)
1, Box
2, Pallet
3, Bag
4, Drum
and the result should look like this;
Name Box Pallet Bag Drum
---------------------------------------
John 1
Mary 1
Albert 1
Jane 1
The following code like I said works great, the issue is the Container table is going to grow and I need to replicated the same report without hard coding the columns.
What you need to build is called a dynamic pivot. There are plenty of good references on Stack if you search out that term.
Here is a solution to your scenario:
IF OBJECT_ID('tempdb..##ref_Table') IS NOT NULL
DROP TABLE ##ref_Table
IF OBJECT_ID('tempdb..##Packages') IS NOT NULL
DROP TABLE ##Packages
IF OBJECT_ID('tempdb..##ContainerType') IS NOT NULL
DROP TABLE ##ContainerType
SET NOCOUNT ON
CREATE TABLE ##ref_Table (PK_ID INT, NAME NVARCHAR(50))
CREATE TABLE ##Packages (PK_ID INT, FK_ref_Table_ID INT, FK_ContainerType_ID INT, PackageStartDate DATE, PackageEndDate DATE)
CREATE TABLE ##ContainerType (PK_ID INT, [Type] NVARCHAR(50))
INSERT INTO ##ref_Table (PK_ID,NAME)
SELECT 1,'John' UNION
SELECT 2,'Mary' UNION
SELECT 3,'Albert' UNION
SELECT 4,'Jane'
INSERT INTO ##Packages (PK_ID, FK_ref_Table_ID, FK_ContainerType_ID, PackageStartDate, PackageEndDate)
SELECT 1,1,4,'2014-01-01','2014-01-30' UNION
SELECT 2,2,3,'2014-01-01','2014-01-30' UNION
SELECT 3,3,2,'2014-01-01','2014-01-30' UNION
SELECT 4,4,1,'2014-01-01','2014-01-30'
INSERT INTO ##ContainerType (PK_ID, [Type])
SELECT 1,'Box' UNION
SELECT 2,'Pallet' UNION
SELECT 3,'Bag' UNION
SELECT 4,'Drum'
DECLARE #DATE DATE, #PARAMDEF NVARCHAR(MAX), #COLS NVARCHAR(MAX), #SQL NVARCHAR(MAX)
SET #DATE = '2014-01-15'
SET #COLS = STUFF((SELECT DISTINCT ',' + QUOTENAME(T.[Type])
FROM ##ContainerType T
FOR XML PATH, TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
SET #SQL = 'SELECT [Name], ' + #COLS + '
FROM (SELECT [Name], [Type], 1 AS Value
FROM ##ref_Table R
JOIN ##Packages P ON R.PK_ID = P.FK_ref_Table_ID
JOIN ##ContainerType T ON P.FK_ContainerType_ID = T.PK_ID
WHERE #DATE BETWEEN P.PackageStartDate AND P.PackageEndDate) X
PIVOT (COUNT(Value) FOR [Type] IN (' + #COLS + ')) P
'
PRINT #COLS
PRINT #SQL
SET #PARAMDEF = '#DATE DATE'
EXEC SP_EXECUTESQL #SQL, #PARAMDEF, #DATE=#DATE
Output:
Name Bag Box Drum Pallet
Albert 0 0 0 1
Jane 0 1 0 0
John 0 0 1 0
Mary 1 0 0 0
Static Query:
SELECT [Name],[Box],[Pallet],[Bag],[Drum] FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( [Box],[Pallet],[Bag],[Drum])
) AS PivotTable
) AS Main
ORDER BY RFID
Dynamic Query:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + [Type] + ']'
FROM ContanerType
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT [Name],' + #columnList + ' FROM
(
SELECT *
FROM
(
SELECT rf.Name,cnt.[Type], pk.PK_ID AS PKID, rf.PK_ID AS RFID
FROM ref_Table rf INNER JOIN Packages pk ON rf.PK_ID = pk.FK_ref_Table_ID
INNER JOIN ContanerType cnt ON cnt.PK_ID = pk.FK_ContainerType_ID
) AS SourceTable
PIVOT
(
COUNT(PKID )
FOR [Type]
IN ( ' + #columnList + ')
) AS PivotTable
) AS Main
ORDER BY RFID;'
EXEC sp_executesql #pivotsql
Following my tutorial below will help you to understand the PIVOT functionality:
We write sql queries in order to get different result sets like full, partial, calculated, grouped, sorted etc from the database tables. However sometimes we have requirements that we have to rotate our tables. Sounds confusing?
Let's keep it simple and consider the following two screen grabs.
SQL Table:
Expected Results:
Wow, that's look like a lot of work! That is a combination of tricky sql, temporary tables, loops, aggregation......, blah blah blah
Don't worry let's keep it simple, stupid(KISS).
MS SQL Server 2005 and above has a function called PIVOT. It s very simple to use and powerful. With the help of this function we will be able to rotate sql tables and result sets.
Simple steps to make it happen:
Identify all the columns those will be part of the desired result set.
Find the column on which we will apply aggregation(sum,ave,max,min etc)
Identify the column which values will be the column header.
Specify the column values mentioned in step3 with comma separated and surrounded by square brackets.
So, if we now follow above four steps and extract information from the above sales table, it will be as below:
Year, Month, SalesAmount
SalesAmount
Month
[Jan],[Feb] ,[Mar] .... etc
We are nearly there if all the above steps made sense to you so far.
Now we have all the information we need. All we have to do now is to fill the below template with required information.
Template:
Our SQL query should look like below:
SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( [Jan],[Feb] ,[Mar],
[Apr],[May],[Jun] ,[Jul],
[Aug],[Sep] ,[Oct],[Nov] ,[Dec])
) AS PivotTable;
In the above query we have hard coded the column names. Well it's not fun when you have to specify a number of columns.
However, there is a work arround as follows:
DECLARE #columnList nvarchar (MAX)
DECLARE #pivotsql nvarchar (MAX)
SELECT #columnList = STUFF(
(
SELECT ',' + '[' + SalesMonth + ']'
FROM Sales
GROUP BY SalesMonth
FOR XML PATH( '')
)
,1, 1,'' )
SET #pivotsql =
N'SELECT *
FROM
(
SELECT SalesYear, SalesMonth,Amount
FROM Sales
) AS SourceTable
PIVOT
(
SUM(Amount )
FOR SalesMonth
IN ( ' + #columnList +' )
) AS PivotTable;'
EXEC sp_executesql #pivotsql
Hopefully this tutorial will be a help to someone somewhere.
Enjoy coding.

How to change row into column?

How can I change this table
Name subject Mark
Aswin physics 100
Aswin chemistry 300
Aswin maths 200
Into
Aswin Physics 100 Chemistry 300 Maths 200
Any one please help me.
you can use PIVOT operator to do this job in sql server.
check these links link1 and link2 they will show how to change row into column.
hope this helps you!
SQLFiddle demo
select Name,
sum(CASE
when [subject]='physics' then Mark
end) as Physics,
sum(CASE
when [subject]='chemistry' then Mark
end) as chemistry,
sum(CASE
when [subject]='maths' then Mark
end) as maths
from t group by Name
Or if you need it in one line:
SQLFiddle demo
SELECT
t1.name,
MemberList = substring((SELECT ( ', ' + subject+' - '+
cast(Mark as varchar(100)) )
FROM t t2
WHERE t1.name = t2.name
ORDER BY
name,
subject
FOR XML PATH( '' )
), 3, 1000 )FROM t t1
GROUP BY name
You need to use SQL Pivoting, check the examples at SQL SERVER – PIVOT and UNPIVOT Table Examples. Using Sql Pivoting you can change the rows to columns and Unpivoting is for columns to rows conversion.
Please note: I am checking if I can provide you exact script but for now the link would help you out.
UPDATE
Code example
Though I have not tested this with actual data but it parses fine.
-- Pivot Table ordered by Name of Student
SELECT Name, Physics, Chemistry, Maths
FROM (
SELECT Name, Subject, Mark
FROM Student) up
PIVOT (SUM(Mark) FOR Student IN (Physics, Chemistry, Maths)) AS pvt
ORDER BY Name
-- Result should be something like
----------------------------------
Name Physics Chemistry Maths
----------------------------------
Aswin 100 300 200
----------------------------------
For creating pivot you need to know the actual rows values to convert into columns.
I have wrote before about dynamic pivoting here if you find it useful.
It is not exactly clear if you want this data in separate columns or in one column.
If you want this in separate columns, then you can apply the PIVOT function which became available in SQL Server 2005.
If you know all of the values that you want to transform or have a limited number, then you can hard-code the query:
select *
from
(
select name, subject +' '+ cast(mark as varchar(9)) as sub_mark,
'Subject_'+cast(row_number() over(partition by name
order by subject) as varchar(10)) col_name
from subjects
) s
pivot
(
max(sub_mark)
for col_name in (Subject_1, Subject_2, Subject_3)
) piv;
See SQL Fiddle with Demo. You will notice that I did this slightly different from the other pivot answer. I placed both the subject/mark in the same column with a column name of Subject_1, etc.
If you have an unknown number of values, then you can use dynamic sql:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME('Subject_'+cast(row_number() over(partition by name
order by subject) as varchar(10)))
from subjects
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT name,' + #cols + ' from
(
select name, subject +'' ''+ cast(mark as varchar(9)) as sub_mark,
''Subject_''+cast(row_number() over(partition by name
order by subject) as varchar(10)) col_name
from subjects
) x
pivot
(
max(sub_mark)
for col_name in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo. The dynamic sql version will increase in the number of columns if a name has more than 3 subjects.
The result of both queries is:
| NAME | SUBJECT_1 | SUBJECT_2 | SUBJECT_3 |
---------------------------------------------------
| Aswin | chemistry 300 | maths 200 | physics 100 |

Separate values of a column as diferrent columns as same query

I have a table UTENSILS with 3 columns like this:
CLASS_NAME RANGE COUNT
---------------------------
pens 0-0.5 200
pencil 0-0.5 50
pens 0.5-1.0 300
pencil 0.5-1.0 40
pens 1.0-1.5 150
pencil 1.0-1.5 45
I want a query that displays the above table result as below:
RANGE Pens Pencils
------------------------------
0-0.5 200 50
0.5-1.0 300 40
1.0-1.5 150 45
Any ideas about this? Thanks in advance!
What you are trying to do is known as a PIVOT. This is when you transform data from rows into columns. Some databases have a PIVOT function that you can take advantage of but you did not specify which RDBMS.
If you do not have a PIVOT function then you can replicate the functionality using an aggregate function along with a CASE statement:
select `range`,
sum(case when class_name = 'pens' then `count` end) pens,
sum(case when class_name = 'pencil' then `count` end) pencils
from yourtable
group by `range`
See SQL Fiddle with Demo
Note: the backticks are for MySQL, if SQL Server then use a square bracket around range and count. These are used to escape the reserved words.
If you are working in an RDBMS that has a PIVOT function, then you can use the following:
select *
from
(
select class_name, [range], [count]
from yourtable
) src
pivot
(
sum([count])
for class_name in ([pens], [pencil])
) piv
See SQL Fiddle with Demo
Both will produce the same result:
| RANGE | PENS | PENCIL |
---------------------------
| 0-0.5 | 200 | 50 |
| 0.5-1.0 | 300 | 40 |
| 1.0-1.5 | 150 | 45 |
The above will work great if you have a known number of values for class_name, if you do not then, depending on your RDBMS there are ways to generate a dynamic version of this query.
In SQL Server a dynamic version will be similar to this:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(CLASS_NAME)
from yourtable
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT [range], ' + #cols + ' from
(
select CLASS_NAME, [RANGE], [COUNT]
from yourtable
) x
pivot
(
sum([COUNT])
for CLASS_NAME in (' + #cols + ')
) p '
execute(#query)
See SQL Fiddle with Demo
This pivot query can be used in all major DBMS. The trick is getting the bad column names 'range' and 'count' quoted property.
SQL Server (below) uses [], MySQL uses backticks (`), Oracle uses double quotes, SQLite can use any of the preceding.
select [range],
sum(case when class_name='pens' then [count] else 0 end) Pens,
sum(case when class_name='pencil' then [count] else 0 end) Pencils
from tbl
group by [range]
order by [range];
here is the dynamic version of the pivot:
IF OBJECT_ID('tempdb..#T') IS NOT NULL
DROP TABLE #T
CREATE TABLE #T(
[CLASS_NAME] VARCHAR(20) NOT NULL
, [range] VARCHAR(10) NOT NULL
, [count] INT NOT NULL
)
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '0-0.5', '200')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '0-0.5', '50')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '0.5-1.0', '300')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '0.5-1.0', '40')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pens', '1.0-1.5', '150')
INSERT INTO #T([CLASS_NAME], [range], [count]) VALUES ('pencil', '1.0-1.5', '45')
DECLARE #PivotColumnHeaders VARCHAR(MAX)
SELECT #PivotColumnHeaders = STUFF((
--SELECT DISTINCT TOP 100 PERCENT
SELECT DISTINCT '],[' + [CLASS_NAME]
FROM #T
ORDER BY '],[' + [CLASS_NAME]
FOR XML PATH('')
), 1, 2, '') + ']';
DECLARE #PivotTableSQL NVARCHAR(MAX)
SET #PivotTableSQL = N'
SELECT *
FROM (
SELECT *
FROM #T
) AS PivotData
PIVOT (
SUM([count])
FOR [CLASS_NAME] IN (
' + #PivotColumnHeaders + '
)
) AS PivotTable
'
EXECUTE(#PivotTableSQL)

Convert Rows to columns using 'Pivot' in mssql when columns are string data type

I need to know whether 'pivot' in MS SQL can be used for converting rows to columns if there is no aggregate function to be used. i saw lot of examples with aggregate function only. my fields are string data type and i need to convert this row data to column data.This is why i wrote this question.i just did it with 'case'. Can anyone help me......Thanks in advance.
You can use a PIVOT to perform this operation. When doing the PIVOT you can do it one of two ways, with a Static Pivot that you will code the rows to transform or a Dynamic Pivot which will create the list of columns at run-time:
Static Pivot (see SQL Fiddle with a Demo):
SELECT *
FROM
(
select empid, wagecode, amount
from t1
) x
pivot
(
sum(amount)
for wagecode in ([basic], [TA], [DA])
) p
Dynamic Pivot:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
select #cols = STUFF((SELECT distinct ',' + QUOTENAME(wagecode)
FROM t1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT empid, ' + #cols + ' from
(
select empid, wagecode, amount
from t1
) x
pivot
(
sum(amount)
for wagecode in (' + #cols + ')
) p '
execute(#query)
Both of these will give you the same results
sample format
empid wagecode amount
1 basic 1000
1 TA 500
1 DA 500
2 Basic 1500
2 TA 750
2 DA 750
empid basic TA DA
1 1000 500 500
2 1500 750 750
THE ANSWER I GOT IS
SELECT empID , [1bas] as basic, [1tasal] as TA,[1otsal] as DA
FROM (
SELECT empID, wage, amount
FROM table) up
PIVOT (SUM(amt) FOR wgcod IN ([1bas], [1tasal],[1otsal])) AS pvt
ORDER BY empID
GO
Try this:
SELECT empid AS EmpID
, ISNULL(SUM(CASE wagecode WHEN 'basic' THEN Amount ELSE 0 END), 0) AS Basic
, ISNULL(SUM(CASE wagecode WHEN 'ta' THEN Amount ELSE 0 END), 0) AS TA
, ISNULL(SUM(CASE wagecode WHEN 'da' THEN Amount ELSE 0 END), 0) AS DA
FROM Employee
GROUP BY empid