1

我有一个如下所示的查询:

@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 = ?东西将应用于该存储的表。

有帮助吗?

4

2 回答 2

2

最佳查询取决于数据分布和其他细节。

只要pack_id子查询中的大多数实际用于连接到packs(大多数packsNOT 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;

在 pg 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;

详情在这里:

如果大多数packsdisabledLATERAL连接会更快(需要 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页中是否有替代方案?

于 2014-11-28T05:07:28.007 回答
0

如果您所追求的是各种类型的计数,那么类似以下内容的代码会少得多,并且更容易阅读/维护,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支持多行字符串,因此无需引用和连接每一行。

您可以对其他表执行类似的操作。

于 2014-11-28T04:24:31.423 回答