Get all parents of multiple objects in same table - mysql

for an mass edit function I need to load the parents of multiple objects.
Doing this with single querys would kill the db. I'm using MySQL 5.7 My table is build like this:
CREATE TABLE `testtable` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`content` text,
`parentid` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
INSERT INTO testtable(`id`, `content`, `parentid`)
VALUES(1, 'parentA1', 0),(5, 'parentA2', 1),(3, 'childA', 2),
(4, 'parentB', 0),(5, 'childB', 4);
For single object querys I use this statement to get all parents:
SELECT t.id, t.content, #pid := t.parentid AS parentid
FROM (SELECT * FROM Table1 order by id DESC) t
JOIN (SELECT #pid := 3) tmp
WHERE t.id = #pid
But I have absolutly no clue how this could work for multiple object at once without using union.
My whised ouput should look like this:
id | content | parentid | searchingChildID
1 | parentA1| 0 | 3
2 | parentA2| 1 | 3
3 | childA | 2 | 3
4 | parentB | 0 | 5
5 | childB | 4 | 5
Thanks in advance!

If you are running MySQL 8.0, this is a typical use case for a recursive query. Say you want all parents of objects 3 and 4, you can do:
with recursive cte as (
select t.id as originalid, t.* from table1 where id in (3, 4)
union all
select c.originalid, t.*
from cte c
inner join table1 t on t.id = c.parentid
)
select * from cte

for an mass edit function I need to load the parents of multiple objects.
For this operation, I would expect:
id | content | parentid | ultimateparent
1 | parentA1| 0 | 0
2 | parentA2| 1 | 0
3 | childA | 2 | 0
4 | parentB | 0 | 0
5 | childB | 4 | 0
Because 0 is the ultimate parent of all the rows. If you know the maximum depth, you can use left joins in pre-8 versions of MySQL:
select t1.*, coalesce(t3.parentid, t2.parentid, t1.parentid) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid
You can trivially extend this with more left joins to handle deeper levels of parentage.
If 0 really means "no parent" (which is an odd choice when NULL is available) then you would seem to want:
id | content | parentid | ultimateparent
1 | parentA1| 0 | 1
2 | parentA2| 1 | 1
3 | childA | 2 | 1
4 | parentB | 0 | 4
5 | childB | 4 | 4
Then you can actually just tweak the above query to:
select t1.*, coalesce(t3.id, t2.id, t1.id) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid;
Here is a db<>fiddle.
You can get the maximum child for each parent as well (although that seems quite arbitrary). In pre-8.0 versions of MySQL, variables are the simplest approach:
select t1.*,
(#max_child := if(#up = ultimateparent, #max_child,
if(#up := ultimateparent, id, id)
)
) as max_childid
from (select t1.*, coalesce(t3.id, t2.id, t1.id) as ultimateparent
from testtable t1 left join
testtable t2
on t2.id = t1.parentid left join
testtable t3
on t3.id = t2.parentid
order by ultimateparent, id desc
) t1 cross join
(select #up := -1, #max_child := -1) params;

Related

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

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

Create a Join leaving one row for any possible duplicate

I have a 2 tables that looks like this
How can I call the same column wihout duplicating it
TYSM for help
Please try below mentioned Query:
select distinct t2.data,t1.key,t1.data from t1.table1 JOIN table as t2 ON t1.key = t2.key
You could assign a row number using a variable to t2 then join to t1 supressing the output of the t1.key.
for example
drop table if exists t1,t2;
create table t1 (id int);
create table t2 (id int, name varchar(2));
insert into t1 values(1),(2),(3),(4);
insert into t2 values(1,'s1'),(1,'s2'),(2,'s3'),(3,'s4'),(4,'s5');
select s.id, s.name,
case when s.rn = 1 then s.rn
else ''
end as something
from t1
join
(
select t2.id,t2.name,
if(t2.id <> #p, #rn:=1,#rn:=#rn+1) rn,
#p:=t2.id
from t2,(select #rn:=0,#p:=0) r
) s on t1.id = s.id
order by t1.id, s.name
Result
+------+------+-----------+
| id | name | something |
+------+------+-----------+
| 1 | s1 | 1 |
| 1 | s2 | |
| 2 | s3 | 1 |
| 3 | s4 | 1 |
| 4 | s5 | 1 |
+------+------+-----------+
5 rows in set (0.00 sec)

Count records in one table where there are no records in linked table matching certain criteria

I have 2 tables:
T1:
id | name
------ | ------
1 | Bob
2 | John
3 | Joe
T2:
id | T1_id | type
------ | ------ | ------
1 | 1 | call
2 | 1 | email
3 | 1 | fax
4 | 2 | call
5 | 2 | email
6 | 2 | fax
7 | 3 | call
8 | 3 | email
I want to count the number of records in T1 which do not have a record in T2 with a type of 'fax'.
So the answer in this case would be 1 (3|Joe)
Currently I have:
SELECT count(*)
FROM `T1`
JOIN `T2` on `T1`.`id` = `T2`.`T1_id`
WHERE `T2`.`type` != 'fax'
But this is obviously counting all the records which are not 'fax'. I just cant get the logic in my head.
Any help would be appreciated!
A subquery is unnecessary:
SELECT COUNT(DISTINCT t1.id)
FROM t1
LEFT
JOIN t2
ON t2.t1_id = t1.id
AND t2.type = 'fax'
WHERE t2.id IS NULL;
select count(*)
from
(
SELECT t1.id
FROM T1
LEFT JOIN T2 on T1.id = T2.T1_id
GROUP BY t1.id
HAVING sum(T2.type = 'fax') = 0
) tmp
The answers given by Strawberry and juergen d are correct, but for completeness, here's another example using NOT EXISTS. All the queries will have different execution plans, so depending on your data in T1 and T2 YMMV:
SELECT COUNT(*)
FROM `T1`
WHERE NOT EXISTS (
SELECT *
FROM `T2`
WHERE `T2`.`T1_id` = `T1`.`id`
AND `T2`.`type` = 'fax'
)

Mysql - Select at least one or select none

I have a table as so...
----------------------------------------
| id | name | group | number |
----------------------------------------
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 3 | james | 2 | 2 |
| 4 | steven | 2 | 5 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
----------------------------------------
I'm running a select like so:
SELECT * FROM table WHERE number IN (2,3);
The problem im trying to solve is that I want to only grab get results from groups that have 1 or more rows of each number. For instance the above query is returning id's 1-2-3-5-6, when I'd like the results to exclude id 3 since the group of '2' can only return 1 result for the number of '2' and not for BOTH 2 and 3, since there's no row with the number 3 for the group 2 i'd like it to not even select id 3 at all.
Any help would be great.
Try it this way
SELECT *
FROM table1 t
WHERE number IN(2, 3)
AND EXISTS
(
SELECT *
FROM table1
WHERE number IN(2, 3)
AND `group` = t.`group`
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
)
or
SELECT *
FROM table1 t JOIN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
) q
ON t.`group` = q.`group`;
or
SELECT *
FROM table1
WHERE `group` IN
(
SELECT `group`
FROM table1
WHERE number IN(2, 3)
GROUP BY `group`
HAVING MAX(number = 2) > 0
AND MAX(number = 3) > 0
);
Sample output (for both queries):
| ID | NAME | GROUP | NUMBER |
|----|-------|-------|--------|
| 1 | joey | 1 | 2 |
| 2 | keidy | 1 | 3 |
| 5 | jason | 3 | 2 |
| 6 | shane | 3 | 3 |
Here is SQLFiddle demo
On this, you can approach from a fun way with multiple joins for what you WANT qualified, OR, apply a prequery to get all qualified groups as others have suggested, but readability is a bit off for me..
Anyhow, here's an approach going through the table once, but with joins
select DISTINCT
T.id,
T.Name,
T.Group,
T.Number
from
YourTable T
Join YourTable T2
on T.Group = T2.Group AND T2.Group = 2
Join YourTable T3
on T.Group = T3.Group AND T3.Group = 3
where
T.Number IN ( 2, 3 )
So on the first record, it is pointing to by it's own group to the T2 group AND the T2 group is specifically a 2... Then again, but testing the group for the T3 instance and T3's group is a 3.
If it cant complete the join to either of the T2 or T3 instances, the record is done for consideration, and since indexes work great for joins like this, make sure you have one index for your NUMBER criteria, and another index on the (GROUP, NUMBER) for those comparisons and the next query sample...
If doing by more than this simple 2, but larger group, prequery qualified groups, then join to that
select
YT2.*
from
( select YT1.group
from YourTable YT1
where YT1.Number in (2, 3)
group by YT1.group
having count( DISTINCT YT1.group ) = 2 ) PreQualified
JOIN YourTable YT2
on PreQualified.group = YT2.group
AND YT2.Number in (2,3)
Maybe this,if I understand you
SELECT id FROM table WHERE `group` IN
(SELECT `group` FROM table WHERE number IN (2,3)
GROUP BY `group`
HAVING COUNT(DISTINCT number)=2)
SQL Fiddle
This will return all ids where BOTH numbers exist in a group.Remove DISTINCT if you want ids for groups where just one numbers is in.

MySQL Query that return "not exists value"

I want to know is it possible to do if I have 4 number as below
1,2,3,4
In my data base have data exists as below
1,2,3,5,6,7
How can I query database and return 4 in 1 query
Please advise
CREATE TABLE `example` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
INSERT INTO example VALUES (1),(2),(3),(5),(6),(7);
SELECT t2.id FROM example AS t1
RIGHT JOIN (
SELECT 1 AS id UNION
SELECT 2 AS id UNION
SELECT 3 AS id UNION
SELECT 4 AS id
) AS t2
ON t1.id = t2.id
WHERE t1.id IS NULL;
+----+
| id |
+----+
| 4 |
+----+
Or use a temporary table:
CREATE TEMPORARY TABLE `tmp` (
`id` int(11) DEFAULT NULL
) ENGINE=InnoDB;
INSERT INTO tmp VALUES (4);
SELECT t2.id FROM example AS t1
RIGHT JOIN tmp AS t2
ON t1.id = t2.id
WHERE t1.id IS NULL;
To see what's happening, switch things around a bit:
SELECT t1.id, t2.id FROM example AS t1
RIGHT JOIN (
SELECT 1 AS id UNION
SELECT 2 AS id UNION
SELECT 3 AS id UNION
SELECT 4 AS id
) AS t2
ON t1.id = t2.id;
+------+----+
| id | id |
+------+----+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| NULL | 4 |
+------+----+
SELECT id FROM
( SELECT 1 AS id UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4
) AS TBL1
WHERE id NOT IN (SELECT id FROM thetable)