SQL Server: Merge two values with regexp - sql

I am using an SQL Server database and have these following tables
Table "Data"
--------------------------------------------------------------------------------------------------|
| Id |col_1_type | col_1_name | col_2_type | col_2_name | col_3_type | col_3_name |
--------------------------------------------------------------------------------------------------|
| 1 |KI | Inflation Rate | KI | Currency Rate | MI | Government Spending |
--------------------------------------------------------------------------------------------------|
And i just want to make my result to be like this:
+----+------------------------+
| Id | results |
+----+------------------------+
| 1 | KI-Inflation Rate |
| 2 | KI-Currency Rate |
| 3 | MI-Government Spending |
+----+------------------------+
The column name is mandatory though, thats what made it complicated i guess?
i know you can merge 2 values or concatenate it, but i'm stuck on the column name such as col_1_name and col_2_type. Do i need to use regexp maybe?

Please try this-
select ROW_NUMBER() OVER (ORDER BY (select null)) id , results
from
(
SELECT CONCAT(col_1_type,'-',col_1_name) results
FROM [Data]
UNION ALL
SELECT CONCAT(col_2_type,'-',col_2_name)
FROM Data
UNION ALL
SELECT CONCAT(col_3_type,'-',col_3_name)
FROM Data
)o
Or this also
SELECT Id,results
FROM Data
CROSS apply
(VALUES (CONCAT(col_1_type,'-',col_1_name),1),(CONCAT(col_2_type,'-',col_2_name),2)
,(CONCAT(col_3_type,'-',col_3_name) ,3) ) cs (results,Id)

I would use of cross apply with the help of CTE and stuff()
;with cte as
(
select a.* from table t
cross apply (
values
(t.col_1_type, 'col_1'),
(t.col_1_name, 'col_1'),
(t.col_2_type, 'col_2'),
(t.col_2_name, 'col_2'),
(t.col_3_type, 'col_3'),
(t.col_3_name, 'col_3')
) a(name, id)
)
select distinct stuff(
(select '-'+name from cte where id= c.id for xml path('')),
1,1, ''
) [Results],
from cte c
EDIT :
Not sure about Id column but my guess that could be resolved by using ranking function
select row_number() over (order by (select 1)) Id,
cc.Results from
(
select distinct stuff(
(select '-'+id from cte where name = c.name for xml path('')),
1,1, ''
) [Results]
from cte c
) cc
Result :
Id Results
1 KI-Currency Rate
2 KI-Inflation Rate
3 MI-Government Spending

Sample data
IF OBJECT_ID('tempdb..#t') iS NOT NULL
DROP TABLE #t
IF OBJECT_ID('dbo.temp','U') iS NOT NULL
DROP TABLE temp
;With CTe(
Id ,col_1_type , col_1_name, col_2_type , col_2_name, col_3_type , col_3_name )
AS
(
SELECT 1,'KI','Inflation Rate','KI','Currency Rate','MI','Government Spending'
)
SELECT * INTO temp FROM CTe
Using Dynamic sql
DECLARE #Sqlstring nvarchar(max)
,#SQlQuery nvarchar(max)
;WITH cte
AS
(
SELECT COLUMN_NAME ,
((ROW_NUMBER()OVER(ORDER BY (SELECT NULL))-1)/2 )+1 AS BatchSeq
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='temp' AND COLUMN_NAME<>'Id'
)
SELECT #Sqlstring=STUFF((SELECT ', '+COLUMN_NAME FROM
(
SELECT DISTINCT '('+STUFF((SELECT ', '+COLUMN_NAME
FROM cte i
WHERE i.BatchSeq=o.BatchSeq FOR XML PATH ('')),1,1,'') +')' AS COLUMN_NAME
FROM cte o
)dt
FOR XML PATH ('')),1,1,'')
SET #SQlQuery='
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT 1)) AS ID,
CONCAT(col_type,''-'',Col_names) AS Result
FROM Temp
CROSS APPLY ( VALUES '+#Sqlstring+') dt(col_type,Col_names)'
PRINT #SQlQuery
EXEC(#SQlQuery)
Result
ID Result
-----------------------
1 KI-Inflation Rate
2 KI-Currency Rate
3 MI-Government Spending

Try like this
select (column1 || ' '|| column2) from table;
or
SELECT tablename.col_1_type + ' ' + tablename.col_1_name AS results;

Related

Syntax Error pivoting a SQL table using string columns

I have a table Occupation like such:
Name | Occupation
-----------------
Sam | Doctor
Joe | Professor
John | Actor
Hailey | Singer
April | Doctor
My goal is to utilize the PIVOT function to display each distinct Occupation as their own column like so:
Doctor | Professor | Singer | Actor
-----------------------------------
Sam | Joe | Hailey | John
April
I went through various stack overflow questions with similar issues, and even utilized the documentation here and followed step by step. My efforts have been futile, so what gives? I am receiving a syntax error every time I run this code. Any suggestions?
Code:
SELECT *
FROM(SELECT Name, Occupation from Occupations) as src
PIVOT
(MAX(Name) FOR Name IN [Doctor], [Professor], [Singer], [Actor]) as piv;
I am solving this problem on Hackerrank, and the compiler was MySQL. I changed it to MSSQL and am no longer receiving a syntax error.
You were missing 2 () One on each side in the PIVOT part, I bolded the brackets I added:
([Doctor], [Professor], [Singer], [Actor]) - These 2
SELECT *
FROM (
SELECT Name, Occupation
FROM Occupations
) as src
PIVOT
(
MAX(Name) FOR Name IN ([Doctor], [Professor], [Singer], [Actor])
) as piv;
This query will generate the answer as per the way you need. The above answer does not provide the answer as the way you required. Try this answer.
DECLARE #sql NVARCHAR(MAX) = N''
DECLARE #cols NVARCHAR(MAX) = N''
--Making the column list dynamically
SELECT #cols = STUFF((SELECT DISTINCT ', '+QUOTENAME( [T2].[Occupation])
FROM Occupation_data [T2]
FOR XML PATH('')), 1, 1, '')
--preparing PIVOT query dynamically.
SET #SQL = ' SELECT
*
into #temp
FROM
(
SELECT row_number() over (order by name) as id, [Name], [Occupation]
FROM Occupation_data
) AS cp
PIVOT
(
min([Name]) FOR [Occupation] IN (' + #cols + ')
) AS up;
Select
row_number() over (order by doctor asc ) as id1
,*
into #temp_1
from #temp where Doctor is not null
Select
row_number() over (Order by Actor asc ) as id2
,*
into #temp_2
from #temp where Actor is not null
Select
row_number() over (Order by Professor asc ) as id3
,*
into #temp_3
from #temp where Professor is not null
Select
row_number() over (Order by Singer asc ) as id4
,*
into #temp_4
from #temp where Singer is not null
select
A.Doctor
,B.Actor
,C.Professor
,D.Singer
from
#temp_1 A inner join
#temp_2 B ON A.id1 = B.id2
INNER JOIN
#temp_3 C ON A.id1 = C.id3
INNER JOIN
#temp_4 D ON A.id1 = d.id4
drop table #temp
drop table #temp_1
drop table #temp_2
drop table #temp_3
drop table #temp_4
';
PRINT( #SQL)
EXEC (#SQL)
Output:
If this answer works for you I will make it more generic and give you so that you can apply this for any work like this.

How to concatenate strings in SQL Server, and sort/ order by a different column?

I've seen many examples of concatenating strings in SQL Server, but if they worry about sorting, it's always by the column being concatenated.
I need to order the values based on data in a different fields.
Sample table:
ClassID | StudentName | SortOrder
-----------------------------
A |James |1
A |Janice |3
A |Leonard |2
B |Luke |2
B |Leia |1
B |Artoo |3
And the results I'd like to get are:
ClassID |StudentName
--------------------------------
A |James, Leonard, Janice
B |Leia, Luke, Artoo
How can this be done in SQL Server 2016?
(I'm looking forward to STRING_AGG in 2017, but we're not there yet...)
Thanks!
Here you go:
SELECT
s1.ClassID
, STUFF((SELECT
',' + s2.StudentName
FROM dbo.Student AS s2
WHERE s1.classID = s2.ClassID
ORDER BY s2.SortOrder
FOR XML PATH('')), 1, 1, '') AS StudentNames
FROM dbo.Student AS s1
GROUP BY s1.ClassID
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE MyTable(ClassID varchar(255),StudentName varchar(255),SortOrder int)
INSERT INTO MyTable(ClassID,StudentName,SortOrder)VALUES('A','James',1),('A','Janice',3),('A','Leonard',2),
('B','Luke',2),('B','Lela',1),('B','Artoo',3)
Query 1:
SELECT
t.ClassID
, STUFF((SELECT
',' + t1.StudentName
FROM MyTable t1
WHERE t.classID = t1.ClassID
ORDER BY t1.SortOrder
FOR XML PATH('')), 1, 1, '') AS StudentNamesConcat
FROM MyTable AS t
GROUP BY t.ClassID
Results:
| ClassID | StudentNamesConcat |
|---------|----------------------|
| A | James,Leonard,Janice |
| B | Lela,Luke,Artoo |
Here the query
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp;
CREATE TABLE #Temp(ClassId varchar(10),studName varchar(100),SortOrder int)
INSERT INTO #Temp(ClassId , studName, SortOrder)
SELECT 'A','James',1 UNION ALL
SELECT 'A','Janice',3UNION ALL
SELECT 'A','Leonard',2 UNION ALL
SELECT 'B','Luke',2 UNION ALL
SELECT 'B','Leia',1 UNION ALL
SELECT 'B','Artoo',3
-- select * from #Temp
select
distinct
stuff((
select ',' + u.studName
from #Temp u
where u.studName = studName and U.ClassId = L.ClassId
order by u.SortOrder
for xml path('')
),1,1,'') as userlist,ClassId
from #Temp L
group by ClassId
You can try using PIVOT also. This will support even older version of SQL server.
Only limitation : you should know the maximum SortOrder value. Below code will work for SortOrder <=20 of any ClassID
SELECT ClassID, ISNULL([1],'') +ISNULL(', '+[2],'')+ISNULL(', '+[3],'')+ISNULL(', '+
[4],'')+ISNULL(', '+[5],'')+ISNULL(', '+[6],'')+ISNULL(', '+[7],'')+ISNULL(', '+
[8],'')+ISNULL(', '+[9],'')+ISNULL(', '+[10],'')+ISNULL(', '+[11],'')+ISNULL(', '+
[12],'')+ISNULL(', '+[13],'')+ISNULL(', '+[14],'')+ISNULL(', '+[15],'')+ISNULL(', '+
[16],'')+ISNULL(', '+[17],'')+ISNULL(', '+[18],'')+ISNULL(', '+[19],'')+ISNULL(', '+
[20],'') AS StudentName
FROM
(SELECT SortOrder,ClassID,StudentName
FROM [Table1] A
) AS SourceTable
PIVOT
(
MAX(StudentName)
FOR SortOrder IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20])
) AS PivotTable

In SQL Count number of distinct values corresponding to a same Id and DISPLAY in a same row

I am stuck on a SQL query. I have results as below in a table and have to display the final result in a report as below:
id Question
-------------
13 ABC
13 ABC
13 QWE
13 ABC
13 QWE
13 ABC
Expected result:
id Result
--------------------
13 4 ABC, 2 QWE
Can somebody please help me out? Thank you.
This requires pre-aggregation and string aggregation.
with t as (
select id, question, count(*) as cnt
from t
group by id
)
select i.id,
stuff( (select ', ' + convert(varchar(255), cnt) + ' ' + question
from t t2
where t2.id = i.id
for xml path ('')
), 1, 2, ''
) as result
from (select distinct id from t) i;
--testdata-begin
if not object_id(N'Tempdb..#T') is null
drop table #T
Go
Create table #T([id] int,[Question] nvarchar(23))
Insert #T
select 13,N'ABC' union all
select 13,N'ABC' union all
select 13,N'QWE' union all
select 13,N'ABC' union all
select 13,N'QWE' union all
select 13,N'ABC'
Go
--testdata-end
WITH cte AS (
Select id,Question,COUNT(1) AS num from #T GROUP BY id,Question
)
SELECT id,
STUFF(
(
SELECT ',' + RTRIM(b.num) + ' ' + b.Question
FROM cte b
WHERE a.id = b.id
FOR XML PATH('')
),
1,
1,
''
) AS Result
FROM cte a
GROUP BY id;

SQL Server 2012 Convert several comma delimited values to table rows/columns

Currently, I have a table that is storing historical data in the following fashion. I have no control over this server or how the data is stored.
ID | FName | LName |Stuff| More
--------+---------+---------+-----+------
1,2,3,4 | j,p,g,r | l,m,h,s | ,,, | a,,b,
I need to get this data into a result set so that it is in the following format:
ID | FName | LName |Stuff| More
--------+---------+---------+-----+------
1 | j | l | | a
2 | p | m | |
3 | g | h | | b
4 | r | s | |
I would like to avoid using a function as I am unsure of the access I will have to the servers in other environments. I have tried using xml with cross apply, which I can get to work for a singular field, but I cannot seem to get the full table to work.
Any suggestions would be greatly appreciated,
Thanks
~JM
It can be done by recursive query
;WITH CTE(ID, ID_tmp, FName, FName_tmp, LName, LName_tmp, Stuf, Stuf_tmp, more, more_tmp)
AS
(
SELECT CAST(LEFT(ID, CHARINDEX(',',ID+',')-1) AS NVARCHAR(50)) ID,
STUFF(ID, 1, CHARINDEX(',',ID+','), '') ID_tmp,
CAST(LEFT(FName, CHARINDEX(',',FName+',')-1) AS NVARCHAR(50)) FName,
STUFF(FName, 1, CHARINDEX(',',FName+','), '') ID_tmp,
CAST(LEFT(LName, CHARINDEX(',',LName+',')-1) AS NVARCHAR(50)) LName,
STUFF(LName, 1, CHARINDEX(',',LName+','), '') LName_tmp,
CAST(LEFT(Stuf, CHARINDEX(',',Stuf+',')-1) AS NVARCHAR(50)) Stuf,
STUFF(Stuf, 1, CHARINDEX(',',Stuf+','), '') Stuf_tmp,
CAST(LEFT(more, CHARINDEX(',',more+',')-1) AS NVARCHAR(50)) more,
STUFF(more, 1, CHARINDEX(',',more+','), '') more_tmp
FROM TAB
UNION ALL
SELECT CAST(LEFT(ID_tmp, CHARINDEX(',',ID_tmp+',')-1) AS NVARCHAR(50)) ID,
STUFF(ID_tmp, 1, CHARINDEX(',',ID_tmp+','), '') ID_tmp,
CAST(LEFT(FName_tmp, CHARINDEX(',',FName_tmp+',')-1) AS NVARCHAR(50)) FName,
STUFF(FName_tmp, 1, CHARINDEX(',',FName_tmp+','), '') FName_tmp,
CAST(LEFT(LName_tmp, CHARINDEX(',',LName_tmp+',')-1) AS NVARCHAR(50)) LName,
STUFF(LName_tmp, 1, CHARINDEX(',',LName_tmp+','), '') LName_tmp,
CAST(LEFT(Stuf_tmp, CHARINDEX(',',Stuf_tmp+',')-1) AS NVARCHAR(50)) Stuf,
STUFF(Stuf_tmp, 1, CHARINDEX(',',Stuf_tmp+','), '') Stuf_tmp,
CAST(LEFT(more_tmp, CHARINDEX(',',more_tmp+',')-1) AS NVARCHAR(50)) more,
STUFF(more_tmp, 1, CHARINDEX(',',more_tmp+','), '') more_tmp
FROM CTE
WHERE ID_tmp > ''
)
SELECT ID, FName , LName, stuf, more
FROM CTE
If, you want to avoid use of UDF, then XML nodes() method still able to help you by using multiple CTE approach.
WITH cteId AS
(
SELECT Ids.value('.', 'INT') Id FROM
(
SELECT
cast('<x>'+replace(Id, ',', '</x><x>')+'</x>' as xml) as Id
FROM table t
)a CROSS APPLY Id.nodes ('/x') as split(Ids)
), ctefname AS
(
SELECT
row_number() over (order by (select 1)) Seq,
FNames.value('.', 'varchar') FNames FROM
(
SELECT
cast('<x>'+replace(FName, ',', '</x><x>')+'</x>' as xml) as FName
FROM table t
)a CROSS APPLY FName.nodes ('/x') as split(FNames)
), cteLname AS
(
SELECT
row_number() over (order by (select 1)) Seq,
LNames.value('.', 'varchar') LNames FROM
(
SELECT
cast('<x>'+replace(LName, ',', '</x><x>')+'</x>' as xml) as LName
FROM table t
)a CROSS APPLY LName.nodes ('/x') as split(LNames)
), ...
SELECT
id.Id, fn.FNames, ln.LNames, ...
FROM cteId id
INNER JOIN ctefname fn on fn.Seq = id.Id
INNER JOIN cteLname ln on ln.Seq = id.Id
...

SQL XML, is there a technique by which I can get some columns in xml format?

I want to store data from multiple tables into one table
table A (
p, q,
PRIMARY KEY (p,q)
)
table B (
p, m, n,
PRIMARY KEY (p,m,n)
)
table output(
p,
xml
)
The output should be like
for table A
-------------------------------
| p | xml |
---------------------------------
|value of p | <q>some value</q> |
-------------------------------
for table B
-----------------------------------------------
| p | xml |
------------------------------------------------
|value of p | <m>some value</m><n>some data</n> |
-----------------------------------------------
This can be achieved by this query
SELECT *
FROM Students s
CROSS APPLY
(
SELECT
(
SELECT *
FROM Students t
WHERE t.DisplayName = s.DisplayName
FOR XML RAW
) x1
)x
But I want to make the SQL generic enough so that given any table name we can get the above output.
The problem with the inner join is that then the query will not be generic
To get the result you want from one table you could use something like this:
select T.Table1ID,
(
select T.*
for xml raw, type
) as x1
from Table1 as T
To apply the same pattern to another table you need to change the table name and the field name of the key column.
select T.Table2ID,
(
select T.*
for xml raw, type
) as x1
from Table2 as T
If you want a even more generic version you can use dynamic SQL and build your query from a table name and a column name.
declare #TableName sysname
declare #KeyColumnName sysname
set #TableName = 'master..spt_values'
set #KeyColumnName = 'Number'
declare #SQL nvarchar(500)
set #SQL = '
select '+#KeyColumnName+',
(
select T.*
for xml raw, type
) as x1
from '+#TableName+' as T'
exec (#SQL)
I'm sorry if I have misunderstood, but is this more or less the principle you are after?
;WITH Base AS
(
SELECT Cat = PC.Name
,SubCat = SC.Name
FROM Production.ProductCategory PC
JOIN Production.ProductSubcategory SC ON SC.ProductCategoryID = PC.ProductCategoryID
)
SELECT Cat, (SELECT SubCat FROM Base T2 WHERE T2.Cat = T1.Cat FOR XML AUTO )DT
FROM Base T1