Mysql总和基于包含多个LEFT JOIN的其他列

时间:2017-05-25 12:02:16

标签: mysql join group-by sum distinct

我有5张表,我想一起LEFT JOIN。表是: 访客,优惠,合同1,合同2和合同3。

QUERY:

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'

问题在于,contract1,contract2和contract3没有公共列以便连接在一起。因此,对于contract1,对于contracs2为30而对于contract3为50,而不是20行,我得到所有这些的所有组合。因为它们是基于访问者加入并提供表格。在查询结束时简单的GROUP BY通常会解决问题,但是如果我在END中为其中一个表(或所有表)使用GROUP BY,它将创建MULTIPLE ROWS而不是我想要的1。并且它将擦除我通过ID计算访问者的部分​​的所有其他结果,并且还通过ID提供...我可以在SELECT的count()部分使用DISTINCT而不是sum()因为PRICE的合同可能是相同的,即使ID不是(你知道例如2个巧克力是2行具有不同的ID但是相同的PRICE,每个10美元)。

所以我的问题是:

有没有办法只汇总那些有DISTINCT ID的contract1,contract2和contract3的价格,尽管不加上重复的东西?是否可以不创建VIEW?

我还在LEFT JOIN中尝试了GROUP BY,但是当我将所有3个合约表连接在一起时,即使我在我最终重复之前将它们分组。

预期结果示例:

在我上面提到的时间范围内,我希望: 80位访客有35份报价和5份合同1,总和1000欧元,12份合同2,总和686欧元和3份合同3,总和12欧元。它是一行,有8列数据。

而不是预期的结果我得到: 80个访客,35个优惠,180个合约1(总和也差),180个合约2(总和也差),180个合约3(总和也差)。

2 个答案:

答案 0 :(得分:2)

使用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;

答案 1 :(得分:0)

仅仅是一个概念证明,我不会考虑时间和活动限制以及付款类型,但这不是那些行吗?

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

我们的想法是,首先将不同的合同合并到一个结果中,然后将其类型存储在名为&#34; TYPE&#34;的列中。 (这是UNION查询的目的),一旦你有一个很好的表,每个合约只有一次,你可以很简单地得到你想要的结果。我刚刚概述了如何获得每种合同的总和和数量。当然,最终的查询会有点复杂,但核心思想应该是一样的。

但是尽管你声明你不想使用(临时)观点,但我鼓励你尝试一下 - 我觉得把那些&#34; all_contracts&#34;加入优惠和访问者进入临时视图可以提高性能,如果这是您的关注,而不会使查询过于丑陋,主要是在您希望仅查看一个访问者的统计数据或过滤它们的情况下进一步(按时间,活动等),因为不必要的行不会被实现。但这只是一个印象,因为我还没有在更大的数据集上尝试查询 - 你可以玩它。