如何在 PostgreSQL 中应用 CROSS JOIN?
Posted
技术标签:
【中文标题】如何在 PostgreSQL 中应用 CROSS JOIN?【英文标题】:How to apply CROSS JOIN in PostgreSQL? 【发布时间】:2021-02-23 10:28:14 【问题描述】:DB-Fiddle
CREATE TABLE sales (
id SERIAL PRIMARY KEY,
country VARCHAR(255),
sales_date DATE,
sales_volume DECIMAL,
fix_costs DECIMAL
);
INSERT INTO sales
(country, sales_date, sales_volume, fix_costs
)
VALUES
('DE', '2020-01-03', '500', '0'),
('NL', '2020-01-03', '320', '0'),
('FR', '2020-01-03', '350', '0'),
('None', '2020-01-31', '0', '2000'),
('DE', '2020-02-15', '0', '0'),
('NL', '2020-02-15', '0', '0'),
('FR', '2020-02-15', '0', '0'),
('None', '2020-02-29', '0', '5000'),
('DE', '2020-03-27', '180', '0'),
('NL', '2020-03-27', '670', '0'),
('FR', '2020-03-27', '970', '0'),
('None', '2020-03-31', '0', '4000');
预期结果:
sales_date | country | sales_volume | fix_costs
-------------|--------------|------------------|------------------------------------------
2020-01-03 | DE | 500 | 37.95 (= 2000/31 = 64.5 x 0.59)
2020-01-03 | FR | 350 | 26.57 (= 2000/31 = 64.5 x 0.41)
2020-01-03 | NL | 320 | 0.00
-------------|--------------|------------------|------------------------------------------
2020-02-15 | DE | 0 | 86.21 (= 5000/28 = 172.4 x 0.50)
2020-02-15 | FR | 0 | 86.21 (= 5000/28 = 172.4 x 0.50)
2020-02-15 | NL | 0 | 0.00
-------------|--------------|------------------|------------------------------------------
2020-03-27 | DE | 180 | 20.20 (= 4000/31 = 129.0 x 0.16)
2020-03-27 | FR | 970 | 108.84 (= 4000/31 = 129.0 x 0.84)
2020-03-27 | NL | 670 | 0.00
-------------|--------------|------------------|-------------------------------------------
参考 this question 中的解决方案,我尝试应用以下查询以获得预期结果:
SELECT
sales_date,
country,
SUM(sales_volume),
(CASE WHEN country = 'NL' THEN 0
WHEN SUM(CASE WHEN country <> 'NL' THEN sales_volume END) OVER (PARTITION BY sales_date) > 0
THEN ((f.fix_costs/ DAY(LAST_DAY(sales_date))) *
sales_volume / NULLIF(SUM(CASE WHEN country <> 'NL' THEN sales_volume END) OVER (PARTITION BY sales_date), 0)
)
ELSE (f.fix_costs / DAY(LAST_DAY(sales_date))) * 1 / SUM(country <> 'NL') OVER (PARTITION by sales_date)
END) AS imputed_fix_costs
FROM sales s
CROSS JOIN
(SELECT
LAST_DAY(sales_date) AS month_ld,
SUM(fix_costs) AS fix_costs
FROM sales
WHERE country = 'None'
GROUP BY month_ld
) f
ON f.month_ld = LAST_DAY(s.sales_date)
WHERE country <> 'None'
GROUP BY 1,2;
这个查询在 MariaDB 中没有任何问题。
但是,现在我切换到 Postgres 9.5 并且在 CROSS JOIN
上出现错误:
ERROR: syntax error at or near "ON" LINE 22: ON f.month_ld = LAST_DAY(s.sales_date)
我需要如何修改查询以使其在 Postgres 9.5 中也能正常工作?
【问题讨论】:
交叉连接不需要连接条件,所以需要去掉ON
部分
与您的问题无关,但是:Postgres 9.5 是no longer supported,您应该尽快计划升级。如果你正在迁移,你应该从 Postgres 13 开始
非常感谢旧版本的额外提示。我也尝试删除 ON 部分,但它仍然不起作用:dbfiddle.uk/…
能否解释一下 fix_costs 的逻辑。 das 0.59 来自哪里?为什么 NL 是 0.00?
如果你想要on
,请使用join
,而不是cross join
。
【参考方案1】:
-
Postgres AFAIK 中没有
LAST_DAY()
和 DAY()
函数。
CROSS JOIN
s 没有加入条件。
TL;DR: 重新阅读您的代码后,我发现我的结构与您的结构惊人地相似。所以,事实上,我想毕竟不需要这么大的解释。尽管如此,我还是把它作为文档放在那里。
但是,我想,您的问题最重要的部分是如何进行连接。这在我强调的第 2 步中进行了描述。
不清楚,第二个 sales_date 组中的最后一个 fix_costs
部分是如何变成 0.5
的(百分比会产生 除零错误...)
但是,这个查询可以展示解决方法:
step-by-step demo:db<>fiddle
SELECT
s1.*,
-- 3:
COALESCE(
s2.fix_costs / date_part('day', s2.sales_date) *
-- 1:
CASE WHEN s1.country != 'NL' THEN s1.sales_volume END
/ NULLIF(SUM(s1.sales_volume) FILTER (WHERE s1.country != 'NL') OVER (PARTITION BY s1.sales_date), 0)
, 0)
FROM sales s1
-- 2:
JOIN sales s2 ON s1.country <> 'None' AND s2.country = 'None'
AND date_trunc('month', s1.sales_date) = date_trunc('month', s2.sales_date)
1a) SUM(...) OVER (PARTITION BY ...)
窗口函数返回来自sales_date
的sales_volumn
值的总和。此结果将作为新列添加到结果中。
1b) 添加FILTER
子句会从此计算中删除country = 'NL'
记录
1c) NULLIF(..., 0)
是为了在计算当前sales_volumn
和先前计算的total
之间的百分比时避免除零(这将发生在第二个sales_date
组中) .
1d) CASE
子句从除法的第一部分删除 NL
值
1e) 计算fix_costs
值第二部分的百分比(据我了解)
2a) 自然连接条件:一个表没有None
国家,第二个只有None
国家记录。
2b) date_trunc('month', ...)
将日期标准化为当月的第一天。所以连接条件是:两个表的月份都适合(date_trunc()
比较的值都是YYYY-MM-01
)
3a) date_part('day', ...)
从日期中提取日期。由于您总是添加一个月的最后一天,因此不需要明确计算。因此,可以计算出fix_costs
值的第一部分。
3b) 将两个 fix_costs
部分相乘
3c) COALESCE(..., 0)
由于过滤了NL
记录和DIV BY ZERO 错误处理,fix_costs
列中存在一些NULL
结果,此功能已修复.
TODO:为第二组添加0.5
部分。
另外:
要在 Postgres 中计算当月的最后一天,您必须执行以下操作:
date_trunc('month', the_date) + interval '1 month - 1 day'
date_trunc('month', ...)
计算月份的第一天,如上所述。然后你加一个月得到下个月的第一天。从中您可以减去一天以获得当月的最后一天。
【讨论】:
【参考方案2】:您可能需要在这里使用 INNER JOIN,而不是 CROSS JOIN。 正如它所说的here, “在 MariaDB 中,CROSS JOIN 的语法等同于 INNER JOIN(它们可以相互替换)。”
在 PostgreSQL 中,CROSS JOIN 的工作方式不同,您的预期行为根本不匹配 - 您可能不想将所有记录相互匹配。只需将“CROSS”替换为“INNER”即可。
但是这段代码也有一个 MariaDB 特有的函数 Last_Day 的问题,它被 here 覆盖。
【讨论】:
【参考方案3】:DB-Fiddle
SELECT
s.sales_date,
s.country,
s.sales_volume,
f.fix_costs,
(CASE WHEN country = 'NL' THEN 0
/* Exclude NL from fixed_costs calculation */
WHEN SUM(CASE WHEN country <> 'NL' THEN sales_volume ELSE 0 END) OVER (PARTITION BY sales_date) > 0
THEN ((f.fix_costs/ extract(day FROM (date_trunc('month', sales_date + INTERVAL '1 month') - INTERVAL '1 day'))) *
sales_volume /
NULLIF(SUM(s.sales_volume) FILTER (WHERE s.country != 'NL') OVER (PARTITION BY s.sales_date), 0)
)
/* Divide fixed_cots equaly among countries in case of no sale*/
ELSE (f.fix_costs / extract(day FROM (date_trunc('month', sales_date + INTERVAL '1 month') - INTERVAL '1 day')))
/ SUM(CASE WHEN country <> 'NL' THEN 1 ELSE 0 END) OVER (PARTITION by sales_date)
END) AS imputed_fix_costs
FROM sales s
JOIN
(SELECT
(date_trunc('MONTH', sales_date)) AS month_ld,
SUM(fix_costs) AS fix_costs
FROM sales
WHERE country = 'None'
GROUP BY month_ld) f ON f.month_ld = date_trunc('month', s.sales_date)
WHERE country NOT IN ('None')
GROUP BY 1,2,3,4
ORDER BY 1;
【讨论】:
以上是关于如何在 PostgreSQL 中应用 CROSS JOIN?的主要内容,如果未能解决你的问题,请参考以下文章
如何应用 cross_val_score 来交叉验证我们自己的模型
如何在 BigQuery 中使用 SPLIT 和 CROSS APPLY 函数
如何在单个 Java 应用程序中使用两个 PostgreSQL DB 版本?
如何在 Django 应用程序中访问 PostgreSQL 数据库