一、题目总结
如果你代码出错,可能是以下问题
1、not in使用出现问题 :not in后面接select子查询的结果中不能出现空值,需要像我这样where id not in (select ifnull(p_id, ‘ad’) as id from Tree tr2) ,使用ifnull将空值转化为其他值,才能正常进行判断,但是 如果是 in的话就不需要这样。
在这里扩充总结:
①NULL值不能用用来(1)±*/, (2) 不能用来<>, (3) 不能用来 not in否则返回空或NULL,即 不可以 not in (select null),返回空
②not like 会把null值排除掉。
③not exists 如果条件中有null值, 外层查询的null值会被返回。
④ in 和 exists 均会过滤掉条件中null值
2、CTE表达式没有给重名的列起别名问题:CTE表达式中临时表如果你写的时候还没有使用cte表达式,也就是写进with t1 as () 中是可以查询成功的,但是如果临时表中存在列名重复的列,在主查询中系统无法分别你要使用的是哪一列,所以需要多走一步,把 cte中的select *认真写完,给要用的列分别起别名
总结:使用CTE表达式如果在子表中使用了类似join连接或者其他操作等,要注意是否有同名列,如果有给列取别名让系统区分它们
二、608题目描述和建表语句
表:Tree
±------------±-----+
| Column Name | Type |
±------------±-----+
| id | int |
| p_id | int |
±------------±-----+
id 是该表中具有唯一值的列。
该表的每行包含树中节点的 id 及其父节点的 id 信息。
给定的结构总是一个有效的树。
树中的每个节点可以是以下三种类型之一:
“Leaf”:节点是叶子节点。
“Root”:节点是树的根节点。
“lnner”:节点既不是叶子节点也不是根节点。
编写一个解决方案来报告树中每个节点的类型。
以 任意顺序 返回结果表。
结果格式如下所示。
示例 1:
输入:
Tree table:
±—±-----+
| id | p_id |
±—±-----+
| 1 | null |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | 2 |
±—±-----+
输出:
±—±------+
| id | type |
±—±------+
| 1 | Root |
| 2 | Inner |
| 3 | Leaf |
| 4 | Leaf |
| 5 | Leaf |
±—±------+
解释:
节点 1 是根节点,因为它的父节点为空,并且它有子节点 2 和 3。
节点 2 是一个内部节点,因为它有父节点 1 和子节点 4 和 5。
节点 3、4 和 5 是叶子节点,因为它们有父节点而没有子节点。
示例 2:
输入:
Tree table:
±—±-----+
| id | p_id |
±—±-----+
| 1 | null |
±—±-----+
输出:
±—±------+
| id | type |
±—±------+
| 1 | Root |
±—±------+
解释:如果树中只有一个节点,则只需要输出其根属性。
注意:本题与 3054. 二叉树节点 一致。
建表语句
create table if not exists Tree
(
id int,
p_id int
);
truncate table Tree;
insert into Tree (id, p_id)
values ('1', null);
insert into Tree (id, p_id)
values ('2', '1');
insert into Tree (id, p_id)
values ('3', '1');
insert into Tree (id, p_id)
values ('4', '2');
insert into Tree (id, p_id)
values ('5', '2');
四、方案一和解题思路
思路解析
首先明确题目要求,输出type的值有多种,毫无疑问适合用case when分多种情况进行判断,然后输出相对应的值。
第一个父节点:很好实现,只要Tree表中的p_id为null值的id就是父节点
select id from Tree where p_id is null;
运行结果:
第二个内部节点:
内部节点要求理解过来就是在id和p_id中都存在的数字即为内部节点id,思路是使用左连接 left join 同样的表,将连接条件设置为表1的id等于表2的p_id,如果四列都有值且不为空,那么就是内部节点Inner,实现代码如下
select distinct t1.id
from Tree t1
left join Tree t2
on t1.id = t2.p_id
where t1.p_id is not null
and t2.p_id is not null;
运行结果:
第三个叶子节点:
可以理解为只有id中存在而p_id中不存在的数字即为叶子结点的ID,因为叶子节点不是任何节点的父节点。可以使用where 子查询加not in将id在p_id中存在的值剔除。
但是要注意!!! not in 后面中不能存在null值,会导致查询不出结果,而in可以有null值,所以需要使用ifnull()函数将null值转化为其他值,我转化为了字符串,具体实现代码如下
select id
from Tree
where id not in (select ifnull(p_id, 'ad') as id from Tree);
运行结果
最后使用case when嵌套这三种情况输出,最终代码如下
select id,
case
when p_id is null then 'Root'
when id in (select distinct t1.id
from Tree t1
left join Tree t2
on t1.id = t2.p_id
where t1.p_id is not null
and t2.p_id is not null) then 'Inner'
when id in (select id
from Tree tr1
where id not in (select ifnull(p_id, 'ad') as id from Tree tr2)
) then 'Leaf'
end as type
from Tree;
运行结果
三、方案二和思路
思路解析
方案二思路简单,但是代码略显臃肿,所以可以考虑使用cte表达式,创建一个连接的表供引用,方便使用,这样在case when 的时候就可以简化代码
但是要注意:cte表达式中,如果有多个列的列名相同,即使你在表达式里面select * 能查询,但是在主查询会引用不了这些列名相同的列,所以,需要在cte表达式中给列明相同的列起一个别名,才能在主查询中引用。
cte临时表代码,左连接同一个表
select t1.id as t1id,
t1.p_id as t1pid,
t2.id as t2id,
t2.p_id as t2pid
from Tree t1
left join Tree t2
on t1.id = t2.p_id;
结果如下
然后使用case when也很好判断了,t1id为空值的就为父节点,四个列值都不为空的就为内部节点,t2id或者t2pid为空的就为叶子节点。实现代码如下
with t1 as (select t1.id as t1id,
t1.p_id as t1pid,
t2.id as t2id,
t2.p_id as t2pid
from Tree t1
left join Tree t2
on t1.id = t2.p_id)
select distinct t1id as id,
case
when t1pid is null then 'Root'
when t1pid is not null and t2id is not null then 'Inner'
else 'Leaf'
end as type
from t1;
结果如图