【问题标题】:Recursive query with hasMany relationship具有hasMany关系的递归查询
【发布时间】:2019-02-08 15:14:58
【问题描述】:

表格表示是对实际问题的简化,因为它捕捉了情况但更容易理解。

两个表包含测量单位的定义。有一个包含所有单位符号的表和一个包含所有单位的定义段的 has-many 相关表。例如,

  • 它将一个码定义为0.9144meter
  • 它将链定义为22
  • 它将弗隆定义为220
  • 它将一英亩定义为1链次1furlong

表格:

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 的比值,我们将看到它源自一弗隆和一链,而后者又源自其他单位。

我无法继续超越:

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);

【问题讨论】:

  • 如何用这些数字计算英亩。一个人怎么知道(对这些测量的尺寸一无所知)一个是多个链条和犁沟?是不是如果它从一个以上的测量“推导出”,那么这些测量要相乘以确定结果?
  • @JNevill 是的,您的假设是正确的。我也没有在这些示例表中包含有关物理维度的信息,因为它在查询中不起作用。

标签: mysql sql common-table-expression


【解决方案1】:

您已经正确设置了递归种子。我们从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;

【讨论】:

  • EXP(SUM(LOG(expression))) 是计算组乘积的好方法,因为我们只使用 > 0 的值。但是,我认为您的解决方案在推导并不像我最初的例子中那样相互“平行”。我在编辑中添加了另一个带有虚构测量单位的示例。
猜你喜欢
  • 1970-01-01
  • 2021-12-18
  • 2013-04-07
  • 2023-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-09
  • 1970-01-01
相关资源
最近更新 更多