Ruby on rails RAILS SQL子查询优化

Ruby on rails RAILS SQL子查询优化,ruby-on-rails,postgresql,crosstab,find-by-sql,aggregate-filter,Ruby On Rails,Postgresql,Crosstab,Find By Sql,Aggregate Filter,我有一个查询,如下所示: @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 W

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

@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")
@inventory = Pack.find_by_sql("SELECT status, count(*)
                               FROM stocks
                               WHERE user_id = ?
                               GROUP BY status
                               ORDER BY status", current_user.id)
我在想有一种方法可以创建一个新的子查询,这样就可以代替

SELECT FROM Stocks
查询从存储的表中进行选择

SELECT FROM (Stocks WHERE (Stocks.pack_id = Packs.id AND Stocks.user_id = #{current_user.id}))
只能查询一次。然后将
WHERE Stocks.status=?
内容应用于该存储表


有什么帮助吗?

如果您想要的是各种类型的计数,那么像下面这样的代码会少得多,而且更易于阅读/维护,我想

您可以将它们拆分为不同的表,因此,对于
股票
,如下所示:

@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")
@inventory = Pack.find_by_sql("SELECT status, count(*)
                               FROM stocks
                               WHERE user_id = ?
                               GROUP BY status
                               ORDER BY status", current_user.id)
请注意使用
防止SQL注入的重要性。此外,Ruby支持多行字符串,因此不需要引用和连接每一行


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

最佳查询取决于数据分布和其他详细信息

只要子查询中的大多数
pack\u id
实际用于连接到
packs
(大多数
pack
未禁用),这是非常有效的:

在pg9.4中,您可以使用聚合筛选子句

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  ... 
详情:

对数据透视表使用
交叉表()
可以加快速度,但:

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;
为什么是横向的?pg9.1中是否有替代方案


请随时提供您的Postgres.9.1版本。我可能会在某个时候更新到9.4。这段代码可以工作,这是我通常尝试做的,但速度大约慢15倍。不过,我没有使用足够的测试数据,因此我必须添加更多数据并进行比较。@user1694853:对于性能优化,我们需要更多数据。特别是表定义、基数和值频率(什么百分比的
pack
禁用的
股票中有多少不同的价值。状态
?Freuquecies?等等。我添加了更多的选项。天哪,你对sql有着疯狂的了解。我需要读一些书来了解这里发生了什么,lol。谢谢你的帮助。我会在再次测试后回复你的安装一个更大的对象池。我想我应该升级到第9.4页。9.5已经发布了还是仅仅是他们的文档?@AronGreenspan:9.5是新的devel版本。请等待升级,直到它发布。