MySQL - 我什么时候不应该加入表格?值的组合爆炸
MySQL - When shouldn't I Join tables? Combinatorial Explosion of values
我正在处理一个名为 classicmodels
的数据库,我发现它位于:https://www.mysqltutorial.org/mysql-sample-database.aspx/
我意识到当我在 'payments' 和 'orders' table 之间执行 Inner Join
时,发生了 'cartesian explosion'。我知道这两个 table 不是要合并的。但是,我想知道是否可以仅通过查看关系模式来识别它,或者我是否应该一个一个地检查 tables。
例如,customer number '141'
在'orders table'中出现了26
次,这是我用下面的代码发现的:
SELECT
customerNumber,
COUNT(customerNumber)
FROM
orders
WHERE customerNumber=141
GROUP BY customerNumber;
并且相同的客户编号 (141) 在付款中出现 13
次 table:
SELECT
customerNumber,
COUNT(customerNumber)
FROM
payments
WHERE customerNumber=141
GROUP BY customerNumber;
最后,我在 'payments' 和 'orders' table 之间执行了一个内部连接,并且只选择了客户编号为 '141' 的行。 MySQL 返回了 338 rows
,这是 26*13
的结果。因此,我的查询是将 'customer n°' 在 'orders' table 中出现的次数乘以它在 'payments'.
中出现的次数
SELECT
o.customernumber,
py.amount
FROM
customers c
JOIN
orders o ON c.customerNumber=o.customerNumber
JOIN
payments py ON c.customerNumber=py.customerNumber
WHERE o.customernumber=141;
我的问题如下:
1 ) 有没有办法查看关系模式并确定是否可以执行 Join(不产生组合爆炸)?或者我应该通过table检查table以了解它们之间的关系?
- 重要说明:我意识到在下面的关系模式中 table 的付款表示中有两个星号。也许这意味着这个 table 有一个复合主键 (customerNumber+checkNumber)。问题是 'checkNumber' 没有出现在任何其他 table.
中
这是 'MySQL Tutorial' 网站提供的数据库关系模式:
感谢您的关注!
这称为“组合爆炸”,它发生在一个 table 中的每行连接到其他 table 中的多行时。
(这不是“高估”或任何类型的估计。它在应该只计算一次数据项时多次计算数据项。)
在一对多关系中汇总数据是一个臭名昭著的陷阱。在您的示例中,每个客户可能没有订单、一个订单或多个订单。独立地,他们可能没有付款,一次或多次。
诀窍是这样的:使用子查询,这样您使用 GROUP BY 的顶级查询就可以避免连续加入一对多关系。在您向我们展示的查询中,这正在发生。
您可以使用此子查询获得每个客户只有一行的结果集。 (试一试。)
SELECT customernumber,
SUM(amount) amount
FROM payments
GROUP BY customernumber
同样你可以用这个
获得每个客户的所有订单的价值
SELECT c.customernumber,
SUM(od.qytOrdered * od.priceEach) amount
FROM orders o
JOIN orderdetails od ON o.orderNumber = od.orderNumber
GROUP BY c.customernumber
这个JOIN不会在你面前爆炸,因为客户可以有多个订单,每个订单可以有多个细节。所以这是一个严格的分层汇总。
现在,我们可以在主查询中使用这些子查询。
SELECT c.customernumber, p.payments, o.orders
FROM customers c
LEFT JOIN (
SELECT c.customernumber,
SUM(od.qytOrdered * od.priceEach) orders
FROM orders o
JOIN orderdetails od ON o.orderNumber = od.orderNumber
GROUP BY c.customernumber
) o ON c.customernumber = o.customernumber
LEFT JOIN (
SELECT customernumber,
SUM() payment
FROM payments
GROUP BY customernumber
) p on c.customernumber = p.customernumber
实用技巧:
- 子查询是一个 table(一个虚拟的 table),可以在您提到 table 或视图的任何地方使用。
- 此查询中的 GROUP BY 内容分别出现在两个子查询中,因此没有组合爆炸。
- 顶层 JOIN 中的所有三个参与者每个
customernumber
都有一行或零行。
- LEFT JOIN 在那里,所以我们仍然可以看到没有订单或没有付款的客户(对企业来说很重要)。对于普通的内部 JOIN,行必须匹配 ON 条件的两侧,否则它们会从结果集中被忽略。
专业提示 仔细格式化您的 SQL 查询:它们真的很冗长。 Adm. Grace Hopper 会感到自豪。这意味着它们变得相当长且嵌套,将 Structured 置于结构化查询语言中。如果您或任何人将来要对它们进行推理,我们必须能够轻松掌握结构。
专业提示 2 设计这个数据库的数据工程师在思考和记录方面做得非常好。渴望达到这种质量水平。 (在现实世界中很少到达。)
在这种特殊情况下,您的行为应该取决于数据库支持的会计风格,这似乎不是“未清项目”风格的会计,即当订单为 1000 时,不需要以 1000 美元作为付款。这在大多数消费者体验中可能 ps 不寻常,因为您会非常熟悉从亚马逊订购的开放式商品 - 您购买了 500 美元的电视和 500 美元的游戏机,订单是一千美元,你付钱,付款与订单不符。但是,如果您使用信用卡为该订单付款,您也熟悉“余额远期”会计,因为您在一个月内每天都进行类似的购买,然后您从银行收到一份声明,说您欠 31000,并且您支付了一大笔钱,甚至不必是 31k。您不需要在月底向银行支付 31 笔金额为 1000 的款项。您的银行将其分配给帐户中最旧的项目(如果它们很好,或者最新的项目如果它们不好)并且最终可能会向您收取未付款交易的利息
1 ) Is there a way to look at the relational schema and identify if a Join can be executed
是的,您可以通过查看架构来判断 - 客户有很多订单,客户进行了很多付款,但是订单和付款之间根本没有关系 tables 所以我们可以看到没有尝试直接将付款附加到订单。您可以看到 customer 是 payment 和 order 的父级 table,因此与他们每个人都有关系,但他们之间没有关系。如果你有 Person, Car 和 Address tables,一个人一生有很多地址,也有很多车,但这并不意味着车和地址之间有关系
在这种情况下,加入 payments to customers to orders
根本没有意义,因为它们之间没有那种关系。如果您想进行这样的连接而不遭受笛卡尔爆炸,那么您绝对必须对一侧或另一侧(或两侧)求和,以确保您的连接是 1:1 和 1:M(或 1:1 和 1:1)。您不能安排一对 1:M.
的联接
回到 car/person/address 示例以进行任何有意义的连接,您必须在问题中构建更多信息并安排连接以创建答案。 Perhaps 问题是“他们住的时候拥有什么车”——这将 Person:Address 关系扁平化为 1:1 但留下 Person:Car 为 1:M 所以他们在那所房子里可能拥有很多汽车。如果“最新”有明显的赢家,“他们住在...时拥有的最新汽车是什么”可能是 1:1 双方(尽管如果他们购买了两辆同时生产的汽车...)
您在订单案例中对哪一方求和取决于您想知道什么,但在这种情况下,我会说您通常想知道“哪些订单尚未付款”,这就是所有付款的总和并滚动汇总所有订单,然后查看滚动总和超过支付总和的点。这些是未支付的订单
再次查看您的 数据库 图表(在您的问题的第一次迭代中出现的图表)。看到 tables 之间的线在一端有 3 个倾斜的腿 - 这是许多端。您可以从图中的任何 table 开始,然后通过沿着关系移动来加入其他 table。如果你从多端到一端,假设你在开始时选择了一行 table (一个订单)你总是可以走到任何其他 table多->一个方向,而不是增加你的行数。如果你走另一条路,你可能会增加你的行数。如果您分开并走两种都增加行数的方式,您会得到笛卡尔爆炸。当然,你也不必只加入关系线,但这超出了问题的范围
ps:这在 db 图上比问题中的 ERD 更容易看到,因为数据库只关心外键列。 ERD 表示客户有零笔或一笔付款,并带有特定的支票号码,但数据库只会关注“客户 ID 在客户 table 中出现一次,在付款中出现多次 table”因为只有部分支付的复合主键键控给客户 table。换句话说,ERD 也关注业务逻辑关系,但数据库图纯粹是 table 的关系,它们不一定对齐。出于这个原因,在四处寻找连接策略时,数据库图可能更容易阅读
看到 Caius Jard 和 O.Jones 的回答(请检查他们的回复),帮助我澄清了这个疑问后,我决定创建一个 table 来识别哪些客户付款对于他们下的所有订单,哪些没有。这创造了加入 'orders'、'orderdetails'、'payments' 和 'customers' table 的相关原因,因为某些订单可能已被取消或仍可能 'On Hold',我们可以在'status'中看到它们对应的'orders'中的table。此外,这使我们能够在不生成 'combinatorial explosion'.
的情况下执行此连接
我是通过使用 CASE statement
来做到这一点的,它在 py.amount
和 amount_in_orders
匹配、不匹配或它们为 NULL(没有下订单的客户或付款):
SELECT
c.customerNumber,
py.amount,
amount_in_orders,
CASE
WHEN py.amount=amount_in_orders THEN 'Match'
WHEN py.amount IS NULL AND amount_in_orders IS NULL THEN 'NULL'
ELSE 'Don''t Match'
END AS Match
FROM
customers c
LEFT JOIN(
SELECT
o.customerNumber, SUM(od.quantityOrdered*od.priceEach) AS amount_in_orders
FROM
orders o
JOIN orderdetails od ON o.orderNumber=od.orderNumber
GROUP BY o.customerNumber
) o ON c.customerNumber=o.customerNumber
LEFT JOIN(
SELECT customernumber, SUM(amount) AS amount
FROM payments
GROUP BY customerNumber
) py ON c.customerNumber=py.customerNumber
ORDER BY py.amount DESC;
查询返回了 122 行。下面的图像是生成的输出的一部分,因此您可以想象发生了什么:
例如,我们可以看到编号为“141”、“124”、“119”和“496”的客户并没有为他们下的所有订单付款。也许他们中的一些人取消了或者他们只是还没有支付。
这张图片显示了一些为 NULL 的列(不是全部):
我正在处理一个名为 classicmodels
的数据库,我发现它位于:https://www.mysqltutorial.org/mysql-sample-database.aspx/
我意识到当我在 'payments' 和 'orders' table 之间执行 Inner Join
时,发生了 'cartesian explosion'。我知道这两个 table 不是要合并的。但是,我想知道是否可以仅通过查看关系模式来识别它,或者我是否应该一个一个地检查 tables。
例如,customer number '141'
在'orders table'中出现了26
次,这是我用下面的代码发现的:
SELECT
customerNumber,
COUNT(customerNumber)
FROM
orders
WHERE customerNumber=141
GROUP BY customerNumber;
并且相同的客户编号 (141) 在付款中出现 13
次 table:
SELECT
customerNumber,
COUNT(customerNumber)
FROM
payments
WHERE customerNumber=141
GROUP BY customerNumber;
最后,我在 'payments' 和 'orders' table 之间执行了一个内部连接,并且只选择了客户编号为 '141' 的行。 MySQL 返回了 338 rows
,这是 26*13
的结果。因此,我的查询是将 'customer n°' 在 'orders' table 中出现的次数乘以它在 'payments'.
SELECT
o.customernumber,
py.amount
FROM
customers c
JOIN
orders o ON c.customerNumber=o.customerNumber
JOIN
payments py ON c.customerNumber=py.customerNumber
WHERE o.customernumber=141;
我的问题如下:
1 ) 有没有办法查看关系模式并确定是否可以执行 Join(不产生组合爆炸)?或者我应该通过table检查table以了解它们之间的关系?
- 重要说明:我意识到在下面的关系模式中 table 的付款表示中有两个星号。也许这意味着这个 table 有一个复合主键 (customerNumber+checkNumber)。问题是 'checkNumber' 没有出现在任何其他 table. 中
这是 'MySQL Tutorial' 网站提供的数据库关系模式:
感谢您的关注!
这称为“组合爆炸”,它发生在一个 table 中的每行连接到其他 table 中的多行时。
(这不是“高估”或任何类型的估计。它在应该只计算一次数据项时多次计算数据项。)
在一对多关系中汇总数据是一个臭名昭著的陷阱。在您的示例中,每个客户可能没有订单、一个订单或多个订单。独立地,他们可能没有付款,一次或多次。
诀窍是这样的:使用子查询,这样您使用 GROUP BY 的顶级查询就可以避免连续加入一对多关系。在您向我们展示的查询中,这正在发生。
您可以使用此子查询获得每个客户只有一行的结果集。 (试一试。)
SELECT customernumber,
SUM(amount) amount
FROM payments
GROUP BY customernumber
同样你可以用这个
获得每个客户的所有订单的价值 SELECT c.customernumber,
SUM(od.qytOrdered * od.priceEach) amount
FROM orders o
JOIN orderdetails od ON o.orderNumber = od.orderNumber
GROUP BY c.customernumber
这个JOIN不会在你面前爆炸,因为客户可以有多个订单,每个订单可以有多个细节。所以这是一个严格的分层汇总。
现在,我们可以在主查询中使用这些子查询。
SELECT c.customernumber, p.payments, o.orders
FROM customers c
LEFT JOIN (
SELECT c.customernumber,
SUM(od.qytOrdered * od.priceEach) orders
FROM orders o
JOIN orderdetails od ON o.orderNumber = od.orderNumber
GROUP BY c.customernumber
) o ON c.customernumber = o.customernumber
LEFT JOIN (
SELECT customernumber,
SUM() payment
FROM payments
GROUP BY customernumber
) p on c.customernumber = p.customernumber
实用技巧:
- 子查询是一个 table(一个虚拟的 table),可以在您提到 table 或视图的任何地方使用。
- 此查询中的 GROUP BY 内容分别出现在两个子查询中,因此没有组合爆炸。
- 顶层 JOIN 中的所有三个参与者每个
customernumber
都有一行或零行。 - LEFT JOIN 在那里,所以我们仍然可以看到没有订单或没有付款的客户(对企业来说很重要)。对于普通的内部 JOIN,行必须匹配 ON 条件的两侧,否则它们会从结果集中被忽略。
专业提示 仔细格式化您的 SQL 查询:它们真的很冗长。 Adm. Grace Hopper 会感到自豪。这意味着它们变得相当长且嵌套,将 Structured 置于结构化查询语言中。如果您或任何人将来要对它们进行推理,我们必须能够轻松掌握结构。
专业提示 2 设计这个数据库的数据工程师在思考和记录方面做得非常好。渴望达到这种质量水平。 (在现实世界中很少到达。)
在这种特殊情况下,您的行为应该取决于数据库支持的会计风格,这似乎不是“未清项目”风格的会计,即当订单为 1000 时,不需要以 1000 美元作为付款。这在大多数消费者体验中可能 ps 不寻常,因为您会非常熟悉从亚马逊订购的开放式商品 - 您购买了 500 美元的电视和 500 美元的游戏机,订单是一千美元,你付钱,付款与订单不符。但是,如果您使用信用卡为该订单付款,您也熟悉“余额远期”会计,因为您在一个月内每天都进行类似的购买,然后您从银行收到一份声明,说您欠 31000,并且您支付了一大笔钱,甚至不必是 31k。您不需要在月底向银行支付 31 笔金额为 1000 的款项。您的银行将其分配给帐户中最旧的项目(如果它们很好,或者最新的项目如果它们不好)并且最终可能会向您收取未付款交易的利息
1 ) Is there a way to look at the relational schema and identify if a Join can be executed
是的,您可以通过查看架构来判断 - 客户有很多订单,客户进行了很多付款,但是订单和付款之间根本没有关系 tables 所以我们可以看到没有尝试直接将付款附加到订单。您可以看到 customer 是 payment 和 order 的父级 table,因此与他们每个人都有关系,但他们之间没有关系。如果你有 Person, Car 和 Address tables,一个人一生有很多地址,也有很多车,但这并不意味着车和地址之间有关系
在这种情况下,加入 payments to customers to orders
根本没有意义,因为它们之间没有那种关系。如果您想进行这样的连接而不遭受笛卡尔爆炸,那么您绝对必须对一侧或另一侧(或两侧)求和,以确保您的连接是 1:1 和 1:M(或 1:1 和 1:1)。您不能安排一对 1:M.
回到 car/person/address 示例以进行任何有意义的连接,您必须在问题中构建更多信息并安排连接以创建答案。 Perhaps 问题是“他们住的时候拥有什么车”——这将 Person:Address 关系扁平化为 1:1 但留下 Person:Car 为 1:M 所以他们在那所房子里可能拥有很多汽车。如果“最新”有明显的赢家,“他们住在...时拥有的最新汽车是什么”可能是 1:1 双方(尽管如果他们购买了两辆同时生产的汽车...)
您在订单案例中对哪一方求和取决于您想知道什么,但在这种情况下,我会说您通常想知道“哪些订单尚未付款”,这就是所有付款的总和并滚动汇总所有订单,然后查看滚动总和超过支付总和的点。这些是未支付的订单
再次查看您的 数据库 图表(在您的问题的第一次迭代中出现的图表)。看到 tables 之间的线在一端有 3 个倾斜的腿 - 这是许多端。您可以从图中的任何 table 开始,然后通过沿着关系移动来加入其他 table。如果你从多端到一端,假设你在开始时选择了一行 table (一个订单)你总是可以走到任何其他 table多->一个方向,而不是增加你的行数。如果你走另一条路,你可能会增加你的行数。如果您分开并走两种都增加行数的方式,您会得到笛卡尔爆炸。当然,你也不必只加入关系线,但这超出了问题的范围
ps:这在 db 图上比问题中的 ERD 更容易看到,因为数据库只关心外键列。 ERD 表示客户有零笔或一笔付款,并带有特定的支票号码,但数据库只会关注“客户 ID 在客户 table 中出现一次,在付款中出现多次 table”因为只有部分支付的复合主键键控给客户 table。换句话说,ERD 也关注业务逻辑关系,但数据库图纯粹是 table 的关系,它们不一定对齐。出于这个原因,在四处寻找连接策略时,数据库图可能更容易阅读
看到 Caius Jard 和 O.Jones 的回答(请检查他们的回复),帮助我澄清了这个疑问后,我决定创建一个 table 来识别哪些客户付款对于他们下的所有订单,哪些没有。这创造了加入 'orders'、'orderdetails'、'payments' 和 'customers' table 的相关原因,因为某些订单可能已被取消或仍可能 'On Hold',我们可以在'status'中看到它们对应的'orders'中的table。此外,这使我们能够在不生成 'combinatorial explosion'.
的情况下执行此连接我是通过使用 CASE statement
来做到这一点的,它在 py.amount
和 amount_in_orders
匹配、不匹配或它们为 NULL(没有下订单的客户或付款):
SELECT
c.customerNumber,
py.amount,
amount_in_orders,
CASE
WHEN py.amount=amount_in_orders THEN 'Match'
WHEN py.amount IS NULL AND amount_in_orders IS NULL THEN 'NULL'
ELSE 'Don''t Match'
END AS Match
FROM
customers c
LEFT JOIN(
SELECT
o.customerNumber, SUM(od.quantityOrdered*od.priceEach) AS amount_in_orders
FROM
orders o
JOIN orderdetails od ON o.orderNumber=od.orderNumber
GROUP BY o.customerNumber
) o ON c.customerNumber=o.customerNumber
LEFT JOIN(
SELECT customernumber, SUM(amount) AS amount
FROM payments
GROUP BY customerNumber
) py ON c.customerNumber=py.customerNumber
ORDER BY py.amount DESC;
查询返回了 122 行。下面的图像是生成的输出的一部分,因此您可以想象发生了什么:
例如,我们可以看到编号为“141”、“124”、“119”和“496”的客户并没有为他们下的所有订单付款。也许他们中的一些人取消了或者他们只是还没有支付。
这张图片显示了一些为 NULL 的列(不是全部):