Mysql sum distinct 基于包含多个 LEFT JOIN 的其他列

Posted

技术标签:

【中文标题】Mysql sum distinct 基于包含多个 LEFT JOIN 的其他列【英文标题】:Mysql sum distinct based on other columns containing multiple LEFT JOIN 【发布时间】:2017-10-26 01:58:21 【问题描述】:

我有 5 个表我想一起 LEFT JOIN。表格是: 访客、报价、合同 1、合同 2 和合同 3。

查询:

SELECT 
        count(DISTINCT visitors.ID) as visitors, 
        sum(
        CASE
        WHEN offers.ACTIVE = 1 THEN 1
        ELSE 0
        END) as offers, 
        count(contracts1.ID) as contracts1, sum(contracts1.PRICE) as sum_contracts1, 
        count(contracts2.ID) contracts2, 
        sum(
        CASE
        WHEN contracts2.PAYMENT = 'YEARLY' THEN contracts2.PRICE
        WHEN contracts2.PAYMENT = 'TWICE' THEN contracts2.PRICE*2
        ELSE contracts2.PRICE*4
        END) as sum_contracts2,
        count(contracts3.ID) as contracts3, sum(contracts3.PRICE) as sum_contracts3
        FROM visitors 
        LEFT JOIN offersON offers.VISITOR_ID = visitors.ID AND (offers.IP > 100 OR offers.IP < 0)
        LEFT JOIN contracts1 ON 
        (offers.ID = contracts1.ID_OFFER)
        LEFT JOIN contracts2 ON 
        (offers.ID = contracts2.ID_OFFER)
        LEFT JOIN contracts3 ON 
        (offers.ID = contracts3.ID_OFFER)
        WHERE  visitors.TIME >= '2017-01-01 00:00:00' AND visitors.TIME <= '2017-05-25 23:59:59'

这里的问题是,contracts1、contracts2 和contracts3 没有共同的列来连接在一起。因此,我得到了所有这些组合的所有组合,而不是contracts1 的 20 行、contracts2 的 30 行和contracts3 的 50 行。因为他们是根据访客和提供表格加入的。查询末尾的简单 GROUP BY 通常可以解决问题,但是如果我在 END 中对这些表之一(或所有表)使用 GROUP BY,它将创建 MULTIPLE ROWS 而不是我想要的 1。而且它还会删除我按 ID 计算访问者并按 ID 提供的部分的所有其他结果......我可以在 SELECT 的 count() 部分使用 DISTINCT,但不能使用 sum() 因为合同的 PRICE即使 ID 不同,也可能相同(例如,您知道 2 块巧克力是 2 行,ID 不同,但价格相同,每块 10 美元)。

所以我的问题是:

有没有什么方法可以只对具有 DISTINCT ID 的合同 1、合同 2 和合同 3 的价格进行求和,但可以避免重复项相加?是否可以不创建 VIEW?

我还在 LEFT JOIN 中尝试了 GROUP BY,但是当我将所有 3 个合同表一起 LEFT JOINED 时,即使我在最终得到重复项之前对它们进行了分组。

预期结果示例:

在我上面所说的那个时间范围内,我期望: 80 名参观者有 35 份报​​价和 5 份总金额为 1000 欧元的合同 1、12 份总金额为 686 欧元的合同 2 和 3 份总金额为 12 欧元的合同 3。它是 ONE ROW,有 8 列数据。

我得到的不是预期的结果: 80 位访客,35 份报​​价,180 份合同1(金额也很差),180 份合同2(金额也很差),180 份合同3(金额也很差)。

【问题讨论】:

乍一看似乎是一个规范化问题。如果您可以在任何地方一对一地加入它们,则无法获得所需的结果。或者,如果有这种可能性,请在子查询中使用它并加入。 是的,数据库是一团糟。我想创建一个选择而不是许多,以使其更快。此外,这个 ONE 选择应该只给我 6 列数据和 1 行。在这种情况下,您能否使用一个示例来说明您所说的 JOINING 子查询?我还将添加一些我想要实现的预期结果的示例(到我的问题中)。 首先用最少的连接得到正确的总和(SUM.. FROM x JOIN y)。使用上面的查询将它加入到主查询中。据我所知,问题是您从所有联接中获得总和。 这会不会适得其反?如果我必须加入 5 个表中的 3 个表 3 次才能再次将它们连接在一起?就像我想我知道你的意思,但是 VIEW 选项不是比你所说的子查询更好吗? 什么是 1:many 或 many:many 关系?例如,每个优惠是否有很多访客(many:1)?或相反亦然?另外,请提供SHOW CREATE TABLE 【参考方案1】:

使用 CTE (Supported by MariaDB 10.2.1) 我会写这样的东西:

WITH v AS (
    SELECT ID as VISITOR_ID
    FROM visitors 
    WHERE visitors.TIME >= '2017-01-01 00:00:00'
      AND visitors.TIME <= '2017-05-25 23:59:59'
), o AS (
    SELECT offers.ID as ID_OFFER
    FROM v
    JOIN offers USING(VISITOR_ID)
    WHERE offers.ACTIVE = 1
      AND (offers.IP > 100 OR offers.IP < 0)
), c1 AS (
    SELECT count(*) as contracts1, sum(contracts1.PRICE) as sum_contracts1
    FROM o JOIN contracts1 USING(ID_OFFER)
), c2 AS (
    SELECT
        count(*) contracts2, 
        sum(CASE contracts2.PAYMENT
            WHEN 'YEARLY' THEN contracts2.PRICE
            WHEN 'TWICE'  THEN contracts2.PRICE*2
            ELSE contracts2.PRICE*4
        END) as sum_contracts2
    FROM o JOIN contracts2 USING(ID_OFFER)
), c3 AS (
    SELECT count(*) as contracts3, sum(contracts3.PRICE) as sum_contracts3
    FROM o JOIN contracts3 USING(ID_OFFER)
)
    SELECT c1.*, c2.*, c3.*,
        (SELECT count(*) FROM v) as visitors,
        (SELECT count(*) FROM o) as offers,
    FROM c1, c2, c3;

如果没有 CTE,您可以重写它以使用临时表:

CREATE TEMPORARY TABLE v AS
    SELECT ID as VISITOR_ID
    FROM visitors 
    WHERE visitors.TIME >= '2017-01-01 00:00:00'
      AND visitors.TIME <= '2017-05-25 23:59:59';

CREATE TEMPORARY TABLE o AS
    SELECT offers.ID as ID_OFFER
    FROM v
    JOIN offers USING(VISITOR_ID)
    WHERE offers.ACTIVE = 1
      AND (offers.IP > 100 OR offers.IP < 0);

CREATE TEMPORARY TABLE c1 AS
    SELECT count(*) as contracts1, sum(contracts1.PRICE) as sum_contracts1
    FROM o JOIN contracts1 USING(ID_OFFER);

CREATE TEMPORARY TABLE c2 AS
    SELECT
        count(*) contracts2, 
        sum(CASE contracts2.PAYMENT
            WHEN 'YEARLY' THEN contracts2.PRICE
            WHEN 'TWICE'  THEN contracts2.PRICE*2
            ELSE contracts2.PRICE*4
        END) as sum_contracts2
    FROM o JOIN contracts2 USING(ID_OFFER);

CREATE TEMPORARY TABLE c3 AS
    SELECT count(*) as contracts3, sum(contracts3.PRICE) as sum_contracts3
    FROM o JOIN contracts3 USING(ID_OFFER);

SELECT c1.*, c2.*, c3.*,
    (SELECT count(*) FROM v) as visitors,
    (SELECT count(*) FROM o) as offers,
FROM c1, c2, c3;

【讨论】:

您好。感谢您的回答,这似乎显示了正确的结果。在我将此标记为正确答案之前,我有 2 个问题。 1.) 当 mysql 中没有 CTE 时,我想要做的唯一方法是查看、临时表或派生表,对吗? (就像没有它就没有简单的方法)。 2.) 这种情况下的临时表是否比创建视图更快? @Redrif - 视图不是一个选项,因为您将硬编码visitors.TIME 的范围,并且无法动态更改它。派生表不是一个好的选择,因为您会一次又一次地重复相同的子查询。您可以对其进行有趣的测试,并将所有出现的vo 替换为相应的子查询。但是,如果您将子查询保存在变量中(使用您的应用程序语言)并多次使用它来构建最终查询,则可能没问题。 性能方面:理论上临时表更快,因为查询只执行一次,然后多次使用结果。但在实践中 - 引擎将缓存子查询结果。所以最后的表现可能是一样的。视图可能只是“存储的子查询”(派生表)。 好的,我明白了。感谢您的启发,也感谢您的回答。祝你有美好的一天:-)【参考方案2】:

只是一个概念证明,我不考虑时间和活动限制以及付款类型,但它不能是类似的东西吗?

SELECT
   VISITOR_ID,
   SUM(CASE WHEN TYPE="contract1" THEN 1 else 0 END) as c1_count,
   SUM(CASE WHEN TYPE="contract1" THEN PRICE else 0 END) as c1_total_price,
   SUM(CASE WHEN TYPE="contract2" THEN 1 else 0 END) as c2_count,
   SUM(CASE WHEN TYPE="contract2" THEN PRICE else 0 END) as c2_total_price,
   SUM(CASE WHEN TYPE="contract3" THEN 1 else 0 END) as c3_count,
   SUM(CASE WHEN TYPE="contract3" THEN PRICE else 0 END) as c3_total_price 
FROM (
    (SELECT "contract1" as TYPE, ID, PRICE, ID_OFFER, PAYMENT FROM contracts1) 
    UNION
    (SELECT "contract2" as TYPE, ID, PRICE, ID_OFFER, PAYMENT FROM contracts2)
    UNION
    (SELECT "contract3" as TYPE, ID, PRICE, ID_OFFER, PAYMENT FROM contracts3)
 ) as all_contracts 
 JOIN offers on offers.id = all_contracts.ID_OFFER
 JOIN visitors on visitors.ID = offers.VISITOR_ID
 GROUP BY visitors.ID

这个想法是,首先你将不同的合约合并到一个结果中,你将它们的类型存储在一个名为“TYPE”的列中(这就是 UNION 查询的目的),一旦你有一个如此好的表,其中每个合约都是准确的一次,您可以非常简单地获得所需的结果。我刚刚概述了您如何获得每种合同类型的总和和计数。当然,最终的查询会稍微复杂一些,但核心思想应该是一样的。

但是,尽管您声明您不想使用(临时)视图,但我还是鼓励您尝试一下 - 我觉得将这些“all_contracts”与优惠和访问者一起放入临时视图会改善性能,如果这是您关心的问题,而不会使查询太难看,主要是在您只想查看一位访问者的统计信息或进一步过滤它们(按时间、活动等)的情况下,因为不必要的行不会t 被物化。但这只是一个印象,因为我没有在更大的数据集上尝试过查询 - 你可以玩一下。

【讨论】:

嘿。 UNION-TYPE 背后的想法很好,但据我所知,将 GROUP BY 放在选择末尾的那一刻,你最终会得到不止一行的结果。您必须遍历所有行才能获得正确的 SUM 和访问者数量。这不是我想要的。 好的,我明白了 - 您想要的是整体统计数据,而不是每个访问者的统计数据。然后你不必按任何东西分组,只需选择 COUNT(DISTINCT VISITOR_ID) 而不是 VISITOR_ID,这应该会给出所需的结果,但也许我遗漏了一些东西,你还应该用左连接替换连接,这样你获得没有优惠的访问者,但您可能已经想通了。

以上是关于Mysql sum distinct 基于包含多个 LEFT JOIN 的其他列的主要内容,如果未能解决你的问题,请参考以下文章

MySQL sum 和 distinct 在具有多个连接的另一列上

MySQL 条件 SUM 使用 GROUP BY 和 DISTINCT

每个月内选择 DISTINCT 计数条​​目。 MYSQL

SQL多个字段如何去重

mysql distinct多个字段怎么用

mysql中去重 distinct 用法