簡體   English   中英

RAILS SQL子查詢優化

[英]RAILS SQL Subquery optimization

我有一個查詢,看起來像這樣:

@inventory =  Pack.find_by_sql("SELECT Packs.id, "+
" (SELECT COUNT(*) FROM Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.status = 'online'      AND Stocks.user_id = #{current_user.id})) AS online,"+
" (SELECT COUNT(*) FROM Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.status = 'offline'     AND Stocks.user_id = #{current_user.id})) AS offline,"+
" (SELECT COUNT(*) FROM Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.status = 'depositing'  AND Stocks.user_id = #{current_user.id})) AS depositing,"+
" (SELECT COUNT(*) FROM Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.status = 'withdrawing' AND Stocks.user_id = #{current_user.id})) AS withdrawing,"+
" (SELECT COUNT(*) FROM Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.status = 'selling'     AND Stocks.user_id = #{current_user.id})) AS selling,"+
" (SELECT COUNT(*) FROM Transactions WHERE (Transactions.pack_id = Packs.id AND Transactions.status = 'buying' AND Transactions.buyer_id = #{current_user.id})) AS buying"+
" FROM Packs WHERE disabled = false")

我在想有一種方法可以進行新的子查詢,而不是

SELECT FROM Stocks

查詢從存儲的表中選擇

SELECT FROM (Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.user_id = #{current_user.id}))

只會被查詢一次 然后WHERE Stocks.status = ? 東西將應用於該存儲的表。

有幫助嗎?

最佳查詢取決於數據分布和其他詳細信息。

只要子查詢中的大多數pack_id實際上用於連接到packs (大多數packs NOT disabled ),這將非常有效:

SELECT p.id
     , s.online, s.offline, s.depositing, s.withdrawing, s.selling, t.buying
FROM   packs p
LEFT   JOIN (
   SELECT pack_id 
        , count(status = 'online'      OR NULL) AS online
        , count(status = 'offline'     OR NULL) AS offline
        , count(status = 'depositing'  OR NULL) AS depositing
        , count(status = 'withdrawing' OR NULL) AS withdrawing
        , count(status = 'selling'     OR NULL) AS selling
   FROM   stocks
   WHERE  user_id = #{current_user.id}
   AND    status = ANY('{online,offline,depositing,withdrawing,selling}'::text[])
   GROUP  BY 1
   ) s ON s.pack_id = p.id 
LEFT   JOIN (
   SELECT pack_id, count(*) AS buying
   FROM   transactions
   WHERE  status = 'buying'
   AND    buyer_id = #{current_user.id}
   ) t ON  t.pack_id = p.id
WHERE  NOT p.disabled;

9.4版中,您可以使用聚合FILTER子句

SELECT pack_id 
     , count(*) FILTER (WHERE status = 'online')      AS online
     , count(*) FILTER (WHERE status = 'offline')     AS offline
     , count(*) FILTER (WHERE status = 'depositing')  AS depositing
     , count(*) FILTER (WHERE status = 'withdrawing') AS withdrawing
     , count(*) FILTER (WHERE status = 'selling')     AS selling
FROM   stocks
WHERE  ... 

細節:

對數據透視表使用crosstab()可以使速度更快,但是:

SELECT p.id
     , s.online, s.offline, s.depositing, s.withdrawing, s.selling, t.buying
FROM   packs p
LEFT   JOIN  crosstab( $$ SELECT pack_id, status, count(*)::int AS ct FROM stocks WHERE user_id = $$ || #{current_user.id} || $$ AND status = ANY('{online,offline,depositing,withdrawing,selling}'::text[]) GROUP BY 1, 2 ORDER BY 1, 2 $$ ,$$SELECT unnest('{online,offline,depositing,withdrawing,selling}'::text[])$$ ) s (pack_id int , online int , offline int , depositing int , withdrawing int , selling int ) USING (pack_id)
LEFT   JOIN (
   SELECT pack_id, count(*) AS buying
   FROM   transactions
   WHERE  status = 'buying'
   AND    buyer_id = #{current_user.id}
   ) t ON  t.pack_id = p.id
WHERE  NOT p.disabled;

詳細信息在這里:

如果大多數packs都被disabled ,則LATERAL聯接會更快(需要pg 9.3或更高版本):

SELECT p.id
     , s.online, s.offline, s.depositing, s.withdrawing, s.selling, t.buying
FROM   packs p
LEFT   JOIN LATERAL (
   SELECT pack_id 
        , count(status = 'online'      OR NULL) AS online
        , count(status = 'offline'     OR NULL) AS offline
        , count(status = 'depositing'  OR NULL) AS depositing
        , count(status = 'withdrawing' OR NULL) AS withdrawing
        , count(status = 'selling'     OR NULL) AS selling
   FROM   stocks
   WHERE  user_id = #{current_user.id}
   AND    status = ANY('{online,offline,depositing,withdrawing,selling}'::text[])
   AND pack_id = p.id
   GROUP  BY 1
   ) s ON TRUE
LEFT   JOIN LATERAL (
   SELECT pack_id, count(*) AS buying
   FROM   transactions
   WHERE  status = 'buying'
   AND    buyer_id = #{current_user.id}
   AND pack_id = p.id
   ) t ON TRUE
WHERE  NOT p.disabled;

為什么要LATERAL 9.1頁有替代方法嗎?

如果您所追求的是各種類型的計數,那么類似於IMO的以下代碼將少得多的代碼並且更易於閱讀/維護。

您可以將它們分成不同的表,因此,對於stocks ,是這樣的:

@inventory = Pack.find_by_sql("SELECT status, count(*)
                               FROM stocks
                               WHERE user_id = ?
                               GROUP BY status
                               ORDER BY status", current_user.id)

注意使用?的重要性? 防止SQL注入 另外, Ruby支持多行字符串,因此無需引用和連接每一行。

您可以對其他表執行類似的操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM