Find leaf nodes in hierarchical tree - sql

I have a table in my database which stores a tree structure. Here are the relevant fields:
mytree (id, parentid, otherfields...)
I want to find all the leaf nodes (that is, any record whose id is not another record's parentid)
I've tried this:
SELECT * FROM mytree WHERE `id` NOT IN (SELECT DISTINCT `parentid` FROM `mytree`)
But that returned an empty set. Strangely, removing the "NOT" returns the set of all the non-leaf nodes.
Can anyone see where I'm going wrong?
Update: Thanks for the answers folks, they all have been correct and worked for me. I've accepted Daniel's since it also explains why my query didn't work (the NULL thing).

Your query didn't work because the sub-query includes NULL. The following slight modification works for me:
SELECT * FROM `mytree` WHERE `id` NOT IN (
SELECT DISTINCT `parentid` FROM `mytree` WHERE `parentid` IS NOT NULL)

No clue why your query didn't work. Here's the identical thing in left outer join syntax - try it this way?
select a.*
from mytree a left outer join
mytree b on a.id = b.parentid
where b.parentid is null

SELECT * FROM mytree AS t1
LEFT JOIN mytree AS t2 ON t1.id=t2.parentid
WHERE t2.parentid IS NULL

Select * from mytree where id not in (Select distinct parentid from mytree where parentid is not null)
http://archives.postgresql.org/pgsql-sql/2005-10/msg00228.php

select *
from `mytree `
where not exists (select *
from `mytree ` as `nodes`
where `categories`.`id` = `nodes`.`parent`)

my table structure is
memberid MemberID joiningposition packagetype
RPM00000 NULL Root free
RPM71572 RPM00000 Left Royal
RPM323768 RPM00000 Right Royal
RPM715790 RPM71572 Left free
RPM323769 RPM71572 Right free
RPM715987 RPM323768 Left free
RPM323985 RPM323768 Right free
RPM733333 RPM323985 Right free
RPM324444 RPM715987 *emphasized text*Right Royal
--
ALTER procedure [dbo].[sunnypro]
as
DECLARE #pId varchar(40) = 'RPM00000';
Declare #Id int
set #Id=(select id from registration where childid=#pId)
begin
-- Recursive CTE
WITH R AS
(
SELECT
BU.DateofJoing,
BU.childid,
BU.joiningposition,
BU.packagetype
FROM registration AS BU
WHERE
BU.MemberID = #pId and
BU.joiningposition IN ('Left', 'Right')
or BU.packagetype in('Royal','Platinum','Majestic')
and BU.Id>#id
UNION All
-- Recursive part
SELECT
BU.DateofJoing,
BU.childid,
R.joiningposition,
BU.packagetype
FROM R
JOIN registration AS BU
ON BU.MemberID = R.childid
WHERE
BU.joiningposition IN ('Left', 'Right') and
BU.packagetype in('Royal','Platinum','Majestic')
and BU.Id>#id
)
INSERT INTO Wallatpayout
(childid
,packagetype
,joiningposition
,DateofJoing
,Total)
-- Final groups of nodes found
SELECT top 3
R.childid,
R.packagetype,
R.joiningposition,
R.DateofJoing,
Total = COUNT_BIG(*)
FROM R where R.packagetype in('Royal','Platinum','Majestic')
GROUP BY R.childid,
R.joiningposition,
R.DateofJoing,
R.packagetype
OPTION (MAXRECURSION 0);
end

Related

Use Exists with a Column of Query Result?

I have 2 tables.
One is bom_master:
CHILD
PARENT
1-111
66-6666
2-222
77-7777
2-222
88-8888
3-333
99-9999
Another one is library:
FileName
Location
66-6666_A.step
S:\ABC
77-7777_C~K1.step
S:\DEF
And I want to find out if the child's parents have related files in the library.
Expected Result:
CHILD
PARENT
FileName
1-111
66-6666
66-6666_A.step
2-222
77-7777
77-7777_C~K1.step
Tried below lines but return no results. Any comments? Thank you.
WITH temp_parent_PN(parentPN)
AS
(
SELECT
[PARENT]
FROM [bom_master]
where [bom_master].[CHILD] in ('1-111','2-222')
)
SELECT s.[filename]
FROM [library] s
WHERE EXISTS
(
SELECT
*
FROM temp_parent_PN b
where s.[filename] LIKE '%'+b.[parentPN]+'%'
)
If you have just one level of dependencies use the join solution proposed by dimis164.
If you have deeper levels you could use recursive queries allowed by WITH clause (
ref. WITH common_table_expression (Transact-SQL)).
This is a sample with one more level of relation in bom_master (you could then join the result of the recursive query with library as you need).
DECLARE #bom_master TABLE (Child NVARCHAR(MAX), Parent NVARCHAR(MAX));
INSERT INTO #bom_master VALUES
('1-111', '66-666'),
('2-222', '77-777'),
('3-333', '88-888'),
('4-444', '99-999'),
('A-AAA', '1-111');
WITH
leaf AS ( -- Get the leaf elements (elements without a child)
SELECT Child FROM #bom_master b1
WHERE NOT EXISTS (SELECT * FROM #bom_master b2 WHERE b2.Parent = b1.Child) ),
rec(Child, Parent, Level) AS (
SELECT b.Child, b.Parent, Level = 1
FROM #bom_master b
JOIN leaf l ON l.Child = b.Child
UNION ALL
SELECT rec.Child, b.Parent, Level = rec.Level + 1
FROM rec
JOIN #bom_master b
ON b.Child = rec.Parent )
SELECT * FROM rec
I think you don't have to use exists. The problem is that you need to substring to match the join.
Have a look at this:
SELECT b.CHILD, b.PARENT, l.[FileName]
FROM [bom_master] b
INNER JOIN [library] l ON b.PARENT = SUBSTRING(l.FileName,1,7)

SQL How to get full Array in a temporary table with condition?

Well, I am really sorry because my explanation was so poor. Thank you for all the answers.
I will explain better what should be the output and what is my question.
So, first of I have an array of tagCodes like ('code0','code1','code2').
Then I have a table that contains Codes and TagTypeId.
I would like to get into a temporary table all the codes I passed in the array with their TagTypeId. So a table like:
Code
TagTypeId
903420012408181609019A18
2456
903420012408181609019A18
2135
TestCodeNull
null
So my attempt was this one:
SELECT Tags.Code AS tagCode, Tags.TagTypeId, TagTypes.Code AS tagType
INTO #TempTable
FROM Tags JOIN TagTypes ON Tags.TagTypeId = TagTypes.Id
WHERE Tags.Code IN ('903420012408181609019A18','90341808151313061101E938', 'TestCodeNull')
SELECT * FROM #TempTable;
But I dont get the codes that are not in the Tag table.
I did this an it seems to be working as intended:
CREATE TABLE #TestTable (tagCode NVARCHAR(25) NOT NULL, TagTypeId INT NULL, tagType NVARCHAR(MAX))
INSERT INTO #TestTable (tagCode) VALUES ('903420012408181609019A18'),('00007E08190D0A34E1F524D0'),('00007E08190D0B25E1F5A98B')
UPDATE #TestTable SET TagTypeId = Tags.TagTypeId, tagType = TagTypes.Code FROM #TestTable
LEFT JOIN Tags ON (#TestTable.tagCode = Tags.Code)
LEFT JOIN TagTypes ON (Tags.TagTypeId = TagTypes.Id)
SELECT * FROM #TestTable;
I think what you mean that 'TestCodeNull' does not exist in tags so you want to show null for 'TestCodeNull' in which case a join may be more appropriate. for example
SELECT S.CODE,Tags.Code AS tagCode, Tags.TagTypeId,
TagTypes.Code AS tagType
INTO #TempTable
FROM (select '903420012408181609019A18' code
union all select '90341808151313061101E938'
union all select 'TestCodeNull') s
left join Tags on tags.code = s.code
left JOIN TagTypes ON Tags.TagTypeId = TagTypes.Id
SELECT * FROM #TempTable;

remove Union from query

I have query to get category and subcategories of same category. I used below query:
Select Id from category where name like '%events%' and deleted = 0 and published = 1
UNION
SELECT Id from category where parentcategoryid = (Select id from category where name like '%events%' and deleted = 0 and published = 1)
I do not want to use UNION, want to use Join only. But not getting how i can achieve. Below is the table structure. Please help me. thanks in Advance
The following might solve your problem:
DECLARE #t TABLE (id int, name varchar(20), parentcatid varchar(200))
insert into #t values(23,'Christmas', 34)
,(29,'Birthday', 34)
,(31,'New year', 34)
,(34,'Events', 0)
,(35,'gfrhrt', 0)
;WITH cte AS(
Select Id from #t
where name like '%events%' --and deleted = 0 and published = 1
),
cte2 AS (
SELECT a.id AS tId, cpa.id, cId2.id AS idPa
from #t a
LEFT JOIN cte AS cId ON cId.Id = a.Id
FULL OUTER JOIN #t AS cPa ON cPa.parentcatid = cId.Id
LEFT JOIN cte cId2 ON cId2.id = cPa.id
WHERE cId.Id IS NOT NULL OR cPa.Id IS NOT NULL
)
SELECT id
FROM cte2
WHERE tId IS NOT NULL
OR id = idPa
The idea is to get all required IDs within the cte and then get all categories, where either ID oder ParentID match the IDs from the cte. However, depending on the size of your table, you might want to add further filters to the cte.

Rewrite query without using temp table

I have a query that is using a temp table to insert some data then another select from to extract distinct results. That query by it self was fine but now with entity-framework it is causing all kinds of unexpected errors at the wrong time.
Is there any way I can rewrite the query not to use a temp table? When this is converted into a stored procedure and in entity framework the result set is of type int which throws an error:
Could not find an implementation of the query pattern Select not found.
Here is the query
Drop Table IF EXISTS #Temp
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName into #Temp
FROM RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join #Temp t on r.ReceiverID = t.ReceiverID;
No need for anything fancy, you can just replace the reference to #temp with an inner sub-query containing the query that generates #temp e.g.
select distinct r.ReceiverID, r.ReceiverName, r.receiverdescription
from RFIDReceiver r
inner join (
select
a.ReceiverID,
a.AntennaID,
a.AntennaName
from RFIDReceiverAntenna a
full join Station b ON (a.ReceiverID = b.ReceiverID) and (a.AntennaID = b.AntennaID)
where (a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
) t on r.ReceiverID = t.ReceiverID;
PS: I haven't made any effort to improve the query overall like Gordon has but do consider his suggestions.
First, a full join makes no sense in the first query. You are selecting only columns from the first table, so you need that.
Second, you can use a CTE.
Third, you should be able to get rid of the SELECT DISTINCT by using an EXISTS condition.
I would suggest:
WITH ra AS (
SELECT ra.*
FROM RFIDReceiverAntenna ra
Station s
ON s.ReceiverID = ra.ReceiverID AND
s.AntennaID = ra.AntennaID)
WHERE s.ReceiverID is NULL
)
SELECT r.ReceiverID, r.ReceiverName, r.receiverdescription
FROM RFIDReceiver r
WHERE EXISTS (SELECT 1
FROM ra
WHERE r.ReceiverID = ra.ReceiverID
);
You can use CTE instead of the temp table:
WITH
CTE
AS
(
SELECT
a.ReceiverID,
a.AntennaID,
a.AntennaName
FROM
RFIDReceiverAntenna a
full join Station b
ON (a.ReceiverID = b.ReceiverID)
and (a.AntennaID = b.AntennaID)
where
(a.ReceiverID is NULL or b.ReceiverID is NULL)
and (a.AntennaID IS NULL or b.antennaID is NULL)
)
select distinct
r.ReceiverID, r.ReceiverName, r.receiverdescription
from
RFIDReceiver r
inner join CTE t on r.ReceiverID = t.ReceiverID
;
This query will return the same results as your original query with the temp table, but its performance may be quite different; not necessarily slower, it can be faster. Just something that you should be aware about.

Confusing use of cte ,inner join and union all

I am confused about the use of inner join on the CTE here. what is in a as appears in the inner join at the end and what is in cte1 c?
WITH cte1 AS
(SELECT id,geographyname,
OriginalGoals,
ParentGeographyname,
0 AS HierarchyLevel,
paradigm
FROM businessobject_RefinementMaster
WHERE Id = #Geo
UNION ALL
SELECT a.id,
a.geographyname,
a.OriginalGoals,
a.ParentGeographyName,
HierarchyLevel-1 AS HierarchyLevel,
a.paradigm
FROM businessobject_RefinementMaster a
INNER JOIN cte1 c ON c.ParentGeographyname = a.geographyname
AND c.paradigm=a.paradigm )
what will be the result of this query?
This is a recursive CTE (hidden-RBAR). I'll try to comment it in a way, that you can understand, what is going on:
WITH cte1 AS
(
/*
This is called "anchor" and reads the "head" lines of a hierarchy
*/
SELECT id,
geographyname,
OriginalGoals,
ParentGeographyname,
0 AS HierarchyLevel, --obviously this starts with a "0"
paradigm
FROM businessobject_RefinementMaster --The source-table
WHERE Id = #Geo --You read elements with Id=#Geo. This is - probably - one single element
--The next SELECT will be "added" to the result-set
UNION ALL
/*
The column-list must be absolutely the same (count and type) of the anchor
*/
SELECT a.id,
a.geographyname,
a.OriginalGoals,
a.ParentGeographyName,
HierarchyLevel-1 AS HierarchyLevel, --this is simple counting. Started with 0 this will lead to -1, -2, -3...
a.paradigm
FROM businessobject_RefinementMaster a --same source-table as above
INNER JOIN cte1 c ON c.ParentGeographyname = a.geographyname --Find rows where the name of the element is the parent-name of the former element
AND c.paradigm=a.paradigm
)
/*
Return the result-set
*/
SELECT * FROM cte1
The result should be a full recursive list of parents to a given element.