MySQL: 计算维恩图集幂的有效方法

MySQL: Efficient way computing set powers of Venn-Diagram

给定 4 个表,每个表包含项目并代表一个集合,如何获得绘制维恩图所需的每个隔间中的项目数,如下所示。计算应在 MySQL 服务器中进行,避免将项目传输到应用程序服务器。

示例表:

s1:         s2:         s3:         s4:
+------+    +------+    +------+    +------+
| item |    | item |    | item |    | item |
+------+    +------+    +------+    +------+
| a    |    | a    |    | a    |    | a    |
+------+    +------+    +------+    +------+
| b    |    | b    |    | b    |    | c    |
+------+    +------+    +------+    +------+
| c    |    | c    |    | d    |    | d    |
+------+    +------+    +------+    +------+
| d    |    | e    |    | e    |    | e    |
+------+    +------+    +------+    +------+
| ...  |    | ...  |    | ...  |    | ...  |

现在,我想我会计算一些设定的权力。 I对应s1II对应s2III对应s3IV对应[=21=的一些例子]:

如果我将 sx 重新解释为一个集合,我会写:

  1. |s1 ∩ s2 ∩ s3 ∩ s4| - 中间的白色 25
  2. |(s1 ∩ s2 ∩ s4) \ s3| - 右下方相对于中心的白色 15
  3. |(s1 ∩ s4) \ (s2 ∪ s3)| - 最下面的白5
  4. |s1 \ (s2 ∪ s3 ∪ s4)|-蓝底深蓝60
  5. ...直到 15。

如何在MySQL服务器上有效地计算那些权力? MySQL是否提供辅助计算的功能?

一种天真的方法是 运行 查询 1.

SELECT count(*) FROM(
SELECT item FROM s1
INTERSECT
SELECT item FROM s2
INTERSECT
SELECT item FROM s3
INTERSECT
SELECT item FROM s4);

和另一个查询 2.

SELECT count(*) FROM(
SELECT item FROM s1
INTERSECT
SELECT item FROM s2
INTERSECT
SELECT item FROM s4
EXCEPT
SELECT item FROM s3);

依此类推,产生 15 个查询。

程序如下:

  1. 创建了一个存储过程,该过程在内存中创建 table 个包含集合的临时文件。
  2. 请注意 MySQL 不允许您在一个查询中多次引用临时内存 table。
  3. 如前所述,MySQL 没有 INTERSECT or 。但是你可以模仿他们。通过从原始数据/原始集中删除重复项,可以进一步简化仿真。
  4. 决定将计算出的值分别存储到一个变量中,并输出一个 table 由对应于组件的所有 15 个值组成。

目前我想到的是https://gist.github.com/Rillke/c2da0921f8f2a047615f41fab8781c11

尝试这样的事情:

with universe as (
    select * from s1 
    union
    select * from s2
    union
    select * from s3
    union
    select * from s4
),
regions as (
    select
        case when s1.item is null then '0' else '1' end
        ||
        case when s2.item is null then '0' else '1' end
        ||
        case when s3.item is null then '0' else '1' end
        ||
        case when s4.item is null then '0' else '1' end as Region
    from universe u
    left join s1 on u.item = s1.item
    left join s2 on u.item = s2.item
    left join s3 on u.item = s3.item
    left join s4 on u.item = s4.item
)
select Region, count(*) from regions group by Region

免责声明:我仅在 SQLite 中对此进行了测试。您可能需要 SET sql_mode='PIPES_AS_CONCAT' 才能使 ANSI 字符串连接在 MySQL 中工作,或者改用 concat 函数。 WITH 语法仅从 MySQL 的 8.0 版开始受支持,但您可以适当地使用临时表或嵌套查询。

如果集合非常大,您可能需要在查询之前为 item 列建立索引,以防 SQL 优化器无法自行解决。

问题有点复杂,所以答案是。让我解释一下K.T。的回答

with universe as (
    select * from s1 
    union
    select * from s2
    union
    select * from s3
    union
    select * from s4
),
regions as (
    select
        case when s1.item is null then '0' else '1' end
        ||
        case when s2.item is null then '0' else '1' end
        ||
        case when s3.item is null then '0' else '1' end
        ||
        case when s4.item is null then '0' else '1' end as Region
    from universe u
    left join s1 on u.item = s1.item
    left join s2 on u.item = s2.item
    left join s3 on u.item = s3.item
    left join s4 on u.item = s4.item
)
select Region, count(*) from regions group by Region

universe 导致所有表的联合(消除重复项),类似于

+------+
| item |
+------+
| a    |
+------+
| b    |
+------+
| c    |
+------+
| d    |
+------+
| e    |
+------+
| ...  |
+------+

然后,s1、s2、s3、s4被加入

+------+---------+---------+---------+---------+
| item | s1.item | s2.item | s3.item | s4.item |
+------+---------+---------+---------+---------+
| a    | a       | a       | a       | a       |
+------+---------+---------+---------+---------+
| b    | b       | b       | b       | NULL    |
+------+---------+---------+---------+---------+
| c    | c       | c       | NULL    | c       |
+------+---------+---------+---------+---------+
| d    | d       | NULL    | d       | d       |
+------+---------+---------+---------+---------+
| e    | NULL    | e       | e       | e       |
+------+---------+---------+---------+---------+
| ...  | ...     | ...     | ...     | ...     |
+------+---------+---------+---------+---------+

并转换为二进制字符串(0:如果单元格为 NULL;1:否则)称为 Region,其中第一个数字对应于 s1,第二个对应于 s2,依此类推

+------+--------+
| item | Region |
+------+--------+
| a    | 1111   |
+------+--------+
| b    | 1110   |
+------+--------+
| c    | 1101   |
+------+--------+
| d    | 1011   |
+------+--------+
| e    | 0111   |
+------+--------+
| ...  | ...    |
+------+--------+

最后按地区聚合分组

+--------+-------+
| Region | count |
+--------+-------+
| 1111   | 1     |
+--------+-------+
| 1110   | 1     |
+--------+-------+
| 1101   | 1     |
+--------+-------+
| 1011   | 1     |
+--------+-------+
| 0111   | 1     |
+--------+-------+
| ...    |       |
+--------+-------+

请注意,其中包含 0 个集合元素的区域不会显示在结果中,并且 0000 永远不会显示(=项目不属于任何集合 s1、s2、s3、s4)所以有 15 个区域.