MySQL - 我什么时候不应该加入表?值的组合爆炸

如何解决MySQL - 我什么时候不应该加入表?值的组合爆炸

我正在开发一个名为 classicmodels 的数据库,我在以下位置找到了该数据库:https://www.mysqltutorial.org/mysql-sample-database.aspx/

我意识到当我在“付款”和“订单”表之间执行 Inner Join 时,发生了“笛卡尔爆炸”。我知道这两个表不是要连接的。但是,我想知道是否可以仅通过查看关系模式来识别这一点,或者我是否应该一张一张地检查表。

例如,customer number '141' 在“订单表”中出现 26 次,这是我使用以下代码找到的:

SELECT
    customerNumber,COUNT(customerNumber) 
FROM
    orders
WHERE customerNumber=141
GROUP BY customerNumber;

并且相同的客户编号 (141) 在支付表中出现 13 次:

SELECT
    customerNumber,COUNT(customerNumber)
FROM
    payments
WHERE customerNumber=141
GROUP BY customerNumber;

最后,我在 'payments' 和 'orders' 表之间执行了内部连接,并且只选择了客户编号为 '141' 的行。 MySQL 返回了 338 rows,这是 26*13 的结果。因此,我的查询是将这个“customer n°”在“orders”表中出现的次数乘以它在“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(不会产生组合爆炸)?还是应该一表一查,了解它们之间的关系?

  • 重要提示:我意识到在下面的关系模式中支付表的表示中有两个星号。也许这意味着这个表有一个复合主键(customerNumber+checkNumber)。问题是“checkNumber”没有出现在任何其他表中。

这是“MySQL 教程”网站提供的数据库关系模式:

enter image description here

感谢您的关注!

解决方法

这被称为“组合爆炸”,当一个表中的每一行都连接到其他表中的多行时就会发生这种情况。

(这不是“高估”或任何类型的估计。它是多次计算数据项,而应该只计算一次。)

在一对多关系中汇总数据是一个臭名昭著的陷阱。在您的示例中,每个客户可能没有订单、一个订单或多个订单。独立地,他们可能没有付款,一项或多项。

诀窍是这样的:使用子查询,这样你的顶级查询和 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

带回家的技巧:

  1. 子查询是一个表(一个虚拟表),可以在您可能提到表或视图的任何地方使用。
  2. 此查询中的 GROUP BY 内容分别出现在两个子查询中,因此不会出现组合爆炸。
  3. 顶级 JOIN 中的所有三个参与者每个 customernumber 都有一行或零行。
  4. LEFT JOIN 存在,因此我们仍然可以看到没有订单或没有付款的客户(对于企业来说很重要)。使用普通的内部 JOIN,行必须匹配 ON 条件的两侧,否则会从结果集中省略。

专业提示 认真地格式化您的 SQL 查询:它们真的很冗长。 Adm. Grace Hopper 会很自豪的。这意味着它们变得很长和嵌套,将 Structured 放在结构化查询语言中。如果您或任何人将来要对它们进行推理,我们必须能够轻松掌握结构。

专业提示 2 设计此数据库的数据工程师非常出色地思考并记录了它。追求这种质量水平。 (在现实世界中很少达到。)

,

在这种特殊情况下,您的行为应取决于数据库支持的会计风格,这似乎不是“未清项目”风格的会计,即当订单为 1000 时,不需要支付 1000 美元……这在大多数消费者体验中可能是不寻常的,因为您会非常熟悉从亚马逊订购的开放式商品样式 - 您购买了 500 美元的电视和 500 美元的游戏机,订单是 1000 美元,而您支付它,付款与订单相反。但是,如果您使用信用卡支付该订单,那么您也熟悉“余额远期”会计,因为您一个月内每天都进行类似的购买,然后您收到银行的对帐单,说您欠了 31000,您支付了一大笔钱,甚至不必是31k。预计您不会在月底向您的银行支付 31 笔 1000 美元。您的银行会将其分配给帐户中最旧的项目(如果它们很好,或者如果它们不是最新的项目),并且最终可能会向您收取未付交易的利息

1 ) 有没有办法查看关系模式并确定是否可以执行联接

是的,您可以通过查看架构看出 - 客户有很多订单,客户进行了很多付款,但是订单和付款表之间根本没有任何关系,因此我们可以看到没有尝试将付款直接附加到订单。您可以看到 customer 是支付和订单的父表,因此与他们每个人都有关系,但彼此之间没有关系。如果你有 Person、Car 和 Address 表,那么一个人一生中有很多地址,也有很多汽车,但这并不意味着汽车和地址之间存在关系

在这种情况下,加入 payments to customers to orders 根本没有意义,因为它们不是那样相关的。如果您想进行这样的连接而不遭受笛卡尔爆炸,那么您绝对必须对一侧或另一侧(或两者)求和以确保您的连接是 1:1 和 1:M(或 1:1 和 1: 1)。您不能安排一对 1:M 的连接。

回到汽车/人/地址示例以进行任何有意义的连接,您必须在问题中构建更多信息并安排连接以创建答案。也许问题是“他们住在那里时拥有哪些汽车”-这将 Person:Address 关系扁平化为 1:1,但将 Person:Car 保留为 1:M,因此他们在那所房子里的时间可能拥有许多汽车。 “他们住在……时拥有的最新汽车是什么?”如果“最新”有明显的赢家,那么双方可能是 1:1(尽管如果他们购买了两辆同时制造的汽车……)>

您在订单案例中对哪一方进行汇总取决于您想知道的内容,但在这种情况下,我会说您通常想知道“哪些订单尚未支付”,这就是对所有付款和滚动进行汇总将所有订单相加,然后查看滚动总和超过付款总和的点。那些是未支付的订单

再次查看您的数据库图(出现在您问题的第一次迭代中的图)。看到桌子之间的线在一端有 3 个成角度的腿 - 那是多端。您可以从图表中的任何表开始,并通过沿关系走来加入其他表。如果您要从多端走到一端,并假设您在起始表中选择了一行(单个订单),则您始终可以沿多 -> 一个方向走到任何其他表,而不是增加你的行数。如果你走另一条路,你可能会增加你的行数。如果你分开走两条路,都会增加行数,你会得到笛卡尔爆炸。当然,您也不必只在关系线上加入,但这超出了问题的范围

ps:这在 db 图上比问题中的 ERD 更容易看到,因为数据库本身只关心外键列。 ERD 表示客户使用特定支票号码进行零次或一次付款,但数据库只会关注“客户 ID 在客户表中出现一次,在付款表中出现多次”,因为只有复合主键的一部分付款的关键在于客户表。换句话说,ERD 也关注业务逻辑关系,但 db 图纯粹是表的关联方式,它们不一定对齐。出于这个原因,数据库图在四处走动以了解连接策略时可能更容易阅读

,

在看到 Caius Jard 和 O.Jones 的回答(请检查他们的回复)后,他们帮助我澄清了这个疑问,我决定创建一个表格来确定哪些客户为他们所做的所有订单付款,哪些客户付款没有。这创造了加入 'orders'、'orderdetails'、'payments' 和 'customers' 表的相关理由,因为一些订单可能已被取消或仍可能处于“暂停”状态,正如我们在相应的“状态”中所见在“订单”表中。此外,这使我们能够在不产生“组合爆炸”的情况下执行此连接。

我通过使用 CASE statement 来做到这一点,它在 py.amountamount_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 行。下面的图像是生成输出的一部分,因此您可以直观地看到发生了什么:

enter image description here

例如,我们可以看到由数字“141”、“124”、“119”和“496”标识的客户并未为他们下的所有订单付款。也许他们中的一些被取消了,或者他们只是还没有付钱。

这张图片显示了一些为 NULL 的列(不是全部):

enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)> insert overwrite table dwd_trade_cart_add_inc > select data.id, > data.user_id, > data.course_id, > date_format(
错误1 hive (edu)> insert into huanhuan values(1,'haoge'); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive> show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 <configuration> <property> <name>yarn.nodemanager.res