SQL - Combine Multiple Columns with Multiple Rows into one row - sql

What I'm trying to do:
I have records in a SQL table where there are 5 columns and thousands of rows.
The rows share duplicate data (i.e. account number) but what makes each unique is that data in one of the columns is different.
As an example:
col1|col2|col3|col4|col5
------------------------
123|abc|456|def|789
123|abc|456|def|date
But the columns can have different values, not necessarily always in column 5.
Here's what I started with:
SELECT TOP (15) stuff((
SELECT ', ' + te.[accountid]
,te.[char1]
,te.[date]
,te.[date2]
,te.[char2]
FROM D AS te
INNER JOIN D AS tue ON tue.[accountid] = te.[accountid]
WHERE tue.[accountid] = ue.[accountid]
FOR XML path('')
,type
).value('.', 'varchar(max)'), 1, 2, '') AS ifile
FROM D AS ue
GROUP BY ue.[accountid]
But I get a monster long string that includes the duplicate rows in one column. I'm not sure what else to try so any insight would be appreciated.

If I had to guess, you have an unnecessary self join in the subquery:
SELECT TOP (15) stuff((
SELECT ', ' + te.[accountid], te.[char1], te.[date], te.[date2], te.[char2]
FROM D te
WHERE te.[accountid] = ue.[accountid]
FOR XML path(''), type
).value('.', 'varchar(max)'), 1, 2, '') AS ifile
FROM D ue
GROUP BY ue.[accountid];
You might also want SELECT DISTINCT in the subquery.

Use UNION to get rid of all the duplicate values and use your FOR XML PATH on the output to append it to a single string:
SELECT TOP (15) stuff((
SELECT ', ' + CAST(te.[accountid] AS varchar(255)) FROM D
UNION
SELECT ', ' + CAST(te.[char1] AS varchar(255)) FROM D
UNION
SELECT ', ' + CAST(te.[date] AS varchar(255)) FROM D
UNION
SELECT ', ' + CAST(te.[date2] AS varchar(255)) FROM D
UNION
SELECT ', ' + CAST(te.[char2] AS varchar(255)) FROM D
FOR XML path('')
,type
).value('.', 'varchar(max)'), 1, 2, '') AS ifile
Untested, treat as pseudo-code to give the general idea.

Related

Merge column value based on another column value - TSQL

I m using the below query to merge column Message based on column 'Customer_Name' from table Customers
SELECT
[Customer_Name],
STUFF((SELECT
', ' + LTRIM(RTRIM([Message]))
FROM [dbo].[Customers] t2
WHERE t2.[Customer_Name] = t1.[Customer_Name]
FOR XML PATH ('')), 1, 1, '')
FROM [dbo].[Customers] t1
GROUP BY [Customer_Name]
Using the above code, the Message are separated by , but i want a new line. i try to use CHAR(13)+CHAR(10) but i getting #x0D; and the merge column seems to be wrong.
Any idea on how to fix it will greatly appreciate.
Answer using #Larnu help and posts on comments
SELECT
[Customer_Name],
STUFF((SELECT
(CHAR(13) + CHAR(10)) + LTRIM(RTRIM([Message]))
FROM [Customers] t2
WHERE t2.[Customer_Name] = t1.[Customer_Name]
FOR XML PATH (''),TYPE
).value('(./text())[1]','varchar(MAX)'),1,2,'')
FROM [Customers] t1
GROUP BY [Customer_Name]
Your solution use xml serialization .
#x0D; is xml serialization of char(13)
If you use at least SQL Server 2017 you can use STRING_AGG function
SELECT
[Customer_Name],
STRING_AGG([Message],', ') as [Messages]
FROM [dbo].[Customers] t1
GROUP BY [Customer_Name]
otherwise you can add replace.
SELECT
[Customer_Name],
REPLACE(
STUFF((SELECT
', ' + LTRIM(RTRIM([Message]))
FROM [dbo].[Customers] t2
WHERE t2.[Customer_Name] = t1.[Customer_Name]
FOR XML PATH ('')), 1, 2, ''),
'#x0D;',
CHAR(13)) as [Messages]
FROM [dbo].[Customers] t1
GROUP BY [Customer_Name]
The correct method to prevent XML entitization is actually to generate it as the xml data type using the TYPE option, then pull it back out with .value
DECLARE #sep NVARCHAR(10) = ', '; -- or you can use CHAR(13)+CHAR(10)
SELECT
[Customer_Name],
STUFF(
(SELECT #sep + LTRIM(RTRIM([Message]))
FROM [dbo].[Customers] t2
WHERE t2.[Customer_Name] = t1.[Customer_Name]
FOR XML PATH (''), TYPE
).value('text()[1]','nvarchar(max)'), 1, LEN(#sep), '')
FROM [dbo].[Customers] t1
GROUP BY [Customer_Name]

Extracting Keywords from a string in SQL

I wanted to write a SQL query in SQL server that extracts certain keywords from a column holding string values. The keywords are sitting in another Table -- (KEYWORDS). Also in case, there are multiple keywords found in the same string, I want all the keywords found to be displayed.
Egs
KEYWORDS -- Tom, Doctor, coach, value
TEXT -- Hi coach, tom here
Final O/p:
**TEXT**
Hi coach, tom here
**KEYWORDS_EXTRACTED**
coach, tom
You can use string_split():
select t.*, k.keyword
from t cross apply
string_split(replace(text, ',', ' ')) s join
keywords k
on k.keyword = s.value;
declare #k table(thekeyword varchar(50));
insert into #k(thekeyword) values ('Tom'), ('Doctor') , ('coach'), ('value');
declare #t table(thetext varchar(1000));
insert into #t(thetext) values('Hi coach, tom here'), ('Tom visited the doctor'), ('minicoach or minibus?'), ('Tomas says hi')
select *
from #t as t
outer apply
(
select
stuff(
(select ',' + k.thekeyword
--string_agg(k.thekeyword, ',') as thekeywords
from #k as k
where ' ' + t.thetext + ' ' like '%[^A-Za-z]'+k.thekeyword+'[^A-Za-z]%' --adjust
for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '') as thekeywords
) as kw;

Combining SQL Server Stuff statements to get single rows

I have an interesting problem. I have two T-SQL STUFF statements. In this example I have used union. This displays the results in two rows.
What I’d like to do is to combine these results so that it returns a single row. I am using SQL Server 2008 R2. The solution should be able to copy with either stuff statement returning null.
Is there a way to achieve this?
Here is the code
SELECT
(SELECT
STUFF((SELECT (' ' + mod_orders.mod_no + '<br>')
FROM mod_serials
INNER JOIN mod_orders ON mod_serials.mod_id = mod_orders.mod_id
INNER JOIN mod_order_items ON mod_orders.mod_id = mod_order_items.mod_id AND mod_order_items.item_id = 170
WHERE
(mod_serials.serial_id = 62 AND
mod_serials.date_implemented IS NOT NULL)
ORDER BY mod_serials.serial_id
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'), 1, 1, ''))
UNION
SELECT
(SELECT
STUFF((SELECT (' ' + char_data_mv.char_value + '<br>')
FROM char_data_mv
WHERE (char_data_mv.object_id = 62 AND char_data_mv.char_id = 177)
ORDER BY char_data_mv.row_id
FOR XML PATH(''), TYPE ).value('.','nvarchar(max)'), 1, 1, '')) as [Impl]
Example data:
Row1 - 00001<br> 00005<br>
Row2 - PMB 12345<br>
Combined results would be:
00001<br> 00005<br> PMB 12345<br>
The UNION statement returns all of the rows from the first table, and then all of the rows from the second table together in one big table - so this is why you are getting 2 rows.
Instead, you can just add 2 strings together with the '+' operator. You also want to return a string if the other is null, and adding nulls begets nulls. So use the ISNULL function to give an empty string instead.
SELECT
ISNULL((SELECT STUFF
((SELECT
(' ' + mod_orders.mod_no + '<br>')
FROM mod_serials
INNER JOIN mod_orders ON mod_serials.mod_id = mod_orders.mod_id
INNER JOIN mod_order_items ON mod_orders.mod_id = mod_order_items.mod_id AND mod_order_items.item_id = 170
WHERE (mod_serials.serial_id = 62 AND mod_serials.date_implemented IS NOT NULL)
ORDER BY mod_serials.serial_id FOR XML PATH(''), TYPE ).value('.', 'nvarchar(max)'), 1, 1, '')),'')
+
ISNULL((SELECT STUFF
((SELECT
(' ' + char_data_mv.char_value + '<br>')
FROM char_data_mv
WHERE (char_data_mv.object_id = 62 AND char_data_mv.char_id = 177)
ORDER BY char_data_mv.row_id FOR XML PATH(''), TYPE ).value('.','nvarchar(max)'), 1, 1, '')),'') as [Impl]
Note that you need to remove bothe the UNION and also the first SELECT after it - which is creating the second table.

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

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.

concatinate all rows of a column into single value

I have table called Rule.
RuleId Name
1 A1
2 A2
3 A3
.
.
.
Now I want all the names as single result.
may be like #allnames = A1,A2,A3
Can somebody advise how to write query for this without using loops?
Thanks in advance...
Try this:
SELECT #allnames = STUFF((SELECT distinct ',' + Name
FROM table1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '');
SQL Fiddle Demo
DECLARE #names NVARCHAR(MAX)
SELECT #names = coalesce(#names + ',', '') + coalesce(Name, '')
FROM (SELECT distinct Name FROM Rule) x
print #names
Try this one -
DECLARE #temp TABLE ([RuleId] INT, Name CHAR(2))
INSERT INTO #temp([RuleId], Name)
VALUES
(1, 'A1'),
(2, 'A2'),
(3, 'A3')
DECLARE #all_names NVARCHAR(MAX)
SELECT #all_names = STUFF((
SELECT DISTINCT ',' + Name
FROM #temp
--ORDER BY Name
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
SELECT #all_names
Output -
---------------
A1,A2,A3