2016-07-28 35 views
1

加入大表的表mydat具有圍繞48.3M記錄與定義:與主鍵

┌────────────────┬──────────────┬───────────┐ 
│  Column  │  Type  │ Modifiers │ 
├────────────────┼──────────────┼───────────┤ 
│ id    │ bigint  │ not null │ 
│ dt    │ integer  │ not null │ 
│ data   │ real   │   │ 
└────────────────┴──────────────┴───────────┘ 
Indexes: 
    "mydat_pkey" PRIMARY KEY, btree (id, dt) 

對於每個對象所確定id,大約有40記錄所表示由dt時間字段。我們的目標是檢查連續記錄之間的變化模式,並且實現是將每個記錄與其下一個記錄相連,每個記錄基於每個id基於dt。查詢如下:

SELECT * 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt - 1; 

查詢計劃如下。 合併加入被使用,並且它永遠運行。另外,我們可以看到行數427198811嚴重高估。似乎postgresql沒有考慮到(id,dt)的唯一性。

┌───────────────────────────────────────────────────────────────────────────────────────────────┐ 
│           QUERY PLAN           │ 
├───────────────────────────────────────────────────────────────────────────────────────────────┤ 
│ Merge Join (cost=19919125.46..25466155.03 rows=247144681 width=222)       │ 
│ Merge Cond: ((dat1.id = dat2.id) AND (dat1.dt = ((dat2.dt - 1))))       │ 
│ -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)       │ 
│   Sort Key: dat1.id, dat1.dt               │ 
│   -> Seq Scan on mydat dat1 (cost=0.00..982694.76 rows=48330876 width=111)   │ 
│ -> Materialize (cost=9959562.73..10201217.11 rows=48330876 width=111)      │ 
│   -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)      │ 
│    Sort Key: dat2.id, ((dat2.dt - 1))            │ 
│    -> Seq Scan on mydat dat2 (cost=0.00..982694.76 rows=48330876 width=111)  │ 
└───────────────────────────────────────────────────────────────────────────────────────────────┘ 

出於好奇,這裏是與自身幼稚加盟mydat

SELECT * 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt; 

的查詢計劃是相似的:

┌───────────────────────────────────────────────────────────────────────────────────────────────┐ 
│           QUERY PLAN           │ 
├───────────────────────────────────────────────────────────────────────────────────────────────┤ 
│ Merge Join (cost=19919125.46..27878413.41 rows=427198811 width=222)       │ 
│ Merge Cond: ((dat1.id = dat2.id) AND (dat1.dt = dat2.dt))         │ 
│ -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)       │ 
│   Sort Key: dat1.id, dat1.dt               │ 
│   -> Seq Scan on act_2003q1 dat1 (cost=0.00..982694.76 rows=48330876 width=111)  │ 
│ -> Materialize (cost=9959562.73..10201217.11 rows=48330876 width=111)      │ 
│   -> Sort (cost=9959562.73..10080389.92 rows=48330876 width=111)      │ 
│    Sort Key: dat2.id, dat2.dt              │ 
│    -> Seq Scan on act_2003q1 dat2 (cost=0.00..982694.76 rows=48330876 width=111) │ 
└───────────────────────────────────────────────────────────────────────────────────────────────┘ 

這樣的查詢計劃我迷惑不解。在這裏,我的問題是這些用例的最佳實踐是什麼?謝謝。

上述查詢在Windows上的Postgresql 9.5.3和Linux上的9.4.6上測試:結果類似。


鑑於@歐文的建議下,在滯後窗函數進行了測試,結果比初始合併連接方式要好得多:511524ms完成查詢。正如Erwin指出的那樣,查詢與原始查詢不完全相同。特別是如果在dt字段中存在間隙,那麼一些記錄將是不希望的。

這是一個例子,我發現表分區是有益的,因爲我使用的數據集比上面給出的例子大。問題的底線是postgresql使用磁盤對所有記錄進行排序,並且兩個查詢都不使用索引。

+0

而不是'select * ...'嘗試'選擇id,dt ...' – Hogan

+0

@Hogan,剛試過,沒有什麼區別。仍將合併與排序合併。 –

+0

'我的目標是加入每條記錄和每個記錄的下一個記錄'。這不是一個目標,那是一個實現細節。你的實際目標是什麼?並且請*總是*包含您的Postgres版本。 –

回答

0

我不知道PostgreSQL的那麼好,但是這將是魔術在DB2和MSSQL

WITH get_em as 
(SELECT id, dt 
    FROM mydat AS dat1 
    JOIN mydat AS dat2 
    ON dat1.id = dat2.id 
    AND dat1.dt = dat2.dt - 1 
) 
select mydat.* 
from mydat 
join get_em on ON mydat.id = get_em.id AND mydat.dt = get_em.dt 
+0

感謝您的回答!我剛剛嘗試了代碼,但它不起作用。 –

+0

@opensrc - 好的,如果你不告訴我更多,我不能幫忙。 – Hogan

2

如果實際的目標是有data每個結果行中的前值,然後用window function

SELECT *, lag(data) OVER (PARTITION BY id ORDER BY dt) AS last_data 
FROM mydat; 

區別:沒有前輩的行仍包括在內。你可能會也可能不想要。要排除,使用子查詢:

SELECT * 
FROM (
    SELECT *, lag(data) OVER (PARTITION BY id ORDER BY dt) AS last_data 
    FROM mydat 
    ) t 
WHERE last_data IS NOT NULL; 

殘存極端情況是這樣的:如果data可以爲空,我們不能告訴一個真正的NULL值和「未發現」的區別。因此,使用不同的不可能默認值一樣表現出「未找到」的情況:

SELECT * 
FROM (
    SELECT *, lag(data, 1, '-infinity') OVER (PARTITION BY id ORDER BY dt) AS last_data 
    FROM mydat 
    ) t 
WHERE last_data IS DISTINCT FROM '-infinity'; 

每個查詢只需要一個順序掃描

+0

感謝您的回答。我會做一個徹底的測試,然後報告回來。 –