简体   繁体   English

Postgres过滤WITH查询

[英]Postgres filtered WITH query

I currently have the following query: 我目前有以下查询:

WITH instances AS (
    SELECT b.ldtc as date, a.fk_item_id, b.movement, a.quantity, 
        CASE WHEN b.movement = 'Inbound' THEN a.quantity ELSE -a.quantity END as absquantity
    FROM inventory_resupplylogiteminstance a
    INNER JOIN inventory_resupplylog b ON b.uid = a.fk_resupply_log_id
)

SELECT a.name, 
    SUM(CASE WHEN b.date < ('2018-10-10'::date) THEN b.absquantity END) as starting_balance,

    SUM(CASE WHEN b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date) AND b.movement = 'aa' THEN b.absquantity END) as aa,
    SUM(CASE WHEN b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date) AND b.movement = 'bb' THEN b.absquantity END) as bb,
    SUM(CASE WHEN b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date) AND b.movement = 'cc' THEN b.absquantity END) as cc,
    SUM(CASE WHEN b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date) AND b.movement = 'dd' THEN b.absquantity END) as dd,

    SUM(CASE WHEN b.date < ('2018-10-12'::date) THEN b.absquantity END) AS ending_balance




FROM inventory_item a
LEFT JOIN instances b ON b.fk_item_id = a.uid

GROUP BY a.uid, a.name
ORDER BY a.name

As you can see, the 2nd-5th SUM lines are redundant in that they're querying for rows where b.date is between 2018-10-10 and 2018-10-12 . 如您所见,第b.date行SUM行是多余的,因为它们正在查询b.date2018-10-102018-10-12之间的行。 Is there any way to rewrite my query so as to only have to write b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date) once and still be able to SELECT starting_balance and ending_balance on the same row? 有什么办法可以重写我的查询,以便只写b.date > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date)一次并仍然能够在同一行上选择starting_balanceending_balance

You can check the range in the CTE and create a flag that indicates if the row is in the indicated range. 您可以检查CTE中的范围,并创建一个标志来指示该行是否在指定的范围内。

Then you can use that flag in the final SELECT. 然后,您可以在最终的SELECT中使用该标志。 Switching to a FILTER () expression also makes it more readable: 切换到FILTER ()表达式还使其更具可读性:

WITH instances AS (
  SELECT b.ldtc as date, 
         a.fk_item_id, 
         b.movement, 
         a.quantity, 
         CASE 
           WHEN b.movement = 'Inbound' THEN a.quantity 
           ELSE -a.quantity 
         END as absquantity,
         -- the column in_range returns either true or false
         (b.ldtc > ('2018-10-10'::date) AND b.date < ('2018-10-12'::date)) as in_range
  FROM inventory_resupplylogiteminstance a
    INNER JOIN inventory_resupplylog b ON b.uid = a.fk_resupply_log_id
)
SELECT a.name, 
       SUM(b.absquantity) filter (where b.date < '2018-10-10'::date) as starting_balance,
       SUM(b.absquantity) filter (where in_range and b.movement = 'aa') as aa,
       SUM(b.absquantity) filter (where in_range and b.movement = 'bb') as bb,
       SUM(b.absquantity) filter (where in_range and b.movement = 'cc') as cc,
       SUM(b.absquantity) filter (where in_range and b.movement = 'dd') as dd,
       SUM(b.absquantity) filter (where b.date < '2018-10-12'::date) AS ending_balance
FROM inventory_item a
  LEFT JOIN instances b ON b.fk_item_id = a.uid
GROUP BY a.uid, a.name
ORDER BY a.name;

If you don't want to repeat the date for the starting and ending balance either, you can put the range you want to test for into the CTE, then use Postgres' range functions in the final query: 如果您也不想重复开始和结束余额的日期,可以将要测试的范围放入CTE中,然后在最终查询中使用Postgres的范围函数

WITH instances AS (
  SELECT b.ldtc as date, 
         a.fk_item_id, 
         b.movement, 
         a.quantity, 
         CASE 
           WHEN b.movement = 'Inbound' THEN a.quantity 
           ELSE -a.quantity 
         END as absquantity,
         daterange('2018-10-10'::date, '2018-10-12'::date, '()') as check_range
  FROM inventory_resupplylogiteminstance a
    INNER JOIN inventory_resupplylog b ON b.uid = a.fk_resupply_log_id
)
SELECT a.name, 
    SUM(b.absquantity) filter (where b.date < lower(check_range)) as starting_balance,
    SUM(b.absquantity) filter (where b.date <@ b.check_range and b.movement = 'aa') as aa,
    SUM(b.absquantity) filter (where b.date <@ b.check_range and b.movement = 'bb') as bb,
    SUM(b.absquantity) filter (where b.date <@ b.check_range and b.movement = 'cc') as cc,
    SUM(b.absquantity) filter (where b.date <@ b.check_range and b.movement = 'dd') as dd,
    SUM(b.absquantity) filter (where b.date < upper(b.check_range))) AS ending_balance
FROM inventory_item a
  LEFT JOIN instances b ON b.fk_item_id = a.uid
GROUP BY a.uid, a.name
ORDER BY a.name;

daterange('2018-10-10'::date, '2018-10-12'::date, '()') creates a date range where the two dates are excluded . daterange('2018-10-10'::date, '2018-10-12'::date, '()')创建一个排除两个日期的日期范围

The <@ operator tests if a date falls into the given range. <@运算符测试日期是否在给定范围内。

So the expression b.date <@ b.check_range is equivalent to b.date > '2018-10-10'::date AND b.date < '2018-10-12'::date (because the range was defined excluding the edges) 因此,表达式b.date <@ b.check_range等效于b.date > '2018-10-10'::date AND b.date < '2018-10-12'::date (因为定义的范围不包括边缘)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM