Get one row of grouped objects - sql

I have a table that contains a Husband to Wife realtion.
The table contains two rows for each realtion(BTW,gender has no meaning.it could be Husband-Husband and Wife-Wife. just saying). meaning, the table might show result of two rows for a "connection":
Wife--Husband and\or Husband--Wife
The table looks like this:
Id1 | Id2 | ConnectiondID | RelatedConnectionId
-----------------------------------------------------
123 | 333 | FF45 | F421
333 | 123 | F421 | FF45
456 | 987 | F333 | F321
987 | 456 | F321 | F333
My expected result is to have only one relation per group:
Id1 | Id2
----------
123 | 333
456 | 987

This is actually very simple assuming you only want couples and your ID values are all unique and numeric, and does not require any self joins, functions or grouping:
declare #t table(Id1 int,Id2 int,ConnectiondID nvarchar(5),RelatedConnectionId nvarchar(5));
insert into #t values(123,333,'FF45','F421'),(333,123,'F421','FF45'),(456,444,'FF46','F422'),(444,456,'F422','FF46'),(789,555,'FF47','F423'),(555,789,'F423','FF47');
select *
from #t
where Id1 < Id2
order by Id1
Output:
+-----+-----+---------------+---------------------+
| Id1 | Id2 | ConnectiondID | RelatedConnectionId |
+-----+-----+---------------+---------------------+
| 123 | 333 | FF45 | F421 |
| 444 | 456 | F422 | FF46 |
| 555 | 789 | F423 | FF47 |
+-----+-----+---------------+---------------------+

If I am understanding your question correctly, you need to perform a self-join on the table e.g. ON t1.id1 = t2.id2 or ON t1.ConnectionId = t2.RelatedConnectionID and obviously this is joining both ways.
To limit this to just one way add a condition on the join predicate such that one of the values is less than or greater than the other; e.g.
DECLARE #tbl table( Id1 smallint PRIMARY KEY, Id2 smallint,ConnectiondID char(5),RelatedConnectionId char(5));
INSERT #tbl(Id1,Id2,ConnectiondID,RelatedConnectionId)
VALUES(123,333,'FF45','F421'),
(333,123,'F421','FF45'),
(456,222,'FF45','F421'),
(222,456,'F421','FF45'),
(789,111,'FF45','F421'),
(111,789,'F421','FF45');
SELECT *
FROM #tbl t1
JOIN #tbl t2 ON t2.Id1 = t1.Id2 AND t2.Id1 > t1.Id1;

For example
DECLARE #T TABLE (id1 int, id2 int,ConnectiondID varchar(5),RelatedConnectionId varchar(5) )
INSERT INTO #T (Id1,Id2,ConnectiondID,RelatedConnectionId)
VALUES
(123 , 333 ,'FF45','F421'),
(333 , 123 , 'F421','FF45'),
(2123 , 2333 ,'2FF45','2F421'),
(2333 , 2123 , '2F421','2FF45'),
(3 , 2 , 'AAAA','BBB'),
(2 , 3 , 'BBB','AAAA')
SELECT
a.*
FROM
#t a
WHERE
CASE
WHEN ConnectiondID > RelatedConnectionId
THEN RelatedConnectionId
ELSE NULL
END IS NULL

Related

Get the information from one column into another SQL

can anyone help me to solve this problem?
I have two tables I have to fetch all customers who have an unexpired transaction. However, this condition must be true for both tables. For this reason I made a join between the two tables to verify that both dates are not expired,if both exist otherwise if the second is null only the first.Finally I should merge the information I have horizontally into a single column.
For example in this case i have this result for the join:
I would like to union the information of the last three columns in the first three so as to have a single block.
id_client,id_tran,expiry with id_client 1,2,3,5,6 all togheter.
create table tab1 (
id_client int,
id_trans int,
expiry date)
create table tab2 (
id_client int,
id_trans int,
expiry date)
insert into tab1 values (1,101,'02-03-2020')
insert into tab1 values (1,102,'22-07-2022')
insert into tab1 values (2,201,'23-05-2023')
insert into tab1 values (3,301,'02-03-2022')
insert into tab1 values (3,302,'12-10-2024')
insert into tab2 values (4,101,'12-03-2023')
insert into tab2 values (5,102,'21-08-2024')
insert into tab2 values (6,201,'21-08-2024')
SELECT * from tab1 a
left join tab2 b
on a.id_trans = b.id_trans
where a.expiry > getdate()
AND coalesce(B.expiry,'2900-12-31') > getdate()
Here are 2 queries, one of which will return all records when a client has more than 1 unexpired transaction per table, and the other which uses max to return the latest one. (We could equally user min to get the oldest one or string_agg to list them all.)
with clients as
(select id_client from tab1
union select id_client from tab2)
select
c.id_client,
t1.id_trans t1,
t1.expiry t1_expiry,
t2.id_trans t2,
t2.expiry t2_expiry
from clients c
left join tab1 t1
on c.id_client = t1.id_client
left join tab2 t2
on c.id_client = t2.id_client
where coalesce(t1.expiry,'2900-12-31') > getdate()
and coalesce(t2.expiry,'2900-12-31') > getdate()
order by c.id_client;
GO
id_client | t1 | t1_expiry | t2 | t2_expiry
--------: | ---: | :--------- | ---: | :---------
1 | 102 | 2022-07-22 | 101 | 2022-09-12
2 | 201 | 2023-05-23 | null | null
3 | 302 | 2024-10-12 | null | null
4 | null | null | 101 | 2023-03-12
5 | null | null | 102 | 2024-08-21
6 | null | null | 201 | 2024-08-21
6 | null | null | 101 | 2022-09-12
with clients as
(select id_client from tab1
union select id_client from tab2)
select
c.id_client,
max(t1.id_trans) t1,
max(t1.expiry) t1_expiry,
max(t2.id_trans) t2,
max(t2.expiry) t2_expiry
from clients c
left join tab1 t1
on c.id_client = t1.id_client
left join tab2 t2
on c.id_client = t2.id_client
where coalesce(t1.expiry,'2900-12-31') > getdate()
and coalesce(t2.expiry,'2900-12-31') > getdate()
group by c.id_client
order by c.id_client;
GO
id_client | t1 | t1_expiry | t2 | t2_expiry
--------: | ---: | :--------- | ---: | :---------
1 | 102 | 2022-07-22 | 101 | 2022-09-12
2 | 201 | 2023-05-23 | null | null
3 | 302 | 2024-10-12 | null | null
4 | null | null | 101 | 2023-03-12
5 | null | null | 102 | 2024-08-21
6 | null | null | 201 | 2024-08-21
db<>fiddle here

Matching records across multiple possible IDs

I have multiple records with sparsely populated identifiers (I will call these ID Numbers). I can have a maximum of two different ID Numbers per record and want to be able to traverse all the related records together so that I can create a single shared identifier. I want to achieve this in a T-SQL query.
Essentially, here is some sample data:
+-------+-------+--------+-----+------+
| RowId | ID1 | ID2 | ID3 | ID4 |
+-------+-------+--------+-----+------+
| 1 | 11111 | | | |
| 2 | 11111 | | | |
| 3 | 11111 | AAAAA | | |
| 4 | | BBBBBB | BC1 | |
| 5 | | | BC1 | O111 |
| 6 | | GGGGG | BC1 | |
| 7 | | AAAAA | | O111 |
| 8 | | CCCCCC | | |
| 9 | 99999 | | | |
| 10 | 99999 | DDDDDD | | |
| 11 | | | | O222 |
| 12 | | EEEEEE | | O222 |
| 13 | | EEEEEE | | O333 |
+-------+-------+--------+-----+------+
So for example,
11111 is linked to AAAAA in RowId3,
and AAAAA is also linked to O111 in rowId 7.
O111 is linked to BC1 in RowId 5.
BC1 is linked to BBBBBB in RowId 4,
etc.
Also,
I want to create a new single identifier once all of these rows are linked.
Here is the output I want to achieve for all of the data above:
Denormalised:
+---------+-------+--------+-----+------+
| GroupId | ID1 | ID2 | ID3 | ID4 |
+---------+-------+--------+-----+------+
| 1 | 11111 | AAAAA | BC1 | O111 |
| 1 | 11111 | BBBBBB | BC1 | O111 |
| 1 | 11111 | GGGGG | BC1 | O111 |
| 2 | | CCCCCC | | |
| 3 | 99999 | DDDDDD | | |
| 4 | | EEEEEE | | O222 |
| 4 | | EEEEEE | | O333 |
+---------+-------+--------+-----+------+
Normalized (probably better to work with):
+--------+----------+---------+
| IDType | IDNumber | GroupId |
+--------+----------+---------+
| ID1 | 11111 | 1 |
| ID2 | AAAAA | 1 |
| ID2 | BBBBBB | 1 |
| ID2 | GGGGG | 1 |
| ID3 | BC1 | 1 |
| ID4 | O111 | 1 |
| ID2 | CCCCCC | 2 |
| ID1 | 99999 | 3 |
| ID2 | DDDDDD | 3 |
| ID2 | EEEEEE | 4 |
| ID4 | O222 | 4 |
| ID4 | O333 | 4 |
+--------+----------+---------+
I am looking for SQL code to generate the output above or similar normalized structure. Thanks.
EDIT:
Here is some code to create data that matches the sample data in the table above.
DROP TABLE IF EXISTS #ID
CREATE TABLE #ID
(
RowId INT,
ID1 VARCHAR(100),
ID2 VARCHAR(100),
ID3 VARCHAR(100),
ID4 VARCHAR(100)
)
INSERT INTO #ID VALUES
(1,'11111',NULL,NULL,NULL),
(2,'11111',NULL,NULL,NULL),
(3,'11111','AAAAA',NULL,NULL),
(4,NULL,'BBBBBB','BC1',NULL),
(5,NULL,NULL,'BC1','O111'),
(6,NULL,'GGGGG','BC1',NULL),
(7,NULL,'AAAAA',NULL,'O111'),
(8,NULL,'CCCCCC',NULL,NULL),
(9,'99999',NULL,NULL,NULL),
(10,'99999','DDDDDD',NULL,NULL),
(11,NULL,NULL,NULL,'O222'),
(12,NULL,'EEEEEE',NULL,'O222'),
(13,NULL,'EEEEEE',NULL,'O333')
It is easy to get your normalized output.
I'm using my query from How to find all connected subgraphs of an undirected graph with minor modification to convert your data into pairs that define edges of a graph. The query treats the data as edges in a graph and traverses recursively all edges of the graph, stopping when the loop is detected. Then it puts all found loops in groups and gives each group a number.
Your source table has four IDs, but each row can have only two IDs, so we know that each row has a pair of IDs. My query expects this kind of data (pairs of IDs). It is easy to convert four IDs into a pair - use COALESCE.
For detailed explanation of how it works, see How to find all connected subgraphs of an undirected graph.
Query
WITH
CTE_Idents
AS
(
SELECT ID1 AS Ident, 'ID1' AS IDType
FROM #T
UNION
SELECT ID2 AS Ident, 'ID2' AS IDType
FROM #T
UNION
SELECT ID3 AS Ident, 'ID3' AS IDType
FROM #T
UNION
SELECT ID4 AS Ident, 'ID4' AS IDType
FROM #T
)
,CTE_Pairs
AS
(
SELECT COALESCE(ID1, ID2, ID3, ID4) AS Ident1, COALESCE(ID4, ID3, ID2, ID1) AS Ident2
FROM #T
UNION
SELECT COALESCE(ID4, ID3, ID2, ID1) AS Ident1, COALESCE(ID1, ID2, ID3, ID4) AS Ident2
FROM #T
)
,CTE_Recursive
AS
(
SELECT
CAST(CTE_Idents.Ident AS varchar(8000)) AS AnchorIdent
, Ident1
, Ident2
, CAST(',' + Ident1 + ',' + Ident2 + ',' AS varchar(8000)) AS IdentPath
, 1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Idents ON CTE_Idents.Ident = CTE_Pairs.Ident1
UNION ALL
SELECT
CTE_Recursive.AnchorIdent
, CTE_Pairs.Ident1
, CTE_Pairs.Ident2
, CAST(CTE_Recursive.IdentPath + CTE_Pairs.Ident2 + ',' AS varchar(8000)) AS IdentPath
, CTE_Recursive.Lvl + 1 AS Lvl
FROM
CTE_Pairs
INNER JOIN CTE_Recursive ON CTE_Recursive.Ident2 = CTE_Pairs.Ident1
WHERE
CTE_Recursive.IdentPath NOT LIKE CAST('%,' + CTE_Pairs.Ident2 + ',%' AS varchar(8000))
)
,CTE_RecursionResult
AS
(
SELECT AnchorIdent, Ident1, Ident2
FROM CTE_Recursive
)
,CTE_CleanResult
AS
(
SELECT AnchorIdent, Ident1 AS Ident
FROM CTE_RecursionResult
UNION
SELECT AnchorIdent, Ident2 AS Ident
FROM CTE_RecursionResult
)
SELECT
CTE_Idents.IDType
,CTE_Idents.Ident
,CASE WHEN CA_Data.XML_Value IS NULL
THEN CTE_Idents.Ident ELSE CA_Data.XML_Value END AS GroupMembers
,DENSE_RANK() OVER(ORDER BY
CASE WHEN CA_Data.XML_Value IS NULL
THEN CTE_Idents.Ident ELSE CA_Data.XML_Value END
) AS GroupID
FROM
CTE_Idents
CROSS APPLY
(
SELECT CTE_CleanResult.Ident+','
FROM CTE_CleanResult
WHERE CTE_CleanResult.AnchorIdent = CTE_Idents.Ident
ORDER BY CTE_CleanResult.Ident FOR XML PATH(''), TYPE
) AS CA_XML(XML_Value)
CROSS APPLY
(
SELECT CA_XML.XML_Value.value('.', 'NVARCHAR(MAX)')
) AS CA_Data(XML_Value)
WHERE
CTE_Idents.Ident IS NOT NULL
ORDER BY GroupID, IDType, Ident;
Result
+--------+--------+------------------------------------+---------+
| IDType | Ident | GroupMembers | GroupID |
+--------+--------+------------------------------------+---------+
| ID1 | 11111 | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID2 | AAAAA | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID2 | BBBBBB | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID2 | GGGGG | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID3 | BC1 | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID4 | O111 | 11111,AAAAA,BBBBBB,BC1,GGGGG,O111, | 1 |
| ID1 | 99999 | 99999,DDDDDD, | 2 |
| ID2 | DDDDDD | 99999,DDDDDD, | 2 |
| ID2 | CCCCCC | CCCCCC, | 3 |
| ID2 | EEEEEE | EEEEEE,O222,O333, | 4 |
| ID4 | O222 | EEEEEE,O222,O333, | 4 |
| ID4 | O333 | EEEEEE,O222,O333, | 4 |
+--------+--------+------------------------------------+---------+
This is how your data looks like as a graph:
I rendered this image using DOT from https://www.graphviz.org/.
How to convert this nomalized output into denormalized? One way is to unpivot it using the help of IDType, though it might get tricky if the graph can have several loops. You'd better ask another question specifically about converting nomalized dataset into denormalized.
Well, this was a real brain twister ;-) and my solution is just close... Try this:
General remarks:
I do not think, that T-SQL is the right tool for this...
This structure is open to deeply nested chains. Although there are only 4 IDs, the references can lead to unlimited depth, circles and loops
This is - in a way - a gaps and island issue
The query
WITH cte AS
(
SELECT RowId
,A.ID
,A.sourceId
,ROW_NUMBER() OVER(PARTITION BY RowId ORDER BY A.SourceId) AS IdCounter
FROM #ID
CROSS APPLY (VALUES('ID1',ID1),('ID2',ID2),('ID3',ID3),('ID4',ID4)) A(sourceId,ID)
WHERE A.ID IS NOT NULL
)
,AllIDs AS
(
SELECT RowId
,MAX(CASE WHEN IdCounter=1 THEN ID END) AS FirstId
,MAX(CASE WHEN IdCounter=1 THEN sourceId END) AS FirstSource
,MAX(CASE WHEN IdCounter=2 THEN ID END) AS SecondId
,MAX(CASE WHEN IdCounter=2 THEN sourceId END) AS SecondSource
FROM cte
GROUP BY RowId
)
,recCTE AS
(
SELECT RowId
,FirstId
,FirstSource
,SecondId
,SecondSource
,CAST(N'|' + FirstId AS NVARCHAR(MAX)) AS RunningPath
FROM AllIDs WHERE SecondId IS NULL
UNION ALL
SELECT ai.RowId
,ai.FirstId
,ai.FirstSource
,ai.SecondId
,ai.SecondSource
,r.RunningPath + CAST(N'|' + ai.FirstId AS NVARCHAR(MAX))
FROM AllIDs ai
INNER JOIN recCTE r ON ai.RowId<>r.RowId AND (ai.FirstId=r.FirstId OR ai.FirstId=r.SecondId OR ai.SecondId=r.FirstId OR ai.SecondId=r.SecondId )
WHERE r.RunningPath NOT LIKE CONCAT('%|',ai.FirstId,'|%')
)
,FindIslands AS
(
SELECT FirstId
,FirstSource
,SecondId
,SecondSource
,CONCAT(CanonicalPath,'|') AS CanonicalPath
FROM recCTE
CROSS APPLY(SELECT CAST('<x>' + REPLACE(CONCAT(RunningPath,'|',SecondId),'|','</x><x>') + '</x>' AS XML)) A(Casted)
CROSS APPLY(SELECT Casted.query('
for $x in distinct-values(/x[text()])
order by $x
return <x>{concat("|",$x)}</x>
').value('.','nvarchar(max)')) B(CanonicalPath)
)
,MaxPaths AS
(
SELECT fi.CanonicalPath
,x.CanonicalPath AS BestPath
,LEN(x.CanonicalPath) AS PathLength
,ROW_NUMBER() OVER(PARTITION BY fi.CanonicalPath ORDER BY LEN(x.CanonicalPath) DESC) AS SortIndex
FROM FindIslands fi
INNER JOIN FindIslands x ON LEN(x.CanonicalPath)>=LEN(fi.CanonicalPath) AND x.CanonicalPath LIKE CONCAT('%',fi.CanonicalPath,'%' )
--GROUP BY fi.CanonicalPath
)
,AlmostCorrect AS
(
SELECT *
FROM
(
SELECT mp.BestPath,fi.FirstId AS ID,FirstSource AS IDSource
FROM FindIslands fi
INNER JOIN MaxPaths mp On mp.SortIndex=1 AND fi.CanonicalPath=mp.CanonicalPath
UNION ALL
SELECT mp.BestPath,fi.SecondId,SecondSource
FROM FindIslands fi
INNER JOIN MaxPaths mp On mp.SortIndex=1 AND fi.CanonicalPath=mp.CanonicalPath
) t
WHERE ID IS NOT NULL
GROUP BY BestPath,ID,IDSource
)
SELECT * FROm AlmostCorrect;
The result
+--------------------------------+--------+----------+
| BestPath | ID | IDSource |
+--------------------------------+--------+----------+
| |11111|AAAAA|BBBBBB|BC1|GGGGG| | 11111 | ID1 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BBBBBB|BC1|GGGGG| | AAAAA | ID2 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BBBBBB|BC1|GGGGG| | BBBBBB | ID2 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BBBBBB|BC1|GGGGG| | BC1 | ID3 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BBBBBB|BC1|GGGGG| | GGGGG | ID2 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BC1|GGGGG| | BC1 | ID3 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BC1|GGGGG| | GGGGG | ID2 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BC1|O111| | BC1 | ID3 |
+--------------------------------+--------+----------+
| |11111|AAAAA|BC1|O111| | O111 | ID4 |
+--------------------------------+--------+----------+
| |11111|AAAAA|O111| | AAAAA | ID2 |
+--------------------------------+--------+----------+
| |11111|AAAAA|O111| | O111 | ID4 |
+--------------------------------+--------+----------+
| |99999|DDDDDD| | 99999 | ID1 |
+--------------------------------+--------+----------+
| |99999|DDDDDD| | DDDDDD | ID2 |
+--------------------------------+--------+----------+
| |CCCCCC| | CCCCCC | ID2 |
+--------------------------------+--------+----------+
| |EEEEEE|O222|O333| | EEEEEE | ID2 |
+--------------------------------+--------+----------+
| |EEEEEE|O222|O333| | O222 | ID4 |
+--------------------------------+--------+----------+
| |EEEEEE|O222|O333| | O333 | ID4 |
+--------------------------------+--------+----------+
The idea behind:
You can see the result of each intermediate step simply by using SELECT * FROM [cte-name] as last select (out-comment the current last select).
The CTE "cte" will transform your side-by-side structure to a row-based set.
Following your statement, that you have a maximum of two different ID Numbers per record the second CTE "AllIDs" will transform this set to a set with two IDs keeping knowledge of where this ID was taken from.
Now we go into recursion. We start with all IDs, where the second ID is NULL (WARNING, You might not catch all, the recursion anchor might need some more thinking) and find any linked row (either by ID1 or by ID2). While traversing down we create a path of all visited IDs and we stop, if we re-visit one of them.
The cte "FindIslands" will transform this path to XML and use XQuery's FLWOR in order to return the path alphabetically sorted.
The cte "MaxPaths" will find the longest path of a group in order to find paths which are completely embedded within other paths.
The cte "AlmostCorrect" will now re-transform this to a row-based set and pick the rows with the longest path.
What we have achieved:
All your IDs show the same "IDSource" as your own example.
You can see, how the IDs are linked with each other.
What we did not yet achieve:
The paths |11111|AAAAA|BBBBBB|BC1|GGGGG|, |11111|AAAAA|BC1|GGGGG|, |11111|AAAAA|BC1|O111|, |11111|AAAAA|O111| are treated as different, although their fragments are overlapping.
At the moment I'm to tired to think about this... Might be a get an idea tomorrow ;-)
I don't quite understand the structure of the expected result, but the key of your query is to assemble the nodes into subgraphs, while giving each subgraph an ID (you call it GroupId).
I leave the final rendering of the result to you since you probably understand in detail why you want to show it in that way. A few LEFT JOINs will do the trick.
Anyway, here's the query that produces the subgraphs:
with
p as (
select
row_id, row_id as min_id,
cast(concat(':', row_id, ':') as varchar(1000)) as walked,
case when id1 is null then ':' else cast(concat(':', id1, ':') as varchar(1000)) end as i1,
case when id2 is null then ':' else cast(concat(':', id2, ':') as varchar(1000)) end as i2,
case when id3 is null then ':' else cast(concat(':', id3, ':') as varchar(1000)) end as i3,
case when id4 is null then ':' else cast(concat(':', id4, ':') as varchar(1000)) end as i4
from t
union all
select
t.row_id, case when t.row_id < p.min_id then t.row_id else p.min_id end,
cast(concat(walked, t.row_id, ':') as varchar(1000)),
case when t.id1 is null then p.i1 else cast(concat(p.i1, id1, ':') as varchar(1000)) end,
case when t.id2 is null then p.i2 else cast(concat(p.i2, id2, ':') as varchar(1000)) end,
case when t.id3 is null then p.i3 else cast(concat(p.i3, id3, ':') as varchar(1000)) end,
case when t.id4 is null then p.i4 else cast(concat(p.i4, id4, ':') as varchar(1000)) end
from p
join t on p.i1 like concat('%:', t.id1, ':%')
or p.i2 like concat('%:', t.id2, ':%')
or p.i3 like concat('%:', t.id3, ':%')
or p.i4 like concat('%:', t.id4, ':%')
where p.walked not like concat('%:', t.row_id, ':%')
),
g as (
select min_id as min_id, min(walked) as nodes
from p
where not exists (
select 1
from t
where (p.i1 like concat('%:', t.id1, ':%')
or p.i2 like concat('%:', t.id2, ':%')
or p.i3 like concat('%:', t.id3, ':%')
or p.i4 like concat('%:', t.id4, ':%'))
and p.walked not like concat('%:', t.row_id, ':%')
)
group by min_id
)
select row_number() over(order by min_id) as group_id, nodes from g
Result:
group_id nodes
-------- ---------------
1 :1:2:3:7:5:4:6:
2 :8:
3 :10:9:
4 :11:12:13:
For reference, here's the data script I used to test:
create table t (
row_id int,
id1 int,
id2 varchar(10),
id3 varchar(10),
id4 varchar(10)
);
insert into t (row_id, id1, id2, id3, id4) values
(1, '11111', null, null, null),
(2, '11111', null, null, null),
(3, '11111', 'AAAAA', null, null),
(4, null, 'BBBBB', 'BC1', null),
(5, null, null, 'BC1', '0111'),
(6, null, 'GGGGG', 'BC1', null),
(7, null, 'AAAAA', null, '0111'),
(8, null, 'CCCCCC', null, null),
(9, '99999', null, null, null),
(10, '99999', 'DDDDD', null, null),
(11, null, null, null, '0222'),
(12, null, 'EEEEE', null, '0222'),
(13, null, 'EEEEE', null, '0333');
Note: I can imagine the performance of this query being quite slow. A solution in PostgreSQL would be much performant since -- unlike SQL Server -- it implements UNION in recursive CTEs. This could remove entire tree branches much earlier in the graph walk compared to UNION ALL (the only choice in SQL Server).
In Such question 2-3 different sample data help us understand the pattern of data.
It help in writing better query.
DROP TABLE IF EXISTS #ID
CREATE TABLE #ID
(
RowId INT,
ID1 VARCHAR(100),
ID2 VARCHAR(100),
ID3 VARCHAR(100),
ID4 VARCHAR(100)
)
INSERT INTO #ID VALUES
(1,'11111',NULL,NULL,NULL),
(2,'11111',NULL,NULL,NULL),
(3,'11111','AAAAA',NULL,NULL),
(4,NULL,'BBBBBB','BC1',NULL),
(5,NULL,NULL,'BC1','O111'),
(6,NULL,'GGGGG','BC1',NULL),
(7,NULL,'AAAAA',NULL,'O111'),
(8,NULL,'CCCCCC',NULL,NULL),
(9,'99999',NULL,NULL,NULL),
(10,'99999','DDDDDD',NULL,NULL),
(11,NULL,NULL,NULL,'O222'),
(12,NULL,'EEEEEE',NULL,'O222'),
(13,NULL,'EEEEEE',NULL,'O333')
;With CTE as
(
select distinct RowId, IDNumber,IDType
--,ROW_NUMBER()over(order by rowid)rn
from
(select * from #ID)p
unpivot(IDNumber for IDType in(ID1,ID2,ID3,ID4)) as unpvt
)
,CTE2 as
(
select c.*
,ROW_NUMBER()over(partition by rowid order by rowid desc)rn1
from CTE C
)
,CTE3 as
(
select *
,dense_rank()over( order by idnumber)rn3
--,1 rn3
from cte2 c
where rn1=1
and not exists(select 1 from cte2 c1
where c1.RowId=c.RowId and c1.rn1>c.rn1)
)
,CTE4 as
(
select RowId,IDNumber,IDType,rn3 as Groupid
,1 lvl
from cte3 c
where rowid>1
union all
select c.RowId,c.IDNumber,c.IDType,c1.GroupID
,lvl+1
from CTE2 C
inner join CTE4 C1 on (
(c.IDNumber=c1.IDNumber and c.RowId<>c1.RowId )
or (c.RowId=c1.RowId and c.IDNumber<>c1.IDNumber)
)
where lvl<=8
)
select distinct IDNumber,IDType,Groupid
--,RowId
from cte4
order by Groupid
IN CTE I have first UnPivoted the result.
In CTE2 & CTE3 together I am creating GroupID beforehand, according to what I have understood.
CTE4 is recursive .
This script can be optimized after checking 2-3 different sample data.
With CTE4 result it can be again PIVOTED to your DeNormalize form.
I think this is ideal situation to try Cursor with optimized script.

Join multiple tables using SQL & T-SQL

Unfortunately, I cannot be sure that the name of my question is correct here.
Example of initial data:
Table 1 Table 2 Table 3
| ID | Name | | ID | Info1 | | ID | Info2 |
|----|-------| |----|-------| |----|-------|
| 1 | Name1 | | 1 | text1 | | 1 | text1 |
| 2 | Name2 | | 1 | text1 | | 1 | text1 |
| 3 | Name3 | | 2 | text2 | | 1 | text1 |
| 2 | text2 | | 2 | text2 |
| 3 | text3 |
In my initial data I have relationship between 3 tables by field ID.
I need to join table2 and table3 to the first table, but if I do sequential join, like left join table2 and left join table3 by ID I will get additional records on second join, because there will be several records with one ID after first join.
I need to get records of table2 and table3 like a list in each column for ID of first table.
Here an example of expected result:
Table 3
| ID | Name |Info1(Table2)|Info2(Table3)|
|-------|-----------|-------------|-------------|
| 1 | Name1 | text1 | text1 |
| 1 | Name1 | text1 | text1 |
| 1 | Name1 | null | text1 |
| 2 | Name2 | text2 | text2 |
| 2 | Name2 | text2 | null |
| 3 | Name3 | null | text3 |
This is the method I would use, however, the table design you have could probably be improved on; why are Table2 and Table3 separate in the first place?
USE Sandbox;
GO
CREATE TABLE dbo.Table1 (ID int, [Name] varchar(5))
INSERT INTO dbo.Table1 (ID,
[Name])
VALUES(1,'Name1'),
(2,'Name1'),
(3,'Name3');
CREATE TABLE dbo.Table2 (Id int,Info1 varchar(5));
CREATE TABLE dbo.Table3 (Id int,Info2 varchar(5));
INSERT INTO dbo.Table2 (Id,
Info1)
VALUES(1,'text1'),
(1,'text1'),
(2,'text2'),
(2,'text2');
INSERT INTO dbo.Table3 (Id,
Info2)
VALUES(1,'text1'),
(1,'text1'),
(1,'text1'),
(2,'text2'),
(3,'text3');
WITH T2 AS(
SELECT ID,
Info1,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) AS RN --SELECT NULL as you have no other columns to actually create an order
FROM Table2),
T3 AS(
SELECT ID,
Info2,
ROW_NUMBER() OVER (PARTITION BY ID ORDER BY (SELECT NULL)) AS RN
FROM Table3),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I --Assuming you have 10 or less matching items
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N))
SELECT T1.ID,
T1.[Name],
T2.info1,
T3.info2
FROM Table1 T1
CROSS JOIN Tally T
LEFT JOIN T2 ON T1.ID = T2.ID AND T.I = T2.RN
LEFT JOIN T3 ON T1.ID = T3.ID AND T.I = T3.RN
WHERE T2.ID IS NOT NULL OR T3.ID IS NOT NULL
ORDER BY T1.ID, T.I;
GO
DROP TABLE dbo.Table1;
DROP TABLE dbo.Table2;
DROP TABLE dbo.Table3;
If you have more than 10 rows, then you could build a "proper" tally table on the fly, or create a physical one. One on the fly is probably going to be a better idea though, as I doubt you're going to have 100's of matching rows.

Get count of combinations of rows having different values in different columns

I want get count of combinations of rows having different values in different columns.
Sample Data as below:
+------+---------+---------+
| GUID | Column1 | Column2 |
+------+---------+---------+
| XXX | A | aaa |
| XXX | B | bbb |
| YYY | C | ccc |
| YYY | D | ddd |
| XXX | A | aaa |
| XXX | B | bbb |
+------+---------+---------+
I am expecting following result. So XXX should be 2 as we are having 2 records in which Column1=A, Column2=aaa and Column1=B, Column2=bbb (Combination of two different columns values)
XXX 2
YYY 1
You can group by GUID and Column2, then take the max of count(*) to get the number of combinations:
declare #tmp table ([GUID] varchar(3), Column1 varchar(1), Column2 varchar(3))
insert into #tmp values ('XXX','A','aaa'),('XXX','B','bbb'),('YYY','C','ccc'),
('YYY','D','ddd'),('XXX','A','aaa'),('XXX','B','bbb')
select T.[GUID], max(T.cnt) as count_combinations
from (
select [GUID], Column2, count(*) as cnt
from #tmp
group by [GUID], Column2
) T
group by T.[GUID]
Results:

How to Select Two tables without union and union all?

Table1:
Id | Text | Parent Id | Number
**************************************
101 |robber | 201 | 1
102 |devel | 202 | 1
103 |programmer | 203 | 3
Table 2
Id | TO id | Parent Id | Number
**************************************
102 |355 | 201 | 1
104 |366 | 202 | 2
105 |377 | 203 | Null
I need to join two tables without using Union and union All
Out Put Like:
(Both table columns are same expect one To Id that columns add to last )
Id | Text | Parent Id | Number | To Id
101 |robber | 201 | 1 | Null
102 |devel | 202 | 2 | null
103 |programmer | 203 | 3 |Null
102 |Null | 201 | 1 |355
104 | Null | 202 | 2 | 366
105 |Null | 203 | null | 377
Try full join
select isnull(a.id,b.id) as id,
a.Text1,isnull(a.ParentId,b.ParentId) parentid,
isnull(a.Number,b.Number) numm,TOid
from #t a
full join #t1 b on a.Id=b.Id and a.ParentId=b.ParentId
data
declare #t table (Id int,Text1 varchar(50),ParentId int, Number int) insert into #t
(Id,Text1,ParentId, Number) values
(101 ,'robber' , 201 , 1),
(102 ,'devel' , 202 , 1),
(103 ,'programmer' , 203 , 3)
declare #t1 table (Id int,TOid int,ParentId int, Number int) insert into #t1
(Id,TOid,ParentId, Number) values
(102 ,355 , 201 , 1),
(104 ,366 , 202 , 2),
(105 ,377 , 203 , Null)
and for the non-union way you can use a temp table as follows
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
CREATE TABLE #TempTable (
Id INT
,[Text] VARCHAR(20)
,ParentId INT
,Number INT
,ToId INT
)
INSERT INTO #TempTable (Id, [Text], ParentId, Number)
SELECT
Id
,[Text]
,ParentId
,Number
FROM
TableNameA
INSERT INTO #TempTable (Id, ToId, ParentId, Number)
SELECT
Id
,ToId
,ParentId
,Number
FROM
TableNameB
SELECT *
FROM
#TempTable
I would only use this way in circumstances that I definitely want a temp table of the results or if my logic has to be broken up for some reason, very rare for the later. There are still other ways yet but if you are not using a temp table union all should perform better than the other ways.