SQL select -one to many joins want to have the manys - sql

I have two tables, TBL_PARENT (parentID, ParentName) and TBL_CHILDREN (ParentID,Child_Name)
A Parent can have 0 to many children
What I want is a query to give me a list of parent and their children in single row per parent.
For example
Parent1 John,Mary
Parent2 jane,steve,jana
And the number of rows to be the total number of parents

try this query :
I have created 3 table 2 of them are already created on your database #parant, #ch
and the third one is a temp table to put the result in.
create table #parant (id int , name varchar(10))
create table #ch (id int , name varchar(10), pid int)
insert into #parant select 1,'PA'
insert into #parant select 2,'PB'
insert into #parant select 3,'PC'
insert into #ch select 1,'Ca',1
insert into #ch select 1,'Cb',1
insert into #ch select 1,'Cc',1
insert into #ch select 1,'Cd',3
insert into #ch select 1,'Cf',3
insert into #ch select 1,'Ch',1
create table #testTable (id int, name varchar(10),chid int, chname varchar(10), cpid int)
insert into #testTable
select x.id , x.name ,isnull( y.id ,0), isnull(y.name,'') ,isnull(y.pid ,0)
from #parant as x
left outer join #ch as y
on x .id = y .pid
SELECT t.ID, t.name , STUFF(
(SELECT ',' + s.chname
FROM #TestTable s
WHERE s.ID = t.ID
FOR XML PATH('')),1,1,'') AS CSV
FROM #TestTable AS t
GROUP BY t.ID, t.name
GO
drop table #testTable
drop table #ch
drop table #parant
for the above data i got the following result
1 PA Ca,Cb,Cc,Ch
2 PB
3 PC Cd,Cf

SELECT COUNT(P.parentID),
P.ParentName,
C.Child_Name
FROM TBL_PARENT as P
INNER JOIN TBL_CHILDREN as C
WHERE P.parentID == c.ParentID
GROUP BY P.ParentName;
The line P.parentID == c.ParentID is doing the Join, and the line count(P.parentID) is doing the count of all the parents and the line GROUP BY P.ParentName is grouping all the rows by the name of the parent so you can display all the children of every single parent.

Related

How to split String after CONCAT over the repetition of Row Number

I have 2 tables as shown:
I want to CONCAT the two tables by joining them and split them over the repetition of Row Number.
CREATE TABLE #portiontable (
PortionKey NVARCHAR(100),
RN INT,
)
CREATE TABLE #finaltable (
Value NVARCHAR(100),
RN INT,
)
INSERT INTO #finaltable (Value,RN)
VALUES ('KRM__21X0E',1),
('C',2),
('',3),
('',4),
('KRM__21X0E',1),
('C',2),
('',3),
('',4)
INSERT INTO #portiontable (PortionKey,RN)
VALUES ('100',1),
('0AD',2),
('D',3)
SELECT * FROM #finaltable f
SELECT * FROM #portiontable p
SELECT (SELECT ''+ ValuePortionKey
FROM (
SELECT f.RN,f.value,P.PortionKey, f.value + P.PortionKey AS ValuePortionKey
FROM #portiontable p
INNER JOIN #finaltable f ON p.rn = f.rn
) ft
FOR XML PATH('')) as PartSignature
DROP TABLE #portiontable
DROP TABLE #finaltable
The desired output is 2 rows:
PartSignature
KRM__21X0E100C0ADD
KRM__21X0J100K0ADD
The actual output is:
PartSignature
KRM__21X0E100C0ADDKRM__21X0J100K0ADD
Firstly, it seems that you have 2 sets of data in the #finaltable. You need another column to identify it as a set. I have added a ValueSet in the #finaltable.
And, I think your sample data does not correspond to the expected output. I have amended the sample data for #finaltable
And finally, using STRING_AGG to perform the string concatenation, you can then GROUP BY the new ValueSet
CREATE TABLE #portiontable
(
PortionKey NVARCHAR(100),
RN INT,
)
CREATE TABLE #finaltable
(
ValueSet INT,
Value NVARCHAR(100),
RN INT,
)
INSERT INTO #portiontable (PortionKey,RN)
VALUES ('100',1),
('0AD',2),
('D',3)
INSERT INTO #finaltable (ValueSet,Value,RN)
VALUES (1,'KRM__21X0E',1),
(1,'C',2),
(1,'',3),
(1,'',4),
(2,'KRM__21X0J',1),
(2,'K',2),
(2,'',3),
(2,'',4)
SELECT f.ValueSet,
STRING_AGG (f.Value + p.PortionKey, '') AS ValuePortionKey
FROM #portiontable p
INNER JOIN #finaltable f ON p.RN = f.RN
GROUP BY f.ValueSet
DROP TABLE #portiontable
DROP TABLE #finaltable
-- Result
1 KRM__21X0E100C0ADD
2 KRM__21X0J100K0ADD

Get hierarchical data is SQL SERVER with fallback logic

Consider the below schema
dbo.Cultures (Id, CultureCode, ParentId)
Culture table stores the data in the parent-child relationship.
Suppose we have below demo data
5 es-ES 3
Now I have another table which stores the multilingual data for the different cultures.
Schema for the table is as following
dbo.LangData(KeyName, CultureId, Value)
here cultureId is the foreign key of dbo.Cultures table.
Suppose this table has following data
Now I require to fetch the data for all the cultures which are in the Culture table and the corresponding value column in the LangData table.
The culture Ids which are not in the LangData table, for those the Value column will the value of the corresponding parent culture Id columns value. I.e. Data will be retrieved using fallback logic
E.g. For the above values the Result set will be following.
5 es-ES Colour_IN
Here for de-DE data is missing in LangData so it's value will be the data in it's parent culture i.e. en-IN, if in case data also not found in en-IN then it will pick the data of it's parent en-US.
Tried Soloution
First I fetch the culture hierarchy using CTE
CREATE FUNCTION [dbo].[ufnGetCultureHierarchyAll] ()
RETURNS #hierarchyResult TABLE(RowNo INT, CultureId INT, ParentCultureId INT)
AS
BEGIN
WITH CultureHierarchy_CTE(RowNo, CultureId, ParentCultureId)
AS (
SELECT 1,
Id,
ParentId
FROM [dbo].Cultures
UNION ALL
SELECT RowNo + 1,
ou.Id,
ou.ParentId
FROM [dbo].Cultures ou
JOIN CultureHierarchy_CTE cte
ON ou.Id = cte.ParentCultureId
)
-- inserting desired records into table and returning
INSERT INTO #hierarchyResult (RowNo,CultureId,ParentCultureId )
SELECT RowNo, CultureId , ParentCultureId FROM CultureHierarchy_CTE
RETURN;
END
This will return the hierarchy of the all the cultures
Now I attempted to apply join of the result set with the LangData table,
DECLARE #cultureHierarchy AS TABLE(
RowNumber INT,
CultureId INT,
ParentCultureId INT
)
--SELECT * FROM master.Cultures
----Get and store culture hierarchy
INSERT INTO #cultureHierarchy
SELECT RowNo, CultureId, ParentCultureId
FROM ufnGetCultureHierarchyAll()
SELECT c.Code AS [CultureCode],
c.CultureId AS [CultureId],
rv.Value
FROM dbo.LangData rv WITH (NOLOCK)
JOIN #cultureHierarchy c ON rv.CultureId = c.CultureId
END
but it is not working.
Is someone have any Idea regarding same.
Solution using Itzik Ben-Gan's hierarchy model. If you can extend the dbo.Cultures table with Hierarchy, Lvl and Root columns and index on Hierarchy, query will be faster. It has to be rewrited in that case though.
drop table if exists dbo.Cultures;
create table dbo.Cultures (
ID int
, Code varchar(50)
, ParentID int
);
insert into dbo.Cultures (ID, Code, ParentID)
values (1, 'en-US', null), (2, 'en-IN', 1), (3, 'de-DE', 2), (4, 'hi-HI', 2)
drop table if exists dbo.LangData;
create table dbo.LangData (
KeyName varchar(100)
, CultureID int
, Value varchar(100)
);
insert into dbo.LangData (KeyName, CultureID, Value)
values ('lblColourName', 1, 'Color'), ('lblColourName', 2, 'Colour-IN');
with cteCultures as (
select
c.ID, c.Code, c.ParentID, 0 as Lvl
, convert(varchar(max), '.' + CONVERT(varchar(50), c.ID) + '.') as Hierarchy
, c.ID as Root
from dbo.Cultures c
where c.ParentID is null
union all
select
c.ID, c.Code, c.ParentID, cc.Lvl + 1 as Lvl
, cc.Hierarchy + convert(varchar(50), c.ID) + '.' as Hierarchy
, cc.Root as Root
from dbo.Cultures c
inner join cteCultures cc on c.ParentID = cc.ID
)
select
ccr.ID
, ccr.Code
, coalesce(ld.Value, ld2.Value) as Value
from cteCultures ccr
left join dbo.LangData ld on ccr.ID = ld.CultureID
outer apply (
select
top (1) tcc.ID
from cteCultures tcc
inner join dbo.LangData tld on tcc.ID = tld.CultureID
where ld.KeyName is null
and ccr.Hierarchy like tcc.Hierarchy + '%'
and ccr.Hierarchy <> tcc.Hierarchy
order by tcc.Lvl desc
) tt
left join dbo.LangData ld2 on tt.ID = ld2.CultureID
If I understand your question:
We just build your hierarchy (SEQ and Lvl are optional) and then perform TWO left joins in concert with a Coalesce().
Example
Declare #Cultures table (id int,ParentId int,Code varchar(50))
Insert into #Cultures values
( 1, NULL,'en-US')
,( 2, 1 ,'en-IN')
,( 3, 2 ,'de-DE')
,( 4, 2 ,'hi-HI')
Declare #LangData table (keyName varchar(50),CultureId int,Value varchar(50))
Insert Into #LangData values
('lblColourName',1,'Color')
,('lblColourName',2,'Color_IN')
;with cteP as (
Select Seq = cast(10000+Row_Number() over (Order by Code) as varchar(500))
,ID
,ParentId
,Lvl=1
,Code
From #Cultures
Where ParentId is null
Union All
Select Seq = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Code)) as varchar(500))
,r.ID
,r.ParentId
,p.Lvl+1
,r.Code
From #Cultures r
Join cteP p on r.ParentId = p.ID)
Select CultureId = A.ID
,A.Code
,Value = Coalesce(C.Value,B.Value)
From cteP A
Left Join #LangData B on (A.ParentId=B.CultureId)
Left Join #LangData C on (A.Id=C.CultureId)
Order By Seq
Returns
CultureId Code Value
1 en-US Color
2 en-IN Color_IN
3 de-DE Color_IN
4 hi-HI Color_IN

Find and insert dummy rows - possible scenario for OUTER APPLY

This is a mock up of the situation we've got:
IF OBJECT_ID('TEMPDB..#People') IS NOT NULL BEGIN DROP TABLE #People END;
CREATE TABLE #People
(
Name VARCHAR(100),
Category VARCHAR(20),
ID INT
);
INSERT INTO #People
values
('x','Bronze',1),
('y','Bronze',2),
('z','Silver',3),
('j','Gold',4),
('q','Bronze',5),
('x','Silver',1);
IF OBJECT_ID('TEMPDB..#Category') IS NOT NULL BEGIN DROP TABLE #Category END;
CREATE TABLE #Category
(
Category VARCHAR(100)
);
INSERT INTO #Category
values
('Gold'),
('Silver'),
('Bronze');
If a name does not have a Category e.g. x does not have Gold then I'd like a row creating and adding into the table #People with an ID of -1.
Current solution I have is this:
WITH x AS
(
SELECT DISTINCT
x.Name,
s.Category
FROM #People x
CROSS JOIN #Category s
)
INSERT INTO #People
SELECT J.Name,
J.Category,
ID = -1
FROM x J
WHERE NOT EXISTS
(
SELECT 1
FROM #People Q
WHERE J.Name = Q.Name
AND J.Category = Q.Category
);
See it works!...:
SELECT *
FROM #People;
I have a feeling CROSS APPLY might be a good operator to use in order to simplify the above - What is the simplest way to find, create and insert these rows?
insert into People(Name, Category, Id)
(
select distinct
p.Name,
c.Category,
p.Id
from
people p
cross join category c
where
Concat(p.id, c.Category) not in (select Concat(id, Category) from people)
);
http://www.sqlfiddle.com/#!6/92cd5/14

TSQL ParentID Recursive SP

I was wondering if someone could help me.
I have a table that is like this ...
ID ParentID
1 0
2 1
3 2
4 3
5 4
What I want to do is write a recursive TSQL statement that will get all of my parents ID. So for example, if I pass 5 into the stored procedure it would return 4,3,2,1.
Does anyone know how to do this? It would be very much appreciated if someone could help!
This is an example how to achieve this using recursive CTE:
DECLARE #id INT
SET #id = 5
CREATE TABLE #tmp (id INT , ParentId INT)
INSERT INTO #tmp VALUES(1,0)
INSERT INTO #tmp VALUES(2,1)
INSERT INTO #tmp VALUES(3,2);
INSERT INTO #tmp VALUES(4, 3);
INSERT INTO #tmp VALUES(5,4);
WITH parent AS
(
SELECT id, parentId from #tmp WHERE id = #id
UNION ALL
SELECT t.id, t.parentId FROM parent
INNER JOIN #tmp t ON t.id = parent.parentid
)
SELECT id FROM parent
WHERE id <> #id;
--OR
WITH parent AS
(
SELECT tmp1.id, tmp1.parentId from #tmp AS tmp1
INNER JOIN #tmp AS tmp2 ON tmp1.id = tmp2.parentId
WHERE tmp2.id = #id
UNION ALL
SELECT t.id, t.parentId FROM parent
INNER JOIN #tmp t ON t.id = parent.parentid
)
SELECT id FROM parent
Use a recursive common table expression :
http://msdn.microsoft.com/en-us/library/ms186243.aspx

Find lowest common parent in recursive SQL table

Suppose I have a recursive table (e.g. employees with managers) and a list of size 0..n of ids. How can I find the lowest common parent for these ids?
For example, if my table looks like this:
Id | ParentId
---|---------
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 2
6 | 3
7 | 3
8 | 7
Then the following sets of ids lead to the following results (the first one is a corner case):
[] => 1 (or NULL, doesn't really matter)
[1] => 1
[2] => 2
[1,8] => 1
[4,5] => 2
[4,6] => 1
[6,7,8] => 3
How to do this?
EDIT: Note that parent isn't the correct term in all cases. It's the lowest common node in all paths up the tree. The lowest common node can also be a node itself (for example in the case [1,8] => 1, node 1 is not a parent of node 1 but node 1 itself).
Kind regards,
Ronald
Here's one way of doing it; it uses a recursive CTE to find the ancestry of a node, and uses "CROSS APPLY" over the input values to get the common ancestry; you just change the values in #ids (table variable):
----------------------------------------- SETUP
CREATE TABLE MyData (
Id int NOT NULL,
ParentId int NULL)
INSERT MyData VALUES (1,NULL)
INSERT MyData VALUES (2,1)
INSERT MyData VALUES (3,1)
INSERT MyData VALUES (4,2)
INSERT MyData VALUES (5,2)
INSERT MyData VALUES (6,3)
INSERT MyData VALUES (7,3)
INSERT MyData VALUES (8,7)
GO
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS TABLE
AS
RETURN (
WITH Ancestors (Id, ParentId)
AS (
SELECT Id, ParentId
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
SELECT Id FROM Ancestors
);
GO
----------------------------------------- ACTUAL QUERY
DECLARE #ids TABLE (Id int NOT NULL)
DECLARE #Count int
-- your data (perhaps via a "split" udf)
INSERT #ids VALUES (6)
INSERT #ids VALUES (7)
INSERT #ids VALUES (8)
SELECT #Count = COUNT(1) FROM #ids
;
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id
HAVING COUNT(1) = #Count
ORDER BY a.ID DESC
Update if the nodes aren't strictly ascending:
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS #result TABLE (Id int, [Level] int)
AS
BEGIN
WITH Ancestors (Id, ParentId, RelLevel)
AS (
SELECT Id, ParentId, 0
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId, a.RelLevel - 1
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
INSERT #result
SELECT Id, RelLevel FROM Ancestors
DECLARE #Min int
SELECT #Min = MIN([Level]) FROM #result
UPDATE #result SET [Level] = [Level] - #Min
RETURN
END
GO
and
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id, a.[Level]
HAVING COUNT(1) = #Count
ORDER BY a.[Level] DESC
After doing some thinking and some hints in the right direction from Marc's answer (thanks), I came up with another solution myself:
DECLARE #parentChild TABLE (Id INT NOT NULL, ParentId INT NULL);
INSERT INTO #parentChild VALUES (1, NULL);
INSERT INTO #parentChild VALUES (2, 1);
INSERT INTO #parentChild VALUES (3, 1);
INSERT INTO #parentChild VALUES (4, 2);
INSERT INTO #parentChild VALUES (5, 2);
INSERT INTO #parentChild VALUES (6, 3);
INSERT INTO #parentChild VALUES (7, 3);
INSERT INTO #parentChild VALUES (8, 7);
DECLARE #ids TABLE (Id INT NOT NULL);
INSERT INTO #ids VALUES (6);
INSERT INTO #ids VALUES (7);
INSERT INTO #ids VALUES (8);
DECLARE #count INT;
SELECT #count = COUNT(1) FROM #ids;
WITH Nodes(Id, ParentId, Depth) AS
(
-- Start from every node in the #ids collection.
SELECT pc.Id , pc.ParentId , 0 AS DEPTH
FROM #parentChild pc
JOIN #ids i ON pc.Id = i.Id
UNION ALL
-- Recursively find parent nodes for each starting node.
SELECT pc.Id , pc.ParentId , n.Depth - 1
FROM #parentChild pc
JOIN Nodes n ON pc.Id = n.ParentId
)
SELECT n.Id
FROM Nodes n
GROUP BY n.Id
HAVING COUNT(n.Id) = #count
ORDER BY MIN(n.Depth) DESC
It now returns the entire path from the lowest common parent to the root node but that is a matter of adding a TOP 1 to the select.