How to combine two rows with comma separator in sql server 2008 - sql

I tried as shown below
SELECT SUBSTRING(
(
SELECT td.Text + ', ' AS 'data()',
Tda.FirmID
FROM tblData1 tda
INNER JOIN tblData2 Td
ON Tda.ID = Td.ID
GROUP BY
Tda.Enable1,
Tda.ID,
Td.Text
HAVING ISNULL(Enable1, 0) = 1
FOR XML PATH('')
),
1,
10000
) AS Csv
Output:
Landlord Tenant, <FirmID>1</FirmID>NJ Traffic, <FirmID>1</FirmID>
Expected Output: Should be in table format
csv FirmID
Landlord Tenant, NJ Traffic 1

I got the results by using COALESCE
DECLARE #Text VARCHAR(MAX)
DECLARE #ID NUMERIC(18,0)
SELECT #Text = COALESCE(#Text + ', ', '') +
CAST(td.Text AS VARCHAR(MAX)),#ID = MAX(tda.FirmID)
FROM tblData1 tda
INNER JOIN tblData2 Td
ON Tda.ID=Td.ID
GROUP BY
Tda.Enable,
Tda.FirmID,Td.Text
HAVING ISNULL(Enable,0)=1
SELECT #Text AS Text,#ID AS ID
OutPut:
text ID
Landlord Tenant, NJ Traffic 1

Leave out the aliases in the subquery and try this:
SELECT STUFF((SELECT ', ' + td.Text
FROM tblData2 Td
WHERE ISNULL(Enable1, 0) = 1
FOR XML PATH('')
), 1, 2, '') AS Csv,
MAX(Tda.FirmID)
FROM tblData1 tda;
You cannot select multiple columns in the subquery either. After all, the purpose is to combine values into a single column. Multiple values in the select create one column, but with multiple XML tags within the string.
You also do not need the group by in the subquery.

Related

How to split multiple strings and insert SQL Server FN_SplitStr

I have 2 strings and one integer:
#categoryID int = 163,
#Ids nvarchar(2000) = '1,2,3',
#Names nvarchar(2000) = 'Bob,Joe,Alex'
I need to select 3 columns 3 rows; The most accomplished is 3 rows 2 columns:
select #categoryID,items from FN_SplitStr(#Ids,',')
resulting:
163,1
163,2
163,3
But I can't figure out how to split both strings.
I tried many ways like:
select #categoryID,items from FN_SplitStr((#Ids,#Names),',')
select #categoryID,items from FN_SplitStr(#Ids,','),items from FN_SplitStr(#Names,',')
EXPECTED OUTPUT:
163,1,Bob
163,2,Joe
163,3,Alex
NOTE1: I looked over tens of questions the most similar is:
How to split string and insert values into table in SQL Server AND SQL Server : split multiple strings into one row each but this question is different.
NOTE2: FN_SplitStr is a function for spliting strings in SQL. And I'm trying to create a stored procedure.
Based on your expected output, you have to use cross apply twice and then create some sort of ranking to make sure that you are getting the right value. As IDs and Names don't seem to have any relationship cross apply will create multiple rows (when you split the string to Names and ID)
There might be better way but this also gives your expected output. You can change this string split to your local function.
1st Dense rank is to make sure that we get three unique names and 2nd dense rank is the rank within the name based on order by with ID and outside of the sub query you have to do some comparison to get only 3 rows.
Declare #categoryID int = 163,
#Ids nvarchar(2000) = '1,2,3',
#Names nvarchar(2000) = 'Bob,Joe,Alex'
select ConcatenatedValue, CategoryID, IDs, Names from (
select concat(#categoryID,',',a.value,',',b.value) ConcatenatedValue, #categoryID CategoryID,
A.value as IDs, b.value as Names , DENSE_RANK() over (order by b.value) as Rn,
DENSE_RANK() over (partition by b.value order by a.value) as Ranked
from string_split(#IDs,',') a
cross apply string_split(#names,',') B ) t
where Rn - Ranked = 0
Output:
Inside your stored procedure do a string split of #Ids and insert into #temp1 table with an identity(1,1) column rowed. You will get:
163,1,1
163,2,2
163,3,3
Then do the second string split of #Names and insert into #temp2 table with an identity(1,1) column rowed. You will get:
Bob,1
Joe,2
Alex,3
You can then do an inner join with #temp1 and #temp2 on #temp1.rowid = #temp2.rowid and get:
163,1,Bob
163,2,Joe
163,3,Alex
I hope this solves your problem.
You can do this with a recursive CTE:
with cte as (
select #categoryId as categoryId,
convert(varchar(max), left(#ids, charindex(',', #ids + ',') - 1)) as id,
convert(varchar(max), left(#names, charindex(',', #names + ',') - 1)) as name,
convert(varchar(max), stuff(#ids, 1, charindex(',', #ids + ','), '')) as rest_ids,
convert(varchar(max), stuff(#names, 1, charindex(',', #names + ','), '')) as rest_names
union all
select categoryId,
convert(varchar(max), left(rest_ids, charindex(',', rest_ids + ',') - 1)) as id,
convert(varchar(max), left(rest_names, charindex(',', rest_names + ',') - 1)) as name,
convert(varchar(max), stuff(rest_ids, 1, charindex(',', rest_ids + ','), '')) as rest_ids,
convert(varchar(max), stuff(rest_names, 1, charindex(',', rest_names + ','), '')) as rest_names
from cte
where rest_ids <> ''
)
select categoryid, id, name
from cte;
Here is a db<>fiddle.
You need to split CSV value with record number. For that you need to use ROW_NUMBER() function to generate record wise unique ID as column like "RID", while you split CSV columns in row.
You can use table value split function or XML as used below.
Please check this let us know your solution is found or not.
DECLARE
#categoryID int = 163,
#Ids nvarchar(2000) = '1,2,3',
#Names nvarchar(2000) = 'Bob,Joe,Alex'
SELECT
#categoryID AS categoryID,
q.Id,
w.Names
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY f.value('.','VARCHAR(10)')) AS RID,
f.value('.','VARCHAR(10)') AS Id
FROM
(
SELECT
CAST('<a>' + REPLACE(#Ids,',','</a><a>') + '</a>' AS XML) AS idXML
) x
CROSS APPLY x.idXML.nodes('a') AS e(f)
) q
INNER JOIN
(
SELECT
ROW_NUMBER() OVER (ORDER BY h.value('.','VARCHAR(10)')) AS RID,
h.value('.','VARCHAR(10)') AS Names
FROM
(
SELECT
CAST('<a>' + REPLACE(#Names,',','</a><a>') + '</a>' AS XML) AS namesXML
) y
CROSS APPLY y.namesXML.nodes('a') AS g(h)
) w ON w.RID = q.RID

Concatenate column values based on type result

Declare #name nvarchar(max),#Id int
SELECT #Id=[EmpType],#name =ISNULL(#name + ',','')+[UserName] FROM [dbo].[TestTable]
Group by [EmpType]
SELECT #Id,#name
Getting error with this code, How can i get the result employee type wise concatenated usernames
Expecting Result set
You can try below using STUFF() function
SELECT [EmpType], abc = STUFF(
(SELECT ',' + [UserName]
FROM [dbo].TestTable] t1
WHERE t1.[EmpType] = t2.[EmpType]
FOR XML PATH (''))
, 1, 1, '') from [dbo].TestTable] t2
group by [EmpType];

SQL Query using inner join

CategoryTable
Code Name
1 Food
2 Non-Food
Existing Table Consists list of category, as for example, I have two only Food and Non-Food
As challenge, I am assigning tenants with category or categories (multiple assignment, as there are tenants which are categorized as food and non-food). I i used to insert Tenant and Code to a new table creating this output
TenantAssignTable
Tenant Code
Tenant1 1,2
Tenant2 1
What I need to do, is to load the TenantAssingTable to gridview consisting the Name of the CategoryCode too like this
Desired Output
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
I used inner join in my code, but this is limited as I have a string of combined code in Code column.
Select a.tenant, a.ccode, b.name
from TenantAssignTable a inner join CategoryTable b
on a.CCode = b.code
Is there anyway to achieve this kind of output? I know that this is unusual in SQL coding but this is what is challenge as what the desired output is concerned and needs which is to have a multiple assignment of category to a single tenant.
Thanks in advance!
Think simple;
You can with LIKE and XML PATH
DECLARE #CategoryTable TABLE (Code VARCHAR(50), Name VARCHAR(50))
INSERT INTO #CategoryTable
VALUES
('1', 'Food'),
('2', 'Non-Food')
DECLARE #TenantAssignTable TABLE (Tenant VARCHAR(50), Code VARCHAR(50))
INSERT INTO #TenantAssignTable
VALUES
('Tenant1', '1,2'),
('Tenant2', '1')
SELECT
T.Tenant ,
T.Code,
STUFF(
(SELECT
',' + C.Name
FROM
#CategoryTable C
WHERE
',' + REPLACE(T.Code, ' ', '') + ',' LIKE '%,' + C.Code + ',%'
FOR XML PATH('')
), 1, 1, '') A
FROM
#TenantAssignTable T
Result:
Tenant Code A
--------------- ------------ ---------------
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
You can use some XML transformations:
DECLARE #x xml
SELECT #x = (
SELECT CAST('<t name="'+a.tenant +'"><a>'+REPLACE(a.code,',','</a><a>') +'</a></t>' as xml)
FROM TenantAssignTable a
FOR XML PATH('')
)
;WITH cte AS (
SELECT t.v.value('../#name','nvarchar(max)') as Tenant,
t.v.value('.','int') as CCode,
ct.Name
FROM #x.nodes('/t/a') as t(v)
INNER JOIN CategoryTable ct
ON ct.Code = t.v.value('.','int')
)
SELECT DISTINCT
c.Tenant,
STUFF((SELECT ','+CAST(CCode as nvarchar(10))
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as CCode,
STUFF((SELECT ','+Name
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as Name
FROM cte c
Output:
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
The first part (defining #x variable) will bring your table to this kind of XML:
<t name="Tenant1">
<a>1</a>
<a>2</a>
</t>
<t name="Tenant2">
<a>1</a>
</t>
Then in CTE part we join XML with table of categories. And after all get data from CTE with the help of FOR XML PATH.
Create Function as below which return Table from separated Value
CREATE FUNCTION [dbo].[fnSplit]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
Create Function as below which return comma separated Name
CREATE FUNCTION [dbo].[GetCommaSeperatedCategory]
(
#Codes VARCHAR(50)
)
RETURNS VARCHAR(5000)
AS
BEGIN
-- Declare the return variable here
DECLARE #Categories VARCHAR(5000)
SELECT #Categories= STUFF
(
(SELECT ',' + convert(varchar(10), Name, 120)
FROM Category
WHERE Code IN (SELECT Id FROM [dbo].[fnSplit] (#Codes,',') )
ORDER BY Code
FOR XML PATH (''))
, 1, 1, '')
RETURN #Categories
END
AND Last:
SELECT
Tenant,
Code,
(SELECT [dbo].[GetCommaSeperatedCategory] (Code)) AS Name
FROM TblTenant

sql Id concatenation in sequence in a separate column like running total

I need running Id concatenation just like running balance or total..
Concatenate the previous Ids to current Id row wise just like shown in picture
query is
with relation (Id, [orderSequence])
as
(
select Id,cast(Id as varchar(20))
from [ACChartofAccount]
union all
select p.Id, cast(Cast(r.Id as varchar) + ',' + cast(p.Id as varchar) as varchar(20))
from [ACChartofAccount] p
inner join relation r on p.ParentId = r.Id
)
select Id,orderSequence
from relation
order by orderSequence
You can use below query to get above result.
DECLARE #Table TABLE(ID VARCHAR(10));
INSERT INTO #table(ID) VALUES ('320'),(332),(333),(334),(335);
SELECT mt.ID,
STUFF((
SELECT ', ' + ID
FROM #table t
WHERE t.ID <= mt.ID
FOR XML PATH('')), 1, 2, '') AS oldersequence
FROM #table mt
ORDER BY ID

How to generate an update query of a dynamic query (automatically)?

I'm storing some queries in a table column so I can execute them later passing some parameters.
But it has been really annoying to format the query into an Update sentence, because of the special characters.
For Example:
SELECT * FROM MOUNTAINS WHERE MON_NAME='PALMA' AND MON_DESC LIKE '%TRANVULCANIA%'
Then I need the string just for the udpate query:
UPDATE QUERIES
SET QUE_SEL='SELECT * FROM MOUNTAINS WHERE MON_NAME='''+'PALMA'+''' AND MON_DESC LIKE '''+'%TRANVULCANIA%'+''' '
WHERE QUE_ID=1
as you can see the first ' must be replaced for '''+' but the next door ' must be replaced by '+'''
This is the query I'm working on:
DECLARE #QUERY VARCHAR(MAX)
SELECT #QUERY='SELECT * FROM QUERIES WHERE QUE_NOMBRE='''+'PRUEBA 1'+''' '
SELECT
t.r.value('.', 'varchar(255)') AS token
, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS id
FROM (
SELECT myxml = CAST('<t>' + REPLACE(#QUERY, '''', '</t><t>''</t><t>') + '</t>' AS XML)
) p
CROSS APPLY myxml.nodes('/t') t(r)
this is the result:
token id
-------------------------------------------------- --------------------
SELECT * FROM QUERIES WHERE QUE_NOMBRE= 1
' 2
PRUEBA 1 3
' 4
5
Now I want a column that tell me when to open and when to close and then I can set the final replace.
Adapting the solution given by #rivarolle
DECLARE #QUERY VARCHAR(MAX)
DECLARE #FORMATTED varchar(max)
SELECT #QUERY='SELECT * FROM QUERIES WHERE QUE_NOMBRE='''+'PRUEBA 1'+''''
;WITH TOKENS AS(
SELECT
t.r.value('.', 'varchar(MAX)') AS token
, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS Id
FROM (
SELECT myxml = CAST('<t>' + REPLACE(#QUERY, '''', '</t><t>''</t><t>') + '</t>' AS XML)
) p
CROSS APPLY myxml.nodes('/t') t(r)
)
,
Tokens2 as (
SELECT
TOKENS.token as token
,quotes.row%2 as tipoapostrofe
from Tokens
left join (select row_number() over( order by Id asc) as row, a.* FROM (SELECT * from Tokens) a where Token = '''') quotes
on quotes.Id = Tokens.Id
)
SELECT #FORMATTED = STUFF((
SELECT ' ' + REPLACE(token,'''',CASE tipoapostrofe WHEN 1 THEN '''''''+''' WHEN 0 THEN '''+''''''' ELSE '' END) AS [text()]
FROM Tokens2
FOR XML PATH('')
), 1, 1, '')
print #FORMATTED
This Works, just need a function for cleaning XML special characters and another for putting back, and the Dynamic queries are printed ready for an update.
I think its not necessary to replace an apostrophe with '''+' to open and '+''' to close, I made some probes and you can exec a query that you replace opening and closing apostrophes with the same.. for example '''+' for open and '''+' for close.
So the query would be:
DECLARE #QUERY VARCHAR(MAX)
DECLARE #FORMATTED varchar(max)
SELECT #QUERY='SELECT * FROM QUERIES WHERE QUE_NOMBRE='''+'PRUEBA 1'+''''
SELECT #FORMATTED= STUFF((
SELECT ' ' +
(SELECT
CASE
WHEN t.r.value('.', 'varchar(250)')='''' THEN REPLACE(t.r.value('.', 'varchar(250)'), '''','''''''+''')
ELSE t.r.value('.', 'varchar(250)')
END
) AS [text()]
-- , ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS id
FROM (
SELECT myxml = CAST('<t>' + REPLACE(#QUERY, '''', '</t><t>''</t><t>') + '</t>' AS XML)
) p
CROSS APPLY myxml.nodes('/t') t(r)
FOR XML PATH('')
), 1, 1, '')
SET #FORMATTED=REPLACE(#FORMATTED,' ','')
PRINT #FORMATTED
then I get:
SELECT * FROM QUERIES WHERE QUE_NOMBRE= '''+' PRUEBA 1 '''+'
then I copy into a variable and execute
DECLARE #VAR VARCHAR(500)
SET #VAR='SELECT * FROM QUERIES WHERE QUE_NOMBRE='''+'PRUEBA 1'''+' '
EXEC(#VAR)
It Works for very simple queries, but with longer and complicated queries it doesn't works..
Assuming your token table is Tokens(Token, Id, Position):
update Tokens
set position = quotes.row%2
from Tokens
left join (select row_number() over( order by Id asc) as row, a.* FROM (SELECT * from Tokens) a where Token = '''') quotes
on quotes.Id = Tokens.Id
The position column will have a value of 1 for starting quote and 0 for closing quote. NULL for the rest.