具有hasMany关系的递归查询

时间:2019-02-08 15:14:59

标签: mysql sql common-table-expression

表表示是实际问题的简化,因为它可以捕获情况,但更易于理解。

两个表包含度量单位的定义。有一个包含所有单位符号的表和一个具有许多相关的表,其中包含所有单位的定义段。例如,

  • 它将码定义为0.9144
  • 它定义了一条链22
  • 它定义了一条220码的弗龙
  • 它定义了一个1链乘以1弗隆的一英亩

表格:

measurement_units
id      name        is_base_unit
----------------------------------
1       meter       1
2       yard        0
3       chain       0
4       furlong     0
5       acre        0


measurement_unit_derivations
id      measurement_unit_id     derives_from_measurement_unit_id    factor
---------------------------------------------------------------------------
1       2                       1                                   0.9144
2       3                       2                                   22
3       4                       2                                   220
4       5                       3                                   1
5       5                       4                                   1

我正在尝试编写一个递归公用表表达式,其中计算每个度量单位的比率相对于相关物理尺寸的基本单位。结果应该例如包含院子与米的比率和英亩与m ^ 2的比率。它应该看起来像这样:

measurement_unit_id     name        ratio
-------------------------------------------
1                       meter       1
2                       yard        0.9144
3                       chain       20.1168
4                       furlong     201.168
5                       acre        4046.8564224

问题在于,一个单位可以衍生自其他多个单位。例如,为了找出一英亩与m ^ 2的比率,我们将看到它来自一条furlong和一条链,这些链又来自其他单位。

我无法继续:

WITH RECURSIVE cte AS (
    SELECT 
        id AS measurement_unit_id
        name,
        1 AS ratio
    FROM measurement_units
    WHERE is_base_unit = 1
    UNION ALL
    ???
)

编辑: 关于米和英亩的示例表很容易描述,但也太简单了。我创建了另一个示例,在该示例中递归查询也应该起作用:

measurement_units
id      name        is_base_unit
----------------------------------
1       A           1
2       B           1
3       C           0
4       D           0
5       E           0

measurement_unit_derivations
id      measurement_unit_id     derives_from_measurement_unit_id    factor
---------------------------------------------------------------------------
1       3                       1                                   2
2       4                       2                                   2
3       4                       3                                   2
4       5                       4                                   2

此结果就是目标:

measurement_unit_id     name        ratio
-------------------------------------------
1                       A           1
2                       B           1
3                       C           2
4                       D           8
5                       E           16

这是创建这两个表及其内容的快速而肮脏的SQL。

CREATE TABLE `measurement_units` (`id` int(10) UNSIGNED NOT NULL, `name` varchar(190) NOT NULL, `is_base_unit` tinyint(3) UNSIGNED NOT NULL);
CREATE TABLE `measurement_unit_derivations` (`id` int(10) UNSIGNED NOT NULL, `measurement_unit_id` int(10) UNSIGNED NOT NULL, `derived_from_measurement_unit_id` int(10) UNSIGNED NOT NULL, `factor` int(10) UNSIGNED NOT NULL);

INSERT INTO `measurement_units` (`id`, `name`, `is_base_unit`) VALUES (1, 'A', 1), (2, 'B', 1), (3, 'C', 0), (4, 'D', 0), (5, 'E', 0);
INSERT INTO `measurement_unit_derivations` (`id`, `measurement_unit_id`, `derived_from_measurement_unit_id`, `factor`) VALUES (1, 3, 1, 2), (2, 4, 2, 2), (3, 4, 3, 2), (4, 5, 4, 2);

1 个答案:

答案 0 :(得分:0)

您已经正确设置了递归种子。我们从base_unit = 1开始。 UNION ALL之后的下一部分是递归项。在这里,您可以参考cte并将其连接到源表中,从而建立迭代关系。

您的递归术语将采用以下形式:

SELECT 
    mu.id,
    mu.name,
    cte.ratio * mud.factor
FROM
    cte
    INNER JOIN measurement_unit_derivations mud
        ON cte.measurement_unit_id = mud.derives_from_measurement_unit_id
    INNER JOIN measurement_units mud
        ON mud.measurement_unit_id = mu.id

这将为从二维或多个维导出的测量创建多个记录。由于规则似乎是我们将多个导出的测量值相乘以确定比率(链*犁沟=英亩),因此我们可以在完成此递归cte后进行汇总。

诀窍在于,没有像加法(SUM())这样的乘法聚合。因此,我们必须对其进行数学计算。我相信以下方法会起作用:

SELECT measurement_unit_id, name, EXP(SUM(LOG(ratio))) FROM cte GROUP BY measurement_unit_id, name;

将所有内容放在一起:

WITH RECURSIVE cte AS (
    SELECT 
        measurement_unit_id
        name,
        1 AS ratio
    FROM measurement_units
    WHERE base_unit = 1
    UNION ALL
    SELECT 
        mu.id,
        mu.name,
        cte.ratio * mud.factor
    FROM
        cte
        INNER JOIN measurement_unit_derivations mud
            ON cte.measurement_unit_id = mud.derives_from_measurement_unit_id
        INNER JOIN measurement_units mud
            ON mud.measurement_unit_id = mu.id
)
SELECT measurement_unit_id, name, EXP(SUM(LOG(ratio))) FROM cte GROUP BY measurement_unit_id, name;