What is the meaning of SELECT ... FOR XML PATH(' '),1,1)? - sql

I am learning sql in one of the question and here I saw usage of this,can some body make me understand what xml path('') mean in sql? and yes,i browsed through web pages I didn't understand it quite well!
I am not getting the Stuff behind,now what does this piece of code do ?(only select part)
declare #t table
(
Id int,
Name varchar(10)
)
insert into #t
select 1,'a' union all
select 1,'b' union all
select 2,'c' union all
select 2,'d'
select ID,
stuff(
(
select ','+ [Name] from #t where Id = t.Id for XML path('')
),1,1,'')
from (select distinct ID from #t )t

There's no real technique to learn here. It's just a cute trick to concatenate multiple rows of data into a single string. It's more a quirky use of a feature than an intended use of the XML formatting feature.
SELECT ',' + ColumnName ... FOR XML PATH('')
generates a set of comma separated values, based on combining multiple rows of data from the ColumnName column. It will produce a value like ,abc,def,ghi,jkl.
STUFF(...,1,1,'')
Is then used to remove the leading comma that the previous trick generated, see STUFF for details about its parameters.
(Strangely, a lot of people tend to refer to this method of generating a comma separated set of values as "the STUFF method" despite the STUFF only being responsible for a final bit of trimming)

SQL you were referencing is used for string concatenation in MSSQL.
It concatenates rows by prepending , using for xml path to result
,a,b,c,d. Then using stuff it replaces first , for , thus removing it.
The ('') in for xml path is used to remove wrapper node, that is being automatically created. Otherwise it would look like <row>,a,b,c,d</row>.
...
stuff(
(
select ',' + CAST(t2.Value as varchar(10)) from #t t2 where t1.id = t2.id
for xml path('')
)
,1,1,'') as Value
...
more on stuff
more on for xml path

Related

Remove duplicates when converting multiple rows into CSV using SQL

I am creating a view where I need to pull all the rows of one column and convert into CSV format.
SELECT
SUBSTRING((SELECT ',' + CAST(s.marketConfigId AS varchar(MAX))
FROM [Metric].[MetricGoalDefMarket] s
WHERE [metricGoalDefId]=21
ORDER BY s.marketConfigId
FOR XML PATH('')),2,200000) AS marketConfigID
Using above query can create a CSV format but it is also displaying duplicates. When you run the above query it is displaying the output as
**marketConfigID**
751,751,742,751,751,784,1850,737
How can I remove duplicates?
PS: As I am creating a view, I don't want to use functions as I see using dbo.DistinctList from here can remove duplicates
It seems distinct would work :
SELECT SUBSTRING((SELECT DISTINCT ',' + CAST(s.marketConfigId AS varchar(MAX))
FROM [Metric].[MetricGoalDefMarket] s
WHERE [metricGoalDefId]=21
ORDER BY s.marketConfigId
FOR XML PATH('')
), 2,200000) AS marketConfigID
You don't need to use substring() you can use stuff() instead :
SELECT STUFF ( (SELECT DISTINCT ',' + CAST(s.marketConfigId AS varchar(MAX))
FROM [Metric].[MetricGoalDefMarket] s
WHERE [metricGoalDefId]=21
FOR XML PATH('')
), 1, 1, ''
) AS marketConfigID
Have you tried DISTINCT?
Edit: Apologies think I misread your query. I think you want to fetch the marketConfigIds using a sub-select query (applying DISTINCT), rather than at the outer level like I did below
SELECT DISTINCT
SUBSTRING((SELECT ',' + CAST(s.marketConfigId AS varchar(MAX))
FROM [Metric].[MetricGoalDefMarket] s
WHERE [metricGoalDefId]=21
ORDER BY s.marketConfigId
FOR XML PATH('')),2,200000) AS marketConfigID

SQL for concatenating strings/rows into one string/row? (How to use FOR XML PATH with INSERT?)

I am concatenating several rows/strings in an table (on Microsoft SQL Server 2010) into a string by using a method as suggested here:
SELECT ',' + col FROM t1 FOR XML PATH('')
However, if I try to insert the resulting string as (single) row into another table like so:
INSERT INTO t2
SELECT ', ' + col FROM t1 FOR XML PATH('')
I receive this error message:
The FOR XML clause is not allowed in a INSERT statement.
t2 currently has a single column of type NVARCHAR(80). How can I overcome this problem, i.e. how can I collapse a table t1 with many rows into a table t2 with row that concatenates all the strings from t1 (with commas)?
Rather than xml path why not do it like this?
DECLARE #Cols VARCHAR(8000)
SELECT #Cols = COALESCE(#Cols + ', ', '') +
ISNULL(col, 'N/A')
FROM t1
Insert into t2 values(#Cols);
You need to cast it back to an nvarchar() before inserting. I use this method, deletes the first separator as well and as I'm doing the , type part, it handles entities correctly.
insert into t2
select stuff((
select ', ' + col from t1
for xml path(''), type
).value('.', 'nvarchar(80)'), 1, 2, '')
So you concatenate all col with prepending comma+space as an xml-object. Then you take the .value() of child with xquery-path . which means "take the child we are at, don't traverse anywhere". You cast it as an nvarchar(80) and replace a substring starting at position 1 and length 2 with an empty string ''. So the 2 should be replaced with however long your separator is.

SQL Server: how to remove last comma after combining rows using XML Path

I found a way to combine multiple row's into one row which is comma separated but now I would like to remove the last comma.
CREATE TABLE supportContacts
(
id int identity primary key,
type varchar(20),
details varchar(30)
);
INSERT INTO supportContacts (type, details)
VALUES ('Email', 'admin#sqlfiddle.com'),
('Twitter', '#sqlfiddle');
This query combines types, but I want to now remove the last comma:
SELECT top (2)
type + ', ' AS 'data()'
FROM
supportContacts
ORDER BY
type DESC
FOR XML PATH('')
This is the current result:
Twitter, Email,
While you already have an answer, another common idiom that you'll see is:
select stuff((
SELECT top (2)
', ' type AS 'data()'
FROM
supportContacts
ORDER BY
type DESC
FOR XML PATH('')
), 1, 2, '')
This says "take the result of the select and replace the two characters starting at position 1 with a zero-length string".
This works for me->
1.Inserting comma Before Data
2.Using Stuff to Remove it
select (stuff((
SELECT ', '+ Name AS 'data()'
FROM Table_1
FOR XML PATH('')),
Count('ID')
, 1, ' '))as Result
declare #BigStrRes8K nvarchar(4000)
SELECT #BigStrRes8K = ( SELECT top (2) [type] + ', ' AS 'data()'
FROM supportContacts
ORDER BY type DESC
FOR XML PATH('') )
SELECT LEFT(RTRIM(#BigStrRes8K), ( LEN(RTRIM(#BigStrRes8K))) - 1) as FinalNoComma
I would never do this where I controlled the render code. I would teach the caller to handle the trailing comma. Also you have to allow for nulls and the 4K or 8K limit of SQL rows

Splitting a variable length column in SQL server safely

I have a column (varchar400) in the following form in an SQL table :
Info
UserID=1123456,ItemID=6685642
The column is created via our point of sale application, and so I cannot do the normal thing of simply splitting it into two columns as this would cause an obscene amount of work. My problem is that this column is used to store attributes of products in our database, and so while I am only concerned with UserID and ItemID, there may be superfluous information stored here, for example :
Info
IrrelevantID=666,UserID=123124,AnotherIrrelevantID=1232342,ItemID=1213124.
What I want to retrieve is simply two columns, with no error given if neither of these attributes exists in the Info column. :
UserID ItemID
123124 1213124
Would it be possible to do this effectively, with error checking, given that the length of the IDs are all variable, but all of the attributes are comma-separated and follow a uniform style (i.e "UserID=number").
Can anyone tell me the best way of dealing with my problem ?
Thanks a lot.
Try this
declare #infotable table (info varchar(4000))
insert into #infotable
select 'IrrelevantID=666,UserID=123124,AnotherIrrelevantID=1232342,ItemID=1213124.'
union all
select 'UserID=1123456,ItemID=6685642'
-- convert info column to xml type
; with cte as
(
select cast('<info ' + REPLACE(REPLACE(REPLACE(info,',', '" '),'=','="'),'.','') + '" />' as XML) info,
ROW_NUMBER() over (order by info) id
from #infotable
)
select userId, ItemId from
(
select T.N.value('local-name(.)', 'varchar(max)') as Name,
T.N.value('.', 'varchar(max)') as Value, id
from cte cross apply info.nodes('//#*') as T(N)
) v
pivot (max(value) for Name in ([UserID], [ItemId])) p
SQL DEMO
You can try this split function: http://www.sommarskog.se/arrays-in-sql-2005.html
Assuming ItemID=1213124. is terminated with a dot.
Declare #t Table (a varchar(400))
insert into #t values ('IrrelevantID=666,UserID=123124,AnotherIrrelevantID=1232342,ItemID=1213124.')
insert into #t values ('IrrelevantID=333,UserID=222222,AnotherIrrelevantID=0,ItemID=111.')
Select
STUFF(
Stuff(a,1,CHARINDEX(',UserID=',a) + Len(',UserID=')-1 ,'' )
,CharIndex
(',',
Stuff(a,1,CHARINDEX(',UserID=',a) + Len(',UserID=')-1 ,'' )
)
,400,'') as UserID
,
STUFF(
Stuff(a,1,CHARINDEX(',ItemID=',a) + Len(',ItemID=')-1 ,'' )
,CharIndex
('.',
Stuff(a,1,CHARINDEX(',ItemID=',a) + Len(',ItemID=')-1,'' )
)
,400,'') as ItemID
from #t

SQL Server query with multiple values in one column relating to another column

Situation: This table holds the relation information between a Documents table and an Users table. Certain Users need to review or approve documents (Type). I would like to have it to where I could get all of the reviewers on one line if needed. So if three users review Document 1, then a row would have 346, 394, 519 as the value, since those are the reviewers
Table: xDocumentsUsers
DocID..UserID....Type...
1........386......approver
1........346......reviewer
1........394......reviewer..
1........519......reviewer..
4........408......reviewer..
5........408......reviewer..
6........408......reviewer..
7........386......approver..
7........111......readdone..
7........346......reviewer..
8........386......approver..
8........346......reviewer..
9........386......approver..
9........346......reviewer..
10.......386......approver..
11.......386......approver..
11......346......reviewer..
12......386......approver..
12......346......reviewer..
13......386......approver..
13......346......reviewer..
14......386......approver..
14......346......reviewer..
15......386......approver
So desired result would be...
DocID..UserID................Type...
1........386....................approver
1........346,394,519......reviewer.
4........408....................reviewer..
5........408....................reviewer..
6........408....................reviewer..
7........386....................approver..
7........111....................readdone..
7........346....................reviewer..
8........386....................approver..
8........346....................reviewer..
9........386....................approver..
9........346....................reviewer..
10......386....................approver..
11......386....................approver..
11......346....................reviewer..
12......386....................approver..
12......346....................reviewer..
13......386....................approver..
13......346....................reviewer..
14......386....................approver..
14......346....................reviewer..
15......386....................approver
The FOR XML PATH is a great solution. You need to be aware, though, that it will convert any special characters in the inner SELECTs result set into their xml equivalent - i.e., & will become & in the XML result set. You can easily revert back to the original character by using the REPLACE function around the inner result set. To borrow from astander's previous example, it would look like (note that the SELECT as the 1st argument to the REPLACE function is enclosed in ():
--Concat
SELECT t.ID,
REPLACE((SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')), '&', '&'))
FROM #Table t
GROUP BY t.ID
Have a look at
Emulating MySQL’s GROUP_CONCAT() Function in SQL Server 2005
Is there a way to create a SQL Server function to “join” multiple rows from a subquery into a single delimited field?
A simple example is
DECLARE #Table TABLE(
ID INT,
Val VARCHAR(50)
)
INSERT INTO #Table (ID,Val) SELECT 1, 'A'
INSERT INTO #Table (ID,Val) SELECT 1, 'B'
INSERT INTO #Table (ID,Val) SELECT 1, 'C'
INSERT INTO #Table (ID,Val) SELECT 2, 'B'
INSERT INTO #Table (ID,Val) SELECT 2, 'C'
--Concat
SELECT t.ID,
(
SELECT tIn.Val + ','
FROM #Table tIn
WHERE tIn.ID = t.ID
FOR XML PATH('')
)
FROM #Table t
GROUP BY t.ID
Does this help?
SELECT DocID
, [Type]
, (SELECT CAST(UserID + ', ' AS VARCHAR(MAX))
FROM [xDocumentsUsers]
WHERE (UserID = x1.UserID)
FOR XML PATH ('')
) AS [UserIDs]
FROM [xDocumentsUsers] AS x1