Display child and parent relationship (if any) in a same table - mysql

I have this table
| id |parent|name|
| 1 | NULL | E |
| 2 | NULL | B |
| 3 | 5 | U |
| 4 | 5 | X |
| 5 | NULL | C |
| 6 | NULL | A |
I would like the list, ordered by parent's name, of all ID whether they have a parent or not:
| id |parent|name|has_child|
| 6 | NULL | A | 0 |
| 2 | NULL | B | 0 |
| 5 | NULL | C | 1 |
| 3 | 5 | U | 0 |
| 4 | 5 | X | 0 |
| 1 | NULL | E | 0 |
Is it possible?
I have tried many things but never get the proper answer, and I don't really know how to add the 'has_child' column
SELECT
t1.parent,
t2.name
FROM tablename AS t1
INNER JOIN
(
SELECT MIN(id) AS id, parent
FROM tablename
GROUP BY parent
) AS t22 ON t22.id = t1.id AND t1.parent = t22.parent
INNER JOIN tablename AS t2 ON t1.parent = t2.id;

I would use a self join here:
SELECT DISTINCT
t1.id,
t1.parent,
t1.name,
1 - ISNULL(t2.id) has_child
FROM tablename t1
LEFT JOIN tablename t2
ON t1.id = t2.parent
ORDER BY
t1.id;
The join condition used here, which matches a given record as a parent to one or more children, is that the current id is also the parent of some other record(s). Note that we need SELECT DISTINCT here, because a given parent might match to more than one child record.

You can use a self join -- because you want the name of the parent and not the id -- and coalesce() for ordering:
select t.*,
(case when exists (select 1 from t tc where tc.parent = t.id)
then 1 else 0
end)
from t left join
t tp
on t.parent = tp.id
order by coalesce(tp.name, t.name), -- group rows by the parent, if any
(tp.name is null) desc, -- put parent first
t.name; -- order by children

I hope that you find this answer a little bit useful. The subquery gets the distinct id of parents and excludes the blanked fills.
SELECT *,
CASE WHEN id IN (SELECT DISTINCT parent
FROM tablename
WHERE parent IS NOT NULL)
THEN '1' ELSE '0'
END AS has_child
FROM tablename
ORDER BY name;

SELECT t1.id, t1.parent, t1.name, MAX(t2.parent is not null) has_child
FROM table t1
LEFT JOIN table t2 ON t1.id = t2.parent_id
GROUP BY t1.id, t1.parent, t1.name

Related

multiple sub query, count more sons but whit a condition

Hi! this work fine, but i need count more sons but whit a condition
select t1.*,(select count(*)
from tabl t2
where t2.tabl = t1.id) as sons
from tabl t1 where tabl.sons = ?;
I try this, but receive the following error...
SQL ERROR: Operand should contain 1 column(s)
select t1.*,(select count(*),(select count(*) from tbl1 t3 where t3.type = 3) as Johnny
from tabl t2
where t2.tabl = t1.id) as sons
from tabl t1 where tabl.sons = ?;
please, and thanks
Edit:
this do exactly i want, but i think is not the correct way to do
(i changed the names sorry, but it look better)
select m1.*,
(select count(*) from node m2 where m2.nodeid = m1.id) as child,
(select count(*) from node m3 where m3.type = 3 and m3.nodeid = m1.id) as del,
(select count(*) from node m3 where m3.type = 1 and m3.nodeid = m1.id) as edit,
(select count(*) from node m3 where m3.type = 4 and m3.nodeid = m1.id) as protect,
(select count(*) from node m3 where m3.type = 2 and m3.nodeid = m1.id) as move
from node m1 where nodeid = ?
+----+--------+------+-------+------+------+---------+------+
| id | nodeid | type | child | del | edit | protect | move |
+----+--------+------+-------+------+------+---------+------+
| 1 | null | 0 | 3 | 0 | 1 | 1 | 1 |
+----+--------+------+-------+------+------+---------+------+
+----+--------+------+-------+------+------+---------+------+
| id | nodeid | type | child | del | edit | protect | move |
+----+--------+------+-------+------+------+---------+------+
| 34 | 1 | 1 | 7 | 2 | 1 | 1 | 1 |
| 23 | 1 | 2 | 3 | 1 | 0 | 0 | 0 |
| 32 | 1 | 3 | 2 | 0 | 0 | 0 | 0 |
+----+--------+------+-------+------+------+---------+------+
basically how many children are and how many are of each type xd
MySQL 8 has got the window functions. Therefore, you can write your query in it like this:
SELECT t1.*, COUNT(*) OVER(PARTITION BY t1.id ORDER BY t1.id) AS sons,
COUNT(CASE WHEN t1.type = 3 THEN t1.type END) OVER (PARTITION BY t1.id ORDER BY t1.id) AS Johnny
FROM tabl t1
WHERE t1.sons = ?;
You cannot define multiple columns to one column.
(select count(*),(select count(*) from tbl1 t3 where t3.type = 3) as Johnny
from tabl t2 where t2.tabl = t1.id) as sons
sons : one column, count(*),,,Johnny : multiple column
Please change it to the following sql...
(select count(*) from tabl t2 where t2.tabl=t1.id), (select count(*) from tbl1 t3 where t3.type=3)
Is this what you are looking for?
select t.*,
count(*) over (partition by id) as sons,
sum(type = 3) over (partition by id) as johnny
from tabl t
having sons = ?;
MySQL extends the having clause so it can filter on table aliases in a non-aggregation query.

Need help on query on same table in MySQL

I have a table with some data, and I need to make a query using that table.
I have:
+----+-----------------+--------+--------+
| Id | Name | Parent | Mark |
+----+-----------------+--------+--------+
| 1 | Name 1 | 0 | 0 |
| 2 | Name 2 | 1 | 20 |
| 3 | Name 3 | 2 | 45 |
| 4 | Name 4 | 0 | 50 |
+----+-----------------+--------+--------+
and I need:
+----+-----------------+--------+--------+
| Id | Name | Parent | Mark |
+----+-----------------+--------+--------+
| 2 | Name 2 | Name 1 | 20 |
| 3 | Name 3 | Name 2 | 45 |
+----+-----------------+--------+--------+
How can I run the query in MySQL?
You can get your result by running this query.
Select tablename.*
from tablename t1
inner join tablename t2 on t2.Id = t1.Parent ;
you can do the inner join in the same table and check if the user have parent or not
try something like
select * from marksTbl m inner join marksTbl p on m.Id = p.Parent
You can use the following using a INNER JOIN. Using this solution also exclude all rows without a matching parent.
SELECT t1.Id, t1.Name, t2.Name AS Parent, t1.Mark
FROM table_name t1 INNER JOIN table_name t2 ON t1.Parent = t2.Id
ORDER BY t1.Id ASC
In case you want to see all rows (also the rows without (matching) parents) you can use the following with LEFT JOIN:
SELECT t1.Id, t1.Name, t2.Name AS Parent, t1.Mark
FROM table_name t1 LEFT JOIN table_name t2 ON t1.Parent = t2.Id
ORDER BY t1.Id ASC
You can also use a sub-select instead of a join:
SELECT Id, Name, (SELECT Name FROM table_name t2 WHERE t2.Id = t1.Parent) AS Parent, Mark
FROM table_name t1
WHERE t1.Parent > 0
ORDER BY t1.Id ASC
demo on dbfiddle.uk

How to select all rows from group, until occurrence of a value

I'm trying to extract all rows from same Group until I hit breakpoint value B. The example data below is ordered virtual table:
+----+--------+------------+
| ID | Group | Breakpoint |
+----+--------+------------+
| 1 | 1 | A |
| 2 | 1 | A |
| 3 | 1 | B |
| 4 | 1 | A |
| 5 | 2 | A |
| 6 | 2 | A |
| 7 | 2 | A |
| 8 | 3 | A |
| 9 | 3 | B |
+----+--------+------------+
This would be my result.
+----+--------+------------+
| ID | Group | Breakpoint |
+----+--------+------------+
| 1 | 1 | A |
| 2 | 1 | A |
| 5 | 2 | A |
| 6 | 2 | A |
| 7 | 2 | A |
| 8 | 3 | A |
+----+--------+------------+
Notice that when there are both A and B breakpoint values within a group, I want to have the rows until the first A value in this order. If there are only A values for a group like in group 2, I want to have all of the items in the group.
Here's a simple solution that uses no subqueries or GROUP BY logic.
SELECT t1.ID, t1.Group, t1.Breakpoint
FROM MyTable AS t1
LEFT OUTER JOIN MyTable AS t2
ON t1.ID >= t2.ID AND t1.`Group` = t2.`Group` AND t2.Breakpoint = 'B'
WHERE t2.ID IS NULL
For each row t1, try to find another row t2 with 'B', in the same Group, with an earlier ID. If none is found, the OUTER JOIN guarantees that t2.ID is NULL. That will be true only up until the desired breakpoint.
From you example above, you are not really grouping the results. you just need to display the records where Breakpoint is A:
Select * From Table
Where Breakpint ='A'
You may use NOT EXISTS
select *
from your_table t1
where not exists (
select 1
from your_table t2
where t1.group = t2.group and t2.id <= t1.id and t2.breakpoint = 'B'
)
or ALL can work as well if you never have NULL in id
select *
from your_table t1
where t1.id < ALL(
select t2.id
from your_table t2
where t1.group = t2.group and t2.breakpoint = 'B'
)
Assuming that we are ordering by ID column, we could do something like this:
SELECT d.*
FROM mytable d
LEFT
JOIN ( SELECT bp.group
, MIN(bp.id) AS bp_id
FROM mytable bp
WHERE bp.breakpoint = 'B'
GROUP BY bp.group
) b
ON b.group = d.group
WHERE b.bp_id > d.id OR b.bp_id IS NULL
ORDER BY d.group, d.id
This takes into account cases where there is no breakpoint='B' row for a given group, and returns all of the rows for that group.
Note that the inline view b gets us the lowest id value from rows with breakpoint='B' for each group. We can outer join that to original table (matching on group), and then conditional tests in the WHERE clause to exclude rows that follow the first breakpoint='B' for each group.
SQL tables represent unordered sets. Hence, there is no "before" or "after" a particular row.
Let me assume that you have some column that specifies the ordering. I'll call it id. You can then do what you want with:
select t.*
from t
where t.id < (select min(t2.id) from t t2 where t2.group = t.group and t2.breakpoint = 'B');
To get all rows when if there are no 'B':
select t.*
from t
where t.id < (select coalesce(min(t2.id), t.id + 1) from t t2 where t2.group = t.group and t2.breakpoint = 'B');

SELECT column different from GROUP BY column in hierarchy structure

I am using SQL to store information about a family tree. I want to be able to input the name of a person born in Generation X and output the names of all of their descendants born in Generation Z, but only if these descendants are brother and sister (have the same parent). I have found similar solutions, but I have found these solutions do not work with a hierarchical structure (due to aliasing). There may also be a parent and their child born in generation Y, so hierarchy depth is not reliable to determine Names.
+----+----------+-----------+------------+--------+
| ID | Name | Parent_ID | Generation | Gender |
+----+----------+-----------+------------+--------+
| 1 | John | NULL | X | Male |
| 2 | Jill | 1 | Y | Female |
| 3 | Andy | 2 | Z | Male |
| 4 | Ralph | 2 | Z | Male |
| 5 | Lisa | NULL | X | Female |
| 6 | Steve | 5 | Y | Male |
| 7 | Sean | 6 | Y | Male |
| 8 | Sarah | 6 | Y | Female |
| 9 | Emily | 7 | Z | Female |
| 10 | Matt | 7 | Z | Male |
+----+----------+-----------+------------+--------+
Desired output: (if SET #GenX = 'Lisa';)
+-------+
| Name |
+-------+
| Emily |
| Matt |
+-------+
This is my code so far.
SET #GenX = 'Lisa';
SELECT t2.name AS Kids
FROM (SELECT name, Parent_ID
FROM FamilyTree
WHERE Gender = 'Male' OR Gender = 'Female') t1
LEFT JOIN FamilyTree t2 ON t2.ID = t1.Parent_ID
LEFT JOIN FamilyTree t3 ON t3.ID = t2.Parent_ID
LEFT JOIN FamilyTree t4 ON t4.ID = t3.Parent_ID
WHERE t3.name = '#GenX' OR t4.name - '#GenX'
GROUP BY t2.name
HAVING SUM(t1.Gender) > 0 AND SUM(t1.Gender) > 0;
This returns the correct parent name, but does not return the kids names. If I SELECT and GROUP BY the kids names, I can't find which kids have sisters/brothers. Thanks for the help. I'm really stuck on this one.
You may assume no repeated names or overlapping family trees.
;with cte
as
(
Select id,name,Parent_id,generation,gender from family_tree where name =#name and generation='x'
Union All
Select t.id, t.name,t.Parent_id,t.generation,t.gender from family_tree t
join cte c on c. id=t.parent_id
)
,condition
as
(
select Parent_id,COUNT(*) cnt from cte Where generation='z' Group by parent_id having COUNT(*)>1
)
,sibling
as
(
select parent_id,Count(gender) cntg from
(select distinct c.parent_id,c.gender from cte c join condition co on co.parent_id=c.parent_id) a
group by parent_id having count(gender)>1
)
select c.name from cte c join sibling s on c.parent_id=s.parent_id
Assuming that you have only 3 generation you can go with the following query
SELECT NAME FROM FamilyTree WHERE PARENT_ID IN
(SELECT ID FROM FamilyTree WHERE PARENT_ID IN
(SELECT ID FROM FamilyTree WHERE PARENT_ID =
(SELECT ID FROM FamilyTree WHERE NAME = 'Lisa')
)
) AND
(SELECT COUNT(ID) FROM
(SELECT ID FROM FamilyTree WHERE PARENT_ID IN
(SELECT ID FROM FamilyTree WHERE PARENT_ID IN
(SELECT ID FROM FamilyTree WHERE PARENT_ID =
(SELECT ID FROM FamilyTree WHERE NAME = 'Lisa')
)
)
)T2
)=2 AND GENARATION = 'Z'
Above query will give result if their is only 3 generation
If its more you have to add more inner query and it will effect you performance.
My original attempt returned the id's (names) of each parent of brother/sister Generation Z kids. The solution required using the IN operator to relate children's names to the parent id's given from the first nested SELECT statement.
SET #GenX = 'Lisa';
SELECT name FROM FamilyTree WHERE Parent_ID IN
(SELECT t2.id AS Kids
FROM
(SELECT name, Parent_ID
FROM FamilyTree
WHERE Gender = 'Male' OR Gender = 'Female') t1
-- Iterate through hierarchy
LEFT JOIN FamilyTree t2 ON t2.ID = t1.Parent_ID
LEFT JOIN FamilyTree t3 ON t3.ID = t2.Parent_ID
LEFT JOIN FamilyTree t4 ON t4.ID = t3.Parent_ID
WHERE t3.name = '#GenX' OR t4.name - '#GenX'
GROUP BY t2.id
HAVING SUM(t1.Gender) > 0 AND SUM(t1.Gender) > 0); -- includes only brother/sister siblings

Get min price id without inner select

I have a table called a with this data:
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 1 | 1 | 100 |
| 2 | 1 | 200 |
| 3 | 1 | 99 |
| 4 | 2 | 1000 |
| 5 | 2 | 999 |
+-----+-----------+-------+
I want to get the id of min pirce for each parent_id.
There is any way to get this result without subquery?
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 3 | 1 | 99 |
| 5 | 2 | 999 |
+-----+-----------+-------+
SELECT D1.id, D1.parent_id, D1.price
FROM Data D1
LEFT JOIN Data D2 on D2.price < D1.price AND D1.parent_id = D2.parent_id
WHERE D2.id IS NULL
Here is a shot at how to do it without subqueries. I haven't tested, let me know if it works!
SELECT t.id, t.parent_id, t.price
FROM table t
LEFT JOIN table t2
ON (t.parent_id = t2.parent_id AND t.price > t2.price)
GROUP BY t.id, t.parent_id, t.price
HAVING COUNT(*) = 1 AND max(t2.price) is null
ORDER BY t.parent_id, t.price desc;
Try this:
SELECT T1.id,T2.parent_id,T2.price FROM
(SELECT id,price
FROM TableName) T1
INNER JOIN
(
SELECT parent_id,MIN(price) as price
FROM TableName
GROUP BY parent_id) T2 ON T1.price=T2.price
See result in SQL Fiddle.
Try group by,
SELECT parent_id,min(price)
FROM TableName
GROUP BY parent_id
You can do this with a LEFT JOIN
SELECT a.id, a.parent_id, a.price
FROM a
LEFT JOIN a AS b ON b.price < a.price AND b.parent_id = a.parent_id
WHERE b.id IS NULL
Find the results at this fiddle:
http://sqlfiddle.com/#!2/09c888/10
You can try this without using any join or subquery you will surely get the desired result.
SELECT TOP 2 FROM a ORDER BY price