如何简化这个postgres查询

时间:2014-12-17 09:39:39

标签: performance postgresql

如何简化此查询?

我想要做的是通过子查询派生列S9_Unlock,其中我只查找从主查询返回的user_ids,但这对我来说看起来很尴尬,特别是因为这里的查询只是一个摘录。实际上,我正在做这些子查询的多个来派生不同的列......

SELECT userid, CAST(to_char(S9_unlock,'YYYY/MM/DD') AS timestamp) AS "S9_Unlock"
FROM (
  SELECT ca.user_id AS userid
  FROM shop_db.invoices AS inv
  LEFT JOIN shop_db.carts AS ca ON inv.id = ca.invoice_id
  LEFT JOIN shop_db.cart_items AS ci ON ca.id = ci.cart_id
  WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
    AND inv.status <> 'do_not_book'
    AND inv.id IS NOT NULL
  GROUP BY user_id) AS master
LEFT JOIN (
  SELECT MIN(s3.unl) AS "S9_Unlock", s3.user_id
  FROM (
    SELECT user_id, challenge_codes.created AS unl,
           MAX /* Check if license contains Suite9 */
             (CASE WHEN substring(bundle_article_code,1,6) = 'BuSu90' THEN 1 ELSE 0 END) AS "S9_Unlock"
    FROM licensing_db.serial_numbers
    LEFT JOIN licensing_db.licenses ON licenses.id = serial_numbers.license_id 
    LEFT JOIN user_db.users ON users.id = licenses.user_id
    LEFT JOIN licensing_db.challenge_codes ON challenge_codes.serial_number_id = serial_numbers.id
    WHERE user_id IN (
      SELECT ca.user_id AS userid
      FROM shop_db.invoices AS inv
      LEFT JOIN shop_db.carts AS ca ON inv.id = ca.invoice_id
      LEFT JOIN shop_db.cart_items AS ci ON ca.id = ci.cart_id
      WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
        AND inv.status <> 'do_not_book'
        AND inv.id IS NOT NULL
      GROUP BY user_id
    )
    GROUP BY user_id, challenge_codes.created) AS s3
  )
  WHERE "S9_Unlock" = 1
    AND s3.unl IS NOT NULL
  GROUP BY s3.user_id) AS "S9_Unlock" ON "S9_Unlock".user_id = master.userid

2 个答案:

答案 0 :(得分:1)

在您的查询中,您有两个相同的子查询;这是一个CTE的尖叫声。

在有关许可问题的子查询中,您可以使用GROUP BY子句在HAVING子句后过滤掉有效许可证。也可以使它成为WITH QUERY,最终会有更可读性:

WITH inv AS (
  SELECT ca.user_id AS userid
  FROM shop_db.invoices AS inv
  LEFT JOIN shop_db.carts AS ca ON ca.invoice_id = inv.id
  LEFT JOIN shop_db.cart_items AS ci ON ci.cart_id = ca.id
  WHERE (inv.created BETWEEN '2014-11-13' AND '2014-11-14' OR inv.created BETWEEN '2013-11-14' AND '2013-11-15')
    AND inv.status <> 'do_not_book'
    AND inv.id IS NOT NULL
), s3 AS (
    SELECT u.user_id, min(cc.created) AS first_unlocked, bundle_article_code
    FROM licensing_db.serial_numbers AS sn
    LEFT JOIN licensing_db.licenses AS lic ON lic.id = sn.license_id 
    LEFT JOIN user_db.users AS u ON u.id = lic.user_id
    LEFT JOIN licensing_db.challenge_codes AS cc ON cc.serial_number_id = sn.id
    WHERE u.user_id IN (SELECT userid FROM inv)
    GROUP BY u.user_id, bundle_article_code
    HAVING bundle_article_code LIKE 'BuSu90%'
       AND first_unlocked IS NOT NULL
)
SELECT userid, date_trunc('day', first_unlocked) AS "S9_Unlock"
FROM inv
LEFT JOIN s3 ON s3.user_id = inv.userid;

因此,主查询现在减少到3行,并且WITH-QUERY都执行数据库的逻辑自包含查询。您引用的其他子查询可以类似地成为WITH-QUERY,然后在主查询中汇编它们。请记住,您可以在with-queries列表中引用先前命名的查询,如上所示inv引用s3。虽然这样的CTE在语法上不提供新功能(RECURSIVE变体除外),但它们确实使复杂查询更具可读性,因此更易于维护。

另一种方法是将逻辑子组件(例如inv子查询)分解出来并从中生成VIEW。然后,您可以简单地引用主查询中的视图。如果您想使查询更灵活,那么将整个事物视为一个视图可能也是一个好主意。如果您想在2014年3月27日查询Suite9.1('BuSu91%')怎么办?取出这些文字,然后在视图中将它们用作WHERE子句,使您的查询更加通用;这可以是子查询,也可以是完整的CTE。

(请检查s3 with-query中的语义是否仍然正确,因为如果没有您的表结构和示例数据,我无法测试上面的代码。)

答案 1 :(得分:0)

我不会将你的问题解决为一个巨大的单片关系sql查询,而是会认真考虑去&#34;程序&#34;通过使用内置的&#34; plpgsql&#34; postgresql的语言。这可以为您的应用程序带来很多清晰度。