Select many to many with hierarchical table - sql

dbo.Tags
---------------
[TagsId]
dbo.TagsDetail
----------------
[TagsDetailId]
[TagsId]
[TagsGroupId]
dbo.TagsGroup (hierarchical table with 2 level)
----------------
[TagsGroupId]
[ParentId]
Tags
+--------+
| Tagsld |
+--------+
| 1 |
| 2 |
+--------+
TagsDetails
+-------------+-----------+
| Tagsld |TagsGroupId|
+-------------+-----------+
| 1 | 1 |
| 2 | 2 |
+-------------+-----------+
TagsGroup
+-------------+-----------+
| TagsGroupId | ParentId |
+-------------+-----------+
| 1 | null |
| 2 | null |
| 3 | 1 |
+-------------+-----------+
Input TagGroupsId = 2 => all taggroup(1, 2, 3)
How can I select all related TagsGroupIds by input one #TagsGroupId?
I tried to solved by selecting all TagsDetailIds by #TagsGroupId so I find all TagsIds and from TagsIds. I found all TagsDetailIds and then get all relate TagsGroupIds and it's descendants and on each TagsGroupId, I started a loop again.
I don't know where I can stop to make sure I have selected all TagsGroupId.

Note: Using plurals is not typically how this is done -- so the column should be called TagId not TagsId and the table should be called TagGroup not TagsGroup. Just easier this way. It has no effect but that is the style convention everyone uses.
So as I understand it, a tag group can have up to two parents and can have children and grand children.
You can do it all in one query (with sub queries and joins) but I think a CTE will make the logic easier.
WITH Parent AS
(
SELECT TD.TagId
FROM TagGroup TG
JOIN TagDetail ON TD.TagGroupId = TG.ParentId
WHERE TG.TagGroupId = #TagGroupId
), GrandParent AS
(
SELECT TD.TagId
FROM TagGroup TG
JOIN TagDetail ON TD.TagGroupId = TG.ParentId
WHERE TG.TagGroupId = (SELECT TagId FROM Parent)
), Child AS
(
SELECT TD.TagId
FROM TagGroup TG
JOIN TagDetail ON TD.TagGroupId = TG.TagGroupId
WHERE TG.ParentId = #TagGroupId
), GrandChild AS
(
SELECT TD.TagId
FROM TagGroup TG
JOIN TagDetail ON TD.TagGroupId = TG.TagGroupId
WHERE TG.ParentId = (SELECT TagId FROM Child)
)
SELECT TagId
FROM Parent
UNION
SELECT TagId
FROM GrandParent
UNION
SELECT TagId
FROM Child
UNION
SELECT TagId
FROM GrandChild

Related

SQL - How to check if users are in the same hierarchy?

I want to find out if users are directly in a parent child relation.
Given my user table schema
User_id | Parent_ID | Name
For example, I have a list of user_id's and I want to know if they are all in the same hierarchical tree.
I have tried using CTE recursive.
Sample data
User_id | Parent_ID | Name
1 | | A
2 | 1 | B
3 | 2 | C
4 | 3 | D
5 | 2 | E
6 | | F
7 | 6 | G
user_id varchar(100)
parent_id varchar(100)
Desired result: Input [2,3,4] => Same Team
Input [2,3,7] => Not same team
Use the top-level parents' parent_id as the hierarchy identifier:
with recursive hierarchies as (
select user_id, user_id as hierarchy_id
from ttable
where parent_id is null
union all
select c.user_id, p.hierarchy_id
from hierarchies p
join ttable c on c.parent_id = p.user_id
)
select * from hierarchies;
With that mapping of each user_id to a single hierarchy_id, you can join to your list of users.
EDIT BEGINS
Since you added sample data and example results that do not match your original question, here is an example of how any minimally competent programmer could slightly tweak the above to match the newly added contradictory examples:
with recursive subhierarchies as (
select user_id, array[user_id] as path
from ttable
where parent_id is null
union all
select c.user_id, p.path||c.user_id as path
from subhierarchies p
join ttable c on c.parent_id = p.user_id
)
select d.user_ids, count(s.path) > 0 as same_team
from (values (array[2, 3, 4]), (array[2, 3, 6])) as d(user_ids)
left join subhierarchies s
on s.path #> d.user_ids
group by d.user_ids
;

SQL Select and Count Statement

I have a table as follows
--------------------------------
ChildId | ChildName | ParentId |
--------------------------------
1 | A | 0 |
--------------------------------
2 | B | 1 |
--------------------------------
3 | C | 1 |
--------------------------------
I would like to select data as -
---------------------------------------
Id | Name | Childs |
---------------------------------------
1 | A | 2 |
---------------------------------------
2 | B | 0 |
---------------------------------------
3 | C | 0 |
---------------------------------------
The pseudo SQL statement should be like this-
SELECT ChildId AS Id, ChildName as Name, (Count (ParentId) Where ParentId=ChildId)
Any Help?
This will do the trick:
select t1.childid, t1.childname, count(*) as childs
from table t1
join table t2 on t1.childid = t2.parentid
group by t1.childid, t1.childname
Something like this?
SELECT ChildId AS Id, ChildName as Name, (SELECT COUNT(*) FROM TestCountOver T WHERE T.ParentID = O.ChildID) FROM TestCountOver O
but that would give you all the nodes plus children who shouldn't be part of the hierarchy
If you want only nodes with children then use a cte
;WITH CTE AS (
SELECT ChildId AS Id, ChildName as Name, (SELECT COUNT(*) FROM TestCountOver T WHERE T.ParentID = O.ChildID) Cnt FROM TestCountOver O
)
SELECT *
FROM CTE
WHERE Cnt > 0
If there is only one level you can use the approach in your pseudo code. It should look like this:
SELECT
ChildId AS Id,
ChildName as Name,
(select Count(ParentId) from t t_inner Where ParentId=t_outer.ChildId) children
from t t_outer
-- optionally where clause to limit result to parents that have children
where exists (select 1 from t where ParentId in (t_outer.ChildId))
but if there can be more than one level you can solve it using a recursive query if you want the total number of children for a parent (including grand-children etc).

Replacing child record columns during SELECT with JOIN operation

I am trying to create a view by join two tables together which is by no means difficult. But there is a but.
I basically have two tables. One of them stores associations between items and tags and the other stores tags in parent-child form (8 parents with 4-6 children each).
Allow me to illustrate starting with my item association table:
AssociationId | ItemId | TagId
------------------------------
1 | 2 | 1
2 | 10 | 2
3 | 3 | 1
4 | 5 | 7
...
And my tags table:
TagId | ParentId | Name
------------------------------
1 | NULL | abc sds
2 | 1 | sjdksd as
3 | 1 | djfsd dfs d
4 | NULL | ujkjsd as
...
As you can see each parent can be found by decorating queries with ParentId IS NULL.
SELECT ITA.AssociationId, ITA.ItemId, ITA.TagId, T.Name as TagName
FROM Item_Tag_Association AS ITA
INNER JOIN Tags AS T
ON T.TagId = ITA.TagId
WHERE T.ParentId IS NULL
If I was to join both tables on TagId with ParentId IS NULL I would find all items with parent tags.
If I was to join both tables on TagId with ParentId IS NOT NULL I would find all items with child tags.
But what I actually want is a list of items and tags where child tags are replaced by their parent tags.
Using above example the result would look something like this:
AssociationId | ItemId | TagId | TagName
----------------------------------------
1 | 2 | 1 | abc sds
2 | 10 | 1 | abc sds
3 | 3 | 1 | abc sds
4 | 5 | 7 | ysdasjdhas
...
As for the reason why I want to do this is so that I could get a count of how many times a specific parent tag (or child of that parent) has been associated with an item. And as you guessed it by now each child should contribute to its parent.
SELECT DISTINCT ItemId, TagId, TagName, COUNT(ItemId) OVER (PARTITION BY TagId) AS Count
FROM vw_My_View
ORDER BY Count DESC
I am working with SQL Server 2008 R2. All constructive suggestions are welcome.
As long as you only have one level in your parent/child hierarchy you could do an extra left join from tags to itself to find parent if it exists like this:
SELECT ita.AssociationId, ita.ItemId, ISNULL(parent.TagId, t.TagId), ISNULL(parent.Name, t.Name) AS TagName
FROM Item_Tags_Association ita
JOIN Tags t ON ita.TagId = t.TagId
LEFT JOIN Tags parent ON t.ParentId = parent.TagId
The first (inner) join can join to either a child tag or a parent tag. The left join will include the parent tag if the tag in association has a parent.
The ISNULL will make sure the value from the second (left) join is used if not null, otherwise use the value from the first join.
If you prefer not dealing with ISNULL(or COALESCE) you could make use of a UNION and a CTE like this:
WITH ParentTags AS (SELECT * FROM Tags WHERE ParentId IS NULL)
SELECT ita.AssociationId, ita.ItemId, t.TagId, t.Name AS TagName
FROM ParentTags t
JOIN Item_Tags_Association ita ON ita.TagId = t.TagId
UNION ALL
SELECT ita.AssociationId, ita.ItemId, t.TagId, t.Name AS TagName
FROM ParentTags t
JOIN Tags child ON t.TagId = child.ParentId
JOIN Item_Tags_Association ita ON ita.TagId = child.TagId

Select record, and if it has children, select the newest child instead

I have a table:
element_id, element_parent_id
Records are:
1, null
2, 1
3, 1
4, null
So, visualization might look like:
1
2
3
4
The question is, how to select for:
form_id
3
4
...in other words: how to select parent if there is no children, or the newest child if those children exist. So far I managed to select for:
1 and 4
2 and 3
1, 2, 3 and 4
Just to be a bit more useful I'll explain my reasoning to arrive at the final query (at the bottom).
You are selecting two different entities from the same table, so you need a JOIN of the table against itself; this will give you parents and children.
But children may not be there and so it will have to be a LEFT JOIN.
SELECT p.id, c.id AS cid
FROM yourtable AS p
LEFT JOIN yourtable AS c ON (c.parent_id = p.id);
+------+------+
| id | cid |
+------+------+
| 1 | 3 | # This one...
| 1 | 2 | # ...and this one must be grouped, and 3 taken.
| 3 | NULL | <-- this must be ignored because it's a child
| 4 | NULL |
| 2 | NULL | <-- this must be ignored because it's a child
+------+------+
Now to refine, we see that we need to ignore children in the "parent" role and to this purpose we add WHERE p.parent_id IS NULL. "Real" children have a parent, and will then be skipped.
Note: this is the point where we would need to do something more complicated if we had a multi-level hierarchy. If we only wanted the
bottom level, i.e. the "true" children (ignore those parents that have
themselves a parent), we could for example run a second LEFT JOIN
to get the *grand*parents, and impose that the grandparent's id be not
NULL. This is only true for third level and greater grandchildren. Or
we could get the children of the children and impose that they have
NULL id, i.e. they don't exist; this is only true for the bottom or
last-level children. Other requirements could call for yet another set
of bounds.
Then you want the "top" child in relation to a parent id, and this calls for a GROUP BY, which will yield the desired result when children are there, or NULL when there aren't.
SELECT p.id, MAX(c.id) AS cid
FROM yourtable AS p
LEFT JOIN yourtable AS c ON (c.parent_id = p.id)
WHERE p.parent_id IS NULL
GROUP BY p.id;
+------+------+
| id | cid |
+------+------+
| 1 | 3 |
| 4 | NULL |
+------+------+
In that case we see the information is in the parent-side column (id).
And to choose which, I would resort to a SUBSELECT.
SELECT CASE WHEN cid IS NULL THEN id ELSE cid END AS wanted
FROM (
SELECT p.id, MAX(c.id) AS cid
FROM yourtable AS p
LEFT JOIN yourtable AS c ON (c.parent_id = p.id)
WHERE p.parent_id IS NULL
GROUP BY p.id
) AS x;

Get list of parent and child records with multiple entries with criteria - SQL 2005

I have parent / child tables:
parent table:
id | description
1 | Alexandra
2 | Natalia
child table:
id | id_parent | description
1 | 1 | Programmer
2 | 1 | Surgery
3 | 2 | Programmer
4 | 2 | IT
How to return set of record according filters, like if we want to get all records with "Programmer" we would like to have set:
response table:
id_parent | id_child | description (from child)
1 | 1 | Programmer
2 | 3 | Programmer
BUT if filter looks like: "Programmer" AND "Surgery", query MUST return only:
response table:
id_parent | id_child | description (from child)
1 | 1 | Programmer
1 | 2 | Surgery
As you can see I also need some kind of "join" in order to "connect" description and code in child table.
Thanks in advance.
SELECT t.id_parent,
t.id,
t.description
FROM CHILD t
JOIN CHILD p ON p.id_parent = t.id_parent AND p.description = 'Programmer'
JOIN CHILD s ON s.id_parent = t.id_parent AND s.description = 'Surgery'
Based on your SQL, you need to use:
SELECT p.ID,
c.ID,
c.ID_SpecMatrix
FROM Parent p
JOIN Child c ON p.ID = c.ID_Parent AND c.ID_SpecMatrix = 4
JOIN Child c2 ON p.ID = c2.ID_Parent AND c2.ID_SpecMatrix = 5
GROUP BY p.ID,
c.ID,
c.ID_SpecMatrix
The key is JOINing additional copies of the CHILD table. The ID_parent to ID links the records together; the AND filters that copy of the CHILD table to contain only rows where the ID_SpecMatrix matches your criteria.
The query needs to use dynamic SQL to be scalable to however many criteria you want to use.
How about this
SELECT * FROM Child
WHERE id_specmatrix IN (4,5) AND
Parent_ID IN (SELECT DISTINCT parent.id FROM parent p
JOIN Child s1 ON s1.id_parent = p.id AND s1.id_specmatrix = 4
JOIN Child s2 ON s2.id_parent = p.id AND s2.id_specmatrix = 5)
select *
from parent p
left join child c
on p.id = c.id_parent
where c.description = 'Programmer'
or c.description = 'Surgen'