How to create a table of name-value pairs in SQL - sql

Using SQL, how do I convert a single row table like this...
Firstname Surname Address1 City Country
--------- ------- --------------- ------ -------
Bob Smith 101 High Street London UK
...to a table of name-value pairs like this:
Name Value
--------- -------
Firstname Bob
Surname Smith
Address1 101 High Street
City London
Country UK
This script will create the original table:
create table #OriginalTable (Firstname varchar(10), Surname varchar(10),
Address1 varchar(50), City varchar(10), Country varchar(10))
insert into #OriginalTable
select
'Bob' Firstname,
'Smith' Surname,
'101 High Street' Address1,
'London' City,
'UK' Country
I'm after a generic solution that does not depend on the columns names always being what they are in the example.
EDIT:
I'm using SQL Server 2005.
The solution I'm after is the SQL script to convert this data into a name-value pair table
ANSWER:
Using the answer that I accepted as the answer, this is what I've used:
select
result.Name,
result.Value
from
(select
convert(sql_variant,FirstName) AS FirstName,
convert(sql_variant,Surname) AS Surname,
convert(sql_variant,Address1) AS Address1,
convert(sql_variant,City) AS City,
convert(sql_variant,Country) AS Country
from #OriginalTable) OriginalTable
UNPIVOT (Value For Name In (Firstname, Surname, Address1, City, Country)) as result

Basically you have two problems - to UNPIVOT, the data types have to be conformed. The other problem is that the number of columns is unknown. You want to reach something of the form:
WITH conformed
AS ( SELECT CONVERT(VARCHAR(255), [Firstname]) AS [Firstname],
CONVERT(VARCHAR(255), [Surname]) AS [Surname],
CONVERT(VARCHAR(255), [Address1]) AS [Address1],
CONVERT(VARCHAR(255), [City]) AS [City],
CONVERT(VARCHAR(255), [Country]) AS [Country]
FROM so1526080
)
SELECT ColumnKey,
ColumnValue
FROM conformed UNPIVOT ( ColumnValue FOR ColumnKey IN ( [Firstname], [Surname], [Address1], [City], [Country] ) ) AS unpvt
So using a dynamic SQL PIVOT using metadata (you might need to fix this up with TABLE_SCHEMA, etc):
DECLARE #table_name AS SYSNAME
SET #table_name = 'so1526080'
DECLARE #conform_data_type AS VARCHAR(25)
SET #conform_data_type = 'VARCHAR(255)'
DECLARE #column_list AS VARCHAR(MAX)
DECLARE #conform_list AS VARCHAR(MAX)
SELECT #conform_list = COALESCE(#conform_list + ', ', '') + 'CONVERT('
+ #conform_data_type + ', ' + QUOTENAME(COLUMN_NAME) + ') AS '
+ QUOTENAME(COLUMN_NAME),
#column_list = COALESCE(#column_list + ', ', '')
+ QUOTENAME(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #table_name
DECLARE #template AS VARCHAR(MAX)
SET #template = '
WITH conformed
AS ( SELECT {#conform_list}
FROM {#table_name}
)
SELECT ColumnKey,
ColumnValue
FROM conformed UNPIVOT ( ColumnValue FOR ColumnKey IN ( {#column_list} ) ) AS unpvt
'
DECLARE #sql AS VARCHAR(MAX)
SET #sql = REPLACE(REPLACE(REPLACE(#template, '{#conform_list}', #conform_list),
'{#column_list}', #column_list), '{#table_name}',
#table_name)
PRINT #sql
EXEC ( #sql
)

This is MS SQL Server solution using JSON. You don't need to specify the column names and data types. All you need to do is to specify the table name and valid where condition on line 3.
DECLARE #TEMP TABLE (JSONHOLDER NVARCHAR(MAX));
INSERT INTO #TEMP
SELECT (SELECT * FROM YOURTABLE WHERE YOURID = 2589 FOR JSON AUTO) AS JSONHOLDER
SELECT [KEY] as ColumnName, [VALUE] as ColumnValue FROM (SELECT [KEY] AS OLDKEY, [VALUE] AS OLDVALUE FROM #TEMP A
CROSS APPLY OPENJSON(A.JSONHOLDER)) B CROSS APPLY OPENJSON(B.OLDVALUE)

Not that it be a complete solution, but is a brainstorm idea, what if you cross join information_schema.columns with your table?
SELECT column_name, OriginalTable.*
FROM information_schema.columns
CROSS JOIN OriginalTable
WHERE table_name = 'OriginalTable'
AND /* PRIMARY KEY FILTER HERE*/

Often it is most effective to pivot in the application using application code. Pivoting does not tend to be a database's strong point.

Use two tables. One table as a table of 'keys' and the main table contains an id to the keys table, together with a value.
Then, you can add stuff like client_id or whatever to the main table as well and set a unique key on client_id and key_id.

This sounds like the kind of things the PIVOT clause can do in SQL Server since 2005 (look for the first example), but you don't mention which database engine you use.

Related

Execute create table query from column in SQL server

I have two select statements which are to create table and insert values into table. How to execute all the queries inside the column at one go? Below is my code and the output:
select n.*
into #norm
from specNormalization n
select n.*
into #raw
from rawdata n
select distinct 'CREATE TABLE raw' + c.desttablename + ' (' + STUFF(
(select ','+QUOTENAME( c.[destfieldname] ) + c.datatype
from #norm c
group by c.destfieldname, c.datatype
for xml path ('')),1,1,'') as createTableSQL
select distinct 'INSERT INTO raw' + c.desttablename +
select d.contents
from #raw d join #norm c on d.tablename = c.desttablename
as insertTableSQL
rawdata table
desttablename
destfieldname
datatype
rawtable
SbjNum
int
rawtable
Surveyor
nvarchar(20)
rawtable
Location
nvarchar(20)
rawtable2
SbjNum
int
rawtable2
Name
nvarchar(20)
rawtable2
Address
nvarchar(20)
specnomalization table
tablename
destfieldname
contents
rawtable
SbjNum
1
rawtable
Surveyor
Alex
rawtable
Location
Georgia
rawtable2
SbjNum
1
rawtable2
Name
Sandra
rawtable2
Address
Portland
createTableSQL
CREATE TABLE rawtable ([Sbjnum])int, ([Surveyor])nvarchar(200), ([Location])nvarchar(200)
CREATE TABLE rawtable2 ([Sbjnum])int, ([Name])nvarchar(200), ([Address])nvarchar(200)
insertTableSQL
INSERT INTO rawtable SELECT [Sbjnum], [Surveyor], [Location] from #raw
INSERT INTO rawtable2 SELECT [Sbjnum], [Name], [Address] from #raw
You can create the create query by using stuff. Since you would have multiple tables so you have to group by the query to return multiple table names and it's columns separately. So the result query you can set into a variable and can execute by using
EXEC sp_executesql
So you can easily create as much as tables based on your table data.
The following query will help you to create multiple tables. This you can optimize if needed, but this will give you the insight to do the same for inserting values to the tables respectively.
Declare #sql nvarchar(MAX) = ( SELECT ' CREATE TABLE '+ desttablename +' (' + STUFF((SELECT ', ' + sn.[destfieldname] + ' ' +datatype
FROM dbo.specNormalization As sn
ORDER BY sn.destfieldname
FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 2, '') + ' )'
FROM dbo.specNormalization as P2
GROUP BY P2.desttablename FOR XML PATH('') )
-- Uncomment to see the created query
-- SELECT #sql
-- To execute the created query. This will create the tables with column and datatype
EXEC sp_executesql #sql
Hope this helps. Happy coding :)
Too long for a comment. Try to generate SELECT .. INTO .. FROM to create and populate a target table. Kind of
SELECT cast([Sbjnum] as int) [Sbjnum], cast([Surveyor] as nvarchar(200)) [Surveyor], cast([Location] as nvarchar(200)) [Location]
INTO rawtable
FROM #raw
If the source types are the same as target, skip cast functions. Can't tell what is the query to generate it as no sample data are provided.

SQL query to obtain counts from two tables based on values and field names

I want to count the alerts of the candidates based on district.
Below is the district-wise alert lookup table
Table_LKP_AlertMastInfo
DistrictID FieldName AlertOptionValue
71 AreYouMarried Yes
71 Gender Female
72 AreYouMarried Yes
The above Table_LKP_AlertMastInfo FieldName should compare with table_RegistrationInfo fields to check the AlertOptionValue to get counts.
Below is the candidate details table:
Table_RegistrationInfo
CandidateId DistrictID AreYouMarried Gender
Can001 71 Yes Female
Can002 71 No Female
Can003 72 Yes Man
Can004 72 No Man
I want output like below:
Can001 2
Can002 1
Can003 1
Explanation of the above output counts:
Can001 have selected AreYouMarried:Yes and Gender:Female then count value 2
Can002 have selected Gender:Female then count value 1
Can003 have selected AreYouMarried:Yes then count value 1
Can004 have not alerts
This won't be possible without dynamic SQL if your data is modeled like it is, i.e. key-value pairs in Table_LKP_AlertMastInfo and columns in Table_RegistrationInfo. So with that out of our way, let's do it. Full code to the stored procedure providing the exact results you need is at the end, I'll follow with the explanation on what it does.
Because the alerts are specified as key-value pairs (field name - field value), we'll first need to get the candidate data in the same format. UNPIVOT can fix this right up, if we can get it the list of the fields. Had we only had only the two fields you mention in the question, it would be rather easy, something like:
SELECT CandidateId, DistrictID
, FieldName
, FieldValue
FROM Table_RegistrationInfo t
UNPIVOT (FieldValue FOR FieldName IN (AreYouMarried, Gender)) upvt
Of course that's not the case, so we'll need to dynamically select the list of the fields we're interested in and provide that. Since you're on 2008 R2, STRING_AGG is not yet available, so we'll use the XML trick to aggregate all the fields into a single string and provide it to the query above.
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = CONCAT('SELECT CandidateId, DistrictID
, FieldName
, FieldValue
FROM Table_RegistrationInfo t
UNPIVOT (FieldValue FOR FieldName IN (',
STUFF((
SELECT DISTINCT ',' + ami.FieldName
FROM Table_LKP_AlertMastInfo ami
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''), ')) upvt')
PRINT #sql
This produces almost the exact output as the query I wrote. Next, we need to store this data somewhere. Temporary tables to the rescue. Let's create one and insert into it using this dynamic SQL.
CREATE TABLE #candidateFields
(
CandidateID VARCHAR(50),
DistrictID INT,
FieldName NVARCHAR(200),
FieldValue NVARCHAR(1000)
);
INSERT INTO #candidateFields
EXEC sp_executesql #sql
-- (8 rows affected)
-- We could index this for good measure
CREATE UNIQUE CLUSTERED INDEX uxc#candidateFields on #candidateFields
(
CandidateId, DistrictId, FieldName, FieldValue
);
Great, with that out of the way, we now have both data sets - alerts and candidate data - in the same format. It's a matter of joining to find matches between both:
SELECT cf.CandidateID, COUNT(*) AS matches
FROM #candidateFields cf
INNER
JOIN Table_LKP_AlertMastInfo alerts
ON alerts.DistrictID = cf.DistrictID
AND alerts.FieldName = cf.FieldName
AND alerts.AlertOptionValue = cf.FieldValue
GROUP BY cf.CandidateID
Provides the desired output for the sample data:
CandidateID matches
-------------------------------------------------- -----------
Can001 2
Can002 1
Can003 1
(3 rows affected)
So we can stitch all that together now to form a reusable stored procedure:
CREATE PROCEDURE dbo.findMatches
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = CONCAT('SELECT CandidateId, DistrictID
, FieldName
, FieldValue
FROM Table_RegistrationInfo t
UNPIVOT (FieldValue FOR FieldName IN (',
STUFF((
SELECT DISTINCT ',' + ami.FieldName
FROM Table_LKP_AlertMastInfo ami
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, ''), ')) upvt')
CREATE TABLE #candidateFields
(
CandidateID VARCHAR(50),
DistrictID INT,
FieldName NVARCHAR(200),
FieldValue NVARCHAR(1000)
);
INSERT INTO #candidateFields
EXEC sp_executesql #sql
CREATE UNIQUE CLUSTERED INDEX uxc#candidateFields on #candidateFields
(
CandidateId, DistrictId, FieldName
);
SELECT cf.CandidateID, COUNT(*) AS matches
FROM #candidateFields cf
JOIN Table_LKP_AlertMastInfo alerts
ON alerts.DistrictID = cf.DistrictID
AND alerts.FieldName = cf.FieldName
AND alerts.AlertOptionValue = cf.FieldValue
GROUP BY cf.CandidateID
END;
Execute with
EXEC dbo.findMatches
You'd of course need to adjust types and probably add a bunch of other things here, like error handling, but this should get you started on the right path. You'll want a covering index on that alert table and it should be pretty fast even with a lot of records.
I managed to get the expected result without using dynamic queries.
Not sure if this is what you are looking for:
SELECT DISTINCT
c.CandidateId, SUM(a.AreYouMarriedAlert + a.GenderAlter) AS AlterCount
FROM
Table_RegistrationInfo c
OUTER APPLY
(
SELECT
CASE
WHEN a.FieldName = 'AreYouMarried' AND c.AreYouMarried = a.AlertOptionValue THEN 1
ELSE 0
END AS AreYouMarriedAlert,
CASE
WHEN a.FieldName = 'Gender' AND c.Gender = a.AlertOptionValue THEN 1
ELSE 0
END AS GenderAlter
FROM
Table_LKP_AlertMastInfo a
WHERE
a.DistrictID = c.DistrictID
) a
GROUP BY c.CandidateId
HAVING SUM(a.AreYouMarriedAlert + a.GenderAlter) > 0
Results:
I asusme that with 100 fields you have a set of alerts which are a combinatioin of values. Further I assume that you can have a select list in a proper order all the time. So
select candidateid,
AreyouMarried || '|' || Gender all_responses_in_one_string
from ....
is psssible. So above will return
candidateid all_responses_in_one_string
can001 Yes|Female
can002 No|Female
So now your alert can be a regular expression for the concatenated string. And your alert is based on how much you matched.
Here is one simple way of doing this:
SELECT subq.*
FROM
(SELECT CandidateId,
(SELECT COUNT(*)
FROM Table_LKP_AlertMastInfo ami
WHERE ami.DistrictID = ri.DistrictID
AND ami.FieldName ='AreYouMarried'
AND ami.AlertOptionValue = ri.AreYouMarried) +
(SELECT COUNT(*)
FROM Table_LKP_AlertMastInfo ami
WHERE ami.DistrictID = ri.DistrictID
AND ami.FieldName ='Gender'
AND ami.AlertOptionValue = ri.Gender) AS [count]
FROM Table_RegistrationInfo ri) subq
WHERE subq.[count] > 0;
See SQL Fiddle demo.
I am not sure if this can be completely done using SQL. If you are using some backend technology such as ADO.NET, then you can store the results in Datatables. Loop through the column names and do the comparison.
Dynamic SQL can be used to make Table_LKP_AlertMastInfo look like Table_RegistrationInfo.
This script can be used in a stored procedure and results can be retrieved in a Datatable.
DECLARE #SQL NVARCHAR(MAX)
DECLARE #PivotFieldNameList nvarchar(MAX)
SET #SQL = ''
SET #PivotFieldNameList = ''
SELECT #PivotFieldNameList = #PivotFieldNameList + FieldName + ', '
FROM (SELECT DISTINCT FieldName FROM Table_LKP_AlertMastInfo) S
SET #PivotFieldNameList = SUBSTRING(#PivotFieldNameList, 1, LEN(#PivotFieldNameList) - 1)
--SELECT #PivotFieldNameList
SET #SQL = ' SELECT DistrictId, ' + #PivotFieldNameList + ' FROM
Table_LKP_AlertMastInfo
PIVOT
( MAX(AlertOptionValue)
FOR FieldName IN (' + #PivotFieldNameList + '
) ) AS p '
PRINT #SQL
EXEC(#SQL)
Above query results like below
DistrictId AreYouMarried Gender
71 Yes Female
72 Yes NULL
If you get results from Table_RegistrationInfo into another Datatable, then both can be used for comparison.
Not tested but this should do the trick:
SELECT CandidateId,
( CASE
WHEN AreYouMarried = "Yes" AND Gender = 'Female' THEN 2
WHEN Gender = 'Female' THEN 1
WHEN AreYouMarried = "Yes" THEN 1
ELSE 0 END
) as CandidateValue
FROM
(SELECT * FROM Table_LKP_AlertMastInfo) as Alert
LEFT JOIN
(SELECT * FROM Table_RegistrationInfo) as Registration
ON (Alert.DistrictID = Registration.DistrictID);
This should give you a list with candidateId matching the condition count

Pivot table with custom Name as columnNames

How can i pivot a table with single column.
result set of my select query contains single column and 3 rows.
i.e my select query looks like :-
contactList table have following columns.
ContactID, PhNumbers, PhTYpe, ContactPersonID
select PhNumbers,PhType from contactList where ContactPersonID=3
PhNumbers PhType
1234567890 1
3456789013 2
4545466756 3
these 3 rows corresponds to 3 types of phone numbers
i need an output like this
homePhone MobilePhone WorkPhone
1234567890 3456789013 4545466756
DECLARE
#SQL varchar(MAX),
#ColumnList varchar(MAX)
SELECT #ColumnList=
COALESCE(#ColumnList + ',','') + QUOTENAME(PhNumbers)
FROM
(
SELECT DISTINCT PhNumbers
FROM contactList
) T
SET #SQL = '
WITH PivotData AS
(
SELECT PhNumbers
FROM contactList
)
SELECT
' + #ColumnList + '
FROM
PivotData
PIVOT
(
MAX(PhNumbers)
FOR PhNumbers
IN (' + #ColumnList + ')
) AS PivotResult'
EXEC (#SQL)
Got the answer. Thanks for all your help guys.
SELECT [1] as HomePhone, [2] as MobilePhone,[3] as WorkPhone,
FROM
(select PhNumbers,PhType from contactList where ContactPersonID=3) AS SourceTable
PIVOT
(
MAX(PhNumbers)
FOR PhType IN ([1],[2],[3])
) AS PivotTable;
Try this query
SELECT 1,2,3
FROM
(SELECT PhNumbers, PhType
FROM contactList where ContactPersonID=3) a
PIVOT(MAX(PhNumbers) FOR PhType IN(1,
2,
3)) r

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.

TSQL Pivot without aggregate function

I have a table like this...
CustomerID DBColumnName Data
--------------------------------------
1 FirstName Joe
1 MiddleName S
1 LastName Smith
1 Date 12/12/2009
2 FirstName Sam
2 MiddleName S
2 LastName Freddrick
2 Date 1/12/2009
3 FirstName Jaime
3 MiddleName S
3 LastName Carol
3 Date 12/1/2009
And I want this...
Is this possible using PIVOT?
CustomerID FirstName MiddleName LastName Date
----------------------------------------------------------------------
1 Joe S Smith 12/12/2009
2 Sam S Freddrick 1/12/2009
3 Jaime S Carol 12/1/2009
yes, but why !!??
Select CustomerID,
Min(Case DBColumnName When 'FirstName' Then Data End) FirstName,
Min(Case DBColumnName When 'MiddleName' Then Data End) MiddleName,
Min(Case DBColumnName When 'LastName' Then Data End) LastName,
Min(Case DBColumnName When 'Date' Then Data End) Date
From table
Group By CustomerId
You can use the MAX aggregate, it would still work. MAX of one value = that value..
In this case, you could also self join 5 times on customerid, filter by dbColumnName per table reference. It may work out better.
WITH pivot_data AS
(
SELECT customerid, -- Grouping Column
dbcolumnname, -- Spreading Column
data -- Aggregate Column
FROM pivot2
)
SELECT customerid, [firstname], [middlename], [lastname]
FROM pivot_data
PIVOT (max(data) FOR dbcolumnname IN ([firstname],[middlename],[lastname])) AS p;
Ok, sorry for the poor question. gbn got me on the right track.
This is what I was looking for in an answer.
SELECT [FirstName], [MiddleName], [LastName], [Date]
FROM #temp
PIVOT
( MIN([Data])
FOR [DBColumnName] IN ([FirstName], [MiddleName], [LastName], [Date])
)AS p
Then I had to use a while statement and build the above statement as a varchar and use dynmaic sql.
Using something like this
SET #fullsql = #fullsql + 'SELECT ' + REPLACE(REPLACE(#fulltext,'(',''),')','')
SET #fullsql = #fullsql + 'FROM #temp '
SET #fullsql = #fullsql + 'PIVOT'
SET #fullsql = #fullsql + '('
SET #fullsql = #fullsql + ' MIN([Data])'
SET #fullsql = #fullsql + ' FOR [DBColumnName] IN '+#fulltext
SET #fullsql = #fullsql + ')'
SET #fullsql = #fullsql + 'AS p'
EXEC (#fullsql)
Having a to build #fulltext using a while loop and select the distinct column names out of the table. Thanks for the answers.
SELECT
main.CustomerID,
f.Data AS FirstName,
m.Data AS MiddleName,
l.Data AS LastName,
d.Data AS Date
FROM table main
INNER JOIN table f on f.CustomerID = main.CustomerID
INNER JOIN table m on m.CustomerID = main.CustomerID
INNER JOIN table l on l.CustomerID = main.CustomerID
INNER JOIN table d on d.CustomerID = main.CustomerID
WHERE f.DBColumnName = 'FirstName'
AND m.DBColumnName = 'MiddleName'
AND l.DBColumnName = 'LastName'
AND d.DBColumnName = 'Date'
Edit: I have written this without an editor & have not run the SQL. I hope, you get the idea.
The OP didn't actually need to pivot without agregation but for those of you coming here to know how see:
sql parameterised cte query
The answer to that question involves a situation where pivot without aggregation is needed so an example of doing it is part of the solution.
This should work:
select * from (select [CustomerID] ,[Demographic] ,[Data]
from [dbo].[pivot]
) as Ter
pivot (max(Data) for Demographic in (FirstName, MiddleName, LastName, [Date]))as bro
Try this:
SELECT CUSTOMER_ID, MAX(FIRSTNAME) AS FIRSTNAME, MAX(LASTNAME) AS LASTNAME ...
FROM
(
SELECT CUSTOMER_ID,
CASE WHEN DBCOLUMNNAME='FirstName' then DATA ELSE NULL END AS FIRSTNAME,
CASE WHEN DBCOLUMNNAME='LastName' then DATA ELSE NULL END AS LASTNAME,
... and so on ...
GROUP BY CUSTOMER_ID
) TEMP
GROUP BY CUSTOMER_ID
Here is a great way to build dynamic fields for a pivot query:
--summarize values to a tmp table
declare #STR varchar(1000)
SELECT #STr = COALESCE(#STr +', ', '')
+ QUOTENAME(DateRange)
from (select distinct DateRange, ID from ##pivot)d order by ID
---see the fields generated
print #STr
exec(' .... pivot code ...
pivot (avg(SalesAmt) for DateRange IN (' + #Str +')) AS P
order by Decile')