如何从 PostgreSQL INNER JOIN 和 UNION ALL 查询中获取包含 NULL 值的表?

问题描述 投票:0回答:1

我有一个带有

inner join
的时间序列图的 PostgreSQL 查询。标签列表用作
where
inner join
条件。我使用
union all
获取包含两种情况数据的结果表:列表中包含标签的查询和标签列表为空的查询。如果标签列表为空,但 PostgreSQL 返回
null
,即根本没有行,我想要从
select
之前的第一个
union all
开始具有
SELECT 0
值的行。我想要
null
行,因为没有它们,我无法以编程方式分隔案例。

如何获得包含

null
行的结果?或者有没有一种方法,如果标签列表不为空,则仅从
select
之前的
union all
获取结果,如果标签列表为空,则仅从
select
之后的
union all
获取结果?如果无法实现其中任何一个,那么非常欢迎有关如何从当前查询中提取两种情况的数据或对其进行修改的建议。无需处理餐厅有多个标签的情况:餐厅要么有一个标签,要么没有任何标签。

查询示例、带有虚拟值和注释的表:db<>fiddle

-- I want null values from this first SELECT before UNION ALL unless
-- a more elegant/efficient solution is possible. But I get 'SELECT 0'.
SELECT 
  f.days AS days,
  SUM (f.waste_kgs) AS waste,
  SUM (f.prepared_kgs) AS prepared,
  f.waste_type_id AS waste_id,
  f.restaurant_id AS restaurant
FROM food f
INNER JOIN tags t
  ON f.restaurant_id = t.restaurant_id
WHERE t.name = ANY (array[]::text[]) -- Empty tag list as an argument.
  AND f.days BETWEEN '2000-1-1' and '2099-1-1'
  AND f.restaurant_id = ANY (array[1, 2, 3])
GROUP BY days, restaurant, waste_id 
-- Separately running the first SELECT returns
-- days     waste   prepared    waste_id    restaurant
-- SELECT 0

UNION ALL 
-- The same query without the INNER JOIN and tag list argument. 
SELECT
  f.days AS days,
  SUM (f.waste_kgs) AS waste,
  SUM (f.prepared_kgs) AS prepared,
  f.waste_type_id AS waste_id,
  f.restaurant_id AS restaurant
FROM food f
WHERE f.days BETWEEN '2000-1-1' and '2099-1-1'
  AND f.restaurant_id = ANY (array[1, 2, 3])
GROUP BY days, restaurant, waste_id;

-- The whole query returns
-- days       waste     prepared    waste_id    restaurant
-- 2023-01-01   2.5        16.0           1              1
-- 2023-01-02   8.6         7.3           1              2
-- 2023-01-03   10.5        1.8           1              3
-- 2023-01-03   0.8         0.0           2              3
-- SELECT 4
-- No null values to differentiate the empty tag list argument case.

以下是示例中使用的 db<>fiddle 中的表和值。

CREATE TABLE restaurants (
  id int PRIMARY KEY,
  name text,
  type text
);
CREATE TABLE food (
  id int PRIMARY KEY,
  restaurant_id int REFERENCES restaurants (id),
  waste_type_id smallint NOT NULL,
  product_id int NOT NULL,
  waste_kgs decimal NOT NULL, 
  prepared_kgs decimal NOT NULL,
  customers smallint NOT NULL,
  days date NOT NULL
);
CREATE TABLE tags (
  id int PRIMARY KEY,
  restaurant_id int REFERENCES restaurants (id),
  name text
);
INSERT INTO restaurants VALUES
(1, 'restaurant_1'),
(2, 'restaurant_2'),
(3, 'restaurant_3');
INSERT INTO food VALUES 
(1, 1, 1, 1, 1.7, 8.0, 96, '2023-1-1'),
(2, 1, 1, 10, 0.5, 7.0, 96, '2023-1-1'),
(3, 1, 1, 15, 0.3, 1.0, 96, '2023-1-1'),
(4, 2, 1, 12, 7.0, 0.8, 39, '2023-1-2'),
(5, 2, 1, 10, 1.1, 5.0, 39, '2023-1-2'),
(6, 2, 1, 11, 0.5, 1.5, 39, '2023-1-2'),
(7, 3, 1, 8, 10.0, 0.3, 97, '2023-1-3'),
(8, 3, 2, 17, 0.8, 0.0, 97, '2023-1-3'),
(9, 3, 1, 11, 0.5, 1.5, 39, '2023-1-3');
INSERT INTO tags VALUES
(1, 1, 'tag_1');

来自具有空标签列表的相同表的以下查询给出了一个表,其中包含来自

null
inner join
值,因此我可以将它与
union all
本身一起使用 - 就像我尝试对上面的查询所做的那样 - 并分开轻松得出结果。它也在 db<>fiddle 中进行了演示。

-- The first row of the result displays calculations from the tagged restaurants. 
-- In this case it has null values because tag list is empty.
-- The second row of the result displays calculations from all the restaurants without
-- considering any tags.
SELECT Total, Total - Drinks AS "Without drinks", Drinks   
FROM (
  SELECT 
  SUM (f.prepared_kgs) 
  FILTER (
    WHERE f.days BETWEEN '2000-1-1' AND '2099-1-1'
    AND f.restaurant_id = ANY (array[1, 2, 3])
  ) AS Total,
  SUM (f.prepared_kgs)
  FILTER (
    WHERE (f.product_id = 10 OR f.product_id = 17) 
    AND f.days BETWEEN '2000-1-1' AND '2099-1-1'
    AND f.restaurant_id = ANY (array[1, 2, 3])
  ) AS Drinks
  FROM food f
  INNER JOIN tags t 
    ON t.restaurant_id = f.restaurant_id 
  WHERE t.name = ANY (array[]::text[]) -- Empty tag list as an argument.  
)
UNION ALL
-- The same query without the INNER JOIN and tag list argument.
SELECT Total, Total - Drinks AS "Without drinks", Drinks   
FROM (
  SELECT 
  SUM (f.prepared_kgs) 
  FILTER (
    WHERE f.days BETWEEN '2000-1-1' AND '2099-1-1'
    AND f.restaurant_id = ANY (array[1, 2, 3])
  ) AS Total,
  SUM (f.prepared_kgs)
  FILTER (
    WHERE (f.product_id = 10 OR f.product_id = 17) 
    AND f.days BETWEEN '2000-1-1' AND '2099-1-1'
    AND f.restaurant_id = ANY (array[1, 2, 3])
  ) AS Drinks
  FROM food f
);
-- If tag list had elements, my program uses the first row.
-- Otherwise it uses the second row. Time series query returns 
-- rows for each day but the logic I try to use is the same.
total   Without drinks  drinks
 null             null    null
 25.1             13.1    12.0
SELECT 2
postgresql inner-join union-all where-in sql-null
1个回答
0
投票

如何从当前查询中提取出两种情况的数据

SELECT 1 AS query, columns... FROM query1
UNION ALL    
SELECT 2 AS query, columns... FROM query2

结果将包含一个值为 1 或 2 的“查询”列,具体取决于行来自 UNION ALL 的哪个分支。

但是,就您的情况而言,我认为有更好(更快)的解决方案。

第一个查询对第二个查询的子集进行聚合。这是一个简化版本,因为我懒得复制您所有的列和表:

SELECT group, sum(qty) FROM table WHERE key IN (...) GROUP BY group
UNION ALL
SELECT group, sum(qty) FROM table GROUP BY group

“key in (...)”是您的标签条件,但它可以是任何布尔条件。如果你这样做:

SELECT group, key IN (...) AS flag, sum(qty) FROM table GROUP BY group, flag

然后结果将包含相同的数据,但组织方式不同:一行表示满足条件的总和,另一行表示不满足条件的总和。要获得总和,您必须将这两行的总和相加。您还可以使用 ROLLUP。这使得查询变得更加简单,但是如果您的应用程序代码需要特定的格式并且您无法更改它,那么它可能不起作用。尽管您可以将上述查询放入具体化的 CTE 中并重复使用它两次,以按照您需要的方式格式化结果。

© www.soinside.com 2019 - 2024. All rights reserved.