2015-01-01 30 views
5

我正在與MySQL 5.6。我創建了一個包含366個分區的表格,用於保存日期數據。在一年中,我們有最多366天的時間,因此我在該表格上創建了​​366個分區。散列分區由一個整數列進行管理,每個記錄存儲1到366個整數列。MySQL:無法從特定分區中選擇記錄?

Report_Summary表:

CREATE TABLE `Report_Summary` (
    `PartitionsID` int(4) unsigned NOT NULL, 
    `ReportTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `Amount` int(10) NOT NULL, 
    UNIQUE KEY `UNIQUE` (`PartitionsID`,`ReportTime`), 
    KEY `PartitionsID` (`PartitionsID`), 
    KEY `ReportTime` (`ReportTime`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPRESSED 
/*!50100 PARTITION BY HASH (PartitionsID) 
PARTITIONS 366 */ 

我的當前查詢:

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= '2014-12-26 00:00:00' AND RS.ReportTime <= '2014-12-30 23:59:59' AND 
     RS.PartitionsID BETWEEN DAYOFYEAR('2014-12-26 00:00:00') AND DAYOFYEAR('2014-12-30 23:59:59') 
GROUP BY ReportDate; 

上面的查詢是完全的工作和使用分區P360P364獲取數據。現在的問題是,當我通過從日期到'2014年12月26日'和日期到'2015-01-01'然後上述查詢將無法正常工作。因爲'2015-01-01'的年份是所以我的條件失敗了。

現在,我一直試圖傳遞值IN運營商則完全在數據庫檢查以下查詢:

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= '2014-12-26 00:00:00' AND RS.ReportTime <= '2015-01-01 23:59:59' AND 
     RS.PartitionsID IN (360,361,362,363,364,365,1) 
GROUP BY ReportDate; 

要產生上述情況下我已經創建了一個功能,並通過兩個日期,併產生逗號分隔的ID

SELECT GenerateRange('2014-12-26 00:00:00', '2015-01-01 23:59:59'); 

這reurns我數據的字符串:

'360,361,362,363,364,365,366,1' 

我想使用的功能在我的查詢,所以我就如下改變了我的查詢:

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= '2014-12-26 00:00:00' AND RS.ReportTime <= '2015-01-01 23:59:59' AND 
     FIND_IN_SET(RS.PartitionsID, GenerateRange('2014-12-26 00:00:00', '2015-01-01 00:00:00')) 
GROUP BY ReportDate; 

然後,我測試過使用EXPLAIN PARTITION SELECT ...上述查詢的執行計劃。我發現我的情況不起作用。它使用所有分區來獲取數據。我只想使用這些日期的特定分區。 必須檢查只有這些360,361,362,363,364,365,366,1分區意味着P360P366P1

爲什麼我的查詢不起作用?這是不正確的方式來實現這一點,然後我想要解決方案我如何實現這一目標?

我知道從編碼我可以實現這一點,但我必須編寫一個查詢來實現這一點。

謝謝...

+0

你打算如何處理你的請求?你在等待什麼樣的結果? – akmozo

+0

@akmozo我想要一個工作查詢,它​​將使用該條件所需的分區。但我現在的狀況利用所有分區是錯誤的。 –

+0

我們是由DAYOFYEAR分區?您是否有許多查詢將今年的某一天與前一年的某一天進行比較?如果不是,那麼如果您的查詢主要是順序數據訪問,那麼按Year或YearMonth或順序進行分區可能會更好。 – BateTech

回答

0

我得到的解決方案我已經改變了我在我的表存儲PartitionsId列的邏輯。最初,我在PartitionsId列中存儲了DayOfYear(reportTime)列。現在我已通過將TO_DAYS(reportTime)存儲到PartitionsId列中來更改該邏輯。

所以我的表結構如下:

CREATE TABLE `Report_Summary` (
    `PartitionsID` int(10) unsigned NOT NULL, 
    `ReportTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 
    `Amount` int(10) NOT NULL, 
    UNIQUE KEY `UNIQUE` (`PartitionsID`,`ReportTime`), 
    KEY `PartitionsID` (`PartitionsID`), 
    KEY `ReportTime` (`ReportTime`) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=COMPRESSED 
/*!50100 PARTITION BY HASH (PartitionsID) 
PARTITIONS 366 */ 

INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735928','2014-12-26 11:46:12','100'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735929','2014-12-27 11:46:23','50'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735930','2014-12-28 11:46:37','44'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735931','2014-12-29 11:46:49','15'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735932','2014-12-30 11:46:59','56'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735933','2014-12-31 11:47:22','68'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735934','2015-01-01 11:47:35','76'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735935','2015-01-02 11:47:43','88'); 
INSERT INTO `Report_Summary` (`PartitionsID`, `ReportTime`, `Amount`) VALUES('735936','2015-01-03 11:47:59','77'); 

檢查SQL FIDDLE DEMO

我的查詢是:

EXPLAIN PARTITIONS 
SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= '2014-12-26 00:00:00' AND RS.ReportTime <= '2015-01-01 23:59:59' AND 
     RS.PartitionsID BETWEEN TO_DAYS('2014-12-26 00:00:00') AND TO_DAYS('2015-01-01 23:59:59') 
GROUP BY ReportDate; 

上面的查詢掃描,我需要特定的分區,它也使用適當的索引。因此,我在更改了PartitionsId列的邏輯後達成了適當的解決方案。

感謝所有的答覆和非常感謝大家的時間......

+0

小心:當您運行較長時間時,您會得到很多分區,因爲每一天都會創建一個分區。我肯定會建議一個持續的日曆表,每天有一行,並且有正確的分區號,從那裏你可以選擇「where in」部分。 – flaschenpost

+0

只有366個分區,您可能會碰到原來的問題,只有PartitionsID 366和1之間的中斷會在Dec 31 - Jan 1以外的地方。 – BateTech

+0

我的觀點是,由於您使用HASH分區,你的分區#使用公式MOD(TO_DAYS(ReportTime),366)'(http://dev.mysql.com/doc/refman/5.7/en/partitioning-hash.html)生成,所以現在你的分區# 「重置」從365回到0將發生在2015-04-02左右,而不是2014-12-31。所以你的SQLFiddle並不能證明對原始問題的修復,因爲它不涵蓋跨越此分區的日期範圍#「reset」。由於'TO_DAYS'fn是線性的,但是你的例子沒有顯示這一點,所以當使用'TO_DAYS'比使用'DAYOFYEAR'更好時,MySQL可以處理這個「重置」。 – BateTech

1

有幾個選項,我能想到的。

  1. 創建case聲明涵蓋多年搜索條件。
  2. 創建一個CalendarDays表並使用它來獲取in子句的清單DayOfYear
  3. 變異選項1,但使用union來搜索每個範圍分別

選項1:使用case語句。它不漂亮,但似乎工作。如果查詢跨越非閏年的年份,則有一種情況可以搜索一個額外的分區366。此外,我不確定優化程序是否會喜歡RS.ParitionsID篩選器中的OR,但您可以嘗試一下。

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= @startDate AND RS.ReportTime <= @endDate 
    AND 
    (
    RS.PartitionsID BETWEEN 
     CASE 
      WHEN 
       --more than one year, search all days 
       year(@endDate) - year(@startDate) > 1 
       --one full year difference 
       OR year(@endDate) - year(@startDate) = 1 
        AND DAYOFYEAR(@startDate) <= DAYOFYEAR(@endDate) 
      THEN 1 
      ELSE DAYOFYEAR(@startDate) 
     END 
     and 
     CASE 
      WHEN 
       --query spans the end of a year 
       year(@endDate) - year(@startDate) >= 1 
      THEN 366 
      ELSE DAYOFYEAR(@endDate) 
     END 
    --Additional query to search less than portion of next year 
    OR RS.PartitionsID <= 
     CASE 
      WHEN year(@endDate) - year(@startDate) > 1 
       OR DAYOFYEAR(@startDate) > DAYOFYEAR(@endDate) 
      THEN DAYOFYEAR(@endDate) 
      ELSE NULL 
     END 
    ) 
GROUP BY ReportDate; 

選項2:使用CalendarDays表。這個選項更清潔。缺點是你需要創建一個新的CalendarDays表,如果你沒有。

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
FROM Report_Summary RS 
WHERE RS.ReportTime >= @startDate AND RS.ReportTime <= @endDate 
    AND RS.PartitionsID IN 
    (
     SELECT DISTINCT DAYOFYEAR(c.calDate) 
     FROM dbo.calendarDays c 
     WHERE c.calDate >= @startDate and c.calDate <= @endDate 
    ) 

編輯:方案3:選項1的變化,但使用Union All來搜索每個範圍分別。這裏的想法是,由於聲明中沒有OR,所以優化器將能夠應用分區修剪。注意:我通常不會在MySQL工作,所以我的語法可能稍微偏離一點,但總體思路就在那裏。

DECLARE @startDate datetime, @endDate datetime; 
DECLARE @rangeOneStart datetime, @rangeOneEnd datetime, @rangeTwoStart datetime, @rangeTwoEnd datetime; 

SELECT @rangeOneStart := 
     CASE 
      WHEN 
       --more than one year, search all days 
       year(@endDate) - year(@startDate) > 1 
       --one full year difference 
       OR year(@endDate) - year(@startDate) = 1 
        AND DAYOFYEAR(@startDate) <= DAYOFYEAR(@endDate) 
      THEN 1 
      ELSE DAYOFYEAR(@startDate) 
     END 
    , @rangeOneEnd := 
     CASE 
      WHEN 
       --query spans the end of a year 
       year(@endDate) - year(@startDate) >= 1 
      THEN 366 
      ELSE DAYOFYEAR(@endDate) 
     END 
    , @rangeTwoStart := 1 
    , @rangeTwoEnd := 
     CASE 
      WHEN year(@endDate) - year(@startDate) > 1 
       OR DAYOFYEAR(@startDate) > DAYOFYEAR(@endDate) 
      THEN DAYOFYEAR(@endDate) 
      ELSE NULL 
     END 
; 

SELECT t.ReportDate, sum(t.Amount) as Total 
FROM 
(
    SELECT DATE(RS.ReportTime) AS ReportDate, RS.Amount 
    FROM Report_Summary RS 
    WHERE RS.PartitionsID BETWEEN @rangeOneStart AND @rangeOneEnd 
     AND RS.ReportTime >= @startDate AND RS.ReportTime <= @endDate 

    UNION ALL 

    SELECT DATE(RS.ReportTime) AS ReportDate, RS.Amount 
    FROM Report_Summary RS 
    WHERE RS.PartitionsID BETWEEN @rangeTwoStart AND @rangeTwoEnd 
     AND @rangeTwoEnd IS NOT NULL 
     AND RS.ReportTime >= @startDate AND RS.ReportTime <= @endDate 
) t 
GROUP BY ReportDate; 
+0

感謝您的有價值的迴應,但我已經嘗試了CASE語句,並且它也沒有使用適當的分區和索引來獲取數據。 Sencond選項也不會考慮正確的分區和索引 –

+0

如果在FROM子句中添加索引提示(如FROM FROM Report_Summary RS USE KEY(UNIQUE)),並且還要移動以便PartitionsID是where子句中的第一條語句然後再嘗試選項2? http://dev.mysql.com/doc/refman/5.7/en/index-hints。html – BateTech

+0

這將利用該查詢上的索引,但不會更改分區的使用。它將使用所有分區而不是特定的分區,並且我想要使用特定的分區應該被我的查詢使用。所以索引不適用於我的情況。 –

0

着手解決這個問題,你需要一個子查詢,給定一個日期範圍,返回包含所有在該範圍內的DAYOFYEAR()值的一個結果。

讓我們解決這個問題。對於初學者,我們需要一個可以返回從0到至少366的所有整數序列的查詢。這是查詢。它返回一列值爲0-624的seq

SELECT A.N + 5*(B.N + 5*(C.N + 5*(D.N))) AS seq 
    FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
         UNION SELECT 3 UNION SELECT 4) AS A 
    JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
         UNION SELECT 3 UNION SELECT 4) AS B 
    JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
         UNION SELECT 3 UNION SELECT 4) AS C 
    JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
         UNION SELECT 3 UNION SELECT 4) AS D 

(這是簡單的交叉聯接弄虛作假產生5個** 4個號碼的所有組合)。

接下來,我們需要用它來生成DAYOFYEAR名單()值。爲了舉例,我們使用你的開始和結束日期。此查詢會生成一個結果集,其中包含一組顯示該日期範圍內年份的日期的整數。

SELECT DISTINCT DAYOFYEAR(first_day + INTERVAL seq DAY) doy 
    FROM (SELECT DATE('2014-12-26 00:00:00') AS first_day, 
       DATE('2015-01-01 23:59:59') AS last_day 
     ) params 
    JOIN (
     SELECT A.N + 5*(B.N + 5*(C.N + 5*(D.N))) AS seq 
      FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
           UNION SELECT 3 UNION SELECT 4) AS A 
      JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
           UNION SELECT 3 UNION SELECT 4) AS B 
      JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
           UNION SELECT 3 UNION SELECT 4) AS C 
      JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
           UNION SELECT 3 UNION SELECT 4) AS D 
     ) seq ON seq.seq <= TIMESTAMPDIFF(DAY,first_day,last_day) 
ORDER BY 1 

我認爲你能說服自己,這一點粗糙的查詢可以正常工作了幾天跨越大約一年半(625天)以內的任何合理的範圍內。如果你使用更長的時間,你可能會搞閏年。

最後,您可以在您的PartitionsID IN()子句中使用此查詢。這看起來像這樣。

SELECT DATE(RS.ReportTime) AS ReportDate, SUM(RS.Amount) AS Total 
    FROM Report_Summary RS 
WHERE RS.ReportTime >= '2014-12-26 00:00:00' 
    AND RS.ReportTime <= '2015-01-01 23:59:59' 
    AND RS.PartitionsID 
    IN (
     SELECT DISTINCT DAYOFYEAR(first_day + INTERVAL seq DAY) doy 
      FROM (SELECT DATE('2014-12-26 00:00:00') AS first_day, 
         DATE('2015-01-01 23:59:59') AS last_day 
       ) params 
      JOIN (
        SELECT A.N + 5*(B.N + 5*(C.N + 5*(D.N))) AS seq 
        FROM (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
             UNION SELECT 3 UNION SELECT 4) AS A 
        JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
             UNION SELECT 3 UNION SELECT 4) AS B 
        JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
             UNION SELECT 3 UNION SELECT 4) AS C 
        JOIN (SELECT 0 AS N UNION SELECT 1 UNION SELECT 2 
             UNION SELECT 3 UNION SELECT 4) AS D 
       ) seq ON seq.seq <= TIMESTAMPDIFF(DAY,first_day,last_day) 
      ORDER BY 1 
     ) 
GROUP BY ReportDate; 

這應該爲你做。

如果您使用的是MariaDB 10+,則有built in sequence tables,名稱可能與seq_0_to_624類似。

有關於這一主題在這裏書面記錄:

http://www.plumislandmedia.net/mysql/filling-missing-data-sequences-cardinal-integers/

+0

我已經測試過這個,但是查詢使用了所有的分區而不是特定的分區。此外,我曾嘗試通過創建一個包含366個數字條目的表並試圖與查詢聯接表,但仍然失敗。 –

+0

我建議你對*實際查詢*進行前後性能測試,而不僅僅是'EXPLAIN',省略或放入語句中的AND AND.PartitionsID IN(...)'部分。這可能是因爲嘗試列出所需的分區,即使它不顯示在「EXPLAIN」中,您仍然可以獲得性能優勢。當然,這可能是因爲你在不久的將來有很多一天查詢的「UNION ALL」。 –

0

根據你的選擇,你真正需要的是一個名爲「彙總表」的數據倉庫技術。通過這種方式,您每天(或小時或其他)總結數據並將小計存儲在更小的表格中。然後,「報告」查看該表格併合計小計。這通常比原始數據的強力掃描速度快10倍。更多細節:http://mysql.rjweb.org/doc.php/datawarehouse

這樣做可以消除原始數據(「事實表」)或彙總表中的PARTITIONing需求。

但是,如果您需要清除舊數據,則由於DROP PARTITION,PARTITIONing可以派上用場。爲此,您可以使用BY RANGE(TO_DAYS(...)),而不是BY HASH。