Need help pivoting some data - sql-server

I'm hoping someone can help me. I'm trying to pivot some data on SQL Server 2005 and can't quite get the results I'm looking for.
This is my current table schema:
| ProductCode | AttributeName | AttributeValue |
| 1 | AttributeA | 10 |
| 1 | AttributeB | 20 |
| 2 | AttributeA | 30 |
| 2 | AttributeB | 40 |
| 3 | AttributeA | 50 |
This is the results I'm trying to achieve:
| ProductCode | AttributeA | AttributeB |
| 1 | 10 | 20 |
| 2 | 30 | 40 |
| 3 | 50 | NULL |
I know that I can achieve this result with the following SQL:
SELECT DISTINCT ProductCode,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeA' AND ProductCode=a.ProductCode) AttributeA,
(SELECT AttributeValue
FROM attributes
WHERE ProductName = 'AttributeB' AND ProductCode=a.ProductCode) AttributeB,
FROM attributes a
Although that SQL does produce the result I'm looking for, it's obviously not dynamic (in reality, I not only have more Attribute Types, but different products have different sets of attributes) and it also scans the table 3 times. It's also a maintenance nightmare.
I tried using the PIVOT functionality of SQL Server, but with no luck.
Can anyone help?

create table #attributes (ProductCode int,
AttributeName varchar(20),
AttributeValue int)
insert into #attributes values (1, 'AttributeA', 10)
insert into #attributes values (1, 'AttributeB', 20)
insert into #attributes values (2, 'AttributeA', 30)
insert into #attributes values (2, 'AttributeB', 40)
insert into #attributes values (3, 'AttributeA', 50)
declare #attributes_columns nvarchar(max)
set #attributes_columns
= (
select ', [' + AttributeName + ']'
from
(
select distinct AttributeName as AttributeName
from #attributes
) t
order by t.AttributeName
for xml path('')
)
set #attributes_columns = stuff(#attributes_columns,1,2,'')
declare #sql nvarchar(max)
set #sql = N'
select ProductCode, <attributes_columns>
from
(select ProductCode, AttributeName, AttributeValue
from #attributes )p
pivot
(
sum(AttributeValue) for AttributeName in (<attributes_columns>)
) as pvt
'
set #sql = replace(#sql, '<attributes_columns>', #attributes_columns)
print #sql
exec sp_executesql #sql
drop table #attributes

Related

How to SQL PIVOT on two columns and with dynamic column names?

I have a key value pair set of rows to associate to a unique identifier (ApplicationId).
The data would look something like this:
| ApplicationId | Key | Value | Date |
| 123 | A | abc | 2020-3-1 14:00:01.000 |
| 123 | B | abd | 2020-3-1 14:00:02.000 |
| 123 | C | abe | 2020-3-1 14:00:03.000 |
| 124 | A | abf | 2020-3-1 14:01:00.000 |
| 124 | D | abg | 2020-3-1 14:01:01.000 |
The end result i'm looking for would be this:
| ApplicationId | A | A_Date | B | B_Date | C | C_Date | D | D_Date |
| 123 | abc | 2020-3-1 14:00:01.000 | abd | 2020-3-1 14:00:02.000 | abe | 2020-3-1 14:00:03.000 | NULL | NULL |
| 124 | abf | 2020-3-1 14:01:00.000 | NULL | NULL | NULL | NULL | abg | 2020-3-1 14:01:01.000 |
The Keys A,B,C,D are unknown so hard coding the column names isn't possible.
Here is something that works with one PIVOT
IF OBJECT_ID('tempdb.dbo.#_BLAH') IS NOT NULL DROP TABLE #_BLAH
SELECT et.[ApplicationId] et.[Key], et.[Value], et.[Date]
INTO #_BLAH
FROM ExampleTbl et
WHERE et.[Date] > DATEADD(dd, -1, GetDate())
DECLARE #_cols AS NVARCHAR(MAX)
DECLARE #_sql AS NVARCHAR(MAX)
SELECT
#_cols += QUOTENAME([Key]) + ','
FROM
#_BLAH
GROUP BY
[Key];
SET #_cols = STUFF((SELECT ',' + QUOTENAME(T.[Key])
FROM #_BLAH AS T
GROUP BY T.[Key]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'),1,1,'')
set #_sql = 'SELECT [ApplicationId], ' + #_cols + '
FROM ( SELECT * FROM #_BLAH) AS SRC
PIVOT ( MAX([Value]) FOR [Key] IN (' + #_cols + ') ) AS p';
EXEC(#_sql)
I've so far been unable to find an example or an article attempting to make a second dynamic column and adding in the value that relates the specific Key in my example.
My SQL above will accomplish creating the row i want except for the #_Date column i need.
Try this:
DROP TABLE IF EXISTS #DataSource;
DROP TABLE IF EXISTS #DataSourcePrepared;
CREATE TABLE #DataSource
(
[ApplicationId] INT
,[Key] CHAR(1)
,[Value] VARCHAR(12)
,[Date] DATETIME2(0)
);
INSERT INTO #DataSource ([ApplicationId], [Key], [Value], [Date])
VALUES (123, 'A', 'abc', '2020-3-1 14:00:01.000')
,(123, 'B', 'abd', '2020-3-1 14:00:02.000')
,(123, 'C', 'abe', '2020-3-1 14:00:03.000')
,(124, 'A', 'abf', '2020-3-1 14:01:00.000')
,(124, 'D', 'abg', '2020-3-1 14:01:01.000');
CREATE TABLE #DataSourcePrepared
(
[ApplicationId] INT
,[ColumnName] VARCHAR(32)
,[Value] VARCHAR(32)
)
INSERT INTO #DataSourcePrepared ([ApplicationId], [ColumnName], [Value])
SELECT [ApplicationId]
,[Key]
,[value]
FROM #DataSource
UNION ALL
SELECT [ApplicationId]
,[Key] + '_Date'
,CONVERT(VARCHAR(19), [Date], 121)
FROM #DataSource;
DECLARE #DymanimcTSQLSatement NVARCHAR(MAX)
,#DynamicColumns NVARCHAR(MAX);
SET #DynamicColumns = STUFF
(
(
SELECT ',' + QUOTENAME([ColumnName])
FROM #DataSourcePrepared
GROUP BY [ColumnName]
ORDER BY [ColumnName]
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1
,1
,''
);
SET #DymanimcTSQLSatement = N'
SELECT *
FROM #DataSourcePrepared
PIVOT
(
MAX([value]) FOR [ColumnName] IN (' + #DynamicColumns +')
) PVT;';
EXECUTE sp_executesql #DymanimcTSQLSatement;
You just need to prepare the data before the actual PIVOT. Also, note that I am ordering the columns when I am building the dynamic part by name. In your real case, you may want to change this to something complex.
you can try this
DECLARE #_cols AS NVARCHAR(MAX) =''
DECLARE #_sql AS NVARCHAR(MAX)
SELECT
#_cols +=','+ QUOTENAME([Key]) + ',' + QUOTENAME([Key]+'_Date')
FROM
(SELECT DISTINCT [Key] FROM ExampleTbl) T
SET #_cols = STUFF(#_cols,1,1,'')
set #_sql = 'SELECT * FROM (
SELECT ApplicationId, [Key], Value FROM ExampleTbl
UNION ALL
SELECT ApplicationId, [Key] + ''_Date'' AS [Key], CONVERT(VARCHAR(30), [Date],121 ) AS Value FROM ExampleTbl
) SRC
PIVOT (MAX(Value) FOR [Key] IN ('+#_cols +' )) AS PVT';
EXEC(#_sql)
Result:
ApplicationId A A_Date B B_Date C C_Date D D_Date
------------- ------- --------------------------- ---------- -------------------------- ------------ ------------------------- ------- -------------------------
123 abc 2020-03-01 14:00:01.000 abd 2020-03-01 14:00:02.000 abe 2020-03-01 14:00:03.000 NULL NULL
124 abf 2020-03-01 14:01:00.000 NULL NULL NULL NULL abg 2020-03-01 14:01:01.000

Merge multiple results to one Column

I would like to create a select statement to merge multiple results into one rot
I was able to realized what I want via PHP but i think it should be possible to work directly with Sql.
Notes
| ID | Name |
|----|------|
| 2 | Test |
| 3 | Test |
EditorAssignment
| UserID | NoteID |
|--------|--------|
| 1 | 2 |
| 2 | 2 |
UserList
| ID | username |
|----|----------|
| 1 | testuser |
| 2 | bUser |
| 3 | cUser |
I would like to have the following Result:
| NoteID | User |
|--------|-----------------|
| 2 | testuser, bUser |
| 3 | cUser |
How can i create a select like that in SQL2012?
Before SQL Server 2017 (which has STRING_AGG aggregate function for this), you need to use the famous FOR XML PATH correlated subquery:
SetUp:
DECLARE #Notes TABLE (ID INT, Name VARCHAR(10))
INSERT INTO #Notes (ID, Name)
VALUES (2, 'Test'), (3, 'Test')
DECLARE #EditorAssignment TABLE (UserID INT, NoteID INT)
INSERT INTO #EditorAssignment (UserID, NoteID)
VALUES (1, 2), (2, 2),
(3, 3) -- Added missing row here
DECLARE #UserList TABLE (ID INT, username VARCHAR(100))
INSERT INTO #UserList (ID, username)
VALUES (1, 'testuser'), (2, 'bUser'), (3, 'cUser')
Query:
SELECT
NoteID = N.ID,
[User] = STUFF(
(
SELECT
', ' + U.username
FROM
#EditorAssignment AS E
INNER JOIN #UserList AS U ON E.UserID = U.ID
WHERE
N.ID = E.NoteID -- Link the outmost Notes' note with the inner EditorAssignment (correlated subquery)
FOR XML
PATH ('') -- FOR XML PATH('') makes the SELECT return a string value, not a result set
),
1,
2,
'')
FROM
#Notes AS N
Results:
NoteID User
2 testuser, bUser
3 cUser
The subquery generates a string with each username for the related assignment for each note.
The STUFF function is used just to replace the first ', ', since the string is build with a leading comma and a space. This is why the parameters of the STUFF are 1 (first position of the string), 2 (amount of places to remove, the comma and the space), and '' (replacement for those characters).
I'm guessing that you have a row missing for the EditorAssignment table that holds the values 3, 3 on your example.

how to copy TreeView's rows from the same table and update with different ID column and copy the parent_id

I want copy the table and put different value on column Type= B and auto_increment id and copy the parent id
Table = Menu
Id | parent_id | order | section | name | url | type
100 | NULL | 7 | web | Tasks | ~/en/Tasks | A
102 | 100 | 1 | web | Pages | ~/en/Pages | A
103 | 100 | 4 | web | Category | ~/en/Category | A
104 | NULL | 3 | web | DLM | ~/en/DLM | A
105 | 104 | 6 | web | ONS | ~/en/ONS | A
106 | 104 | 2 | web | HBO | ~/en/HBO | A
107 | NULL | 7 | web | Tasks | ~/en/Tasks | B
108 | 107 | 1 | web | Pages | ~/en/Pages | B
109 | 107 | 4 | web | Category | ~/en/Category | B
110 | NULL | 3 | web | DLM | ~/en/DLM | B
111 | 110 | 6 | web | ONS | ~/en/ONS | B
112 | 110 | 2 | web | HBO | ~/en/HBO | B
This probably isn't the most efficient, but it gets the job done. It assumes that name is unique. I left out columns unnecessary to the example. Also, you can't put a variable into the identity clause, so that needs to be wrapped in an EXEC
IF OBJECT_ID (N'paths', N'U') IS NOT NULL
DROP TABLE paths
IF OBJECT_ID (N'new_paths', N'U') IS NOT NULL
DROP TABLE new_paths
CREATE TABLE paths (
id INT,
parent_id INT,
name NVARCHAR(20)
)
INSERT INTO dbo.paths
(id,parent_id,name)
VALUES
(100, NULL, 'Tasks'),
(102, 100, 'Pages'),
(103, 100, 'Category'),
(104, NULL, 'DLM'),
(105, 104, 'ONS'),
(106, 104, 'HBO')
DECLARE #start_value INT
SET #start_value = (SELECT MAX(id) FROM paths) + 1
DECLARE #sql nvarchar(1000)
SET #sql = N'
CREATE TABLE new_paths (
id INT IDENTITY(' + CAST(#start_value AS nvarchar) + ',1),
parent_id INT,
name NVARCHAR(20)
)
'
EXEC sp_executesql #stmt = #sql
INSERT INTO new_paths (parent_id,name)
SELECT Parent_id, name FROM dbo.paths
;WITH mappings AS (
SELECT n.*, p.id AS old_id
FROM new_paths n
INNER JOIN paths p
ON p.name = n.name
)
UPDATE n
SET n.parent_id = m.id
FROM new_paths n
INNER JOIN mappings m
ON m.old_id = n.parent_id
--SELECT * FROM new_paths
Please see below approach to resolve an issue, ask questions in the comments if something is unclear, I have added some explanation in code comments
EDITED, to manage GUID (as per comment)
-- declare table var
declare #table table ([Increment] int identity(1,1), Id uniqueidentifier, [parent_id] nvarchar(50), [order] int, [section] nvarchar(50), [name] nvarchar(50), [url] nvarchar(50), [type] nvarchar(50))
-- insert values into this table
insert into #table
select [Id],
[parent_id],
[order],
[section],
[name],
[url],
'B'
from your_table
where [type] = 'A'
-- loop your temp table
declare #max_temp int = (select max(Increment) from #table)
declare #curr int = 1
declare #parent_value uniqueidentifier = null
while (#curr <= #max_temp)
begin
-- do diffrent inserts depend on parent_id value
if (select parent_id from #table) = null
begin
-- set below var, it will be used in next insert where parent_id is not null
set #parent_value = (select Id from #table where Increment = #curr)
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
[parent_id],
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
else
begin
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
isnull(#parent_value, [parent_id]),
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
-- update current
set #curr = #curr + 1
end

SQL Server Transpose Rows into Columns using IDs as Column Names

I have a large file that has the following fields:
Table 1:
+---------+--------+-----------+
| User_Id | Key_Id | Value |
+---------+--------+-----------+
| 100 | 74 | 37 |
| 100 | 65 | Male |
| 100 | 279 | G235467 |
+---------+--------+-----------+
and I have another file that tells what each 'Key_Id' is called (they are column names) e.g.
Table 2:
+--------+------------------+
| Key_Id | Key |
+--------+------------------+
| 65 | Gender |
| 66 | Height |
| 74 | Age |
| 279 | ReferenceNo |
I want to create a table using the Key_Id names found in the Key column of table 2, transpose all of the values from table 1 into table 2, but also include the User_Id from table 1 as this relates to an individual.
PS. Table 2 has nearly 300 keys that would need turning into individual fields
So ultimately I would like a table that looks like this:
+---------+---------+--------+-------+--------------+--------+
| User_Id | Gender | Height | Age | ReferenceNo | etc |
+---------+---------+--------+-------+--------------+--------+
| 100 | Male | | 37 | G235467 | |
So that each User_Id is a row and that all the Keys are columns with their respective values
You can use a dynamic sql query as below.
Query
declare #sql as varchar(max);
select #sql = 'select t1.[User_Id], ' + stuff((select +
', max(case t2.[Key_Id] when ' + cast([Key_Id] as varchar(100)) +
' then t1.[Value] end) as [' + [Key] + '] '
from Table2
for xml path('')
), 1, 2, '') +
'from Table1 t1 left join Table2 t2 on t1.[Key_Id] = t2.[Key_Id] group by t1.[User_Id];'
exec(#sql);
Find a demo here
You need to get a coma-separated list of those 300 key names to be used in PIVOT/UNPIVOT operators in T-SQL like described here
https://learn.microsoft.com/en-us/sql/t-sql/queries/from-using-pivot-and-unpivot
you can use pivot as below:
Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in ([Gender], [Height], [Age], [ReferenceNo]) ) p
For dynamic list of keys you can use dynamic sql as below:
Declare #cols1 varchar(max)
Declare #query nvarchar(max)
Select #cols1 = stuff((select ','+QuoteName([Key]) from table2 group by [Key] for xml path('')),1,1,'')
Set #Query = 'Select * from (
Select u.UserId, k.[key], u.[Value] from table1 u
join table2 k on u.keyid = k.keyid ) a
pivot ( max([Value]) for [key] in (' + #cols1 + ') ) p '
Select #Query --Check the generated query and execute by uncommenting below query
--exec sp_executesql #Query

Scalar function occurring request timeout issue in SQL Server while using in stored procedure

This is salar fuction . I am using this in Stored procedure with muliple parameters. For small results it is good but for big data it gets time out and also taking a long execuition time. Please share some other alternate or some enhancements
ALTER FUNCTION [dbo].[FNGETMULIPLEASSIGNESS_NEW2]
(
#TIMELINEID INT,
#MILSTONEID INT,
#TASKID INT
)
RETURNS varchar(max)
AS
BEGIN
DECLARE #Assignees varchar(max)='', #isExists bit=0
if(#TASKID=0)
BEGIN
Select #Assignees = #Assignees+ FIRSTNAME +' ' + LASTNAME+', '
FROM CASETIMELINEPEOPLE
INNER JOIN USERDETAIL ON
CASETIMELINEPEOPLE.PEOPLEUSERID=USERDETAIL.USERID
WHERE (CASETIMELINEID= #TIMELINEID) AND
(TEMPLATEMILESTONEID=#MILSTONEID) AND
(TEMPLATETASKID is null) and CASETIMELINEPEOPLE.isdeleted=0
END
else
BEGIN
Select #Assignees = #Assignees+ FIRSTNAME +' ' + LASTNAME+','
FROM CASETIMELINEPEOPLE
INNER JOIN USERDETAIL ON
CASETIMELINEPEOPLE.PEOPLEUSERID=USERDETAIL.USERID
WHERE (CASETIMELINEID= #TIMELINEID) AND
(TEMPLATEMILESTONEID=#MILSTONEID) AND
(TEMPLATETASKID=#TASKID) and CASETIMELINEPEOPLE.isdeleted=0
END
SELECT #Assignees=SUBSTRING(#Assignees, 0,LEN(#Assignees))
RETURN #Assignees
END
Using an inline table valued function will improve performance.
Reference:
When is a SQL function not a function? "If it’s not inline, it’s rubbish." - Rob Farley
Inline Scalar Functions - Itzik Ben-Gan
Scalar functions, inlining, and performance: An entertaining title for a boring post - Adam Machanic
TSQL User-Defined Functions: Ten Questions You Were Too Shy To Ask - Robert Sheldon
Here is an inline table valued function version of your scalar function that uses the stuff() with select ... for xml path ('') method of string concatenation.:
create function dbo.fn_get_multiple_assigness_itvf (
#timelineid int
, #milstoneid int
, #taskid int
) returns table as return (
select Assignees = stuff((
select ',' + firstname + ' ' + lastname
from casetimelinepeople ctp
inner join userdetail ud
on ctp.peopleuserid=ud.userid
where casetimelineid = #timelineid
and templatemilestoneid = #milstoneid
and (templatetaskid = #taskid
or (#taskid = 0 and templatetaskid is null)
)
and ctp.isdeleted=0
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
)
go
rextester demo: http://rextester.com/UZTJS46485
test setup:
create table casetimelinepeople (
casetimelineid int
, peopleuserid int
, templatemilestoneid int
, templatetaskid int
, isdeleted bit not null default 0
);
insert into casetimelinepeople values
(1,1,1,null,0)
,(1,2,1,null,0)
,(1,3,1,null,0)
,(1,2,1,1,0)
,(1,3,1,1,0)
create table userdetail (
userid int not null
, firstname varchar(32) not null
, lastname varchar(32) not null);
insert into userdetail values
(1, 'Some', 'One')
,(2, 'Avinash', 'Raikwar')
,(3, 'Sql','Zim');
go
And querying the inline table valued function like so:
select *
from dbo.fn_get_multiple_assigness_itvf(1,1,0)
returns
+----------------------------------+
| Assignees |
+----------------------------------+
| Some One,Avinash Raikwar,Sql Zim |
+----------------------------------+
select *
from dbo.fn_get_multiple_assigness_itvf(1,1,1)
returns:
+-------------------------+
| Assignees |
+-------------------------+
| Avinash Raikwar,Sql Zim |
+-------------------------+
Using cross apply() to call the function for each row in a query:
select *
from casetimelinepeople ctp
cross apply dbo.fn_get_multiple_assigness_itvf(
ctp.casetimelineid
, ctp.templatemilestoneid
, ctp.templatetaskid
) x
returns:
+----------------+--------------+---------------------+----------------+-----------+----------------------------------+
| casetimelineid | peopleuserid | templatemilestoneid | templatetaskid | isdeleted | Assignees |
+----------------+--------------+---------------------+----------------+-----------+----------------------------------+
| 1 | 1 | 1 | NULL | False | Some One,Avinash Raikwar,Sql Zim |
| 1 | 2 | 1 | NULL | False | Some One,Avinash Raikwar,Sql Zim |
| 1 | 3 | 1 | NULL | False | Some One,Avinash Raikwar,Sql Zim |
| 1 | 2 | 1 | 1 | False | Avinash Raikwar,Sql Zim |
| 1 | 3 | 1 | 1 | False | Avinash Raikwar,Sql Zim |
+----------------+--------------+---------------------+----------------+-----------+----------------------------------+

Resources