Row result manipulation - sql

I have an existing query that retrieves this data:
Key Type TextF
--- ---- ------
1 R NULL
1 T TEST
1 T TEST2
2 R NULL
2 T FOO
3 R NULL
Scenario:
Row type R will always have a NULL on TextF. However if the Key has a type T data existing, I should place the TextF on R data, joining them with CRLF or char(13)
Expected output based on given data:
Key Type TextF
--- ---- ----------
1 R TEST TEST2
2 R FOO
3 R NULL
How can I achieve this through a query? I'm trying to make my existing query to be a subquery but I cant seem to make it work.
SELECT T0.*, *formatting here* FROM ( [myQuery] ) T0

I don't think it's the best solution but you could use the STUFF function to achieve your desired results:
SELECT t1.[Key],
'R' [Type],
STUFF((SELECT ' ' + t2.[TextF]
FROM yourTable t2
WHERE t2.[Key] = t1.[Key]
FOR XML PATH('')), 1, 1, '') [TextF]
FROM yourTable t1
GROUP BY t1.[Key]

You can use this.
DECLARE #MyTable TABLE ([Key] INT, Type VARCHAR(5), TextF VARCHAR(100))
INSERT INTO #MyTable VALUES
(1 ,'R', NULL),
(1 ,'T', 'TEST'),
(1 ,'T', 'TEST2'),
(2 ,'R', NULL),
(2 ,'T', 'FOO'),
(3 ,'R', NULL)
SELECT
T.[Key],
T.Type,
CASE WHEN Type = 'R' THEN REPLACE(STUFF(X.TextF,1,1,''),'|', CHAR(13)) ELSE T.TextF END TextF
FROM #MyTable T
OUTER APPLY( SELECT '|' + TextF FROM #MyTable T1
WHERE T.[Key] = T1.[Key]
AND T1.Type <> 'R'
AND T1.TextF IS NOT NULL FOR XML PATH('')) X(TextF)
WHERE T.Type = 'R'
Result:
Key Type TextF
----------- ----- -------------
1 R TEST
TEST2
2 R FOO
3 R NULL

In SQL Server 2017 you can use a new built-in function STRING_AGG
SELECT T0.[Key], T0.[Type],
(SELECT STRING_AGG (T1.TextF, CHAR(13)) AS TextF
FROM [myTable] T1
WHERE T1.[Type]='T' AND T1.[Key]=T0.[Key]
) TextF
FROM [myTable] T0
WHERE T0.[Type]='R'

Slightly different from other solutions--
DECLARE #MyTable TABLE ([Key] INT, Type VARCHAR(5), TextF VARCHAR(100))
INSERT INTO #MyTable VALUES
(1 ,'R', NULL),
(1 ,'T', 'TEST'),
(1 ,'T', 'TEST2'),
(2 ,'R', NULL),
(2 ,'T', 'FOO'),
(3 ,'R', NULL)
SELECT
T.[Key],
T.Type,
STUFF
((
SELECT ' ' + TextF
FROM #MyTable a
WHERE ( a.[Key] = T.[Key] )
FOR XML PATH('')
) ,1,2,'')
AS cusr
FROM #MyTable T
WHERE T.Type = 'R'
OUTPUT
Key Type cusr
----------- ----- --------------
1 R TEST TEST2
2 R FOO
3 R NULL
(3 rows affected)

Related

Need query to join table on column with comma separated

Table1
ID
Notes
ReasonID
1
Test1
[11,12]
2
Test2
[13,14]
Table 2
Reasonid
Name
11
Other1
12
Other2
13
Other3
14
Other4
Result should look like this, where Notes column from Table1 should concat with Name column from Table2.
ID
Final_Notes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
If you use SQL Server 2017+, you may try to parse the ReasonID column as JSON, use an appropriate JOIN and then aggregate with STRING_AGG().
Sample data:
SELECT *
INTO Table1
FROM (VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]')
) t (ID, Notes, ReasonID)
SELECT *
INTO Table2
FROM (VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4')
) t (ReasonID, Name)
Statement:
SELECT
ID,
FinalNotes = CONCAT(
Notes,
',',
(
SELECT STRING_AGG(t2.Name, ',') WITHIN GROUP (ORDER BY CONVERT(int, j.[key]))
FROM OPENJSON(ReasonID) j
-- Important, JOIN with possible implicit conversion
JOIN Table2 t2 ON j.[value] = t2.ReasonID
)
)
FROM Table1
Result:
ID
FinalNotes
1
Test1,Other1,Other2
2
Test2,Other3,Other4
db<>fiddle
Please try the following solution.
It will work starting from SQL Server 2012 onwards.
It is using the following:
XML/XQuery to tokenize comma separated list of values.
FOR XML PATH to compose FinalNotes comma separated list.
SQL
-- DDL and sample data population, start
DECLARE #Table1 TABLE(ID INT, Notes VARCHAR(60), ReasonID VARCHAR(60));
INSERT INTO #Table1(ID, Notes, ReasonID) VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(Reasonid INT, Name VARCHAR(60));
INSERT INTO #Table2(Reasonid, Name) VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT ID, Notes, Name
FROM #Table1 AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(REPLACE(REPLACE(ReasonID,'[',''),']',''), #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c)
CROSS APPLY c.nodes('/root/r/text()') AS t2(x)
INNER JOIN #Table2 AS t3 ON t3.Reasonid = x.value('.', 'INT')
)
SELECT ID, CONCAT(Notes
, (SELECT #separator + c.Name AS [text()]
FROM rs AS c
WHERE c.ID = p.ID
FOR XML PATH(''))) AS FinalNotes
FROM rs AS p
GROUP BY ID, Notes;
Output
+----+---------------------+
| ID | FinalNotes |
+----+---------------------+
| 1 | Test1,Other1,Other2 |
| 2 | Test2,Other3,Other4 |
+----+---------------------+
use SUBSTRING(string, 2, LEN(string)-2) for deleting [] and Parsename to split based on comma and join and concat as follows
Your data
DECLARE #Table1 TABLE(
ID INTEGER NOT NULL,
Notes VARCHAR(60) NOT NULL,
ReasonID VARCHAR(60) NOT NULL
);
INSERT INTO #Table1(ID, Notes, ReasonID)
VALUES
(1, 'Test1', '[11,12]'),
(2, 'Test2', '[13,14]');
DECLARE #Table2 TABLE(
Reasonid INTEGER NOT NULL,
Name VARCHAR(60) NOT NULL
);
INSERT INTO #Table2(Reasonid, Name)
VALUES
(11, 'Other1'),
(12, 'Other2'),
(13, 'Other3'),
(14, 'Other4');
your query
SELECT id,
Concat(notes, ',', T2.name, ',', T3.name) FinalNotes
FROM (SELECT id,
notes,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 2) R1,
Parsename(Replace(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2), ',', '.'), 1) R2
FROM #table1) T1
join #table2 T2
ON T1.R1 = T2.reasonid
join #table2 T3
ON T1.R2 = T3.reasonid
by using XML
DROP TABLE IF EXISTS #t -- temporary table
select t1.ID,t1.Notes, Name into #t -- temporary table
from
(
SELECT A.ID,a.Notes,
Split.a.value('.', 'VARCHAR(100)') AS String
FROM (SELECT ID, Notes,
CAST ('<M>' + REPLACE(SUBSTRING(ReasonID, 2, LEN(ReasonID)-2) , ',', '</M><M>') + '</M>' AS XML) AS String
FROM #Table1) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) t1
join #Table2 t2 on t1.String=t2.Reasonid
---XML Path
SELECT ID,concat(notes,',',
STUFF((SELECT ', ' + CAST(name AS VARCHAR(10)) [text()]
FROM #t t1
WHERE t1.ID = t.ID
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ')) FinalNotes
FROM #t t
GROUP BY ID,notes

Selecting data against numeric values saved as comma separated string

I have two sql tables and looking for a sql query to select data against each numeric value in Table1.ValueID column from Table2.ValueDescription column and save result in Table3
Table1:
ID ValueID
1 1,12,14
2 3,5,15
3 2,6,13,16
Table2:
ValueID ValueDescription
1 Motor
2 Low
3 Failed
4 New Install
5 New Item
6 Max Value
7 AC Current
8 DC Current
9 Not Reached
10 NA
11 Cutoff
12 Manual
13 Automatic
14 Device Not Found
15 Halt
16 Renew
Expected Result:
Table3:
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Using SQL Server Management Studio
Here is the query I tried
SELECT Table1.ValueID,
Stuff((SELECT ',' + CAST(Table2.Description AS VARCHAR(100))
FROM Table2
WHERE Table1.ValueID LIKE Table2.ValueID
FOR Xml Path('')),1,1,'')
FROM Table1
what I am missing here?
If in fact you really using SQL Server 2017, you can use both the STRING_SPLIT and the STRING_AGG functions. They make for a very easy syntax.
IF OBJECT_ID('tempdb..#Table1', 'U') IS NOT NULL
DROP TABLE #Table1;
CREATE TABLE #Table1 (
ID INT NOT NULL PRIMARY KEY,
ValueID VARCHAR(50) NOT NULL
);
INSERT #Table1 (ID, ValueID) VALUES
(1, '1,12,14'),
(2, '3,5,15'),
(3, '2,6,13,16');
IF OBJECT_ID('tempdb..#Table2', 'U') IS NOT NULL
DROP TABLE #Table2;
CREATE TABLE #Table2 (
ValueID INT NOT NULL PRIMARY KEY,
ValueDescription VARCHAR(50) NOT NULL
);
INSERT #Table2(ValueID, ValueDescription) VALUES
(1, 'Motor'),
(2, 'Low'),
(3, 'Failed'),
(4, 'New Install'),
(5, 'New Item'),
(6, 'Max Value'),
(7, 'AC Current'),
(8, 'DC Current'),
(9, 'Not Reached'),
(10, 'NA'),
(11, 'Cutoff'),
(12, 'Manual'),
(13, 'Automatic'),
(14, 'Device Not Found'),
(15, 'Halt'),
(16, 'Renew');
--SELECT * FROM #Table1 t1;
--SELECT * FROM #Table2 t2;
--========================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',')
FROM
STRING_SPLIT(t1.ValueID, ',') ss
JOIN #Table2 t2
ON CONVERT(INT, ss.value) = t2.ValueID
) csv;
The results...
ID ValueID Result
----------- -------------- -----------------------------------
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Edit:
-
-============================================================================
-- This is an idea that I've been kicking around for a little while now.
-- It's based on the SUSPICION that, when left to it's own devices. STRING_SPLIT
-- will always retun rows in the original order and attaching a row_number()
-- to the output, right out of the gate, will effectively serve as an "ItemNumber.
--============================================================================
SELECT
t1.ID,
t1.ValueID,
csv.Result
FROM
#Table1 t1
CROSS APPLY (
SELECT
Result = STRING_AGG(t2.ValueDescription, ',') WITHIN GROUP (ORDER BY rs.rn DESC) -- sort in the descending order for no real eason...
FROM (
SELECT
rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
ValueID = CONVERT(INT, ss.value)
FROM
STRING_SPLIT(t1.ValueID, ',') ss
) rs
JOIN #Table2 t2
ON rs.ValueID = t2.ValueID
) csv;
ID ValueID Result
----------- ------------- --------------------------------
1 1,12,14 Device Not Found,Manual,Motor
2 3,5,15 Halt,New Item,Failed
3 2,6,13,16 Renew,Automatic,Max Value,Low
This will keep the proper sequence
Example
Select A.*
,B.*
From Table1 A
Cross Apply (
Select Result = Stuff((Select ',' +B2.ValueDescription
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>' + replace(A.ValueID,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B1
Join Table2 B2 on B1.RetVal=B2.ValueID
Order by RetSeq
For XML Path ('')),1,1,'')
) B
Returns
ID ValueID Result
1 1,12,14 Motor,Manual,Device Not Found
2 3,5,15 Failed,New Item,Halt
3 2,6,13,16 Low,Max Value,Automatic,Renew
Oops -- Just saw you are 2017
It's not that much prettier but the new built-in functions in SQL Server 2017 do make this a little easier to follow, and can still be made to respect the order of the original list (well, I can't even tell if you intended to order by location in the list or by numerical order, since those are the same), then provided it is all integers and there are no duplicates:
;WITH explode(ID, ValueID, value, i) AS
(
SELECT t1.ID,
t1.ValueID,
TRY_CONVERT(int,f.value),
CHARINDEX(',' + f.value + ',', ',' + t1.ValueID + ',')
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID,
-- guarantee respect original order:
Result = STRING_AGG(t2.ValueDescription,',') WITHIN GROUP (ORDER BY x.i)
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
If order doesn't matter, and you are sure there can be no duplicates or non-integers in the ValueID list in Table1, it is much simpler:
;WITH explode(ID, ValueID, value) AS
(
SELECT t1.ID, t1.ValueID, f.value
FROM dbo.Table1 t1
CROSS APPLY STRING_SPLIT(t1.ValueID, ',') AS f
)
SELECT x.ID, x.ValueID, STRING_AGG(t2.ValueDescription,',')
FROM explode AS x
INNER JOIN dbo.Table2 AS t2
ON x.value = t2.ValueID
GROUP BY x.ID, x.ValueID
ORDER BY x.ID;
You can do like
SELECT *,
STUFF(
(
SELECT ',' + ValueDescription
FROM T2
WHERE ',' + T1.ValueID + ',' LIKE '%,' + CAST(T2.ValueID AS VARCHAR) + ',%'
FOR XML PATH('')
),
1, 1, ''
) ValueDescription
FROM T1;
Returns:
+----+-----------+-------------------------------+
| ID | ValueID | ValueDescription |
+----+-----------+-------------------------------+
| 1 | 1,12,14 | Motor,Manual,Device Not Found |
| 2 | 3,5,15 | Failed,New Item,Halt |
| 3 | 2,6,13,16 | Low,Max Value,Automatic,Renew |
+----+-----------+-------------------------------+
Demo

Can't figure out how to join tables due to comma separated values

I have a table of the following structure.
select loginid,alloted_area from tbllogin
Which returns this result.
loginid alloted_area
------------- ---------------------------
01900017 22,153,169,174,179,301
01900117 254,91,92,285,286,287
01900217 2,690,326,327,336
17900501 null
17900601 28,513,409,410
17901101 254,91,92,285
17901701 59,1302,1303
17902101 2,690,326,327
17902301 20,159,371,161
17902401 null
I have another table tblarea whose ids are stored in comma separated values in the above tables when an area is assigned to a user. I want to join these two tables and leave entries like the last one that has not yet been assigned an area. Now I have been told several times on that storing data in comma separated values is a bad practice(I suppose it's because of the problem that I am facing) I know that but this structure has been created by another developer at my company not me so please help instead of downvoting. This is what I have tried:
declare #csv varchar(max)='';
SELECT #CSV = COALESCE(#CSV + ', ', '') + case when alloted_area is null or alloted_area='' then '0' else alloted_area end from tbllogin;
select * from tblarea where id in (select 0 union select sID from splitstring(#CSV,','));
This does get the area but there is no way it can give me the login of users that the areas have been assigned to. Sample input and output.
tbllogin
loginid alloted_area
------------- ---------------------------
a1 1,3,5
a2 2,4
a3 1,4
a4 null
tblarea
id area_name
------------- ---------------------------
1 v
2 w
3 x
4 y
5 z
After joining I need this result
login_id area_name
------------- ---------------------------
a1 v
a1 x
a1 z
a2 w
a2 y
a3 v
a3 y
By Using Split and CROSS APPLY we can achieve the desired Output
DECLARE #tbllogin TABLE (LoginID CHAR(2) NOT NULL PRIMARY KEY, alloted_area VARCHAR(MAX));
INSERT #tblLogin (LoginID, alloted_area)
VALUES ('a1', '1,3,5'), ('a2', '2,4'),('a3', '1,4'), ('a4', NULL);
DECLARE #tblArea TABLE (ID INT NOT NULL PRIMARY KEY, Area_Name CHAR(1));
INSERT #tblArea (ID, Area_Name)
VALUES (1, 'v'), (2, 'w'), (3, 'x'), (4, 'y'), (5, 'z');
SELECT Dt.LoginID,A.Area_Name FROm
(
SELECT LoginID,Split.a.value('.', 'VARCHAR(1000)') AS alloted_area
FROM (
SELECT LoginID,CAST('<S>' + REPLACE(alloted_area, ',', '</S><S>') + '</S>' AS XML) AS alloted_area
FROM #tbllogin
) AS A
CROSS APPLY alloted_area.nodes('/S') AS Split(a)
)DT
Inner join
#tblArea A
on A.ID=DT.alloted_area
OutPut
LoginID Area_Name
--------------------
a1 v
a1 x
a1 z
a2 w
a2 y
a3 v
a3 y
Consider this split function:
CREATE FUNCTION [dbo].[SplitString]
(
#List NVARCHAR(MAX),
#Delim VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT [Value] FROM
(
SELECT
[Value] = LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim, #List + #Delim, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim + #List, [Number], LEN(#Delim)) = #Delim
) AS y
);
Then you can do a query like this:
SELECT
t.loginid,tblarea.area_name
FROM
tbllogin AS t
CROSS APPLY(SELECT value FROM SplitString(t.alloted_area,',')) as split
JOIN tblarea ON tblarea.id=split.Value
You can join using LIKE, e.g. CONCAT(',', alloted_area, ',') LIKE CONCAT('%,', ID, ',%')
So for a full example
-- SAMPLE DATA
DECLARE #tbllogin TABLE (LoginID CHAR(2) NOT NULL PRIMARY KEY, alloted_area VARCHAR(MAX));
INSERT #tblLogin (LoginID, alloted_area)
VALUES ('a1', '1,3,5'), ('a2', '2,4'),('a3', '1,4'), ('a4', NULL);
DECLARE #tblArea TABLE (ID INT NOT NULL PRIMARY KEY, Area_Name CHAR(1));
INSERT #tblArea (ID, Area_Name)
VALUES (1, 'v'), (2, 'w'), (3, 'x'), (4, 'y'), (5, 'z');
-- QUERY
SELECT l.LoginID,
a.Area_Name
FROM #tblLogin AS l
INNER JOIN #tblArea AS a
ON CONCAT(',', l.alloted_area, ',') LIKE CONCAT('%,', a.ID, ',%')
ORDER BY l.LoginID;
OUTPUT
LoginID Area_Name
--------------------
a1 v
a1 x
a1 z
a2 w
a2 y
a3 v
a3 y
You could arguably split allocated_area into separate rows, but the article Split strings the right way – or the next best way by Aaron Bertrand shows that in these circumstances LIKE will outperform the any of the split functions.
Although you have said that you know it is a bad design, I can't in good conscience not mention it in my answer, so whichever method you choose is not a substitute for fixing how this is stored. If not by you, by whoever designed it.
The correct method would be a junction table, tblLoginArea:
LoginID AreaID
------------------
a1 1
a1 3
a1 5
a2 2
a2 4
....etc
Then if the developers still need the csv format, then they can create a view, and update their references to that:
CREATE VIEW dbo.LoginAreaCSV
AS
SELECT l.LoginID,
Allocated_Area = STUFF(la.AllocatedAreas.value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM tblLogin AS l
OUTER APPLY
( SELECT CONCAT(',', la.AreaID)
FROM tblLoginArea AS la
WHERE la.LoginID = l.LoginID
ORDER BY la.AreaID
FOR XML PATH(''), TYPE
) AS la (AllocatedAreas);
And your query can be done using equality predicates that can be optimised with indexed:
SELECT l.LoginID, a.Area_Name
FROM tblLogin AS l
INNER JOIN tblLoginArea AS la
ON la.LoginID = l.LoginID
INNER JOIN tblArea AS a
ON a.ID = la.AreaID;
Example on DB Fiddle

SQL - string combine based on id

Need suggestion to split string in table 1, match its Ids with table 2 and concatenate the values.
Table - 1
Id Tbl1Col
1 2
2 2,4
3
4 6
5 3
Table - 2
Id Tbl2Col
1 E
2 F
3 M
4 U
5 P
6 C
7 N
8 G
Query -
SELECT T2.Tbl2Col
FROM Table1 AS T1
LEFT JOIN Table2 AS T2 WHERE T1.Tbl1Col= T2.Id
WHERE T1.Id = #Id
Now If #Id = 1, Output is F -- works fine
Now If #Id = 2, Output should be FU -- should not be F,U
Yuck! But you can use LIKE:
SELECT T2.Tbl2Col
FROM Table1 T1 LEFT JOIN
Table2 T2
WHERE ',' + T1.Tbl1Col + ',' LIKE '%,' + CAST(T2.Id as VARCHAR(255)) + ',%'
WHERE T1.Id = #Id;
You have a lousy data format, so this cannot make use of indexes. You should really have a separate table, with one row per Table1.id and Table2.id. Such a table is called a junction table or an association table.
create table dbo.Table01 (
Id int
, Col varchar(100)
);
create table dbo.Table02 (
Id int
, Col varchar(100)
);
insert into dbo.Table01 (Id, Col)
values (1, '2'), (2, '2, 4');
insert into dbo.Table02 (Id, Col)
values (1, 'E'), (2, 'F'), (4, 'U');
select
t.Id
, replace(STRING_AGG (t02.Col, ','), ',', '') as StringAgg
from dbo.Table01 t
cross apply string_split (t.Col, ',') as ss
inner join dbo.Table02 t02 on ss.value = t02.Id
group by t.id
Follow the next approach:-
1) Turning a Comma Separated string into individual rows via using CROSS APPLY with XML
2) Join the two tables with left join.
3) Concatenate many rows with same id via using STUFF & FOR XML
4) Use Replace function for removing comma.
Demo:-
declare #MyTable table (id int , Tbl1Col varchar(10))
insert into #MyTable values (1,'2'),(2,'2,4'),(3,''),(4,'6'),(5,'3')
declare #MyTable2 table (id int , Tbl2Col varchar(10))
insert into #MyTable2 values (1,'E'),(2,'F'),(3,'M'),(4,'U'),(5,'P'),(6,'C'),(7,'N'),(8,'G')
select a.id , Tbl2Col
into #TestTable
from
(
SELECT A.id,
Split.a.value('.', 'VARCHAR(100)') AS Tbl1Col
FROM
(
SELECT id,
CAST ('<M>' + REPLACE(Tbl1Col, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM #MyTable
) AS A CROSS APPLY Data.nodes ('/M') AS Split(a) ) a
left join #MyTable2 b
on a.Tbl1Col = b.id
order by a.id
SELECT id, Tbl2Col =
Replace(STUFF((SELECT DISTINCT ', ' + Tbl2Col
FROM #TestTable b
WHERE b.id = a.id
FOR XML PATH('')), 1, 2, ''),',','')
FROM #TestTable a
GROUP BY id
Output:-
1 F
2 F U
3 NULL
4 C
5 M
References:-
Turning a Comma Separated string into individual rows
How to concatenate many rows with same id in sql?
Finally:-
Don't use this approach, and normalize your database instead , just use it as fun/training/trying .... etc code.

Query to select and combine uppermost and lowermost value from tables

We have the following tables on our SQL Server 2012.
Table A (data):
ID, Description
---------------
1 , Bla 1
2 , Bla 2
3 , Bla 3
Table P (data):
ID, ParentID, Name
------------------
1 , NULL , AAA
2 , 3 , CCC
3 , 1 , XXX
Table X (foreign keys A_ID to A.ID and P_ID to P.ID):
ID, A_ID, P_ID
--------------
1 , 1 , 1
2 , 1 , 2
3 , 2 , 1
4 , 2 , 2
5 , 2 , 3
6 , 3 , 1
Question:
We need a query something like:
SELECT ...
WHERE A_ID = 1
which should return this result:
ID, Name, Subname
-----------------
2 , AAA , CCC
Name needs to contain the upper most Name from Table P, i.e. the one that has no ParentID.
Subname needs to contain the bottom most Name from the Table P for which the ID still exists in Table X.
ID needs to contain the ID from Table X where P_ID is the ID of the bottom most child.
Another example:
SELECT ...
WHERE A_ID = 2
should return this result:
ID, Name, Subname
-----------------
4 , AAA , CCC
And
SELECT ...
WHERE A_ID = 3
should return this result:
ID, Name, Subname
-----------------
6 , AAA , NULL
We've tried various queries, but some work only for 'where A_ID = 1' and not for 'where A_ID = 2'. In order to select the lowest level child from P, we've looked at the 'How to select lowest level in hierarchy form table post' which probably comes in handy for the query we're looking for.
A single query would be nice, but we will accept a stored procedure as well.
Thanks in advance!
Information
The ID columns in all tables are primary keys
The ID columns in any given table can be changed to any other value in the sample data, while taking into account the primary and foreign key constraints. (E.g. changing P.ID '2' to '4' also results in the change of X.P_ID's '2' to '4'.) This is to show that ID's are not necessarily in order.
Values in the column P.Name can be any non-null value.
Table P can have multiple rows with ParentId set to null.
Sample Data
Taken from #NEER
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
Try with the below query. I think Table A is not required for getting the desired result.
SELECT TOP 1 First_VALUE(x.ID) OVER(ORDER BY x.ID desc) ID
,First_VALUE(Name) OVER(ORDER BY p.ID) Name
,CASE WHEN First_VALUE(Name) OVER(ORDER BY p.ID) = First_VALUE(Name) OVER(ORDER BY p.ID desc) THEN NULL
ELSE First_VALUE(Name) OVER(ORDER BY p.ID desc) END SubName
FROM [table P] p
JOIN [table X] x
ON p.ID=x.[P_ID]
WHERE x.[A_ID]=3
Try following query
DECLARE #TableA AS TABLE (ID INT,Des NVARCHAR(MAX));
Insert Into #TableA VALUES(1,'Bal 1'); Insert Into #TableA VALUES(2,'Bal 2'); Insert Into #TableA VALUES(3,'Bal 3');
DECLARE #TableP AS TABLE (ID INT,ParentID INT,Name NVARCHAR(MAX));
Insert Into #TableP VALUES(1,Null,'AAA'); Insert Into #TableP VALUES(2,1,'BBB'); Insert Into #TableP VALUES(3,2,'CCC');
DECLARE #TableX AS TABLE (ID INT,A_ID INT,P_ID INT);
Insert Into #TableX Values(1,1,1); Insert Into #TableX Values(2,1,2); Insert Into #TableX Values(3,2,1); Insert Into #TableX Values(4,2,3); Insert Into #TableX Values(5,3,1);
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Inner Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=1
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=2
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=3
Order by X.ID Desc
You can try the following
with report as (
select max(x.ID) as ID, min(x.P_ID) as MinP, max(x.P_ID) as MaxP
from X x
where x.A_ID = 1 -- <-- here you can change the value
)
select r.ID,
mn.Name as Name,
case when r.MinP = r.MaxP then null else mx.Name end as Subname
from report r
inner join P mn on mn.ID = r.MinP
inner join P mx on mx.ID = r.MaxP
Hope this will help you
Try it with a GROUP BY:
SELECT x.a_id, max(x.id) AS id, min(p.name) AS name,
CASE WHEN max(p.name) = min(p.name) THEN NULL
ELSE max(p.name) END AS subname
FROM p INNER JOIN x
ON p.id = x.p_id
GROUP BY x.a_id
HAVING x.a_id = 1
Still works with your updated sample data. Tested here: http://sqlfiddle.com/#!9/99597f/1
You can use Recursive CTE as the below:
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
DECLARE #A_ID INT = 2
;WITH Parents
AS
(
SELECT
P.ID, P.ParentID, P.Name
FROM #P P WHERE P.ParentID IS NULL
UNION ALL
SELECT
P.ID, Parent.ID, Parent.Name
FROM
#P P INNER JOIN
Parents Parent ON P.ParentID = Parent.ID
), Temp
AS
(
SELECT
X.ID,
Parent.Name Name,
IIF(P.ParentID IS NULL, NULL, P.Name) SubName
FROM
#A A INNER JOIN
#X X ON X.A_ID = A.ID INNER JOIN
#P P ON X.P_ID = P.ID LEFT JOIN
Parents Parent ON P.ParentID = Parent.ID OR (P.ParentID IS NULL AND P.ID = Parent.ID)
WHERE
A.ID = #A_ID
), MainTable
AS
(
SELECT
Temp.ID ,
Temp.Name ,
Temp.SubName,
COUNT(Temp.ID) OVER (PARTITION BY Temp.Name ORDER BY (SELECT NULL)) CountOfRowByParent
FROM
Temp
)
SELECT
MainTable.ID ,
MainTable.Name ,
MainTable.SubName
FROM
MainTable
WHERE
(
MainTable.CountOfRowByParent > 1 AND
MainTable.SubName IS NOT NULL
) OR
MainTable.CountOfRowByParent = 1
Result for 2:
ID Name SubName
4 AAA CCC
5 AAA XXX