Substitute for STRING_AGG pre SQL Server 2016 - sql-server

I need to group a table by a set of values together with all matching row numbers/id:s for each set. This operation must be done within the boundaries of SQL Server 2016.
Let's suppose I have the following table (Places):
ID
Country
City
1
Sweden
Stockholm
2
Norway
Oslo
3
Iceland
Reykjavik
4
Sweden
Stockholm
The result that I'm after (No curly-brackets because Stack Overflow thinks it's code, preventing me from posting):
ID
Json
1,4
"Country":"Sweden","City":"Stockholm"
2
"Country":"Norway ","City":"Oslo"
3
"Country":"Iceland ","City":"Reykjavik"
In SQL Server 2017 the above result can be achieved with:
SELECT STRING_AGG(ID) ID, (SELECT Country, City FOR JSON PATH) Json
FROM Places GROUP BY Country, City
I managed to get a similar result in SQL Server 2016 with the code below. (But with my actual amount of data and columns, this solution is too slow.)
SELECT DISTINCT Country, City INTO #temp FROM Places
SELECT (SELECT ID From Places WHERE Country = P.Country AND City = P.City FOR JSON PATH) ID,
(SELECT Country, City FOR JSON Path) Json FROM #temp P
Is there any more performance-effective way of achieving the result that I'm after?
EDIT: As people suggested me to try "FOR XML Path" I tried the code below. This gives the following error "Places.ID is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause ":
SELECT stuff((select ',' + cast(ID as varchar(max)) for xml path ('')), 1, 1, '') ID,
(SELECT Country, City FOR JSON PATH) Json
FROM Places GROUP BY Country, City

Here's a solution you can try with for xml path
Basically select and group the json columns needed and using an apply, use the for xml path solution to aggregate the correlated ID values; because the outer query needs to refer to the output of the apply it needs to be aggregated also, I chose to use max
select max(x.Ids), (select country,city for json path) as [Json]
from t
outer apply (
select Stuff((select ',' + Convert(varchar(10),t2.Id)
from t t2
where t2.city=t.city and t2.country=t.country
for xml path(''),type).value('(./text())[1]','varchar(10)'),1,1,'') as Ids
)x
group by country,city
Working Fiddle

Here is another possible solution:
Declare #testTable Table (ID int, Country varchar(30), City varchar(30));
Insert Into #testTable (ID, Country, City)
Values (1, 'Sweden', 'Stockholm')
, (2, 'Normway', 'Oslo')
, (3, 'Iceland', 'Reykjavik')
, (4, 'Sweden', 'Stockholm');
Select Distinct
ID = stuff((Select concat(',', tt2.ID)
From #testTable tt2
Where tt2.City = tt.City
And tt2.Country = tt.Country
For xml path (''), Type).value('.', 'varchar(10)'), 1, 1, '')
, json = (Select Country, City For JSON PATH)
From #testTable tt;
No idea if this will perform any better though. It is essentially the same - just using DISTINCT instead of GROUP BY.

Related

JSON_VALUE is not working in where clause in SQL Server

I have JSON data in a column in my table. I am trying to apply where condition on the JSON column and fetch records.
Employee table:
Here is my SQL query:
SELECT ID, EMP_NAME
FROM EMPLOYEE
WHERE JSON_VALUE(TEAM, '$') IN (2, 3, 4, 5, 7, 10)
I am getting an empty result when I use this query. Any help on how to do this?
You need to parse the JSON in the TEAM column with OPENJSON():
Table:
CREATE TABLE EMPLOYEE (
ID int,
EMP_NAME varchar(50),
TEAM varchar(1000)
)
INSERT INTO EMPLOYEE (ID, EMP_NAME, TEAM)
VALUES
(1, 'Name1', '[2,11]'),
(2, 'Name2', '[2,3,4,5,7,10]'),
(3, 'Name3', NULL)
Statement:
SELECT DISTINCT e.ID, e.EMP_NAME
FROM EMPLOYEE e
CROSS APPLY OPENJSON(e.TEAM) WITH (TEAM int '$') j
WHERE j.TEAM IN (2,3,4,5,7,10)
Result:
ID EMP_NAME
1 Name1
2 Name2
As an additional option, if you want to get the matches as an aggregated text, you may use the following statement (SQL Server 2017 is needed):
SELECT e.ID, e.EMP_NAME, a.TEAM
FROM EMPLOYEE e
CROSS APPLY (
SELECT STRING_AGG(TEAM, ',') AS TEAM
FROM OPENJSON(e.TEAM) WITH (TEAM int '$')
WHERE TEAM IN (2,3,4,5,7,10)
) a
WHERE a.TEAM IS NOT NULL
Result:
ID EMP_NAME TEAM
1 Name1 2
2 Name2 2,3,4,5,7,10
JSON_VALUE returns a scalar value, not a data set, which you appaer to think it would. If you run SELECT JSON_VALUE('[2,3,4,5,7,10]','$') you'll see that it returns NULL, so yes, no rows will be returned.
You need to treat the JSON like a data set, not a single value:
SELECT ID, EMP_NAME
FROM EMPLOYEE E
WHERE EXISTS (SELECT 1
FROM OPENJSON (E.TEAM) OJ
WHERE OJ.Value IN (2,3,4,5,7,10))

Unable to use XML PATH with UNION ALL in SQL Server

I have used below code to generate Comma separate values, it was before perfect working without UNION ALL but once I apply UNION ALL with another SELECT then it is showing an XML like output. The query looks like:
SELECT ISNULL(STUFF((
SELECT plant from (
SELECT DISTINCT ', ' + Plant as Plant
FROM ASP_MstSKUPlantMapping SKUPM
WHERE SKUID = '702953' AND Plant LIKE 'P%'
UNION ALL
SELECT DISTINCT ', ' + Plant as Plant
FROM ASP_MstSKUPlantMapping SKUPM
WHERE SKUID = '702953' AND Plant LIKE 'S%'
) Temp
FOR XML PATH('')), 1, 2,''),'')
Current Output:
lant>, PN05</plant><plant>, PN10</plant>
Expected Output:
PN05, PN10
EDIT: If I removed as Plant this alias name then it showing an error as:
No column name was specified for column 1 of 'Temp'.
I don't think you need to use a UNION ALL for this.
It's from the same table, so it's just something for the WHERE clause to deal with.
SELECT
ISNULL(STUFF((
SELECT CONCAT(', ', Plant)
FROM ASP_MstSKUPlantMapping SKUPM
WHERE SKUID = '702953' AND Plant LIKE '[PS]%'
GROUP BY Plant
ORDER BY Plant
FOR XML PATH('')
), 1, 2, ''),'') AS [Plants];

Generate XML in SQL Server

I have a table like this -
Version itemid sampleid
--------------------------------
1 3 23
1 3 24
1 4 45
2 5 24
2 5 23
Where for each version there can be multiple itemid, and for each itemid there can be multiple sampleid.
I want to generate XML for this table in the following manner
<UserVersioningHistory>
<History>
<Version>1</Version>
<itemid>3</itemid>
<sampleid>23,24</sampleid>
</History>
<History>
<Version>1</Version>
<itemid>4</itemid>
<sampleid>45</sampleid>
</History>
<History>
<Version>2</Version>
<BusinessId>5</BusinessId>
<sampleid>24,23</sampleid>
</History>
</UserVersioningHistory>
Each node here can have only one version and itemid but can have multiple sampleid for corresponding itemid and Version Pair.
As I am not familiar in with generating XML in SQL Server, can someone give me a hint to what approach I should use?
Can I accomplish this task using while loop, or I should do this writing a subquery?
Try it out:
select * from
(SELECT
version, itemid,
STUFF(
(SELECT ',' + sampleid
FROM test
WHERE version = a.version AND itemid = a.itemid
FOR XML PATH (''))
, 1, 1, '') AS sampleid
FROM test AS a
GROUP BY version, itemid) as History
for xml auto, root ('UserVersioningHistory')
Always avoid WHILE loops unless when truly necessary.
Preferably you would have multiple tags for the SampleId section if more than one exists.
But to give you the result that you want the following would work. (I created a temp table to imitate your situation.)
I used FOR XML to do the XML formatting and had to use a second FOR XML to concatenate the SampleId sepparated by a comma.
STUFF is only used to remove the first comma in the string.
SET XACT_ABORT ON;
BEGIN TRANSACTION;
SELECT
X.Version,
X.ItemId,
X.SampleId
INTO
#Temp
FROM
(VALUES
(1, 3, 23),
(1, 3, 24),
(1, 4, 45),
(2, 5, 24),
(2, 5, 23)
) X ([Version], ItemId, SampleId)
SELECT
T.Version,
T.ItemId,
STUFF((
SELECT
',' + CONVERT(varchar(MAX), T2.SampleId)
FROM
#Temp T2
WHERE
T2.Version = T.Version AND
T2.ItemId = T.ItemId
FOR XML PATH ('')
),
1,
1,
''
) AS [SampleId]
FROM
#Temp T
GROUP BY
T.Version,
T.ItemId
FOR XML RAW ('History'), ROOT ('UserVersioningHistory'), ELEMENTS
ROLLBACK TRANSACTION;

Which concept i want use to get following output using SQL server 2012

My Table having following data's
ID | Name
--- | ---------
1 | Apple
2 | Microsoft
3 | Samsung and so on...
In my case, input is '1,2,3'.
And i need output is 'Apple,Microsoft,Samsung'.
SELECT STUFF((
SELECT ','+ Name
FROM MyTable
WHERE ID in (1, 2, 3)
FOR XML PATH('')
), 1, 1, '') AS Names
Result:
Apple,Microsoft,Samsung
You can do with XML PATH
SELECT
(
SELECT
T.Name + ', '
FROM
Tbl T
WHERE
Id in (1, 2, 3)
FOR XML PATH ('')
) DesiredOutput
Result looks like Apple, Microsoft, Samsung,
USE [Database Name]
SELECT COLUMN_NAME,*
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YourTableName'
As SQL server doesn't support input in runtime, you can either create a stored procedure and pass the input value while executing it. or just run the following query.
SELECT NAME
FROM MyTable
WHERE ID IN
(SELECT TOP 3 ID FROM MyTable ORDER BY ID)

SQL Server: Joining in rows via. comma separated field

I'm trying to extract some data from a third party system which uses an SQL Server database. The DB structure looks something like this:
Order
OrderID OrderNumber
1 OX101
2 OX102
OrderItem
OrderItemID OrderID OptionCodes
1 1 12,14,15
2 1 14
3 2 15
Option
OptionID Description
12 Batteries
14 Gift wrap
15 Case
[etc.]
What I want is one row per order item that includes a concatenated field with each option description. So something like this:
OrderItemID OrderNumber Options
1 OX101 Batteries\nGift Wrap\nCase
2 OX101 Gift Wrap
3 OX102 Case
Of course this is complicated by the fact that the options are a comma separated string field instead of a proper lookup table. So I need to split this up by comma in order to join in the options table, and then concat the result back into one field.
At first I tried creating a function which splits out the option data by comma and returns this as a table. Although I was able to join the result of this function with the options table, I wasn't able to pass the OptionCodes column to the function in the join, as it only seemed to work with declared variables or hard-coded values.
Can someone point me in the right direction?
I would use a splitting function (here's an example) to get individual values and keep them in a CTE. Then you can join the CTE to your table called "Option".
SELECT * INTO #Order
FROM (
SELECT 1 OrderID, 'OX101' OrderNumber UNION SELECT 2, 'OX102'
) X;
SELECT * INTO #OrderItem
FROM (
SELECT 1 OrderItemID, 1 OrderID, '12,14,15' OptionCodes
UNION
SELECT 2, 1, '14'
UNION
SELECT 3, 2, '15'
) X;
SELECT * INTO #Option
FROM (
SELECT 12 OptionID, 'Batteries' Description
UNION
SELECT 14, 'Gift Wrap'
UNION
SELECT 15, 'Case'
) X;
WITH N AS (
SELECT I.OrderID, I.OrderItemID, X.items OptionCode
FROM #OrderItem I CROSS APPLY dbo.Split(OptionCodes, ',') X
)
SELECT Q.OrderItemID, Q.OrderNumber,
CONVERT(NVarChar(1000), (
SELECT T.Description + ','
FROM N INNER JOIN #Option T ON N.OptionCode = T.OptionID
WHERE N.OrderItemID = Q.OrderItemID
FOR XML PATH(''))
) Options
FROM (
SELECT N.OrderItemID, O.OrderNumber
FROM #Order O INNER JOIN N ON O.OrderID = N.OrderID
GROUP BY N.OrderItemID, O.OrderNumber) Q
DROP TABLE #Order;
DROP TABLE #OrderItem;
DROP TABLE #Option;

Resources