存在子選擇與內部連接?
我正在進入我的神秘查詢的下一個級別。看起來存在內部有一個子選擇,但在同一個表上。我認為這可能可以通過
INNER JOIN
更高層來簡化。使用 PostgreSQL 9.4.2。
表定義(
/d+
):https
://gist.github.com/neezer/879f5d3649ca1903c6f3 基數:
billing_pricequote
:1,462,625 行
billing_pricequotestatus
:3,331,657 行
billing_lineitem
:43,687,855 行這是原始查詢,不建議對裡面的子查詢進行修改
EXISTS
:SELECT i.quote_id, i.acct_id AS account_id, SUM(i.delta_amount) AS amt FROM billing_lineitem i INNER JOIN billing_pricequote pq ON i.quote_id = pq.id WHERE pq.date_applied AT TIME ZONE 'PST' BETWEEN '2016-02-02T00:00:00'::timestamp AND '2016-03-03T22:27:41.734102-08:00'::timestamptz AND EXISTS( SELECT s1.quote_id FROM billing_pricequotestatus s1 INNER JOIN ( SELECT DISTINCT ON (quote_id) quote_id, MAX(created_at) AS max_created_at FROM billing_pricequotestatus WHERE quote_id=i.quote_id GROUP BY quote_id, created_at ORDER BY quote_id, created_at DESC ) AS s2 ON s1.quote_id = s2.quote_id AND s1.created_at = s2.max_created_at WHERE s1.name IN ('adjustment','payment','billable') ) GROUP BY i.quote_id, i.acct_id ;
我注意到看起來很奇怪的部分是
SELECT
onbilling_pricequotestatus
,然後是INNER JOIN
.我嘗試通過我的其他 SO 文章的修改來改變它:
SELECT i.quote_id, i.acct_id AS account_id, SUM(i.delta_amount) AS amt FROM billing_lineitem i INNER JOIN billing_pricequote pq ON i.quote_id = pq.id WHERE pq.date_applied AT TIME ZONE 'PST' BETWEEN '2016-02-02T00:00:00'::timestamp AND '2016-03-03T22:27:41.734102-08:00'::timestamptz AND EXISTS( SELECT quote_id, MAX(created_at) AS max_created_at FROM billing_pricequotestatus WHERE quote_id=i.quote_id AND name IN ('adjustment','payment','billable') GROUP BY quote_id ) GROUP BY i.quote_id, i.acct_id ;
這將我的執行時間減少了一半(約 40 秒到約 20 秒),但產生的結果略有不同(原始查詢返回 28,895 行,但我的新查詢返回 28,917 行)。我不清楚為什麼我的修改沒有產生等效的輸出(它需要)。
非常感謝任何幫助/指導!
我嘗試用 a 更新@ypercubeᵀᴹ 的答案
LATERAL JOIN
,並且性能似乎大致相同(每個獲勝次數都相同,相差不到一秒):SELECT i.quote_id, i.acct_id AS account_id, SUM(i.delta_amount) AS amt FROM billing_lineitem i INNER JOIN billing_pricequote pq ON i.quote_id = pq.id LEFT JOIN LATERAL ( SELECT name FROM billing_pricequotestatus WHERE quote_id = i.quote_id ORDER BY created_at DESC LIMIT 1 ) pqs ON true WHERE pq.date_applied AT TIME ZONE 'PST' BETWEEN '2016-02-02T00:00:00'::timestamp AND '2016-03-03T22:27:41.734102-08:00'::timestamptz AND pqs.name IN ('adjustment', 'payment', 'billable') GROUP BY i.quote_id, i.acct_id ;
還有什麼其他建議可以讓這個時間低於 10 秒嗎?
據我了解,您的子查詢的目的:
選擇其中最新相關條目
billing_pricequotestatus
具有限定符的行name
。第二次查詢不正確
我不清楚為什麼我的修改沒有產生等效的輸出
第一個查詢從中挑選最新行
billing_pricequotestatus
並檢查是否name
符合條件 (name IN ('adjustment','payment','billable')
)。第二個查詢是向後的:它檢查任何符合條件的行
name
(不僅僅是最後一個)。EXISTS
此外,在半連接中計算聚合也沒有意義。你不想要那個。而且不等價。因此,您可以從第二個查詢中獲得更多行。
時間範圍不正確
這個謂詞是一團糟。效率低下且可能不正確 - 或者至少是一個定時炸彈:
WHERE pq.date_applied AT TIME ZONE 'PST' BETWEEN '2016-02-02T00:00:00'::timestamp AND '2016-03-03T22:27:41.734102-08:00'::timestamptz
該列
date_applied
的類型為**timestamptz
**。該構造AT TIME ZONE 'PST'
將其轉換為類型timestamp
,並按硬編碼為時區縮寫“PST”的時間偏移量進行移位——這從一開始就是一個糟糕的舉動。它使表達式不可分割。這更昂貴,更重要的是,排除在date_applied
.更糟糕的是,時區縮寫
'PST'
不知道 DST 或任何歷史性的時間變化。如果您的時區有(或過去有)夏令時,並且您的設置跨越不同的 DST 時段,則您目前的表達式很可能是不正確的:您需要使用適用的時區名稱而不是縮寫來獲得一致的本地時間 - 這更加昂貴。
還有另一個問題:雖然列值移動了硬編碼的時間偏移量(‘PST’),但您的上限
'2016-03-03T22:27:41.734102-08:00'::timestamptz
是提供的,timestamptz
並且會默默地強制匹配數據類型timestamp
。由於沒有提供明確的時間偏移量,因此強制轉換預設為目前會話的時區。因此,您可以根據會話的目前時區設置獲得不同的結果。我想不出一個有意義的案例。不要做任何這些。根本不要將
timestamptz
列轉換date_applied
為本地時間***,***不要像你一樣混合數據類型,不要混合不同的投射方式。而是按原樣使用該列並提供timestamptz
參數。詢問
SELECT i.quote_id, i.acct_id AS account_id, sum(i.delta_amount) AS amt FROM billing_pricequote pq JOIN LATERAL ( SELECT name FROM billing_pricequotestatus WHERE quote_id = pq.id ORDER BY created_at DESC LIMIT 1 ) pqs ON pqs.name IN ('adjustment', 'payment', 'billable') JOIN billing_lineitem i ON i.quote_id = pq.id WHERE pq.date_applied BETWEEN (timestamp '2016-02-02T00:00:00' AT TIME ZONE 'PST') -- ! AND timestamptz '2016-03-03T22:27:41.734102-08:00' GROUP BY 1,2;
請注意
LATERAL
連接,但不要LEFT JOIN
立即INNER JOIN
實現您的謂詞。或者使用@ypercube 概述的等效相關子查詢。不確定哪個更快。
另請注意,我
LATERAL JOIN
在加入billing_pricequote
大表之前建立了billing_lineitem
基礎。這樣我們可以儘早消除行,這應該更便宜。指數
目前,您將獲得:
billing_pricequote pq 上的序列掃描
您的 1,5M 行中只有 70k 被選中,大約是 5%。上的索引
date_applied
可能會有所幫助,但作用不大。但是,如果您可以從中獲取僅**索引掃描**,則此多列索引應該會有所幫助:CREATE INDEX foo ON billing_pricequotestatus (quote_id, created_at DESC, name);
name_id
使用而不是name
如下建議的更有效。統計數據
Postgres 高估了您的時間範圍的選擇性:
(成本=0.00..88,546.50行=7,313寬度=4)(實際時間=2.353..767.408 行=70,623循環=1)
它可能有助於增加僅列的統計目標
date_applied
。詳情在這裡:表定義
範例
billing_pricequotestatus
:
name
似乎是幾種可能的類型之一。規範化更多內容並僅使用 4 字節integer
引用查找表而不是varchar(20)
重複超過 3.3M 行將有助於提高性能。此外,像我展示的那樣對列重新排序(如果可能)會有所幫助:Column | Type | Modifiers ------------+--------------------------+------------------------------------------ id | integer | not null default nextval('... quote_id | integer | not null created_at | timestamp with time zone | not null updated_at | timestamp with time zone | not null name_id | integer | not null **REFERENCES name_table(name_id)** notes | text | not null
有關對齊和填充,請參見上面的連結。要測量行大小:
而且“名稱”不是一個好的標識符。我會改用描述性的東西。