SQL 选择一行的后代
假设在 SQL 中实现了一个树结构,如下所示:
CREATE TABLE nodes (
id INTEGER PRIMARY KEY,
parent INTEGER -- references nodes(id)
);
虽然可以在此表示中创建循环,但我们假设我们永远不会让这种情况发生。该表将仅存储根(父级为空的记录)及其后代的集合。
目标是,给定表中节点的 id,找到其后代的所有节点。
如果 A 的父级是 B 或 A,则 A 是 B 的后代> 的父级是B 的后代。注意递归定义。
下面是一些示例数据:
INSERT INTO nodes VALUES (1, NULL);
INSERT INTO nodes VALUES (2, 1);
INSERT INTO nodes VALUES (3, 2);
INSERT INTO nodes VALUES (4, 3);
INSERT INTO nodes VALUES (5, 3);
INSERT INTO nodes VALUES (6, 2);
它表示:
1
`-- 2
|-- 3
| |-- 4
| `-- 5
|
`-- 6
我们可以通过这样做来选择 1
的(直接)子代:
SELECT a.* FROM nodes AS a WHERE parent=1;
我们可以通过这样做来选择 1
的子代和孙代:
SELECT a.* FROM nodes AS a WHERE parent=1
UNION ALL
SELECT b.* FROM nodes AS a, nodes AS b WHERE a.parent=1 AND b.parent=a.id;
我们可以通过执行以下操作来选择 1
的子代、孙子和曾孙:
SELECT a.* FROM nodes AS a WHERE parent=1
UNION ALL
SELECT b.* FROM nodes AS a, nodes AS b WHERE a.parent=1 AND b.parent=a.id
UNION ALL
SELECT c.* FROM nodes AS a, nodes AS b, nodes AS c WHERE a.parent=1 AND b.parent=a.id AND c.parent=b.id;
如何构造一个查询来获取节点 1
的所有后代而不是固定的后代深度?看来我需要创建一个递归查询或其他东西。
我想知道使用 SQLite 是否可以进行这样的查询。但是,如果这种类型的查询需要 SQLite 中不可用的功能,我很好奇是否可以在其他 SQL 数据库中完成。
Suppose a tree structure is implemented in SQL like this:
CREATE TABLE nodes (
id INTEGER PRIMARY KEY,
parent INTEGER -- references nodes(id)
);
Although cycles can be created in this representation, let's assume we never let that happen. The table will only store a collection of roots (records where parent is null) and their descendants.
The goal is to, given an id of a node on the table, find all nodes that are descendants of it.
A is a descendant of B if either A's parent is B or A's parent is a descendant of B. Note the recursive definition.
Here is some sample data:
INSERT INTO nodes VALUES (1, NULL);
INSERT INTO nodes VALUES (2, 1);
INSERT INTO nodes VALUES (3, 2);
INSERT INTO nodes VALUES (4, 3);
INSERT INTO nodes VALUES (5, 3);
INSERT INTO nodes VALUES (6, 2);
which represents:
1
`-- 2
|-- 3
| |-- 4
| `-- 5
|
`-- 6
We can select the (immediate) children of 1
by doing this:
SELECT a.* FROM nodes AS a WHERE parent=1;
We can select the children and grandchildren of 1
by doing this:
SELECT a.* FROM nodes AS a WHERE parent=1
UNION ALL
SELECT b.* FROM nodes AS a, nodes AS b WHERE a.parent=1 AND b.parent=a.id;
We can select the children, grandchildren, and great grandchildren of 1
by doing this:
SELECT a.* FROM nodes AS a WHERE parent=1
UNION ALL
SELECT b.* FROM nodes AS a, nodes AS b WHERE a.parent=1 AND b.parent=a.id
UNION ALL
SELECT c.* FROM nodes AS a, nodes AS b, nodes AS c WHERE a.parent=1 AND b.parent=a.id AND c.parent=b.id;
How can a query be constructed that gets all descendants of node 1
rather than those at a fixed depth? It seems like I would need to create a recursive query or something.
I'd like to know if such a query would be possible using SQLite. However, if this type of query requires features not available in SQLite, I'm curious to know if it can be done in other SQL databases.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
有些数据库允许使用递归公用表表达式,但 SQLite 不允许。
您可以考虑更改表定义。使用这样的表,很容易查询 1 的所有后代:
这允许您查询 1 的所有后代,如下所示:
听起来有点古怪,但在实践中效果很好。
Some databases allow that using recursive common table expressions, but not SQLite.
You could consider changing your table definition. With a table like this, it's easy to query all descendants of 1:
This allows you to query all descendants of 1 like:
It sounds a bit whacky, but works very well in practice.
您设置模式的方式并不真正适合关系模型(正如您所发现的)。但还有另一种方法,一开始可能不那么明显,但要灵活得多。它被称为“嵌套集合模型”,只是结构略有不同。
管理 MySQL 中的分层数据(查找“嵌套”部分)设置模型”)展示了如何在 MySQL 中执行此操作,但它应该相当容易地转换为 SQLite。我不会在这里讨论太多细节,因为那篇文章实际上很长,但它应该为您提供开始所需的所有内容。
The way you've set up your schema doesn't really suit itself very well to the relational model (as you've discovered). But there is another way that may not be so obvious at first, but is much more flexible. It's called a "nested set model" and it just structures things slightly differently.
Managing Hierarchical Data in MySQL (look for the section "The Nested Set Model") shows how to do this in MySQL, but it should translate to SQLite fairly easily. I won't go into too much detail here, because that article is actually pretty long, but it should give you all you need to get going.
Oracle 有一个 CONNECT BY 语法可以用于此目的,但当然它是 Oracle 特有的。
Oracle has a CONNECT BY syntax that could be used for this, but of course it's specific to oracle.