pgSQL FULL OUTER JOIN 'WHERE' 条件

Posted

技术标签:

【中文标题】pgSQL FULL OUTER JOIN \'WHERE\' 条件【英文标题】:pgSQL FULL OUTER JOIN 'WHERE' ConditionpgSQL FULL OUTER JOIN 'WHERE' 条件 【发布时间】:2019-08-07 04:03:30 【问题描述】:

我正在尝试创建一个查询来检索当前价格和特价商品如果正在进行促销;当没有销售时,我希望 store_picture_monthly_price_special.price AS special_price 返回为 null

在添加第二个 WHERE 条件之前,查询会按照我的预期执行:store_picture_monthly_price_special.price 返回 null,因为没有促销活动目前。

 store_picture_monthly_reference | tenure |   name    | regular_price | special_price 
                               3 |     12 | 12 Months |        299.99 | Null             
                               2 |      3 | 3 Months  |         79.99 | Null            
                               1 |      1 | 1 Month   |         29.99 | Null            

pgSQL 将第二个 WHERE 条件视为“全部或全部”。如果没有销售,就没有结果。

是否可以调整此查询,以便我每次都能获得常规定价和特价商品,当特价商品正在运行或返回时以美元值形式返回 null 是我想要做的需要子查询来完成吗?

这是我目前的查询:

SELECT store_picture_monthly.reference AS store_picture_monthly_reference , store_picture_monthly.tenure , store_picture_monthly.name , store_picture_monthly_price_regular.price AS regular_price , store_picture_monthly_price_special.price AS special_price

FROM ( store_picture_monthly INNER JOIN store_picture_monthly_price_regular ON store_picture_monthly_price_regular.store_picture_monthly_reference = store_picture_monthly.reference )

FULL OUTER JOIN store_picture_monthly_price_special ON store_picture_monthly.reference = store_picture_monthly_price_special.store_picture_monthly_reference

WHERE 
    ( store_picture_monthly_price_regular.effective_date < NOW() ) 
    AND
    ( NOW() BETWEEN store_picture_monthly_price_special.begin_date AND store_picture_monthly_price_special.end_date )

GROUP BY store_picture_monthly.reference , store_picture_monthly_price_regular.price , store_picture_monthly_price_regular.effective_date , store_picture_monthly_price_special.price

ORDER BY store_picture_monthly_price_regular.effective_date DESC

表格“store_picture_monthly

reference bigint,
name text,
description text,
tenure bigint,
available_date timestamp with time zone,
available_membership_reference bigint

表格store_picture_monthly_price_regular

reference bigint ,
store_picture_monthly_reference bigint,
effective_date timestamp with time zone,
price numeric(10,2),
membership_reference bigint

表格store_picture_monthly_price_special

reference bigint,
store_picture_monthly_reference bigint,
begin_date timestamp with time zone,
end_date timestamp with time zone,
price numeric(10,2),
created_date timestamp with time zone DEFAULT now(),
membership_reference bigint

【问题讨论】:

请查找并使用表别名。了解什么是左连接返回:行上的内连接加上由空值扩展的不匹配的左表行。作为左连接的一部分,始终知道您想要什么内连接。在左连接删除任何由空值扩展的行之后,内连接或需要右表列不为空的地方,即仅在行上留下内连接,即“将外连接变为内连接”。你有那个。 这是一个常见问题解答。在考虑发布之前,请始终在谷歌上搜索您的问题/问题/目标和/或错误消息的许多清晰、简洁和准确的措辞,包括和不包括您的特定字符串/名称,并阅读许多答案。如果您发布问题,请使用一个短语作为标题。请在代码问题中给出minimal reproducible example--cut & paste & runnable code 加上所需的输出加上清晰的规范和解释。最小意味着将最少的问题代码添加到最少的工作代码中。因此,给出你所展示的最少代码,并在你出错的第一个地方提供最少的代码。 Left Outer Join doesn't return all rows from my left table?的可能重复 我支持 PhilipXY 对 Alison 你的表的评论。你的表名很长,在每个 sql 中写了 100 次,这意味着上面的等宽墙大部分只是表名,很难阅读 我还没有真正深入研究,但你为什么想要一个完整的外层?如果我有两个正常价格和特价表,我的查询基本上如下所示:SELECT COALESCE(sp.price, no.price) FROM normalprices no LEFT JOIN specialprices sp ON no.prodid=sp.prodid AND NOW() BETWEEN sp.promostart AND sp.promoend - 这将连接产品 ID 上的两个表,并且仅在促销期有效时才提供特价,然后 coalesce 给出“获得特价或者如果 null 得到正常价格” 【参考方案1】:

问题的描述表明您需要LEFT JOIN,而不是FULL JOINFULL JOINs 非常罕见,尤其是在具有明确定义的外键关系的数据库中。

在您的情况下,WHERE 子句无论如何都会将您的FULL JOIN 变成LEFT JOIN,因为WHERE 子句需要第一个表中的有效值。

SELECT spm.reference AS store_picture_monthly_reference, 
       spm.tenure, spm.name, 
       spmpr.price AS regular_price,
       spmps.price AS special_price
FROM store_picture_monthly spm INNER JOIN
     store_picture_monthly_price_regularspmpr
     ON spmpr.store_picture_monthly_reference = spm.reference LEFT JOIN
     store_picture_monthly_price_special spmps 
     ON spm.reference = spmps.store_picture_monthly_reference AND
        NOW() BETWEEN spmps.begin_date AND spmps.end_date
WHERE spmpr.effective_date < NOW();

注意事项:

我引入了表别名,以便查询更易于编写和阅读。 销售日期的条件现在位于ON 子句中。 我删除了GROUP BY。似乎没有必要。如果是,您可以改用SELECT DISTINCT。如果需要,我会调查数据问题。 我对日期比较持怀疑态度。 NOW() 有一个时间组件。比较列的命名表明它们只是没有时间的日期。

【讨论】:

【参考方案2】:

任何时候你将 where 谓词放在外连接的表上,它都会将外连接转换为内连接,因为外连接引入的空值永远不能与任何东西进行比较以产生真值(所以外连接在行不匹配的地方放置大量行与空值,然后 WHERE 再次取出整行)

考虑这个更简单的例子:

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id
WHERE b.col = 'value'

等同于:

SELECT * FROM 
a INNER JOIN b ON a.id = b.id
WHERE b.col = 'value'

要解决此问题,请将谓词从 where 移到 ON

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id AND b.col = 'value'

你也可以考虑:

SELECT * FROM 
a LEFT JOIN b ON a.id = b.id
WHERE b.col = 'value' OR b.col IS NULL

但是如果 b.col 自然包含一些空值,这可能会获取您不想要的数据;它无法区分 b.col 中原生存在的空值和连接中失败引入的空值,以匹配来自 b 的行与来自 a 的行(除非我们还查看连接的 id 列的空性)

A
id
1
2
3

B
id, col
1, value
3, null

--wrong, behaves like inner
A left join B ON a.id=b.id WHERE b.col = 'value'
1, 1, value

--maybe wrong, b.id 3 might be unwanted
A left join B ON a.id=b.id WHERE b.col = 'value' or b.col is null
1, 1, value
2, null, null
3, 3, null

--maybe right, simpler to maintain than the above
A left join B ON a.id=b.id AND b.col = 'value' 
1, 1, value
2, null, null
3, null, null

在最后两个中,区别在于 b.id 是否为空,尽管行数相同。如果我们计算 b.id,我们的计数可能会出错。了解这种加入行为的细微差别很重要。如果您希望排除第 3 行但包含第 2 行,您甚至可能需要它,方法是制作 a LEFT JOIN b ON a.id=b.id WHERE b.col = 'value' OR b.id IS NULL 的 where 子句 - 这将保留第 2 行但排除第 3 行,因为即使连接成功找到 b.id的 3 它不被任何谓词保留

【讨论】:

以上是关于pgSQL FULL OUTER JOIN 'WHERE' 条件的主要内容,如果未能解决你的问题,请参考以下文章

sql MS SQL Full Outer Join

两个 INNER JOIN 的 FULL OUTER JOIN

Oracle表与表之间的连接方式(内连接:inner join 外连接 全连接: full outer join左连接:left outer join 右连接:right outer join(代码

oracle 内连接(inner join)外连接(outer join)全连接(full join)

SQL的JOIN语法解析(inner join, left join, right join, full outer join的区别)

FULL OUTER JOIN 在这里真的是一件坏事吗?