How can I combine multiple rows into one during a Select? - sql

I'm joining a bunch of tables and then inserting that data into a table variable. I then SELECT those records from the table. The data looks like this:
As you can see from the example, the unique data is only in column 7 and 8. In this example, there's only two rows. But it can be an infinite number. So instead of sending a bunch of rows to the client and then sorting out the data, I want to do it in SQL and only send back one row.
Since all of the data is the same, except for two columns, I wanted to concatenate the data and separate them by commas. This will make the client side operations much easier.
So in the end, I'll have one row and Col7 and Col8 will look like this:
Any ideas on how to accomplish this task?

You could try using FOR XML:
SELECT STUFF((SELECT',' + Col7 FROM #test
FOR XML PATH('')), 1, 1, '' ) as col7

Assuming you want to collapse the entire table into a single row, and discard the data in columns like ID:
DECLARE #x TABLE(Col7 varchar(255), Col8 varchar(255));
INSERT #x SELECT 'foo','bar'
UNION ALL SELECT 'blat','splunge';
SELECT Col7 = STUFF((SELECT ',' + Col7 FROM #x FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'varchar(max)'), 1, 1, ''),
Col8 = STUFF((SELECT ',' + Col8 FROM #x FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'varchar(max)'), 1, 1, '');
Result:
Col7 Col8
-------- -----------
foo,blat bar,splunge
On SQL Server 2017+, it is much simpler:
SELECT Col7 = STRING_AGG(Col7, ','),
Col8 = STRING_AGG(Col8, ',')
FROM #x;

Related

Replicating VBA solution in SQL

I have a SQL query that provides me with the output in the 'Output Before' pic.
Column 5 if essentially doing a countif on column1 for items in column1, similar to Excel.
I would like to add some code/sub query, so that the output becomes like the 'Output After' pic.
Does anyone have any ideas how I can do this?
I can do it in excel with VBA but just cant get my head around how to do it in SQL.
Output Before
Output After
Since you are using SQL Server 2017 you can get what you want with STRING_AGG:
select column1, column2, string_agg(column3, '&') as column3, column4
from outputbefore
group by column1, column2, column4
You're looking to concatenate multiple rows into a single value. Your options depend on your version of SQL Server. In older versions (I think 2005+) you have to use a torturous XML procedure. Run this on your server and you'll see how it works, but I'll leave it to you to work out the query for your data:
SELECT STUFF(
(SELECT ', <' + name + '>'
FROM sys.databases
WHERE database_id > 4
ORDER BY name
FOR XML PATH('') ,
ROOT('MyString') ,
TYPE
).value('/MyString[1]', 'varchar(max)'), 1, 2, '') AS namelist;
As of SQL 2017 you can use the STRING_AGG function, explained here.
As I see this is a string concatenation issue. I used a replace to handle the character & in XML.
select a.col1, Col3=replace(stuff((SELECT '#' + b.col3 AS 'data()'
FROM OutputBefore) b
where b.Col1=a.Col1
FOR XML PATH('')),1,1,''),'#','&')
from (select distinct Col1 from OutputBefore) a;
As mentioned by forpas and Russell, as of SQL 2017 you're able to use STRING_AGG function.
For SQL 2008+
Refer back to this:
How Stuff and 'For Xml Path' work in Sql Server
In your case you want the delimiter to be '&' which will cause a problem with FOR XML PATH due to XML Special character. So you'll want to escape XML Special characters, example:
DECLARE #TableA TABLE (Col1 NVARCHAR(10), Col2 INT, Col3 NVARCHAR(10), Col4
NVARCHAR(10), Col5 INT)
INSERT INTO #TableA (Col1, Col2, Col3, Col4, Col5)
VALUES ('Dave' , 24 , 'house' , 'married' , 2)
, ('Dave' , 24 , 'car' , 'married' , 2)
, ('Bob' , 32 , 'House' , 'single' , 1)
, ('George' , 12 , 'house' , 'divorced' , 1)
SELECT
t2.Col1
, t2.Col2
, STUFF ( ( SELECT '&' + Col3 -- Adding '&' as delimited
FROM #TableA t1
WHERE t1.Col2 = t2.Col2
FOR XML PATH (''), TYPE
).value('.', 'VARCHAR(MAX)'),1,1,''-- To escape special characters
) AS Col3
, t2.Col4
FROM #TableA AS t2
GROUP BY t2.Col1
, t2.Col2
, t2.Col4

Replacing unwanted portions of a string value in SQL column

I have a table in the following format where COL1 contains a unique identifier and COL2 contains a collection of phone numbers followed by a tag (<abc> or <def>) and delimited by pipe (|). Number of phone records in each row is unknown - it may contain just one phone number followed by tag or upto 10.
Table
----------
COL1 : COL2
----------
ID1 : 1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>
I need to copy this data into a new table with the result in following format i.e. remove all portion of the string with the tag <def>.
Table
----------
COL1 : COL2
----------
ID1 : 1234567890<abc>,4312314124<abc>,4131234131<abc>
What are the best ways to do this to get optimum performance? I need the program to transform data in a table that contains about a million records.
If performance is important then I would suggest delimitedSplit8k_Lead. You can just use the pipe as a delimiter to split the string then exclude items (tokens) that don't end with .
DECLARE #table TABLE (COL1 VARCHAR(10), COL2 VARCHAR(1000));
INSERT #table
VALUES
('ID1','1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>'),
('ID2','2662314129<abc>|7868845133<abc>|6831234131<abc>|41234139999<xxx>|1234567999<abc>')
SELECT t.COL1, ds.item
FROM #table t
CROSS APPLY dbo.DelimitedSplit8K_LEAD(t.COL2,'|') ds
WHERE ds.Item LIKE '%<abc>';
Returns
COL1 item
---------- -----------------
ID1 1234567890<abc>
ID1 4312314124<abc>
ID1 4131234131<abc>
ID2 2662314129<abc>
ID2 7868845133<abc>
ID2 6831234131<abc>
ID2 1234567999<abc>
Then you use XML PATH for concatenation like this:
DECLARE #table TABLE (COL1 VARCHAR(10), COL2 VARCHAR(1000));
INSERT #table
VALUES
('ID1','1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>'),
('ID2','2662314129<abc>|7868845133<abc>|6831234131<abc>|41234139999<xxx>|1234567999<abc>')
SELECT t.COL1, stripBadNumbers.newString
FROM #table t
CROSS APPLY
(VALUES((
SELECT ds.item
FROM dbo.DelimitedSplit8K_LEAD(t.COL2,'|') ds
WHERE ds.Item LIKE '%<abc>'
FOR XML PATH(''), TYPE
).value('.', 'varchar(1000)'))) stripBadNumbers(newString);
Returns:
COL1 newString
---------- -------------------------------------------------------------------
ID1 1234567890<abc>4312314124<abc>4131234131<abc>
ID2 2662314129<abc>7868845133<abc>6831234131<abc>1234567999<abc>
That string of yours can easily be transformed into some XML basically using replace(). The phone numbers with the right tag can then be selected using XQuery. As a bonus this might work with an arbitrary number of phone numbers.
(I don't get your schema, so I use my own. Translate it into yours yourself.)
CREATE TABLE elbat
(nmuloc nvarchar(MAX));
INSERT INTO elbat
(nmuloc)
VALUES ('1234567890<abc>|4312314124<abc>|1232345133<def>|4131234131<abc>|41234134132<def>');
WITH
cte AS
(
SELECT convert(xml,
concat('<phonenumbers><phonenumber number="',
replace(replace(substring(nmuloc,
1,
len(nmuloc) - 1),
'<',
'" tag="'),
'>|',
'"/><phonenumber number="'),
'"/></phonenumbers>')) phonenumbers
FROM elbat
)
SELECT stuff((SELECT ',' + nodes.node.value('concat(./#number, "<", ./#tag, ">")',
'nvarchar(max)')
FROM cte
CROSS APPLY phonenumbers.nodes('/phonenumbers/phonenumber[#tag="abc"]') nodes(node)
FOR XML PATH(''),
TYPE).value('(.)[1]',
'nvarchar(max)'),
1,
1,
'');
But while you're at it you should really consider to normalize your schema and don't use delimiter separated lists and the also non atomic number and tag combination in a string anymore!
SQL Fiddle
I didn't understand your question at first.but for answer you can you following code if you sql server is 2016 or upper.I think it has a good performance
Insert into table2 (ID1)
SELECT
STUFF((SELECT [value] +N',' AS 'data()' FROM STRING_SPLIT(ID1,'|') WHERE [value] LIKE'%<abc>' FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') AS ID1
FROM
table1

Using Dynamic SQL to reformat a table to having column names as rows

I've been stuck with this for a while and I've not found something on the website that answers something like this so please point me to the right direction if an existing question exists. In SQL Server 2012, I have a table with ID as the primary key:
ID col1 col2 col3 ....
--- ---- ----- -----
1 a z k
2 g b p
3 k d a
I don't know the length of the table nor the amount of columns/ column names
but I want to be able to get a table that gives me something like:
ID ColName Value
--- ---- -----
1 col1 a
1 col2 z
1 col3 k
2 col1 g
2 col2 b
2 col3 p
3 col1 k
3 col2 d
3 col3 a
...
I know that
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = 'table'
gets me my columns and I've tried trying to use that to create a temp table to
insert my desired format into a temp table but I'm not sure how to go through each row in a table and then grab the desired values dynamically for each column name and display it. I've been able to kind of achieve this with double cursors but that is painfully slow and I'm not sure how else to approach this since I'm relatively new at SQL. Any help would be appreciated!
Edit
Thank you so very much Lamak! I did have varying data types and coverting them to varchars for now shows me that the concept does work. However, I have 4 common datatypes (varchar, float, int, datetime) that I want to account for so I have 4 value fields for each of those where I would insert the column value into one of those 4 depending on it and leave the other 3 blank. I know that INFORMATION_SCHEMA.COLUMNS also provides the data_types so I was wondering what the syntax would be to convert the datatype in the "STUFF" variables based on a simple IF statement. I tried mapping the data_types to the column names but having any type of conditional statement breaks the query. If anyone has a simple example, that would be great :)
Edit
Actually, I've been able to figure out that I would need to create 4 variables to each data type rather than do them all in just one of them. Thank you all for your help!
As the comments said, you'll need to use dynamic unpivot.
If every column aside ID have the same datatype, you can use the following query:
DECLARE #colsUnpivot AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT *
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN (' + #colsunpivot + ')
) u
';
EXEC(#query);
Now, if the datatypes are different, then you'll need to first convert every column to a common datatype. In the following example, I'll use NVARCHAR(1000), but you'll need to convert them to the right datatype:
DECLARE #colsUnpivot1 AS NVARCHAR(MAX),
#colsUnpivot2 as NVARCHAR(MAX),
#query AS NVARCHAR(MAX);
SELECT #colsUnpivot1 = STUFF((SELECT ', ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SELECT #colsUnpivot2 = STUFF((SELECT ', CONVERT(NVARCHAR(1000),' + QUOTENAME(C.name)
+ ') ' + QUOTENAME(C.name)
FROM sys.columns as C
WHERE C.object_id = object_id('table') AND
C.name <> 'ID'
FOR XML PATH('')), 1, 1, '');
SET #query = '
SELECT ID,
ColName,
Value
FROM
(
SELECT ID, ' + #colsUnpivot2 + '
FROM dbo.table
) x
UNPIVOT
(
Value FOR ColName IN ('+ #colsunpivot1 +')
) u
';
EXEC(#query);
You don't have to go DYNAMIC. Another Option with with a CROSS APPLY and a little XML.
UnPivot is more performant, but you will find the performance of this approach very respectable.
An added bonus of this approach is that the From #YourTable A could be any query (not limited to a table). For example From ( -- Your Complex Query --) A
Declare #YourTable table (ID int,Col1 varchar(25),Col2 varchar(25),Col3 varchar(25))
Insert Into #YourTable values
(1,'a','z','k')
,(2,'g','b','p')
,(3,'k','d','a')
Select C.*
From #YourTable A
Cross Apply (Select XMLData=cast((Select A.* for XML Raw) as xml)) B
Cross Apply (
Select ID = r.value('#ID','int')
,Item = attr.value('local-name(.)','varchar(100)')
,Value = attr.value('.','varchar(max)')
From B.XMLData.nodes('/row') as A(r)
Cross Apply A.r.nodes('./#*') AS B(attr)
Where attr.value('local-name(.)','varchar(100)') not in ('ID','OtherFieldsToExclude')
) C
Returns
ID Item Value
1 Col1 a
1 Col2 z
1 Col3 k
2 Col1 g
2 Col2 b
2 Col3 p
3 Col1 k
3 Col2 d
3 Col3 a

SQL Server: select value, split on a delimiter, then update two columns

I have a SQL Server 2008 database table with three varchar Columns - Col1, Col2, Col3. Col1 has data in it with a single space in between, Col2 and Col3 are empty.
I need to write a query to select the data from Col1, break up each value using the space as the delimiter, and inserting the data on either side of the space into Col2 and Col3 respectively.
I am not too sure how to proceed. Can this be accomplished in SQL, or should I create a small program to do the work for me? I'd appreciate a pointer in the right direction if this can be accomplished via SQL.
Thanks.
UPDATE table SET
Col2 = SUBSTRING(Col1, 1, CHARINDEX(' ', Col1)-1),
Col3 = SUBSTRING(Col1, CHARINDEX(' ', Col1)+1, 8000)
WHERE Col1 LIKE '% %';
If you can guarantee there is only one space:
create table #temp (col1 varchar(50),col2 varchar(50), col3 varchar(50))
insert into #temp (col1)
select 'test 1'
union all
select 'test 2'
union all
select 'test 3'
update #temp
set col2 = left(col1, charindex (' ', col1)),
col3 = substring(col1,charindex (' ', col1)+1, len(col1))
from #temp

How to split strings in SQL Server

I have the following input:
Data
-----
A,10
A,20
A,30
B,23
B,45
Expected output:
col1 Col2
---- -----
A 10
A 20
A 30
B 23
B 45
How can I split the string to produce the desired output?
SELECT substring(data, 1, CHARINDEX(',',data)-1) col1,
substring(data, CHARINDEX(',',data)+1, LEN(data)) col2
FROM table
I know the points has already been given, going to post it anyway because i think it is slightly better
DECLARE #t TABLE (DATA VARCHAR(20))
INSERT #t VALUES ('A,10');INSERT #t VALUES ('AB,101');INSERT #t VALUES ('ABC,1011')
SELECT LEFT(DATA, CHARINDEX(',',data) - 1) col1,
RIGHT(DATA, LEN(DATA) - CHARINDEX(',', data)) col2
FROM #t
if the values in column 1 are always one character long, and the values in column 2 are always 2, you can use the SQL Left and SQL Right functions:
SELECT LEFT(data, 1) col1, RIGHT(data, 2) col2
FROM <table_name>
declare #string nvarchar(50)
set #string='AA,12'
select substring(#string,1,(charindex(',',#string)-1) ) as col1
, substring(#string,(charindex(',',#string)+1),len(#string) ) as col2![my sql server image which i tried.][1]
it is so easy, you can take it by below query:
SELECT LEFT(DATA, CHARINDEX(',',DATA)-1) col1,RIGHT(Data,LEN(DATA)-(CHARINDEX(',',DATA))) col2 from Table