Using UNPIVOT in constructed String Query Dynamic - sql-server

I am building a query where I will need a UNPIVOT on dynamic columns. (abcd are example string name)
data1 data2 com fr random
1 2 a d sq
3 4 b a fd
UNPIVOT like so :
data1 data2 Name Website random
1 2 a com sq
1 2 d fr sq
3 4 b com fd
3 4 a fr fd
The matter here is for building my First table I use dynamic SQL (#QueryFinal) because of the column.
Here is my UNPIVOT dynamic Query
'SELECT data1, data2, Name, Website
FROM '+#QueryFinal+'
UNPIVOT (
Name FOR Website in ('+#WebsiteCol+')
) f;'
In my #QueryFinal I have a WHERE .... ORDER BY, it seems like the UNPIVOT can't handle it.
When I delete the WHERE and ORDER BY clause I get the error :
Incorrect syntax near the keyword 'UNPIVOT'.

Try the following dynamic-pivot:
--drop table if exists unpivottest
create table unpivotTest (data1 int, data2 int, com char(1), fr char(1))
insert into unpivotTest
select 1, 2, 'a' , 'd' union all
select 3, 4, 'b', 'a'
select * from unpivotTest
declare #colsunpivot as nvarchar(max),
#query as nvarchar(max)
select #colsunpivot = stuff((select ','+ quotename(c.name)
from sys.columns c
where c.object_id = object_id('dbo.unpivottest') and c.name not like '%data%'
for xml path('')), 1, 1, '')
set #query
= 'select data1, data2, name, website
from unpivottest
-- you cannot do the ordering here
unpivot
(
name
for website in ('+ #colsunpivot +')
) u
where data1 = 1 -- here you can use your where clause
order by data1' -- here you can do the ordering by any col
--print #query
exec sp_executesql #query;
Check a working demo here.

Even if it isn't the same column name as in the example here is the final Query built thanks for the help. I put the WHERE clause in the first SELECT and the ORDER BY in the UNPIVOT
DECLARE #QueryFinal VARCHAR(max) = #Query + #QueryBis + '
from #CALENDAR_FINAL temp
LEFT JOIN #BURSTS b on b.bur_id = temp.bur_id
LEFT JOIN digital_calendar_status dcs ON dcs.dcs_date = temp.[Date] AND dcs.dif_id = '+convert(varchar(2),#FormatId)+'
LEFT JOIN digital_calendar_event dce ON dce.dce_date = temp.[Date]
WHERE '+#ConditionConflict+#ConditionIcon+#ConditionDate
DECLARE #Pivot VARCHAR(2000)='
SELECT
DateStr, f.Conflict, f.dcs_id, f.Status, f.Website, f.Advertiser, f.Comment, f.EventName, f.EventIcone
FROM ('+#QueryFinal+') AS a
UNPIVOT
(
Advertiser
FOR Website IN ('+#WebsiteCol+')
) f
ORDER BY Date;
'

Related

What is SQL Query (T-SQL) which counts transaction status for particular Name against all categorical statuses available?

I am trying to get a table that looks like:
Columns : [District] [Name] [Status1] [Status2] [Status3]
Data: DistrictA MrChan 1 1 1
Data: DistrictB MrFoo 1 0 2
Data: DistrictB MsLucy 0 1 0
(sorry the table turns out unexpected after posting)
select StatusID, StatusCode from
BookingStatus retrieves all categorical statuses Status1, Status2, Status3
select userid, DistrictA, StatusID from
UnitBooking retrieve multiple rows which represent booking transactions.
In example above Ms Lucy has done 1 booking she would have 1 row in UnitBooking. Mr Foo have 3 rows and Mr Chan also have 3 rows.
Example Data:
select [userid], [username], [District], [StatusID] from UnitBooking
[1],[MrChan],[DistrictA],[1]
[1],[MrChan],[DistrictA],[2]
[1],[MrChan],[DistrictA],[3]
[2],[MrFoo],[DistrictB],[1]
[2],[MrFoo],[DistrictB],[3]
[2],[MrFoo],[DistrictB],[3]
[3],[MsLucy],[DistrictB],[2]
select [StatusID], [StatusCode] from BookingStatus
[1],[Status1]
[2],[Status2]
[3],[Status3]
What is the T-SQL that produces the result set?
Thanks a lot
select
district,
username,
sum(case when bs.statusid=1 then 1 else 0 end) 'status1',
sum(case when bs.statusid=2 then 1 else 0 end) 'status2',
sum(case when bs.statusid=3 then 1 else 0 end) 'status13'
from
unitbooking ub
join
BookingStatus Bs
on bs.statusid=ub.statusid
group by district,username
I have come up with an SQL that works for my report but I am not good at what the STUFF function and XML PATH part do, except it works. Here goes:
DECLARE #cols AS NVARCHAR(MAX),#query AS NVARCHAR(MAX)
SELECT #cols = STUFF(( SELECT ', SUM(CASE WHEN ub.BookingStatus='
+ Cast(ItemID as nvarchar(128))
+ ' THEN 1 ELSE 0 END) '
+ QUOTENAME(StatusCode)
FROM customtable_BookingStatus
--WHERE ItemID Not In (1)
GROUP BY StatusCode
,ItemID
ORDER BY ItemID
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SET #query = 'SELECT usr.FullName, usi.District, '
+'cnt.[OPEN] as [DUALLANGUAGE], cnt.BOOKED, cnt.CONFIRMED, cnt.SOLD from customtable__User usr
left join customtable_Public_User_Info usi on usi.UserID = usr.UserID
inner join '
+'(SELECT ub.UserID,' + #cols
+'from customtable_UnitBooking ub
join customtable_BookingStatus bs on bs.ItemID = ub.BookingStatus
group by ub.UserId, ub.BookingStatus) '
+'cnt on cnt.UserID = usr.UserID'
--Print #query
EXEC sp_executesql #query;
Here's some value added conveniences:
-Support new entries in BookingStatus table dynamically. Still need to explicitly state the name of new column in final #query.
-Technically the names of new column could also be dynamic inside #query by adding an additional STUFF function with similar signature that retrieves plain comma separated columns. Here's the snippet:
DECLARE #colsForQuery AS NVARCHAR(MAX)
SELECT #colsForQuery = STUFF(( SELECT ',cnt.' + QUOTENAME(StatusCode)
FROM customtable_BookingStatus
--WHERE ItemID Not In (1)
GROUP BY StatusCode
,ItemID
ORDER BY ItemID
FOR XML PATH('')
,TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
produces this:
cnt.[OPEN],cnt.[BOOKED],cnt.[SOLD],cnt.[CONFIRMED]
-My report has dual language but not easy if all columns are entirely dynamic
-Omit columns using 'WHERE ItemID Not In (1)' or remove it in #query
-Watch out for columns that clashes with T-SQL keyword, example I must encase [OPEN] because its keyword
Overall I think TheGameiswar is correct answer. I am just extending his solution based on the URL suggested inside comments.

Display multiple values from two tables in one row in SQL Server

I have the following two tables
TableA Table B
id bid bname btitle
---- ------------------------------
1 1 john titlejohn
2 1 william titlewilliam
3 1 george titlegeorge
2 bill titlebill
3 kyle titlekyle
3 seb titleseb
I need a query in SQL Server which displays the following output:
id name title
1 john,william,george titlejohn,titlewilliam,titlegeorgw
2 bill titlebill
3 kyle,seb titlekyle,titleseb
Please help.
select id, name = stuff(n.name, 1, 1, ''), title = stuff(t.title, 1, 1, '')
from TableA a
outer apply
(
select ',' + bname
from TableB x
where x.bid = a.id
for xml path('')
) n (name)
outer apply
(
select ',' + btitle
from TableB x
where x.bid = a.id
for xml path('')
) t (title)
Here's one solution. It only handles bname but you can extend it to handle btitle. Concatenating column values for a given key is not a natural thing in SQL so you need a trick to loop through the table extracting each row with same key. The trick is to create a memory table with an identity column (n say) which autoincrements on each insert. You can loop through then, picking n=1, then n=2, etc to build up the string.
create function tbl_join_name( #id int)
returns varchar(max)
as
begin
declare #tbl table (n int identity(1,1), name varchar(max), title varchar(max))
insert #tbl( name, title )
select bname, btitle from TableB where bid = #id
declare #n int = 1, #name varchar(max) = '', #count int = (select count(*) from #tbl)
while #n <= #count begin
set #name = #name + (case #name when '' then '' else ',' end) + (select name from #tbl where n = #n)
set #n = #n + 1
end
return #name
end
go
select id, tbl_join_name(id) as bname --, tbl_join_title(id) as btitle
from TableA
It's not very efficient, though. Tested with Sql Server 2008 R2.
Another way:
SELECT A.id,
STUFF((SELECT ','+bname
FROM TableB B
WHERE B.bid = A.id
FOR XML PATH('')),1,1,'') as name,
STUFF((SELECT ','+btitle
FROM TableB B
WHERE B.bid = A.id
FOR XML PATH('')),1,1,'') as title
FROM TableA A
Output:
id name title
1 john,william,george titlejohn,titlewilliam,titlegeorge
2 bill titlebill
3 kyle,seb titlekyle,titleseb

SQL Pivot table without aggregate

I have a number of text files that are in a format similar to what is shown below.
ENTRY,1,000000,Widget 4000,1,,,2,,
FIELD,Type,A
FIELD,Component,Widget 4000
FIELD,Vendor,Acme
ENTRY,2,000000,PRODUCT XYZ,1,,,3,
FIELD,Type,B
FIELD,ItemAssembly,ABCD
FIELD,Component,Product XYZ - 123
FIELD,Description1,Product
FIELD,Description2,XYZ-123
FIELD,Description3,Alternate Part #440
FIELD,Vendor,Contoso
They have been imported into a table with VARCHAR(MAX) as the only field. Each ENTRY is a "new" item, and all the subsequent FIELD rows are properties of that item. The data next to the FIELD is the column name of the property. The data to the right of the property is the data I want to display.
The desired output would be:
ENTRY Type Component Vendor ItemAssembly Description1
1,000000,Widget 4000 A Widget 4000 Acme
2,000000,Product XYZ B Product XYZ-123 Contoso ABCD Product
I've got the column names using the code below (there are several tables that I have UNIONed together to list all the property names).
select #cols =
STUFF (
(select Distinct ', ' + QUOTENAME(ColName) from
(SELECT
SUBSTRING(ltrim(textFileData),CHARINDEX(',', textFileData, 1)+1,CHARINDEX(',', textFileData, CHARINDEX(',', textFileData, 1)+1)- CHARINDEX(',', textFileData, 1)-1) as ColName
FROM [MyDatabase].[dbo].[MyTextFile]
where
(LEFT(textFileData,7) LIKE #c)
UNION
....
) A
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
Is a Pivot table the best way to do this? No aggregation is needed. Is there a better way to accomplish this? I want to list out data next to the FIELD name in a column format.
Thanks!
Here is the solution in SQL fiddle:
http://sqlfiddle.com/#!3/8f0b0/8
Prepare raw data in format (entry, field, value), use dynamic SQL to make pivot on unknown column count.
MAX() for string is enough to simulate "without aggregate" behavior in this case.
create table t(data varchar(max))
insert into t values('ENTRY,1,000000,Widget 4000,1,,,2,,')
insert into t values('FIELD,Type,A')
insert into t values('FIELD,Component,Widget 4000')
insert into t values('FIELD,Vendor,Acme ')
insert into t values('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
insert into t values('FIELD,Type,B')
insert into t values('FIELD,ItemAssembly,ABCD')
insert into t values('FIELD,Component,Product XYZ - 123')
insert into t values('FIELD,Description1,Product ')
insert into t values('FIELD,Description2,XYZ-123 ')
insert into t values('FIELD,Description3,Alternate Part #440')
insert into t values('FIELD,Vendor,Contoso');
create type preparedtype as table (entry varchar(max), field varchar(max), value varchar(max))
declare #prepared preparedtype
;with identified as
(
select
row_number ( ) over (order by (select 1)) as id,
substring(data, 1, charindex(',', data) - 1) as type,
substring(data, charindex(',', data) + 1, len(data)) as data
from t
)
, tree as
(
select
id,
(select max(id)
from identified
where type = 'ENTRY'
and id <= i.id) as parentid,
type,
data
from identified as i
)
, pivotsrc as
(
select
p.data as entry,
substring(c.data, 1, charindex(',', c.data) - 1) as field,
substring(c.data, charindex(',', c.data) + 1, len(c.data)) as value
from tree as p
inner join tree as c on c.parentid = p.id
where p.id = p.parentid
and c.parentid <> c.id
)
insert into #prepared
select * from pivotsrc
declare #dynamicPivotQuery as nvarchar(max)
declare #columnName as nvarchar(max)
select #columnName = ISNULL(#ColumnName + ',','')
+ QUOTENAME(field)
from (select distinct field from #prepared) AS fields
set #dynamicPivotQuery = N'select * from #prepared
pivot (max(value) for field in (' + #columnName + ')) as result'
exec sp_executesql #DynamicPivotQuery, N'#prepared preparedtype readonly', #prepared
Here your are, this comes back exactly as you need it. I love tricky SQL :-). This is a real ad-hoc singel-statement call.
DECLARE #tbl TABLE(OneCol VARCHAR(MAX));
INSERT INTO #tbl
VALUES('ENTRY,1,000000,Widget 4000,1,,,2,,')
,('FIELD,Type,A')
,('FIELD,Component,Widget 4000')
,('FIELD,Vendor,Acme ')
,('ENTRY,2,000000,PRODUCT XYZ,1,,,3,')
,('FIELD,Type,B')
,('FIELD,ItemAssembly,ABCD')
,('FIELD,Component,Product XYZ - 123')
,('FIELD,Description1,Product ')
,('FIELD,Description2,XYZ-123 ')
,('FIELD,Description3,Alternate Part #440')
,('FIELD,Vendor,Contoso');
WITH OneColumn AS
(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS inx
,CAST('<root><r>' + REPLACE(OneCol,',','</r><r>') + '</r></root>' AS XML) AS Split
FROM #tbl AS tbl
)
,AsParts AS
(
SELECT inx
,Each.part.value('/root[1]/r[1]','varchar(max)') AS Part1
,Each.part.value('/root[1]/r[2]','varchar(max)') AS Part2
,Each.part.value('/root[1]/r[3]','varchar(max)') AS Part3
,Each.part.value('/root[1]/r[4]','varchar(max)') AS Part4
,Each.part.value('/root[1]/r[5]','varchar(max)') AS Part5
FROM OneColumn
CROSS APPLY Split.nodes('/root') AS Each(part)
)
,TheEntries AS
(
SELECT DISTINCT *
FROM AsParts
WHERE Part1='ENTRY'
)
SELECT TheEntries.Part2 + ',' + TheEntries.Part3 + ',' + TheEntries.Part4 AS [ENTRY]
,MyFields.AsXML.value('(fields[1]/field[Part2="Type"])[1]/Part3[1]','varchar(max)') AS [Type]
,MyFields.AsXML.value('(fields[1]/field[Part2="Component"])[1]/Part3[1]','varchar(max)') AS Component
,MyFields.AsXML.value('(fields[1]/field[Part2="Vendor"])[1]/Part3[1]','varchar(max)') AS Vendor
,MyFields.AsXML.value('(fields[1]/field[Part2="ItemAssembly"])[1]/Part3[1]','varchar(max)') AS ItemAssembly
,MyFields.AsXML.value('(fields[1]/field[Part2="Description1"])[1]/Part3[1]','varchar(max)') AS Description1
FROM TheEntries
CROSS APPLY
(
SELECT *
FROM AsParts AS ap
WHERE ap.Part1='FIELD' AND ap.inx>TheEntries.inx
AND ap.inx < ISNULL((SELECT TOP 1 nextEntry.inx FROM TheEntries AS nextEntry WHERE nextEntry.inx>TheEntries.inx ORDER BY nextEntry.inx DESC),10000000)
ORDER BY ap.inx
FOR XML PATH('field'), ROOT('fields'),TYPE
) AS MyFields(AsXML)

SQL Server Concatenate Query Results

I am new to sql server.
I have a query that retrieves the desired data from various tables.
The result looks like this with other columns, that contain things like name, removed for simplicity.
id xvalue
1 x
1 y
1 z
2 x
2 y
2 z
3 x
3 y
3 z
I would like to wrap the query with a select to concatenate the result set into an new result set like this.
id xvalue
1 x,y,z
2 x,y,z
3 x,y,z
I have tried to figure out how use the for xml path option and cannot seem to find the
correct syntax.
We have two tables in SQL one with columns (id,mdf,hist) another table with columns (id,hist). First table called historial, second table is resultado. Then we need to concat hist column when the mfn is the same and copy the result to the column hist in resultado table.
DECLARE #iterator INT
SET #iterator = 1
WHILE (#iterator < 100) /* Number of rows */
BEGIN
DECLARE #ListaIncidencias VARCHAR(MAX) = ''
SELECT #ListaIncidencias = #ListaIncidencias + ';' + p.hist
FROM historial p WHERE mfn = #iterator
SET #ListaIncidencias = SUBSTRING(#ListaIncidencias,2,LEN(#ListaIncidencias))
SELECT #ListaIncidencias as 'Incidencias'
INSERT INTO resultado(hist) VALUES(#ListaIncidencias) /* Insert results in new table */
SET #iterator = #iterator + 1
END
Try this
CREATE TABLE #Tmp
(
id INT ,
xValue VARCHAR(10)
)
INSERT INTO #Tmp VALUES ( 1, 'x' )
INSERT INTO #Tmp VALUES ( 1, 'y' )
INSERT INTO #Tmp VALUES ( 1, 'Z' )
INSERT INTO #Tmp VALUES ( 2, 'A' )
INSERT INTO #Tmp VALUES ( 2, 'B' )
SELECT id ,
( STUFF(( SELECT DISTINCT
',' + CAST(xValue AS VARCHAR(20))
FROM #Tmp
WHERE id = t.id
FOR
XML PATH('')
), 1, 1, '') ) AS Data
FROM #Tmp t
GROUP BY id
Something like this should do the trick for you. SQL Server should really just have an easy syntax for this. But it'll give you a comma and space separated list of the xValues.
SELECT id
,STUFF(
(SELECT DISTINCT
', ' + t.xvalue
FROM tableName t
WHERE t.id = t1.id
ORDER BY ', ' + t.xvalue
FOR XML PATH(''), TYPE
).value('.','varchar(max)')
,1,2, ''
) AS xvalue
FROM tableName t1
GROUP BY id
select id,Ids=Stuff((SELECT ',' + xvalue FROM t t1 WHERE t1.id=t.id
FOR XML PATH (''))
, 1, 1, '' )
from t
GROUP BY id
FIDDLE
There is very new functionality in Azure SQL Database and SQL Server 2016 to handle this exact scenario. Example:
select id, STRING_AGG(xvalue, ',') as xvalue
from some_table
group by id
STRING_AGG - https://msdn.microsoft.com/en-us/library/mt790580.aspx

Inserting a dynamically built 'for xml' statement into a table or variable

I've got a situation where I'm trying to get a list of unfilled fields from a temp table into a comma separated statement.
So given the example data (which will always be a single row, and probably in a temp table (as the actual data will come from a multitude of source tables)):
Field1 Field2 Field3 Field4
'aaa' null '' null
And the mapping table of
FieldName Question Section
'Field1' 'Q1' 'Sec1'
'Field2' 'Q2' 'Sec1'
'Field3' 'Q3' 'Sec2'
'Field4' 'Q4' 'Sec2'
I would like the following result:
Section UnansweredQs
'Sec1' 'Q2'
'Sec2' 'Q3, Q4'
I've got as far as the comma separated list of questions by doing:
create table #testData (f1 varchar(50), f2 int, f3 varchar(50), f4 varchar(50))
create table #qlist (fieldName varchar(5), question varchar(3), section varchar(5))
insert into #qlist values ('f1', 'q1', 'sec1'), ('f2', 'q2', 'sec1'), ('f3', 'q3', 'sec2'), ('f4', 'q4', 'sec2')
insert into #testData values ('asda', null, '', null)
Then
declare #usql nvarchar(max) = ''
declare #sql nvarchar(max)
declare #xml xml
--build a gargantuan set of union statements, comparing the column value to null/'' and putting q# if it is
set #usql =
(
select 'select case when ' + c.name + ' is null or ' + c.Name + ' = '''' then ''' + q.question + ', '' else '''' end from #testData union '
from tempdb..syscolumns c
inner join #qlist q
on c.name = q.fieldName
where c.id = object_id('tempdb..#testData')
for xml path('')
);
--remove the last 'union', append for xml path to pivot the rows into a single column of concatenated rows
set #usql = left(#usql, len(#usql) - 6) + ' for xml path('''')'
print #usql
--remove final comma
--get the position of the last comma in the select statment (ie after the final unanswered question)
declare #lastComma int = charindex(',', reverse(#usql))
--add the bit before the last comma, and the bit after the last comma but skip the actual comma :)
set #usql = left(#usql, len(#usql) - #lastComma) + right(#usql, #lastComma - 2)
exec (#usql)
With this I get
XML_F52E2B61-18A1-11d1-B105-00805F49916B
----------------------------------------
q2, q3, q4
But I can't get that result set into another table or variable (via insert into #tmpresult exec (#usql) approach).
Usually with the Msg 1086, Level 15, State 1, Line 1
The FOR XML clause is invalid in views, inline functions, derived tables, and subqueries when they contain a set operator. To work around, wrap the SELECT containing a set operator using derived table syntax and apply FOR XML on top of it. error.
I've tried various things, wrapping, removing the unions, CTE's but can't get it to work.
I have a query for you:
with cte as (
select
N.Name
from Table1
cross apply (values
('Field1', Field1),
('Field2', Field2),
('Field3', Field3),
('Field4', Field4)
) as N(Name,Value)
where N.Value is null or N.Value = ''
)
select distinct
T2.Section,
stuff(
(
select ', ' + TT.Question
from Table2 as TT
inner join cte as c on c.Name = TT.FieldName
where TT.Section = T2.Section
for xml path(''), type
).value('.', 'nvarchar(max)')
, 1, 2, '') as UnansweredQs
from Table2 as T2
you can turn it into dynamic by yourself :)
sql fiddle demo
There is no need to use dynamic SQL to do this.
declare #X xml
set #X = (
select *
from #testData
for xml path('root'), elements xsinil, type
)
select section,
(
select ', '+Q2.question
from #qlist as Q2
where Q1.section = Q2.section and
#X.exist('/root/*[local-name() = sql:column("Q2.fieldName")][. = ""]') = 1
for xml path(''), type
).value('substring(text()[1], 2)', 'varchar(max)') as UnansweredQs
from #qlist as Q1
group by Q1.section
SQL Fiddle

Resources