sql select parent child recursive in one field - sql

I do not know how to select query recursive..
id idparent jobNO
--------------------------------
1 0 1
2 1 2
3 1 3
4 0 4
5 4 5
6 4 6
how do the results like this With SqlServer
id idparent jobNO ListJob
----------------------------------------
1 0 1 1
2 1 2 1/2
3 1 3 1/3
4 0 4 4
5 4 5 4/5
6 5 6 4/5/6

You need to use a Recursive Common Table Expression.
There are many useful articles online.
Useful Links
Simple Talk: SQL Server CTE Basics
blog.sqlauthority: Recursive CTE
Here is a solution to your question:
CREATE TABLE #TEST
(
id int not null,
idparent int not null,
jobno int not null
);
INSERT INTO #Test VALUES
(1,0,1),
(2,1,2),
(3,1,3),
(4,0,4),
(5,4,5),
(6,5,6);
WITH CTE AS (
-- This is end of the recursion: Select items with no parent
SELECT id, idparent, jobno, CONVERT(VARCHAR(MAX),jobno) AS ListJob
FROM #Test
WHERE idParent = 0
UNION ALL
-- This is the recursive part: It joins to CTE
SELECT t.id, t.idparent, t.jobno, c.ListJob + '/' + CONVERT(VARCHAR(MAX),t.jobno) AS ListJob
FROM #Test t
INNER JOIN CTE c ON t.idParent = c.id
)
SELECT * FROM CTE
ORDER BY id;

Related

Recursive query with parent-child relation

I am trying to make a recursive query in SQL Server, that display data hierarchically. Here is the structure of the table
[id] [int] IDENTITY(1,1) NOT NULL,
[name] [varchar(100)] NOT NULL,
[Parent_Id] [int] NULL,
Each product has a parent. The column Parent_Id content the id of the parent. The parent_id is null for root products.
I want to make a sql query that display products hierarchically. The following image is an example of how the products could be organized.
Products can have products childs.
For the picture above, the query result should be like the following :
id name parent_id
1 P1 NULL
2 P2 NULL
3 P2-1 2
4 P2-2 2
5 P2-3 2
6 P2-3-1 5
7 P2-3-2 5
8 P3 NULL
9 P3-1 8
Here is the request I wrote to achieve it :
with tree as (select * from products
union all
select * from tree where parent_id = tree.id
)
select * from tree;
But I get a result similar to the following:
1 P1 NULL
2 P2 NULL
8 P3 NULL
3 P2-1 2
4 P2-2 2
5 P2-3 2
9 P3-1 8
6 P2-3-1 5
7 P2-3-2 5
What I want is to group each products sibling so that each product is displayed under its direct parent.
Just another option using the data type hierarchyid
There are some additional features and functions associated with hierarchyid
Example
-- Optional See 1st WHERE
Declare #Top int = null --<< Sets top of Hier Try 2
;with cteP as (
Select ID
,parent_id
,Name
,HierID = convert(hierarchyid,concat('/',ID,'/'))
From YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(parent_id ,-1) else ID end
--Where parent_id is null -- Use this where if you always want the full hierarchy
Union All
Select ID = r.ID
,parent_id = r.parent_id
,Name = r.Name
,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
From YourTable r
Join cteP p on r.parent_id = p.ID)
Select Lvl = HierID.GetLevel()
,ID
,parent_id
,Name
From cteP A
Order By A.HierID
Results
Lvl ID parent_id Name
1 1 NULL P1
1 2 NULL P2
2 3 2 P2-1
2 4 2 P2-2
2 5 2 P2-3
3 6 5 P2-3-1
3 7 5 P2-3-2
1 8 NULL P3
2 9 8 P3-1
Just for fun, If I set #Top to 2, the results would be
Lvl ID parent_id Name
1 2 NULL P2
2 3 2 P2-1
2 4 2 P2-2
2 5 2 P2-3
3 6 5 P2-3-1
3 7 5 P2-3-2
Construct the path in the recursive query. The following does this as a string with fixed length ids:
with tree as (
select p.id, p.name, p.parentid,
format(p.parentid, '0000') as path
from products p
where p.parentid is null
union all
select p.id, p.name, p.parentid,
concat(cte.path, '->', format(p.id, '0000')
from tree join
products p
where p.parent_id = t.id
)
select *
from tree;
If I understand this correct and you have the result you want but just unordered, you should be able to just order the result by name.
with tree as (select * from products
union all
select * from tree where parent_id = tree.id
)
select * from tree order by name asc;

SQL Join table to itself

I have a table where there are two columns like below.
value1 DerivedFrom
1 0
2 1
3 2
4 3
5 4
Basically, what it is saying is 1 was new, 2 was derived from 1, 3 was derived from 2 and so on.
I want the out put with 1 as the master key and 2,3,4 and 5 as children.
value1 DerivedFrom
1 0
1 1
1 2
1 3
1 4
Is it achiveble in SQL ? Thanks in advance
As mentioned in the comment, the simplest way is with an rCTE (recursive Common Table Expression):
--Sample Data
WITH YourTable AS(
SELECT *
FROM (VALUES(1,0),
(2,1),
(3,2),
(4,3),
(5,4))V(value1,DerivedFrom)),
--Solution
rCTE AS(
SELECT YT.value1 as rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
WHERE YT.DerivedFrom = 0
UNION ALL
SELECT r.rootValue,
YT.value1,
YT.DerivedFrom
FROM YourTable YT
JOIN rCTE r ON YT.DerivedFrom = r.value1)
SELECT r.rootValue AS value1,
r.DerivedFrom
FROM rCTE r;

how to sql recursion solve first node first then move to another in CTE

suppose i have a data like that
ID ParentID Name
1 null a
2 1 b
3 2 c
4 1 d
5 4 e
if i use cte(common table expression) provided by sql it shows me result like this
ID ParentID Name
1 null a
2 1 b
4 1 d
3 2 c
5 4 e
but i want to arrange data like, query should complete first node till end , then move to other node . like
ID ParentID Name
1 null a
2 1 b
3 2 c
4 1 d
5 4 e
Note: i have a primary key with datatype :uniqueidentifier so i cannot use order by clause after CTE
Example
Declare #Top int = null --<< Sets top of Hier Try 2
;with cteP as (
Select ID
,ParentID
,Name
,Path = cast('/'+[ID]+'/' as varchar(500))
From YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(ParentID ,-1) else ID end
Union All
Select r.ID
,r.ParentID
,r.Name
,cast(p.path + '/'+r.[ID]+'/' as varchar(500))
From YourTable r
Join cteP p on r.ParentID = p.ID)
Select ID
,ParentID
,Name
From cteP A
Order By Path
Returns
Without seeing your query, I think you can do something like this:
WITH CTE_Example
AS
(
YOUR QUERY
)
SELECT *
FROM CTE_Example
ORDER BY ID

Duplicate rows when joining three tables

I'm using SQL Server 2014, and I've got a problem with a query. I've got three tables. A Report consists of ten each of ClothingObservation and HygieneObservation. The way I do this is by referencing the ReportId of Report in ten rows each of the two types of observations, for 20 observations per report in total. I want to select all the rows of one report. When I try to do this, I get 100 rows. My goal is to get 10 rows, or 20 rows with NULL values. This is for testing purposes at the moment, so Report contains just 1 row, and ClothingObservation and HygieneObservation contains 10 rows each, all referencing the ReportId of the one existing report.
My tables, details omitted for clarity:
CREATE TABLE HygieneObservation
(
HygieneObservationId int PRIMARY KEY IDENTITY NOT NULL,
...
ReportId int NOT NULL
)
CREATE TABLE ClothingObservation
(
ClothingObservationId int PRIMARY KEY IDENTITY NOT NULL,
...
ReportId int NOT NULL
)
CREATE TABLE Report
(
ReportId int PRIMARY KEY IDENTITY NOT NULL,
Period Date NOT NULL,
Reporter nvarchar(8) NOT NULL,
DepartmentId int NOT NULL
)
My query:
SELECT
Report.ReportId,
Report.Period,
Report.Reporter,
Report.DepartmentId,
ClothingObservation.ClothingObservationId,
HygieneObservation.HygieneObservationId
FROM Report
LEFT JOIN ClothingObservation ON
(ClothingObservation.ReportId = Report.ReportId)
LEFT JOIN HygieneObservation ON
(HygieneObservation.ReportId = Report.ReportId)
GROUP BY
Report.ReportId,
Period,
Reporter,
DepartmentId,
ClothingObservation.ClothingObservationId,
HygieneObservation.HygieneObservationId
This gives me 100 rows, which I understand is because each row in ClothingObservation is matched to each row in HygieneObservation. I thought that using GROUP BY would cause duplicates to be removed, but I'm obviously doing something wrong. Any hints?
Edit: Here's my data right now (details omitted).
Report:
ReportId Period Reporter DepartmentId
----------- ---------- -------- ------------
1 2016-05-01 username 1
ClothingObservation:
ClothingObservationId ... ReportId
--------------------- ... -----------
1 ... 1
2 ... 1
3 ... 1
4 ... 1
5 ... 1
6 ... 1
7 ... 1
8 ... 1
9 ... 1
10 ... 1
HygieneObservation:
HygieneObservationId ... ReportId
-------------------- ... -----------
3 ... 1
4 ... 1
5 ... 1
6 ... 1
7 ... 1
8 ... 1
9 ... 1
10 ... 1
12 ... 1
13 ... 1
Edit 2: If I run these two queries, I get my desired output (again, irrelevant details omitted from result):
SELECT * FROM Report
LEFT JOIN ClothingObservation ON
(ClothingObservation.ReportId = Report.ReportId)
SELECT * FROM Report
LEFT JOIN HygieneObservation ON
(HygieneObservation.ReportId = Report.ReportId)
ReportId Period Reporter DepartmentId ClothingObservationId ... ReportId
----------- ---------- -------- ------------ --------------------- ...- -----------
1 2016-05-01 username 1 1 ... 1
1 2016-05-01 username 1 2 ... 1
1 2016-05-01 username 1 3 ... 1
1 2016-05-01 username 1 4 ... 1
1 2016-05-01 username 1 5 ... 1
1 2016-05-01 username 1 6 ... 1
1 2016-05-01 username 1 7 ... 1
1 2016-05-01 username 1 8 ... 1
1 2016-05-01 username 1 9 ... 1
1 2016-05-01 username 1 10 ... 1
ReportId Period Reporter DepartmentId HygieneObservationId ... ReportId
----------- ---------- -------- ------------ -------------------- ... -----------
1 2016-05-01 username 1 3 ... 1
1 2016-05-01 username 1 4 ... 1
1 2016-05-01 username 1 5 ... 1
1 2016-05-01 username 1 6 ... 1
1 2016-05-01 username 1 7 ... 1
1 2016-05-01 username 1 8 ... 1
1 2016-05-01 username 1 9 ... 1
1 2016-05-01 username 1 10 ... 1
1 2016-05-01 username 1 12 ... 1
1 2016-05-01 username 1 13 ... 1
My goal is to get this output (or something like it) with one query.
What is happening is that joining Report (1 row) to ClothingObservation (10 rows) produces 10 row (1 x 10), you then join to HygieneObservation (10 rows) which gives you 100. The reason this is happening is because after the initial join you have 10 rows with the same ReportID so the next join takes each of these 10 rows and joins to the 10 rows in HygieneObservation.
The solution for "20 rows with NULL values":
SELECT
Report.ReportId,
Report.Period,
Report.Reporter,
Report.DepartmentId,
ClothingObservation.ClothingObservationId,
NULL AS HygieneObservationId
FROM Report
LEFT JOIN ClothingObservation ON
(ClothingObservation.ReportId = Report.ReportId)
UNION ALL
SELECT
Report.ReportId,
Report.Period,
Report.Reporter,
Report.DepartmentId,
NULL AS ClothingObservationId,
HygieneObservation.HygieneObservationId
FROM Report
LEFT JOIN HygieneObservation ON
(HygieneObservation.ReportId = Report.ReportId)
How it works:
You essentially write two separate queries: one that join Report and ClothingObservation and another that joins Report to HygieneObservation. You then combine the two queries with UNION ALL.
The solution for "get 10 rows"
This is complex as it involves what I call "vertical merging" or "Merge Join". Below is the query (Update: I have tested it).
SELECT
Report.ReportId,
Report.Period,
Report.Reporter,
Report.DepartmentId,
MergedObservations.ClothingObservationId,
MergedObservations.HygieneObservationId
FROM Report
LEFT JOIN
( SELECT COALESCE( ClothingObservation.ReportID, HygieneObservation.ReportID ) AS ReportID,
HygieneObservationID, ClothingObservationID -- Add appropriate columns
FROM
( SELECT ROW_NUMBER() OVER( PARTITION BY ReportID ORDER BY ClothingObservationID ) AS ResultID, ReportID, ClothingObservationID
FROM ClothingObservation ) AS ClothingObservation
FULL OUTER JOIN
( SELECT ROW_NUMBER() OVER( PARTITION BY ReportID ORDER BY HygieneObservationID ) AS ResultID, ReportID, HygieneObservationID
FROM HygieneObservation ) AS HygieneObservation
ON ClothingObservation.ReportID = HygieneObservation.ReportID
AND ClothingObservation.ResultID = HygieneObservation.ResultID
) AS MergedObservations
ON Report.ReportID = MergedObservations.ReportID
How it works:
Because ClothingObservation and HygieneObservationId are not directly related to each other and have differing number of rows per ReportID, I use a ROW_NUMBER() function to generate a join key. I then do a "Merge Join" using ReportID and the output of the ROW_NUMBER() function.
Sample Data
I have converted your sample data into a usable table data to test above queries.
CREATE TABLE Report( ReportId INT, Period DATETIME, Reporter VARCHAR( 20 ), DepartmentId INT )
CREATE TABLE ClothingObservation( ClothingObservationID INT, ReportId INT )
CREATE TABLE HygieneObservation( HygieneObservationID INT, ReportId INT )
INSERT INTO Report
VALUES( 1, '2016-05-01', 'username', 1 )
INSERT INTO ClothingObservation
VALUES
( 1, 1 ), ( 2, 1 ), ( 3, 1 ), ( 4, 1 ), ( 5, 1 ), ( 6, 1 ), ( 7, 1 ), ( 8, 1 ), ( 9, 1 ), ( 10, 1 )
INSERT INTO HygieneObservation
VALUES
( 3, 1 ), ( 4, 1 ), ( 5, 1 ), ( 6, 1 ), ( 7, 1 ), ( 8, 1 ), ( 9, 1 ), ( 10, 1 ), ( 11, 1 ), ( 12, 1 ), ( 13, 1 )
You can also try to use the query below:
SELECT
ReportId = ISNULL(v1.ReportId, v2.ReportId),
Period = ISNULL(v1.Period, v2.Period),
Reporter = ISNULL(v1.Reporter, v2.Reporter),
DepartmentId = ISNULL(v1.DepartmentId, v2.DepartmentId),
v1.ClothingObservationId,
v2.HygieneObservationId
FROM
(
SELECT
RowNumber = ROW_NUMBER() OVER(Partition BY r.ReportId ORDER BY c.ClothingObservationId),
r.ReportId,
r.Period,
r.Reporter,
r.DepartmentId,
c.ClothingObservationId
FROM
Report r
LEFT JOIN ClothingObservation c ON c.ReportId = r.ReportId) v1
FULL JOIN
(
SELECT
RowNumber = ROW_NUMBER() OVER(Partition BY r.ReportId ORDER BY h.HygieneObservationId),
r.ReportId,
r.Period,
r.Reporter,
r.DepartmentId,
h.HygieneObservationId
FROM Report r
LEFT JOIN HygieneObservation h ON h.ReportId = r.ReportId) v2 ON v1.RowNumber = v2.RowNumber AND v1.ReportId = v2.ReportId
ORDER BY ReportId

SQL query for excluding a key value matching record for a specific condition

I've a table as below :
declare #temp table(
PkId int,
DetailId int,
Type int
)
insert into #temp(PkId,DetailId,[Type])
select 1,1,5
union
select 2,1,3
union
select 3,1,4
union
select 4,2,5
union
select 5,3,5
union
select 6,3,3
select * from #temp order by DetailId
returns me
PkId DetailId TypeID
1 1 5
2 1 3
3 1 4
4 2 5
5 3 5
6 3 3
Conditions for getting the records are
For the given 'DetaildID' if only TypeID 5 is present, shall return 5
If 3 or 4 are present then exclude 5
I'm expecting the output as
2 1 3
3 1 4
4 2 5
6 3 3
Please help me with a query.
I don't understand the meaning of the rules, if not some sql puzzle, but it's possible to use the windowing function of SQLServer 2008 to write them
WITH C AS (
SELECT pkId, DetailId, typeID
, _34 = SUM(CASE WHEN TypeID IN (3, 4) THEN 1 ELSE 0 END)
OVER (PARTITION BY DetailId)
, _5 = SUM(TypeID) OVER (PARTITION BY DetailId)
FROM Table1
)
SELECT pkId, DetailId, typeID
FROM C
WHERE (_34 > 0 AND TypeID <> 5)
OR (_5 = 5)
SQLFiddle demo
For every row of a DetaildID group:
_34 is positive if there is a TypeID 3 or a TypeID 4 in the group
_5 will be 5 if the only TypeID in the group is 5
Those value are used in the WHERE condition of the main query to filter the data. The second condition (_5 = 5) don't check for the value of _34 as it's already implicit.
There should probably be a fallback condition in case TypeID has a value different from 3, 4 or 5, the query as it is will return them in a group with 3 or 4 (_34 > 0 AND TypeID <> 5) and remove it otherwise (_34 = 0 AND _5 <> 5).
select pkid, detailid, type
from temp
where type <> 5
group by pkid, detailid, type
union
select pkid, detailid, type
from temp
where detailid not in (
select detailid
from temp
where type <> 5
group by pkid, detailid, type
)
order by pkid
TEST