【问题标题】:Is there an alternative to using ROW_NUMBER() in the recursive part of a CTE?在 CTE 的递归部分中使用 ROW_NUMBER() 是否有替代方法?
【发布时间】:2019-08-07 16:48:43
【问题描述】:

我正在尝试生成完整的物料清单分解,其中显示以下内容: (a) 所有子组件在其父/高级产品之后立即出现 (b) CTE的anchor部分读取的产品等级为1 (c) 在 CTE 的递归部分中读取的组件的级别比其父组件的级别大 1

查询在 SQL Server 中有效,但在 MySQL 8 中失败。

我尝试了获取与 ROW_NUMBER() 等效的值的方法,但没有成功。我不能使用变量,因为 CTE 不允许使用这些变量。

这是我的表结构:

CREATE TABLE IF NOT EXISTS `bill_of_materials` (
  `product_id_snr` varchar(40) NOT NULL,
  `product_id_jnr` varchar(40) NOT NULL,
  `quantity` decimal(18,9) unsigned NOT NULL DEFAULT '1.000000000',
  PRIMARY KEY (`product_id_snr`,`product_id_jnr`)
) ENGINE=InnoDB;

这是我的数据:

INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('SNAFU', 'B-2A_ASSY', 1);
INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('SNAFU', 'TMJ_TEST', 1);
INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('B-2A_ASSY', 'TMJ', 1);
INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('B-2A_ASSY', 'TMJ_ASSY_B', 1);
INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('TMJ', 'TMJ_CP', 1);
INSERT INTO `bill_of_materials` (`product_id_snr`, `product_id_jnr`, `quantity`) VALUES ('TMJ', 'TMJ_WAFER', 1);

这是在 SQL Server 中有效但在 MySQL 中失败的查询:

WITH RECURSIVE bom (sort_seq, level, product_id_snr, product_id_jnr, quantity)
AS ( SELECT CAST(CONCAT('/', LPAD(ROW_NUMBER() OVER (ORDER BY pc.product_id_snr ASC, pc.product_id_jnr ASC), 4, '0')) AS char(4000) CHARACTER SET UTF8MB4) AS sort_seq
, 1 AS level
, pc.product_id_snr, pc.product_id_jnr, pc.quantity
FROM bill_of_materials AS pc
WHERE product_id_snr='SNAFU'
  UNION ALL
  SELECT CONCAT(bom.sort_seq, '/', LPAD(ROW_NUMBER() OVER (ORDER BY pc.product_id_snr ASC, pc.product_id_jnr ASC), 4, '0')) AS sort_seq
, level+1, pc.product_id_snr, pc.product_id_jnr, pc.quantity
FROM bill_of_materials AS pc
INNER JOIN bom ON (pc.product_id_snr  = bom.product_id_jnr)
)
SELECT bom.* FROM bom
ORDER BY sort_seq ASC 

错误消息是“递归公用表表达式'bom'在递归查询块中既不能包含聚合函数也不能包含窗口函数”

预期的输出应该是:

sort_seq            ;level ;product_id_snr ;product_id_jnr ;quantity
/0001               ;1     ;SNAFU          ;B-2A_ASSY      ;1
/0001/0001          ;2     ;B-2A_ASSY      ;TMJ            ;1
/0001/0002/0001     ;3     ;TMJ            ;TMJ_CP         ;1
/0001/0002/0002     ;3     ;TMJ            ;TMJ_WAFER      ;1
/0001/0003          ;2     ;B-2A_ASSY      ;TMJ_ASSY_B     ;1
/0002               ;1     ;SNAFU          ;TMJ_TEST       ;1

如果我将 ROW_NUMBER() 替换为 product_id_jnr,如下所示:

CONCAT(bom.sort_seq, '/', pc.product_id_jnr) AS sort_seq

我得到以下结果:

sort_seq            ;level ;product_id_snr ;product_id_jnr ;quantity
/0001               ;1     ;SNAFU          ;B-2A_ASSY      ;1
/0001/TMJ           ;2     ;B-2A_ASSY      ;TMJ            ;1
/0001/TMJ_ASSY_B    ;2     ;B-2A_ASSY      ;TMJ_ASSY_B     ;1
/0001/TMJ/TMJ_CP    ;3     ;TMJ            ;TMJ_CP         ;1
/0001/TMJ/TMJ_WAFER ;3     ;TMJ            ;TMJ_WAFER      ;1
/0002               ;1     ;SNAFU          ;TMJ_TEST       ;1

这是错误的,因为 TMJ_ASSY_B 出现在第 3 行,而它应该在第 5 行。

【问题讨论】:

    标签: mysql common-table-expression


    【解决方案1】:

    将要排序的列包含在路径中的想法是正确的。您只需要确保分隔符在其他所有内容之前按字典顺序排序(因为您示例中的问题是TMJ_ASSY_B 中的_ 位于TMJ/TMJ_CP 中的/ 之前)。

    如果您的产品 ID 不包含空格(ASCII 32),这将是显而易见的选择,否则您可以使用 更多 个空格(比您的数据中出现的更多)或例如char(9)char(13) 或类似名称:

    WITH RECURSIVE bom (sort_seq, level, product_id_snr, product_id_jnr, quantity)
    AS (
      SELECT CAST(CONCAT(pc.product_id_snr, ' ', pc.product_id_jnr) 
                  AS char(4000) CHARACTER SET UTF8MB4) AS sort_seq
        , 1 AS level
        , pc.product_id_snr, pc.product_id_jnr, pc.quantity
      FROM bill_of_materials AS pc
      WHERE product_id_snr='SNAFU'
      UNION ALL
      SELECT CONCAT(bom.sort_seq, ' ', pc.product_id_snr, ' ',
               pc.product_id_jnr) AS sort_seq
        , level+1, pc.product_id_snr, pc.product_id_jnr, pc.quantity
      FROM bill_of_materials AS pc
      INNER JOIN bom ON (pc.product_id_snr  = bom.product_id_jnr)
    )
    SELECT bom.* FROM bom
    ORDER BY sort_seq ASC 
    

    在第 5 行返回一个正确放置的 TMJ_ASSY_B

    +----------------------------------------------+--------+-----------------+-----------------+----------+
    |                  sort_seq                    | level  | product_id_snr  | product_id_jnr  | quantity |
    +----------------------------------------------+--------+-----------------+-----------------+----------+
    | SNAFU B-2A_ASSY                              |     1  | SNAFU           | B-2A_ASSY       |        1 |
    | SNAFU B-2A_ASSY B-2A_ASSY TMJ                |     2  | B-2A_ASSY       | TMJ             |        1 |
    | SNAFU B-2A_ASSY B-2A_ASSY TMJ TMJ TMJ_CP     |     3  | TMJ             | TMJ_CP          |        1 |
    | SNAFU B-2A_ASSY B-2A_ASSY TMJ TMJ TMJ_WAFER  |     3  | TMJ             | TMJ_WAFER       |        1 |
    | SNAFU B-2A_ASSY B-2A_ASSY TMJ_ASSY_B         |     2  | B-2A_ASSY       | TMJ_ASSY_B      |        1 |
    | SNAFU TMJ_TEST                               |     1  | SNAFU           | TMJ_TEST        |        1 |
    +----------------------------------------------+--------+-----------------+-----------------+----------+
    

    同时使用pc.product_id_snrpc.product_id_jnr,以匹配您的原始订单(ORDER BY pc.product_id_snr ASC, pc.product_id_jnr ASC)。如果这两列不是唯一的,则可以附加主键以使顺序明确(尽管在您的情况下,它已经是主键)。

    【讨论】:

    • 我可以确认这是可行的。我已经在具有 200 多个条目和 5 个级别的 BOM 上进行了尝试,并且输出是 100% 正确的。非常感谢。
    【解决方案2】:

    我收到了 MySQL 某人的以下答复:

    WITH RECURSIVE bill_of_m_with_nr AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY pc.product_id_snr ORDER BY pc.product_id_snr ASC, pc.product_id_jnr ASC) AS child_nr FROM bill_of_materials pc)
    , bom (sort_seq, level, product_id_snr, product_id_jnr, quantity)
    AS ( SELECT CAST(CONCAT('/', LPAD(pc.child_nr, 4, '0')) AS char(4000)) AS sort_seq
    , 1 AS level
    , pc.product_id_snr, pc.product_id_jnr, pc.quantity
    FROM bill_of_m_with_nr AS pc
    WHERE product_id_snr='SNAFU'
      UNION ALL
      SELECT CONCAT(bom.sort_seq, '/', LPAD(pc.child_nr, 4, '0')) AS sort_seq
    , level+1, pc.product_id_snr, pc.product_id_jnr, pc.quantity
    FROM bill_of_m_with_nr AS pc
    INNER JOIN bom ON (pc.product_id_snr  = bom.product_id_jnr)
    )
    SELECT bom.* FROM bom
    ORDER BY sort_seq ASC;
    

    我在文档中没有找到对此的描述,但它确实有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多