在 CTE 的递归部分是否有替代方法来使用 ROW_NUMBER()?

Is there an alternative to using ROW_NUMBER() in the recursive part of a CTE?

我正在尝试生成完整的物料清单展开图,其中显示以下内容: (a) 所有子组件都紧跟在它们的 parent/senior 产品之后 (b) CTE的anchor部分读到的产品level为1 (c) 在 CTE 的递归部分中读取的组件的级别比其父级别大 1

查询在 SQL 服务器中有效,但在 MySQL 8.

中失败

我尝试了多种方法来获取等同于 ROW_NUMBER() 的值,但没有成功。我不能使用变量,因为这些在 CTE 中是不允许的。

这是我的 table 结构:

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 服务器中有效但在 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 

错误信息是"Recursive Common Table Expression 'bom' can contain neither aggregate nor window functions in recursive query block"

预期的输出应该是:

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 行。

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

如果您的产品 ID 不包含 space (ASCII 32),这将是显而易见的选择,否则您可以使用 more space s(比您的数据中出现的任何次数都多)或例如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 

returns 正确放置在第 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)。如果这两列不是唯一的,则可以附加主键以使顺序定义明确(尽管在您的情况下,它已经是主键)。

我从 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;

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