How to get second parent with recursive query in Common Table - sql

I am using SQL Server 2008. I have a table like this:
UnitId ParentId UnitName
---------------------------
1 0 FirstUnit
2 1 SecondUnit One
3 1 SecondUnit Two
4 3 B
5 2 C
6 4 D
7 6 E
8 5 F
I want to get second parent of the record. For example:
If I choose unit id that equal to 8, It will bring unit id is equal to 2 to me. It needs to be SecondUnit One. or If I choose unit id that equal to 7, It will bring unit id is equal to 3 to me. It needs to be SecondUnit Two.
How can I write a SQL query this way?

It took me a while, but here it is :)
with tmp as (
select unitId, parentId, unitName, 0 as iteration
from t
where unitId = 7
union all
select parent.unitId, parent.parentId, parent.unitName, child.iteration + 1
from tmp child
join t parent on child.parentId = parent.unitId
where parent.parentId != 0
)
select top 1 unitId, parentId, unitName from tmp
order by iteration desc
Here is also a fiddle to play with.

SELECT t.*, tParent1.UnitId [FirstParent], tParent2.UnitId [SecondParent]
FROM Table t
LEFT JOIN Table tParent1 ON t.ParentId = tParent1.UnitId
LEFT JOIN Table tParent2 ON tParent1.ParentId = tParent2.UnitId
WHERE t.UnitId = <Unit ID search here>
AND NOT tParent2.UnitId IS NULL
Edit: And leave out second part of the WHERE clause if you want results returned even if they don't have a second parent.

Related

SQL - first common ancestor in hierarachy

I am attempting to use a CTE to find the first common ancestor in a hierarchy for all values in a source table. I am able to return the entire hierarchy, but cannot seem to find a way to get the first common ancestor.
The ultimate goal is to update the source table to use only common ancestors.
Source table:
Type_Id Id Parent_Id Group_Id
A 3 2 1
A 4 2 1
A 5 4 1
After the CTE the table I get is:
Type_Id Id Child_Id Parent_Id Group_Id Level
A 3 3 2 1 1
A 4 4 2 1 1
A 5 5 4 1 1
A 5 4 2 1 2
What I'm looking to do is update the id of the source so that for each type and group combination, the new id is to be the first common parent. In this case, it would be set all ids to 2 as that's the first common entry.
You can try to use cte recursive self-join by your logic.
;WITH CTE as (
SELECT Type_Id,Id,id Child_Id,Parent_Id,Group_Id,1 Level
FROM T
UNION ALL
SELECT c.Type_Id,c.Id,t.id Child_Id,t.Parent_Id,t.Group_Id,c.Level+1
FROM CTE c
INNER JOIN T t
ON c.Parent_Id = t.ID AND c.Type_Id = t.Type_Id
)
SELECT *
FROM CTE
sqlfiddle
By building a list of all ancestor relationships (including self) and identifying the depth of each node from the root, you can then query all ancestors for the N selected nodes of interest. Any ancestors occurring N times are candidate common ancestors. The one with the deepest depth is the answer.
The following is what I came up with using simplified Node data containing only Id and ParentId. (The initial CTE was adapted from D-Shih's post.)
;WITH Ancestry as (
-- Recursive list of ancestors (including self) for each node
SELECT ChildId = N.Id, AncestorLevel = 0, AncestorId = N.Id, AncestorParentId = N.ParentId
FROM #Node N
UNION ALL
SELECT C.ChildId, AncestorLevel = C.AncestorLevel + 1, AncestorId = P.Id, AncestorParentId = P.ParentId
FROM Ancestry C
INNER JOIN #Node P ON P.Id = C.AncestorParentId
),
AncestryDepth AS (
-- Calculate depth from root (There may be a quicker way)
SELECT *, Depth = COUNT(*) OVER(PARTITION BY ChildId) - AncestorLevel
FROM Ancestry
),
Answer AS (
-- Answer is deepest node that is an ancestor of all selected nodes
SELECT TOP 1 Answer = A.AncestorId
FROM #Selected S
JOIN AncestryDepth A ON A.ChildId = S.Id
GROUP BY A.AncestorId, A.Depth
HAVING COUNT(*) = (SELECT COUNT(*) FROM #Selected) -- Ancestor covers all
ORDER BY A.Depth DESC -- Deepest
)
SELECT *
FROM Answer
There may be room for improvement, possibly a more direct calculation of depth, but this appears to give the correct result.
See this db<>fiddle for a working demo with data and several test scenarios.

How add more rows when find string in column Oracle

Would it be possible to add more rows base on Keyword string in SQL ?
table A
PID PromotionName
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 OUT_EC_D5_V50K_Lunchbox_PROCO
3 OUT_EC_D5_V50K_PROCO
table B
promotion_code itm_name quantity
Lunchbox Item name 1 1
FamilyCare Item name 2 1
FamilyCare Item name 3 1
BUY1FREE6 Item name 4 1
HiSummer Item name 5 1
FamilyCare Item name 6 1
Example:
SELECT * FROM A where pid = '1';
Output of the SQL should be -
PID PromotionName Itm_name quantity
1 OUT_EC_D10_V500K_FamilyCare_PROCO
2 FamilyCare Item name 2 1
3 FamilyCare Item name 3 1
4 FamilyCare Item name 6 1
How to find string with keyword 'FamilyCare' in PromotionName of table A base on promotion_code of table B? If it exist it will add more rows in output
Any help with the SQL?
Here is how you can achieve this:
SELECT PID,PromotionName, '' as Itm_name, NULL as quantity
FROM A
WHERE pid = '1'
UNION
SELECT PID, PROMOTION_NAME, Itm_name, quantity
FROM
(SELECT * FROM A inner join B on a.promotionName LIKE '%'||b.promotion_name||'%')
WHERE pid='1'
You have to update your pid in both the places (before and after UNION).
Notice that tables were joined using LIKE operator with % before and after the word. Hence this joins if a part of a string is present in another column.
db<>fiddle link here
An option would be starting to construct a subquery factoring along with joining tables through a.promotionName LIKE '%'||b.promotion_code||'%' condition while filtering by b.promotion_code = 'FamilyCare', then add another query to combine the result sets by UNION ALL, and then enumerate with an id column by ROW_NUMBER() analytic function such as
WITH ab AS
(
SELECT a.*, b.*
FROM a
JOIN b
ON a.promotionName LIKE '%'||b.promotion_code||'%'
WHERE b.promotion_code = 'FamilyCare'
), ab2 AS
(
SELECT promotion_code, itm_name, quantity
FROM ab
UNION ALL
SELECT DISTINCT promotionName, NULL, NULL
FROM ab
)
SELECT ROW_NUMBER() OVER (ORDER BY itm_name NULLS FIRST) AS pid,
a.*
FROM ab2 a
if there's mismatch for the topmost query, then no row will be returned. eg. that query will check for the existence for the literal you provide
Demo

Find children of a most top level parent

There are similar question asking how to find the top level parent of a child (this, this and this). I have a similar question but I want to find all childern of a top level parent. This is similar question but uses wordpress predefined functions.
sample table:
id parent
1 0
2 0
3 1
4 2
5 3
6 3
7 4
I want to select ID with most top parent equals 1. The output should be 3 and all children of 3 I mean (5,6) and even more deep level children if available.
I know I can select them using two times of inner join but the hirearchy may be more complex with more levels.
A simple "Recursive CTE" will do what you want:
with n as (
select id from my_table where id = 1 -- starting row(s)
union all
select t.id
from n
join my_table t on t.parent_id = n.id
)
select id from n;
This CTE will go down all levels ad infinitum. Well... by default SQL Server limits it to 128 levels (that you can increase to 65k).
Since you aren't climbing the entire ladder...
select *
from YourTable
where parent = (select top 1 parent from YourTable group by parent order by count(parent) desc)
If you were wanting to return the parent of 3, since 3 was listed most often, then you'd use a recursive CTE.

SQL- How to select all dynamic parent and child data in 1 query

I want to select all data that have parent and child. I have a sample table like this:
ID Name Parent
1 Mike 6000
2 Mike_x1 1
3 Mike_x2 2
4 Mike_x3 6333
5 Mike_x4 2
6 Mike_x5 3
7 Bob_x2 5
first of all i select data to get a header:
select * from table1 where parent = 1
and i get header data like this:
ID Name Parent
2 Mike_x1 1
from the header i got a new ID = 2 as a header data. So i select again to get a child:
select * from table1 where parent = 2
and i get data like this:
ID Name Parent
3 Mike_x2 2
5 Mike_x4 2
from the first child i got a new ID = 3 and ID = 5. So i select again to get another child:
select * from table1 where parrent = 3
and
select * from table1 where parrent = 5
and i got data like this:
ID Name Parrent
6 Mike_x5 3
and
ID Name Parrent
7 Bob_x2 5
Thats my steps to get parent and child. But i want to select using 1 query to get all parent and child data. When i do first select to get header select * from table1 where parrent = 1 , i will get a result like this:
ID Name Parrent
2 Mike_x1 1
3 Mike_x2 2
5 Mike_x4 2
6 Mike_x5 3
7 Bob_x2 5
Anybody help me? Is it possible for me to do it by 1 query, which is don't need to select manual like select * from where parent = 1,2,3,5 etc
SELECT DISTINCT * FROM table1
WHERE ID IN (SELECT Parent FROM table1)
AND Parent IN (SELECT ID FROM table1)
ORDER BY ID ASC
Assuming my understanding of the question is correct, this will " select all data that have parent and child"
Select T1.*
FROM TableName T1
INNER JOIN tableName Parents
on T1.ParentID = Parents.ID
INNER JOIN tableName Kids
on Kids.ParentID = T1.ID
corrected inner join on last element Kids.ID should have been Kids.ParentID
Should result in
ID Name Parent
2 Mike_x1 1
3 Mike_x2 2
5 Mike_x4 2
This works because of the inner self joins which exclude records that don't have parents or kids.
If these aren't the desired results: what are the expected results?
If you need to know who the parents are and who the kids are simply add to the select
Select t1.*, Parents.*, Kids.*
However if you need to traverse the hieracy and list the parents kids of kids etc... then XML path or a cte is needed.

SQL - how to return rows containing ALL children rows

Given a parent + reference tables where Reference table is as follows
Ref_ID PARENT_ID
-------------------
1 1
2 1
1 2
3 2
1 3
3 3
4 3
2 4
3 4
I'm trying to return all distinct parent rows where ref_id contains both 2 & 3
The query
SELECT *
FROM Parent
WHERE parent_id in (SELECT parent_id from XRefTable where ref_id in (2, 3) )
returns all parent_id 1, 2, 3, 4
WHEREAS the correct result required is to return parent_id 4 which has BOTH ref_id's 2 & 3, others have EITHER 2 OR 3
Any help is appreciated
FYI - there are 4-7 tables in the query (depending on user selections) so performance is a huge factor
SORRY cannot use stored procedures as it has to work on SQL Server CE too
SELECT parent_id
from XRefTable
where ref_id in ( 2, 3 )
group by PARENT_ID
having count(distinct ref_id) = 2
You could do this:
SELECT
ParentReference.Parent_ID
FROM
ParentReference
INNER JOIN ParentReference B ON ParentReference.Parent_ID = B.Parent_ID AND ParentReference.Ref_ID = 2 AND B.Ref_ID = 3
You are trying to do a set-wise comparison. For this, I strongly recommend the group by and having clauses:
select parent_id
from Reference r
group by parent_id
having sum(case when ref_id = 2 then 1 else 0 end) > 0 and
sum(case when ref_id = 3 then 1 else 0 end) > 0
Each component of the having clause is counting one of the fields. The logic requires that both are present.
The reason that I prefer this approach over others is because you can change the logic, using essentially the same structure.
If you have the list in a comma delimited string, the following will work. Perhaps not "elegant" and "relational", but it works:
set #Ref_ids = "1,2,3,4"
select parent_id
from Reference r
where charindex(','+cast(ref_id as varchar(255))+',', '+#ref_ids+',') > 0
group by parent_id
having count(distinct ref_id) = (len(replace(#ref_ids, ',', '')) - len(#ref_ids))+1
This is doing string manipulation to determine whether the ref_id is in the list. The having clause then counts the number of matches, making sure that it is the same size as the list. This will work, assuming there are no spaces in the list and no blank values.