根据其他列包含多个LEFT JOIN Mysql总结不同

问题描述:

我有5个表,我想LEFT JOIN在一起。表格为: 访问者,优惠,合同1,合同2和合同3。根据其他列包含多个LEFT JOIN Mysql总结不同

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' 

问题是,contracts1,contracts2和contracts3没有共同的列,以便被接合在一起。所以不是20行的合约1,30的是合同2和50的合约3,我得到了所有这些组合。因为他们根据访问者加入并提供表格。简单的GROUP BY在查询结束时通常会解决问题,但是如果我在END中为其中一个表(或全部)使用GROUP BY,它将创建多行而不是1。而且它也会清除我通过ID计数访问者的部分​​的所有其他结果以及ID提供的部分...我可以在SELECT的count()部分上使用DISTINCT,但不能使用sum(),因为合同的PRICE可能是相同的,即使身份证不是(你知道,例如2个巧克力是2行不同的ID,但价格相同,每个10美元)。

所以我的问题是:

有什么办法来总结只有contracts1,contracts2和contracts3的那些价格,即有不同的ID,虽然摆脱加起来的复制品?这有可能没有创建VIEW?

我也尝试了GROUP BY里面的LEFT JOIN,但是再次当我左侧连接所有3个合同表时,即使我在他们之前对它们进行了重新分组。预期的结果的

实施例:

在我指出上述我期望的时间范围:具有35个报价和5 contracts1与千欧元总和,12 contracts2与686欧元总和 80访问者3份合同3,总计12欧元。它是8行数据的一行。

取而代之的是我得到的结果: 80个访客,35个offer,180个contract1(总数也不好),180个contract2(总数也不好),180个contract3(总数也不好)。

+0

乍一看似乎是一个正常化问题所在。您可以在任何地方一对一地加入它们,无法获得所需的结果。或者如果存在这种可能性,请在子查询中使用它并在其上进行JOIN。 – Mihai

+0

是的,数据库是一个大混乱。我想创建一个选择而不是许多,以使其更快。另外这一选择应该给我只有6列的数据,只有一行。在这种情况下,您可以使用一个例子来说明加入子查询的含义吗?此外,我会添加一些例子(进入我的问题)的预期结果,我想实现。 – Redrif

+0

首先用最少的连接(SUM .. FROM x JOIN y)获得正确的总和。使用上面的查询将它加入到主查询中。问题,据我所见,是你从所有的连接。 – Mihai

由于热膨胀系数(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; 

没有热膨胀系数,你可以重写它使用临时表:

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; 
+0

你好。感谢您的回答,这似乎显示正确的结果。在将此标记为正确答案之前,我有两个问题。 1.)当Mysql中没有CTE时,做我想做的事情的唯一方法是视图,临时表或派生表是否正确? (就像没有它的简单方法一样)。 2)在这种情况下临时表比创建视图更快吗? – Redrif

+0

@Redrif - 视图不是一个选项,因为你会硬编码'visitor.TIME'的范围,并且不能动态改变它。派生表不是一个好的选择,因为你会一次又一次地重复相同的子查询。你可以测试它的乐趣,并用相应的子查询替换所有'v'和'o'的出现。但是,如果将子查询保存在变量中(以您的应用程序语言)并将其多次用于构建最终查询,则可能会有问题。 –

+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 

的想法是,首先你合并不同的合同到一个结果,你存储的类型在一个名为“类型”列(这是UNION查询的目的),一旦你拥有这样一个漂亮的表,其中每份合约只有一次,您可以非常直接地得到您想要的结果。我刚刚概述了你如何得到每种合同的总和和数量。当然,最终的查询会更复杂一些,但核心思想应该是一样的。

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

+0

嘿。 UNION-TYPE背后的想法很好,但据我所知,当你在选择结束时放入GROUP BY的时候,你最终会得到不止一行结果。你将不得不遍历整个行以获得正确的SUM和访问者数量。而那不是我想要的。 – Redrif

+0

好的,我看到了 - 你想要的是整体统计数据,而不是每个访客的统计数据。然后你不需要任何组合,只需选择COUNT(DISTINCT VISITOR_ID)而不是VISITOR_ID,它应该给出所需的结果,但也许我错过了某些东西,并且还应该用左连接替换连接,以便您让访客没有优惠,但你可能已经知道了。 –