How Do I Use PIVOT On This Data:? - sql

I have a SQL Server table that looks like this:
RESOURCE | DESCRIPTION | VALUE
Test A Name | Resource A-xyz
Test A | Height | 20
Test A | Unit | ft
Test A | Location | Site 1
Test B | Volume | 30
Test C | Width | 10
Test C | Unit | in
I would like to get it into this format:
RESOURCE | Name | Height | Unit | Location | Volume | Width
Test A | Resource A-xyz | 20 | ft | Site 1 | |
Test B | | | | | 30 |
Test C | | | in | | | 10
One of the issues that I have is that there is no set pattern for description; for example, resource "Test B" might have all of the same descriptions as "Test A", while "Test C", might be missing some, and "Test D" might have a totally different set.
So far Google is suggesting that I want to use a pivot table, but I am still not sure how to do that with the above data.

The resulting column list is based on the ordering you provide within the PIVOT:
SELECT *
FROM Table1
PIVOT(MAX(VALUE) FOR DESCRIPTION IN (Name,Height,Unit,Location,Volume,Width))p
Demo: SQL Fiddle
If you have changing values for DESCRIPTION it's worthwhile to build this query dynamically, there are plenty of good examples for 'dynamic pivot' to be found.

try this code:
SELECT resource,Name,Height,Unit,Location,Volume,Width
FROM
#T1 AS SourceTable
PIVOT
(
max(value)
FOR description IN ([Name],[Height],[Unit],[Location],[Volume],[Width])
) AS PivotTable
ORDER BY 1

If you don't want to hardcode the columns and want to generate same view on the fly, you can use this. This will generate dynamic pivot
CREATE TABLE demo
(
RESOURCE VARCHAR(100),
DESCRIPTION VARCHAR(100), VALUE VARCHAR(100)
)
INSERT INTO demo VALUES
('Test A' , 'Name' , 'Resource A-xyz')
,('Test A' , 'Height' , '20')
,('Test A' , 'Unit' , 'ft')
,('Test A' , 'Location' , 'Site 1')
,('Test B' , 'Volume' , '30')
,('Test C' , 'Width' , '10')
,('Test C' , 'Unit' , 'in')
SELECT DISTINCT DESCRIPTION INTO #tbl FROM demo
//Get list of values to be pivoted
DECLARE #var NVARCHAR(1000)=''
SELECT #var = #var +', ' + DESCRIPTION FROM #tbl
SELECT #var = SUBSTRING(#var, 2, LEN(#var))
SELECT #var
DECLARE #query NVARCHAR(2000) = 'SELECT * FROM demo PIVOT(MAX(VALUE) FOR DESCRIPTION IN ('+ #var + '))p'
EXEC sp_executesql #query

In the end, I did the following:
Selected all distinct descriptions (more than 70!).
Created a table that had resource and every single distinct description as fields
Populated resource column distinct resource names
Ran a series of
updates to populate remaining columns for each distinct resource
name.
For example
CREATE TABLE #tb1
(
[RESOURCE] varchar(100),
[FIELD1] varchar(100),
[FIELD2] varchar(50),
.
.
.
[LAST FIELD] varchar(50),
)
INSERT INTO #tb1 (RESOURCE)
SELECT DISTINCT RESOURCE FROM tb2
ORDER BY Resource ASC
UPDATE #tb1 SET [FIELD1] = (SELECT VALUE FROM tb2 WHERE Resource = #tb1.Resource and Property = [FIELD1])
.
.
.
UPDATE #tb1 SET [LAST FIELD] = (SELECT VALUE FROM tb2 WHERE Resource = #tb1.Resource and Property = [LAST FIELD])

Related

How can I select column headers returned from EXEC statement into a temp table?

I'm trying to select the column headers/names from an EXEC statement in SQL.
For example, if I run the code
SET #ls_SQL = 'EXEC dbo.Generic_Proc ' + #Param
EXEC(#ls_SQL)
and it returns:
|---------------------|------------------|
| ColumnName1 | ColumnName2 |
|---------------------|------------------|
| 12 | 34 |
|---------------------|------------------|
How can I get the strings 'ColumnName1' and 'ColumnName2' into a temporary table? Something like:
|---------------------|------------------|
| Row | Header |
|---------------------|------------------|
| 1 | ColumnName1 |
|---------------------|------------------|
| 2 | ColumnName2 |
|---------------------|------------------|
I tried using sp_describe_first_result_set, but 'Generic_Proc' is using dynamic SQL, so I get an error. The error states that I can explicitly describe the result set, but unfortunately the returned columns will be different depending on the parameter sent to it. Is there any way around this?
I'm trying to select the column headers/names from an EXEC statement in SQL . . . [but] the returned columns will be different depending on the parameter sent to it.
There is no first-class way in TSQL to do that.
You can do that in SQL CLR, or by introducing a Loopback Linked Server, with the caveat that
Loopback linked servers are intended for testing and are not supported
for many operations, such as distributed transactions.
Linked Servers
select *
into #foo
from openquery(Loopback,'select 1 a, 3 b')
select c.name, t.name type_name
from tempdb.sys.columns c
join tempdb.sys.types t
on c.system_type_id = t.system_type_id
where object_id = object_id('tempdb..#foo')
I'd recommend using UNPIVOT for this. To do an unpivot dynamically, I'd put the results from your stored procedure into a temp table.
Then here's the sql that will take care of the rest:
SELECT 1 AS Column1, 2 AS Column2, 3 AS Column3, 4 AS Column4
INTO #Temp --Put your SP results here
DECLARE #columnsToUnpivot AS NVARCHAR(MAX),
#unpivotQuery AS NVARCHAR(MAX)
SELECT #columnsToUnpivot
= STUFF((SELECT ','+QUOTENAME(name)
FROM Tempdb.Sys.Columns Where Object_ID = Object_ID('tempdb..#Temp')
for xml path('')), 1, 1, '')
SELECT #columnsToUnpivot
set #unpivotQuery
= ' SELECT
*
FROM
#Temp AS t
UNPIVOT
(
VALUE
for Col in ('+ #columnsToUnpivot +')
) u'
exec sp_executesql #unpivotQuery;
DROP TABLE #Temp

T-Sql Query with dynamic (unknown) number of columns

We have a project where we should provide the possible to the user to add own custom columns to various tables.
Edit: these are 2 tables, not one.
**Products**
ProductId
Name
Price
Date
UserId
**ProductsCustomColumns**
ProductId
ColumnName
ColumnValue
EDIT: Please note that the dynamic columns are recorded as values and we don't know the count of these...it can be 0 or 200 or any.
Here is an example:
Now when we query the products tables we want to show all the predefined columns and after them all custom columns.
Obviously each user can have own number of columns with values and names.
SELECT *, (and the custom columns) FROM Products WHERE UserId = 3 AND ProductId = 1
Here are 2 questions:
Would that be good solution from performance point of view or there is better approach for solving the dynamic columns requirement?
How can I create a query that could read all records from ProductsCustomColumns for given userId and productId and append the records as columns to the query?
Thanks.
You need to write dynamic Query
DECLARE #COLUMNS VARCHAR(MAX)='', #QRY VARCHAR(MAX);
SELECT #COLUMNS = #COLUMNS +COLUMN_NAME +',' FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products'
SELECT #COLUMNS =SUBSTRING (#COLUMNS,1 ,LEN(#COLUMNS)-1)
SELECT #QRY ='SELECT '+#COLUMNS + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#QRY)
EDIT: From your Comments & Edited Question
Schema I assumed from your Question
CREATE TABLE Products (
ProductId INT,
Name VARCHAR(250),
Price DECIMAL(18,2),
DateS DATETIME,
UserId INT)
INSERT INTO Products
SELECT 1,'Oil Product', 2000, GETDATE(), 3
UNION ALL
SELECT 2,'Amway', 600, GETDATE(), 2
UNION ALL
SELECT 3,'Thermal', 5000, GETDATE(), 1
UNION ALL
SELECT 4,'Oil Product', 500, GETDATE(), 4
CREATE TABLE ProductsCustomColumns
(
ProductId INT ,
ColumnName VARCHAR(200),
ColumnValue VARCHAR(15))
INSERT INTO ProductsCustomColumns
SELECT 1, 'Licence_No', '1545'
UNION ALL
SELECT 1, 'Location ', 'Atlanta'
UNION ALL
SELECT 2, 'Qty ', '5'
UNION ALL
SELECT 3, 'Gross', '80000'
Now your Dynamic Code goes here
DECLARE #COLUMN_PCC VARCHAR(MAX)='', #PRODUCT_ID INT=1,#USER_ID INT=3, #QRY VARCHAR(MAX) ;
--preparing Custom Column Name List with comma ','
SELECT #COLUMN_PCC = #COLUMN_PCC+ [COLUMNNAME] +',' FROM ProductsCustomColumns
WHERE ProductId= #PRODUCT_ID
SELECT #COLUMN_PCC =SUBSTRING(#COLUMN_PCC,1,LEN(#COLUMN_PCC)-1)
--Preparing Dynamic Query
SELECT #QRY =' SELECT P.*, AV.* FROM Products P
INNER JOIN
(
SELECT * FROM (
SELECT * FROM ProductsCustomColumns WHERE ProductId= '+CAST(#PRODUCT_ID AS VARCHAR(50))+'
)
AS A
PIVOT
(
MAX (COLUMNVALUE)
FOR [COLUMNNAME] IN ('+#COLUMN_PCC +')
)AS PVT
)AS AV ON P.ProductId= AV.ProductId
AND P.UserId='++CAST(#USER_ID AS VARCHAR(50))+'
'
EXEC ( #QRY)
And the Result will be
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| ProductId | Name | Price | DateS | UserId | ProductId | Licence_No | Location |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| 1 | Oil Product | 2000.00 | 2016-12-09 18:06:24.090 | 3 | 1 | 1545 | Atlanta |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
You need dynamic sql no other way to do this
DECLARE #sql VARCHAR(max),
#cust_col VARCHAR(max)
SET #cust_col = (SELECT Quotename(CustomColumns) + ','
FROM ProductsCustomColumns
FOR xml path(''))
SELECT #cust_col = LEFT(#cust_col, Len(#cust_col) - 1)
SET #sql = 'SELECT *, ' + #cust_col + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#sql)
In general it is a very bad idea to add custom data in additional columns of your main table. Just imagine 100 customers using this. All of them have differing table schemas and you wnat to write an update script for all of them?
It is a pain in the neck, if you have to deal with result sets where you don't know the structure in advance!
You have several choices:
Add one column of type XML. The advantage: The resultset is fix. You just need a customer specific rule, how to interpret the XML. You can solve this with an inline table valued function. Pass in the XML and get a derived table back. Call this with CROSS APPLY and you are out...
Add a new table with the customerID and Key-Value-Pairs
If the additional data is not completely different, add some of the columns to your main table as SPARSE columns

Append "_Repeat" to Ambiguous column names

I have a query that joins a table back onto itself in order to display orders that generated a repeat within a certain window.
The table returns something like the following:
id | value | note | id | value | note
------------------------------------------------------
01 | abcde | .... | 03 | zyxxx | ....
06 | 12345 | .... | 09 | 54321 | ....
In actuality, the table returns over 150 columns, so when the join occurs, I end up with 300 columns. I end up having to manually rename 150 columns to "id_Repeat","value_Repeat","note_Repeat" etc...
I'm looking for some way of automatically appending "_Repeat" to the ambiguous columns. Is this possible in T-SQL, (Using SQL Server 2008) or will I have to manually map out each column using:
SELECT [value] AS [value_Repeat]
The only way I can see this working is to construct some dynamic SQL (ugh!). I put together a quick example of how this might work:
CREATE TABLE test1 (id INT, note VARCHAR(50));
CREATE TABLE test2 (id INT, note VARCHAR(20));
INSERT INTO test1 SELECT 1, 'hello';
INSERT INTO test2 SELECT 1, 'world';
DECLARE #SQL VARCHAR(4096);
SELECT #SQL = 'SELECT ';
SELECT #SQL = #SQL + t.name + '.' + c.name + CASE WHEN t.name LIKE '%test2%' THEN ' AS ' + c.name + '_repeat' ELSE '' END + ','
FROM sys.columns c INNER JOIN sys.tables t ON t.object_id = c.object_id WHERE t.name IN ('test1', 'test2');
SELECT #SQL = LEFT(#SQL, LEN(#SQL) - 1);
SELECT #SQL = #SQL + ' FROM test1 INNER JOIN test2 ON test1.id = test2.id;';
EXEC(#SQL);
SELECT #SQL;
DROP TABLE test1;
DROP TABLE test2;
Output is:
id note id_repeat note_repeat
1 hello 1 world
This isn't possible in T-SQL. A column will have the name it had in its source table, or any alias name you specify, but there is no way to systematically rename them.
For cases like this, it pays off to take it one level higher: write some code (using sys.columns) that generates the query you're after, including renames. Why do something manually for 150 columns when you have a computer at your disposal?

SQL Maybe Pivot? But I need actual value not number

I want to put all the column names where it's "Lock" or "Editable" into "Visible by" field for each field name. There are hundred of columns, so I am looking an automated way to do it.
EG:
Field Name visible by Business Development VC Panel Admin
Certification_Complete__c Hidden Lock Editable
Certification_Status__c Hidden Hidden Editable
To:
Field Name visible by Business Development VC Panel Admin
Certification_Complete__c **VC Panel,Admin** Hidden Lock Editable
Certification_Status__c **Admin** Hidden Hidden Editable
Many thanks
Your table structure is part of the problem to get this result. Ideally, you should consider restructuring your table into a more normalized structure.
You could alter the structure to something similar to this:
CREATE TABLE certs
(
[cert_id] int,
[FieldName] varchar(25)
);
INSERT INTO certs ([cert_id], [FieldName])
VALUES (1, 'Certification_Complete__c'),
(2, 'Certification_Status__c');
CREATE TABLE permissions
(
[permission_id] int,
[permissionName] varchar(20)
);
INSERT INTO permissions ([permission_id], [permissionName])
VALUES (1, 'Business Development'),
(2, 'VC Panel'),
(3, 'Admin');
CREATE TABLE certs_permissions
(
[cert_id] int,
[permission_id] int,
[permission_type] varchar(8)
);
INSERT INTO certs_permissions ([cert_id], [permission_id], [permission_type])
VALUES
(1, 1, 'Hidden'),
(1, 2, 'Lock'),
(1, 3, 'Editable'),
(2, 1, 'Hidden'),
(2, 2, 'Hidden'),
(2, 3, 'Editable');
Then you could get the result by joining the tables and using the PIVOT function to convert the rows into columns. The code would be similar to this:
;with cte as
(
select c.cert_id, c.fieldname, p.permissionName,
cp.permission_type
from certs c
inner join certs_permissions cp
on c.cert_id = cp.cert_id
inner join permissions p
on cp.permission_id = p.permission_id
)
select fieldname,
visibleby,
[Business Development],
[VC Panel],
[Admin]
from
(
select c1.fieldname,
STUFF(
(SELECT ', ' + cast(t.permissionName as varchar(50))
FROM cte t
where c1.fieldname = t.fieldname
and t.permission_type in ('Lock', 'Editable')
FOR XML PATH (''))
, 1, 1, '') AS [visibleby],
c1.permissionname,
c1.permission_type
from cte c1
) d
pivot
(
max(permission_type)
for permissionname in ([Business Development], [VC Panel],
[Admin])
) piv
See SQL Fiddle with Demo. This gives a result:
| FIELDNAME | VISIBLEBY | BUSINESS DEVELOPMENT | VC PANEL | ADMIN |
---------------------------------------------------------------------------------------------
| Certification_Status__c | Admin | Hidden | Hidden | Editable |
| Certification_Complete__c | VC Panel, Admin | Hidden | Lock | Editable |
You could alter the script above to easily implement dynamic SQL to generate the query string to execute if you don't want to hard-code all of the column values.
If you cannot fix your database structure, then I would suggest a two-step process. Use dynamic SQL to normalize your current database structure so you can easily get the comma-separated list of visible_by values. When you use the UNPIVOT function, you can throw the data into a temporary table that can be used to get the comma-separated list. The code would be similar to this:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #colsUnpivot = STUFF((SELECT distinct ','+ quotename(c.column_name)
from INFORMATION_SCHEMA.COLUMNS as C
where (TABLE_NAME = 'yt') and
c.column_name not in ('Field Name', 'visible by')
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= 'select [Field Name], [visible by], colList, value
into temp
from yt
unpivot
(
value
for colList in ('+ #colsunpivot +')
) u'
exec(#query);
select distinct
t.[field name],
STUFF(
(SELECT ', ' + cast(t2.colList as varchar(50))
FROM temp t2
where t.[Field Name] = t2.[Field Name]
and t2.value in ('Lock', 'Editable')
FOR XML PATH (''))
, 1, 1, '') AS [visible by],
t.[Business Development],
t.[VC Panel],
t.[Admin]
from yt t;
drop table temp;
See SQL Fiddle with Demo

SQL Server 2005 - Pivoting Data without a sum / count and dynamic list of values

Sorry if this is covered elsewhere in another question, but I can't find any examples which exactly match what I need and I'm getting confused.
I have data in the table like this :-
Name | Value
---------------
John | Dog
John | Cat
John | Fish
Bob | Python
Bob | Camel
And I would like the data like this....
Name | Value_1 | Value_2 | Value_3
-------------------------------------
John | Dog | Cat | Fish
Bob | Python | Camel | NULL
I don't want to use case statements because the dog, cat, fish etc there are 20+ possible values and they could change overtime.
Anyone got any pointers?
This comes close to what you're after. i've also included a table creation script and sample data for others to use. Inspiration for this solution came from here
-- Dynamic PIVOT
DECLARE #T AS TABLE(y INT NOT NULL PRIMARY KEY);
DECLARE
#cols AS NVARCHAR(MAX),
#y AS INT,
#sql AS NVARCHAR(MAX);
-- Construct the column list for the IN clause
-- e.g., [Dog],[Python]
SET #cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT DISTINCT [Value] AS y FROM dbo.table_1) AS Y
ORDER BY y
FOR XML PATH('')),
1, 1, N'');
-- Construct the full T-SQL statement
-- and execute dynamically
SET #sql = N'SELECT *
FROM (SELECT *
FROM dbo.table_1) AS D
PIVOT(MIN(value) FOR value IN(' + #cols + N')) AS P;';
PRINT #sql
EXEC sp_executesql #sql;
GO
Table creation:
CREATE TABLE [dbo].[Table_1](
[Name] [varchar](50) COLLATE Latin1_General_CI_AS NULL,
[Value] [varchar](50) COLLATE Latin1_General_CI_AS NULL
) ON [PRIMARY]
GO
Sample data:
insert into dbo.table_1 values ('John','Dog')
insert into dbo.table_1 values ('John','Cat')
insert into dbo.table_1 values ('John','Fish')
insert into dbo.table_1 values ('Bob ','Python')
insert into dbo.table_1 values ('Bob ','Camel')
This question is somewhat similar:
Cross Tab - Storing different dates (Meeting1, Meeting2, Meeting 3 etc) in the same column
Take a look at this :
http://dotnetgalactics.wordpress.com/2009/10/23/using-sql-server-20052008-pivot-on-unknown-number-of-columns-dynamic-pivot/
Might be helpful ..