2017-05-25 69 views
3

我有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

回答

2

由於熱膨脹係數(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

性能明智:在理論上,臨時表更快,因爲查詢只執行一次,然後多次使用結果。但在實踐中 - 引擎會緩存子查詢結果。所以最後的表現可能是一樣的。一個視圖可能不是其他的「存儲子查詢」(派生表)。 –

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,它應該給出所需的結果,但也許我錯過了某些東西,並且還應該用左連接替換連接,以便您讓訪客沒有優惠,但你可能已經知道了。 –