Group Columns based on Row ID - sql

I have a table pulling data, like:
ID FID Value
001 20 200
001 20 400
001 50 600
002 50 100
How do write a query to get a column for each row ID that would sum the Value's?
For example, I want to return the following:
ID 20 50
001 600 600
002 NULL 100

A pattern like this:
SELECT
ID,
SUM(CASE WHEN FID = 20 THEN Value END) as sum20,
SUM(CASE WHEN FID = 50 THEN Value END) as sum50 --extend by adding more CASE WHEN rows
FROM
table
GROUP BY ID
..has the advantage of working in databases that don't support PIVOT syntax.
If you'd like PIVOT syntax:
SELECT ID, [20], [50] --extend by providing more values in square brackets
FROM
table
PIVOT
(
SUM(Value)
FOR FID IN ([20], [50]) --extend by providing more values in square brackets
) pvt

If you have dynamic list of FID's you can use dynamic query as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select Distinct ','+QuoteName(Fid) from #data for xml path('')),1,1,'')
Set #query = ' Select * from (
Select Id, [Fid], [Value] from #data ) a
pivot (sum([Value]) for [Fid] in (' + #cols1 + ') ) p '
Exec sp_executesql #query

Related

Converting column values (rows) to columns and aggregate count on two rows of two different tables

I have two tables
Program
Student
Student:
Name Status1 Syear SCode
--------------------
kk A 2000 1
ra A 2001 2
Paras L 2000 2
Prit L 2001 2
Poot A 2002 4
Program:
PName PCode
--------------------
Msc 1
DC 2
PO 4
Join on ID
Required output :
SELECT *
FROM
(SELECT
Program.PName AS v, Status1
FROM
Student, Program
WHERE
Student.PCode = SCode
GROUP BY
Program.PName, Student.Syear, Status1) AS src
pivot
(
count(v)
FOR Status1 IN ([A],)
) as piv
It does not display PNAME in output
A L
-----------
1 0
1 2
1 0
Desired output
PNAME A L
-----------
Msc 1 0
DC 1 2
PO 1 0
1. STATIC PIVOT
You can do this if the column names is known in advance
SELECT PName,ISNULL([A],0) [A],ISNULL([L],0)[L] FROM
(
-- Source data for pivoting
SELECT P.PName,Status1,
COUNT(Status1)OVER(PARTITION BY PNAME,Status1)CNT
FROM #PROGRAM P
JOIN #Student S ON P.PCODE=S.SCODE
) x
PIVOT
(
--Defines the values in each dynamic columns
MIN(CNT)
-- Get the names of columns to pivot
FOR Status1 IN ([A],[L])
) p
ORDER BY PName
Click here to view result
2. DYNAMIC PIVOT
Dynamic pivoting can be done if the number of columns is not known in advance.
First of all get columns dynamically to pivot
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + Status1 + ']', '[' + Status1 + ']')
FROM (SELECT DISTINCT Status1 FROM #Student) PV
ORDER BY Status1
Now the below variable is used to replace NULL with zero.
DECLARE #NulltoZeroCols NVARCHAR (MAX)
SELECT #NullToZeroCols = SUBSTRING((SELECT ',ISNULL(['+Status1+'],0) AS ['+Status1+']'
FROM (SELECT DISTINCT Status1 FROM #Student)TAB
ORDER BY Status1 FOR XML PATH('')),2,8000)
Now pivot it. I have written the logic inside
DECLARE #query NVARCHAR(MAX)
SET #query = '-- This outer query forms your pivoted result
SELECT PName,'+#NullToZeroCols+' FROM
(
-- Source data for pivoting
SELECT P.PName,Status1,
COUNT(Status1)OVER(PARTITION BY PNAME,Status1)CNT
FROM #PROGRAM P
JOIN #Student S ON P.PCODE=S.SCODE
) x
PIVOT
(
--Defines the values in each dynamic columns
MIN(CNT)
-- Get the names from the #cols variable to show as column
FOR Status1 IN (' + #cols + ')
) p
ORDER BY PName;'
EXEC SP_EXECUTESQL #query
Click here to view result

Pivot a resultset (rows to columns)

Consider the following #table:
guid field value
--------------------
123 A foo
123 B foobar
123 C 123
234 A bar;baz
234 B 3464
345 A foobaz
I need to transpose / pivot this into the following #table2:
guid A B C
---------------------------
123 foo foobar 123
234 bar;baz 3464 -
345 foobaz - -
In other words: the guid needs to stay the table key but all the fields need to be transposed into columns.
Is this possible in SQL Server?
Usually, I would use a server-side (php, python, asp) script to pull #table from the database and transpose the resultset by iterating to the resultset as an array but this is not an option in this case. I need a sql-only solution.
Any help would be greatly appreciated.
This should pivot your data and will keep your GUID.
select tbl.guid, tbl.[A], tbl.[B], tbl.[C] from (
select * from (
select guid, field, value
from #table
) t
pivot (
max(value) for field in ([A],[B],[C])
) p
) tbl
Creating your columns in preparation for the dynamic SQL:
declare #columns nvarchar(max) = (select stuff((
select distinct ',[' + t.field + ']'
from #table t
for xml path('')
),1,1,''))
Mixing the columns into the dynamic SQL:
declare #sql = N'
select tbl.guid, ' + #columns + ' from (
select * from (
select guid, field, value
from #table
) t
pivot (
max(value) for field in (' + #columns + ')
) p
) tbl'
And execute:
execute (#sql)

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.

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)

How do i transform rows into columns in sql server 2005

There is a question here in stackoverflow with the same title but that is not what I am looking for.
I have a table like the one below
Name | Count
----------------
Chery | 257
Drew | 1500
Morgon | 13
Kath | 500
Kirk | 200
Matt | 76
I need to trasform this result set into something like this
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
How do i acheive this using sql server 2005?
There are similar questions here,here answered in stackoverflow.
You need to use the operator PIVOT in your query to acheive this.Here is the example and explanation on how you can do that.The example is referenced from this source.
---I assumed your tablename as TESTTABLE---
DECLARE #cols NVARCHAR(2000)
DECLARE #query NVARCHAR(4000)
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
SET #query = N'SELECT '+ #cols +' FROM
(SELECT t1.Name , t1.Count FROM TESTTABLE AS t1) p
PIVOT (MAX([Count]) FOR Name IN ( '+ #cols +' ))
AS pvt;'
EXECUTE(#query)
Explanation
1.The first part of the query
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
gives you a nice flattened result of your Name column values in a single row as follow
[Cheryl],[Drew],[Karen],[Kath],[Kirk],[Matt]
You can learn more about the STUFF and XML PATH here and here.
2.SELECT + #cols + FROM will select all the rows as coloumn names for the final result set (pvt - step 3)
i.e
Select [Chery],[Drew],[Morgan],[Kath],[Kirk],[Matt]
3.This query pulls all the rows of data that we need to create the cross-tab results. The (p) after the query is creating a temporary table of the results that can then be used to satisfy the query for step 1.
(SELECT t1.Name, t1.Count FROM TESTTABLE AS t1) p
4.The PIVOT expression
PIVOT (MAX (Count) FOR Name IN ( #cols) AS pvt
does the actual summarization and puts the results into a temporary table called pvt as
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
See Using PIVOT and UNPIVOT.
You can use the PIVOT and UNPIVOT
relational operators to change a
table-valued expression into another
table. PIVOT rotates a table-valued
expression by turning the unique
values from one column in the
expression into multiple columns in
the output, and performs aggregations
where they are required on any
remaining column values that are
wanted in the final output. UNPIVOT
performs the opposite operation to
PIVOT by rotating columns of a
table-valued expression into column
values.
The quick answer is
SELECT Chery, Drew, Morgon, Kath, Kirk, Matt
FROM
(SELECT [Name], [Count] From Foo)
PIVOT
(
MIN([Count])
FOR [Name] IN (Chery, Drew, Morgon, Kath, Kirk, Matt)
) AS PivotTable
If you want to avoid anything complicated like a pivot or the accepted answer, you can do this! (most of the code is just setting up the test data just in case anyone wants to try it)
/* set up your test table */
declare #TestData table (Name Varchar(80),[Count] int)
insert into #TestData (Name, [count])
Select 'Chery' as name, 257 as [count]
union all select 'Drew', 1500
union all select 'Morgon',13
union all select 'Kath', 500
union all select 'Kirk', 200
union all select 'Matt', 76
/* the query */
Declare #Query Varchar(max)
Select #Query=Coalesce(#query+', ','SELECT ') +Convert(VarChar(5),[count]) +' as ['+name+']'
from #TestData
Execute (#Query)
/* result
Chery Drew Morgon Kath Kirk Matt
----------- ----------- ----------- ----------- ----------- -----------
257 1500 13 500 200 76
*/