Need distinct comma delimited product values order by date - sql

I have below table structure
create TABLE PRODUCTDeatils
(
Product varchar(50),
Date Datetime
)
I want an output where i get comma separated list with distinct values but order by date.
I first split the values by below query
SELECT DISTINCT
LTRIM(RTRIM(value)) AS Product, date
INTO #tmp3
FROM PRODUCTDeatils
CROSS APPLY STRING_SPLIT(LTRIM(RTRIM(Product)), ',')
SELECT *
FROM #tmp3
ORDER BY date
Then I used below two queries to achieve it but not successful.
Query #1:
DECLARE #cols AS NVARCHAR(MAX);
SELECT #COLS = substring(list, 1, LEN(list) - 1)
FROM
(SELECT
list = (SELECT DISTINCT Product + ';'
FROM #tmp3
ORDER BY Product + ';'
FOR XML PATH(''), TYPE).value('.', 'nvarchar(MAX)')) AS T
SELECT #COLS
Query #2:
SELECT
STUFF((SELECT '; ' + Product
FROM
(SELECT DISTINCT TOP 100
Product,date
FROM #tmp3) x
ORDER BY date
FOR XML PATH('')), 1, 2, '') Text
I want output like above but bat should only come once.

In the more recent versions of SQL Server, you should just use string_agg():
SELECT STRING_AGG(Product, ',') WITHIN GROUP (ORDER BY date)
FROM (SELECT LTRIM(RTRIM(s.value)) AS Product, MIN(date) as date
FROM ProductDetails pd CROSS APPLY
STRING_SPLIT(LTRIM(RTRIM(pd.Product)), ',') s
GROUP BY LTRIM(RTRIM(s.value))
) p
Here is a db<>fiddle.

You can add row_number and select only one value per product.
SELECT DISTINCT
LTRIM(RTRIM(value)) AS Product, date, ROW_NUMBER() over (partition by trim(Value) order by date) rn
INTO #tmp3
FROM PRODUCTDeatils
CROSS APPLY STRING_SPLIT(LTRIM(RTRIM(Product)), ',')
SELECT
STUFF((SELECT '; ' + Product
FROM
(SELECT DISTINCT TOP 100
Product,date
FROM #tmp3 where rn = 1) x
ORDER BY date
FOR XML PATH('')), 1, 2, '') Text
Please find the db<>fiddle here.

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

SQL to Make XML Path list Columns

I have the following SQL Server query which cranks out a comma delimited list into one field.
Result looks like this 2003, 9083, 4567, 3214
Question: What would be the best way (SQL syntax) to put this into columns?
Meaning, I need these to show up as 1 column for "2003", 1 column for "9083" 1 column, for "4567" ..etc.
Obviously the number of columns would be dynamic based on the policy ID I give it . Any idea would be most appreciated.
My query is below .
SELECT DISTINCT x.ClassCode + ', '
FROM PremByClass x
WHERE x.PolicyId = 1673885
FOR XML PATH('')
If you take out the XML and the comma you are left with
SELECT DISTINCT x.ClassCode
FROM PremByClass x
WHERE x.PolicyId = 1673885
Which gives you a single column of the values, to turn this into columns you need to PIVOT it. However, you need to specify the names of the columns.
There is some more information in this answer https://stackoverflow.com/a/15931734/350188
You need PIVOT and if number of values could be different - dynamic SQL:
SELECT *
FROM (
SELECT DISTINCT ClassCode,
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM PremByClass
WHERE PolicyId = 1673885
) as t
PIVOT (
MAX(ClassCode) FOR RN IN ([1],[2],[3],[4])
) as pvt
Will give you:
1 2 3 4
-----------------------------
2003 9083 4567 3214
Dynamic SQL will be something like:
DECLARE #sql nvarchar(max),
#columns nvarchar(max)
SELECT #columns = STUFF((
SELECT DISTINCT ','+QUOTENAME(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))
FROM PremByClass
WHERE PolicyId = 1673885
FOR XML PATH('')
),1,1,'')
SELECT #sql = N'
SELECT *
FROM (
SELECT DISTINCT ClassCode,
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM PremByClass
WHERE PolicyId = 1673885
) as t
PIVOT (
MAX(ClassCode) FOR RN IN ('+#columns+')
) as pvt'
EXEC sp_executesql #sql
Assuming that there's a limit to the amount of numbers in that csv string.
You could cast or convert it to an xml type, and then put the values in as many columns you expect.
In this example it's assumed that there's no more than 6 values in the text:
declare #PolicyId INT = 1673885;
select PolicyId
,x.value('/x[1]','int') as n1
,x.value('/x[2]','int') as n2
,x.value('/x[3]','int') as n3
,x.value('/x[4]','int') as n4
,x.value('/x[5]','int') as n5
,x.value('/x[6]','int') as n6
from (
select
PolicyId,
cast('<x>'+replace(ClassCode,',','</x><x>')+'</x>' as xml) as x
from PremByClass
where PolicyId = #PolicyId
) q;

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

Select values from multiple rows from 1 table as 1 record

I'm struggling to even explain what I need to do so please be patient with me.
I have the following table and rows in it:
TBNAME: Distances
Track, Person, Date, Distance
TRACK1, P1, 1/1/2014, 15
TRACK2, P1, 13/1/2014, 12
TRACK1, P1, 20/2/2014, 10
TRACK2, P1, 15/1/2014, 9
TRACK1, P2, 2/1/2014, 11
TRACK2, P2, 14/1/2014, 13
TRACK1, P2, 21/2/2014, 8
TRACK2, P2, 16/1/2014, 6
What I would, ideally, like to see as a result is something like this:
P1, TRACK1, 20/2/2014, 10, TRACK2, 15/1/2014, 9
P2, TRACK1, 21/2/2014, 8, TRACK2, 16/1/2014, 6
Or, in other words, for each person, the most recent date and distance for that date for each track in one row.
Hope someone can understand this and offer a solution too :)
Cheers,
Pierre
Try this:
SELECT T1.Person, T1.Track, MAX(T1.Date), MIN(T1.Distance),
T2.Track, MAX(T2.Date), MIN(T2.Distance)
FROM Distances AS T1 INNER JOIN
Distances AS T2 ON T1.Person = T2.Person
WHERE T1.Track <> T2.Track AND T1.Track = 'Track1'
GROUP BY T1.Track, T1.Person, T2.Track
The output result of the query is showing exactly the same of your expected result.
Try combining the table by itself and connecting them with the common column.
In your case you want Person.
Select t1.Person,
t1.Tract,
t1.Date,
t1.Distance,
t2.Tract,
t2.date,
t2.Distance
From table_name t1, table_name t2
WHERE t1.Person = t2.Person;
Try this:
SELECT DISTINCT ON ("Person", "Track") *
FROM "Table"
ORDER BY "Person", "Date" DESC NULLS LAST;
Here is a query to get the records needed. First get the maximum date per track and person. Then join with the table to get the complete record.
If you know beforehand which tracks you will get, you can use a pivot query for this. As I've never done this, I ask you to look this up yourself. However, as mentioned in my comment to your request, I would use a programming language (C#, Java, PHP or whatever) to care about that.
select d.track, d.person, d.date, d.distance
from
(
select track, person, max(distances.date) as `date`
from distances
group by track, person
) lastd
inner join distances d on d.track = lastd.track and d.person = lastd.person and d.date = lastd.date
order by d.track, d.person;
BTW: date is a reserved keyword. I would not recommend to use it for a column name. Whenever you use it without a qualifier you will have to use those strange quotes.
Look for ROW_NUMBER() and OVER PARITION BY.
Idea is something like (I did not try to run this query):
;WITH
data AS
(
SELECT
*,
-- returns number for each pair of person and track starting from most recent date
--Code enhanced at here
row_number() over (partition BY person, track order by dte DESC) nr
FROM distances
)
SELECT
*
FROM
data
WHERE
nr = 1 -- we want just the most recent one
ORDER BY
person, -- "group by" person
track ;
It's still doesn't support showing one row for each person...
I don't think you can do it with SQL (because of unknown number of tracks).
There is PIVOT/UNPIVOT, but I don't think it fits here.
WITH CTE AS
(
Select P1.Track,P1.Person,ROW_NUMBER() OVER (Partition by Person,Track Order by Date
Desc) AS RN1
,Date,Distance
from Distances P1
)Select T.Person,T.Track1,T.T1Date
,T.T1Distance,T.Track2,T.T2Date,T.T2Distance
From (
Select C1.Person,C1.Track AS 'Track1',C1.Date AS 'T1Date',
C1.Distance 'T1Distance',
C2.Track AS 'Track2',C2.Date As 'T2Date',C2.Distance 'T2Distance',
ROW_NUMBER() OVER (Partition BY C1.Person Order by C1.Date Desc) RNX
from
CTE C1
JOIN
CTE C2 ON C1.RN1=1 AND C2.RN1=1
AND C1.Person=C2.Person
AND C1.Track<>C2.Track
)t Where t.RNX=1
you may also use dynamic query to achieve your expected result :)
DECLARE #nCount INT
DECLARE #nStart INT
DECLARE #Query NVARCHAR(MAX) =' '
DECLARE #sPerson NVARCHAR(MAX)
DECLARE #sTrack NVARCHAR(MAX)
SET #nCount = (SELECT COUNT(DISTINCT(person)) FROM DISTANCES)
SET #nStart = 1
WHILE #nStart <= #nCount
BEGIN
SET #sPerson = (SELECT PERSON FROM (
SELECT PERSON, ROW_NUMBER() OVER (ORDER BY PERSON) RN FROM (
SELECT DISTINCT(PERSON) FROM DISTANCES
) T1
) T2 WHERE RN = #nStart
)
SET #Query = #Query + '
SELECT '''+#sPerson+''' + '','' + STUFF( '','' +(
SELECT TRACK + '', '' + DATE + '', '' + DISTANCE FROM (
SELECT TRACK, DATE,DISTANCE,
ROW_NUMBER() OVER (PARTITION BY TRACK ORDER BY DATE DESC) RN FROM (
SELECT TRACK,date,DISTANCE FROM DISTANCES WHERE PERSON = '''+#sPerson+'''
) T1
) T2
WHERE RN = 1 FOR XML PATH('''')
),1,1,''''
)
'
IF(#nStart != #nCount)
SET #Query = #Query + ' UNION ALL '
SET #nStart = #nStart + 1
END
EXEC SP_EXECUTESQL #Query
To have a general query it need to be dynamic
DECLARE #query AS NVARCHAR(MAX)
DECLARE #pivotCols AS NVARCHAR(MAX)
DECLARE #cols AS NVARCHAR(MAX)
SELECT #pivotCols = STUFF((SELECT DISTINCT ',' + QUOTENAME([Track])
FROM Distances
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') , 1, 1, '')
;WITH T AS (
SELECT Track
, _ID = CAST(Row_Number() OVER (ORDER BY Track) AS VARCHAR)
FROM Distances
GROUP BY Track
)
SELECT #Cols = STUFF((
SELECT ', Track_' + _ID + ' = ''' + Track + ''''
+ ', LastRun_' + _ID + ' = ' + QUOTENAME([Track])
+ ', Distance_' + _ID + '
= SUM(CASE WHEN d.Date = ' + QUOTENAME([Track]) + '
AND d.Track = ''' + Track + '''
THEN d.Distance ELSE NULL END)'
FROM T FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') , 1, 1, '')
SELECT #query = '
With LR AS (
SELECT Person, ' + #pivotCols + '
FROM (SELECT Track, Person, [Date] FROM Distances) d
PIVOT (MAX([Date]) FOR Track IN (' + #pivotCols + ')) pvt
)
SELECT d.Person, ' + #Cols + '
FROM Distances d
INNER JOIN LR ON d.Person = LR.Person AND d.Date IN (' + #pivotCols + ')
GROUP BY d.Person, ' + #pivotCols + ''
execute(#query);
SQLFiddle demo
The first query generate the list of field for the PIVOT.
The second one generate the fields for the compound query.
The PIVOT is used to get, for every person, the last run on every track, that is than joined back to the base data to get the distance

Adding a WHERE statement refering another table in Dynamic SQL

I currently have the following script which is pivoting results from rows into columns. It works a Great, apart from two issues;
I have a flag in another table which I want to filter by (basically: WHERE Table.Stats=YES). I'd normally do this in a basic query with an inner join followed by that WHERE statement. In the query below, I already have a WHERE FileSeq=25, which works, but this criteria I need to get working is calling on a different table.
The query is returning a lot of uncessary NULL fields.
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(col+CAST(rn AS varchar(6)))
FROM
(
SELECT row_number() over(partition by UID ORDER BY ClassCode) rn
FROM dbo.StudentClasses
) d
CROSS APPLY
(
SELECT 'ClassCode', 1
) c (col, so)
GROUP BY col, rn, so
ORDER BY rn, so
FOR XML PATH(''), TYPE
).VALUE('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT UID,' + #cols + '
FROM
(
SELECT UID, col+CAST(rn AS varchar(10)) col, VALUE
FROM
(
SELECT UID, classcode,
row_number() over(partition by UID ORDER BY classcode) rn
FROM StudentClasses WHERE FileSeq=25
) t
CROSS APPLY
(
SELECT ''classcode'', CAST(classcode AS varchar(6))
) c (col, VALUE)
) x
PIVOT
(
MAX(VALUE)
for col in (' + #cols + ')
) p '
EXECUTE(#query)
Any assistance appreciated