在 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_snr
和 pc.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;
我在文档中找不到对此的描述,但它确实有效。
我正在尝试生成完整的物料清单展开图,其中显示以下内容: (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_snr
和 pc.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;
我在文档中找不到对此的描述,但它确实有效。