获取特定值的两个many-to-many关系的交集

Get the intersection of two many-to-many relationship of specific values

N.B。我已经用 SQLAlchemy 和 Python 标记了它,因为问题的重点是开发一个查询以转换为 SQLAlchemy。这在我发布的答案中很清楚。同样适用于MySQL.

我用三个相互关联的 table 来描述一本书。 (在下面的 table 描述中,我删除了手头问题的无关行。)

MariaDB [icc]> describe edition;
+-----------+------------+------+-----+---------+----------------+
| Field     | Type       | Null | Key | Default | Extra          |
+-----------+------------+------+-----+---------+----------------+
| id        | int(11)    | NO   | PRI | NULL    | auto_increment |
+-----------+------------+------+-----+---------+----------------+
7 rows in set (0.001 sec)

MariaDB [icc]> describe line;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| edition_id | int(11)      | YES  | MUL | NULL    |                |
| line       | varchar(200) | YES  |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.001 sec)

MariaDB [icc]> describe line_attribute;
+------------+------------+------+-----+---------+-------+
| Field      | Type       | Null | Key | Default | Extra |
+------------+------------+------+-----+---------+-------+
| line_id    | int(11)    | NO   | PRI | NULL    |       |
| num        | int(11)    | YES  |     | NULL    |       |
| precedence | int(11)    | YES  | MUL | NULL    |       |
| primary    | tinyint(1) | NO   | MUL | NULL    |       |
+------------+------------+------+-----+---------+-------+
5 rows in set (0.001 sec)

line_attribute.precedence 是给定标题的层级。因此,如果 War 并且 Peace 有 Books > Chapters,则所有行都有一个对应于它们所在的 Book 的属性(例如,Book 1 有 precedence=1num=1)和一个他们所在章节的属性(例如,第 2 章有 precedence=2num=2)。这使我能够翻译书籍的层次结构,包括卷、书、部分、章、节,甚至是幕和场景。主列是一个布尔值,因此每一行都有 one 属性是主要的。如果是书名,就是Book属性,如果是章节标题,就是Chapter属性。如果是文本中的规则行,则为 line 属性,优先级为 0,因为它不是层次结构的一部分。

我需要能够查询具有特定 edition_id 并且还具有两个 line_attributes.

交集的所有行

(这将允许我从特定版本中获取所有行,例如,War 和和平的第 1 章第 2 章)。

我可以通过

获取所有包含第 1 册的行
SELECT
    line.*
FROM
    line
INNER JOIN
    line_attribute
ON
    line_attribute.line_id=line.id
WHERE
    line.edition_id=2 AND line_attribute.precedence=1 AND line_attribute.num=1;

而且我可以获得所有包含第 2 章的行:

SELECT
    line.*
FROM
    line
INNER JOIN
    line_attribute
ON
    line_attribute.line_id=line.id
WHERE
    line.edition_id=2 AND line_attribute.precedence=2 AND line_attribute.num=1;

除了第二个查询 returns War 和 Peace 中每本书的第 2 章。

如何从这两个查询中得到 只是 第 1 本书第 2 章中的行?

评论中来自Raymond Nijland的警告:

Note for future readers.. Because this question is tagged MySQL.. MySQL does not support INTERSECT keyword.. MariaDB is indeed a fork off the MySQL source code but supports extra features which MySQL does not support.. In MySQL you can simulate the INTERSECT keyword with a INNER JOIN or IN()

尝试在 SO 上写一个问题可以帮助我理清思路并最终在我不得不提出问题之前解决问题。上面的查询比我最初的查询要清楚得多,而且问题本身几乎可以回答,但我从来没有找到一个明确的答案来讨论 intersect 实用程序,所以我还是发布了这个答案。

解决方案 INTERSECT 运算符。

解决方案只是这两个查询的交集:

SELECT
    line.*
FROM
    line
INNER JOIN
    line_attribute
ON
    line_attribute.line_id=line.id
WHERE
    line.edition_id=2 AND line_attribute.precedence=1 AND line_attribute.num=1

INTERSECT    /* it is literally this simple */

SELECT
    line.*
FROM
    line
INNER JOIN
    line_attribute
ON
    line_attribute.line_id=line.id
WHERE
    line.edition_id=2 AND line_attribute.precedence=2 AND line_attribute.num=2;

这也意味着我可以通过简单地添加一个附加约束 (line_attribute.primary=1) 来获取特定书籍的所有书籍和章节标题。

这个解决方案似乎广泛适用于我。例如,假设您在 Whosebug 克隆中有问题,这些问题已被标记,您可以获得带有两个标记的问题的交集(例如,所有同时具有 SQLAlchemyPython 标记的帖子)。我肯定会使用这种方法进行此类查询。

我在 MySQL 中对此进行了编码,因为它可以帮助我直接将查询转换为 SQLAlchemy。

SQLAlchemy 查询非常简单:

[nav] In [10]: q1 = Line.query.join(LineAttribute).filter(LineAttribute.precedence==1, LineAttribute.num==1)                              

[ins] In [11]: q2 = Line.query.join(LineAttribute).filter(LineAttribute.precedence==2, LineAttribute.num==1)                              

[ins] In [12]: q1.intersect(q2).all()  

希望这个问题中的数据库结构可以帮助某人。自己解决问题后不想删题