启发本文的基本问题/请求是关于是否可以设计针对表/视图运行的查询的内部问题,该表/视图可能具有不同的列,具体取决于 PostgreSQL 版本。具体细节在这里并不重要,但基本思想很有趣。所以让我们完善我们的工具包,看看我们能完成什么。
天真的方法
从基础开始总是好的,所以让我们尝试设置/模拟我们的环境;我们希望有两个表,它们在一个表上添加一列而有所不同。我们将使用相同的数据库但有两个不同的模式,以便我们只需更改以模拟对两个单独的表运行查询。 search_path
这是 SQL:
CREATE SCHEMA db1;
CREATE SCHEMA db2;
CREATE TABLE db1.albums (album_name text, band_name text, release_date date, favorite_song text, rating int);
CREATE TABLE db2.albums (album_name text, band_name text, release_date date);
COPY db1.albums FROM STDIN;
Led Zeppelin Led Zeppelin 01-12-1969 Babe I'm Gonna Leave You 5
Led Zeppelin II Led Zeppelin 10-22-1969 The Lemon Song 5
Led Zeppelin III Led Zeppelin 10-05-1970 Since I've Been Loving You 5
The Wall Pink Floyd 11-30-1979 Comfortably Numb 5
Wish You Were Here Pink Floyd 09-12-1975 Wish You Were Here 5
Black Sabbath Black Sabbath 02-13-1970 N.I.B. 5
Paranoid Black Sabbath 09-18-1970 War Pigs 5
\.
INSERT INTO db2.albums SELECT album_name, band_name, release_date FROM db1.albums;
复制
CREATE SCHEMA
CREATE SCHEMA
CREATE TABLE
CREATE TABLE
COPY 7
INSERT 0 7
复制
我们制作了一个简单的表来保存我们的数据库,但其中一个表具有另一个没有的附加列。让我们为我们的查询尝试直接的方法。
-- running the query against db1-version of the table
SET search_path = db1;
SELECT album_name,favorite_song,rating FROM albums WHERE band_name = 'Led Zeppelin';
复制
album_name favorite_song rating Led Zeppelin Babe I'm Gonna Leave You 5 Led Zeppelin II The Lemon Song 5 Led Zeppelin III Since I've Been Loving You 5
复制
-- running the query against db2-version of the table
SET search_path = db2;
SELECT album_name,favorite_song,rating FROM albums WHERE band_name = 'Led Zeppelin';
复制
ERROR: column "favorite_song" does not exist
LINE 1: SELECT album_name,favorite_song,rating FROM albums WHERE ban...
复制
下次尝试
毫无疑问,相同的查询不会针对缺少相关列的表运行。但是我们能不能更狡猾一点?
让我们稍微思考一下:我们在 SQL 中是否有一种数据类型可以帮助我们摆脱列名的结构化一面,更轻松地处理事情?这样如果我们提到一个不存在的列,解析器就不会讨厌我们?答:是的,有一些,但我们关心的是我们的朋友 JSON!有一些函数可以让我们将任意表格行转换为 JSON 记录(又名等),然后我们可以将整行作为单个对象进行操作,从中提取列等。 to_json()
让我们通过将行转换为 JSON 并查看动态拉出字段来进行下一次尝试。(注意:这里我们使用 PostgreSQL-14 之前风格的 JSON 键查找语法。)
SET search_path = db1;
SELECT
json->>'album_name',
json->>'favorite_song',
json->>'rating'
FROM
(
SELECT to_json(albums) json
FROM albums
WHERE
band_name = 'Led Zeppelin'
) j
;
复制
?column? ?column? ?column? Led Zeppelin Babe I'm Gonna Leave You 5 Led Zeppelin II The Lemon Song 5 Led Zeppelin III Since I've Been Loving You 5
复制
SET search_path = db2;
SELECT
json->>'album_name',
json->>'favorite_song',
json->>'rating'
FROM
(
SELECT to_json(albums) json
FROM albums
WHERE
band_name = 'Led Zeppelin'
) j
;
复制
?column? ?column? ?column? Led Zeppelin Led Zeppelin II Led Zeppelin III
复制
好的,没有错误!
修复缺陷
我们在这里有一些不足之处,我们可以改正;我们丢失了列名,数据类型都是文本,而且当我们针对丢失的表运行时,我们没有处理事物的默认值。处理这样的事情并让结果尽可能像一个“真实的表格”会很好。
所以我们当然可以构造这个查询,使用字段描述的语法再次给它适当的标签。因为我们知道预期的列数据类型,所以我们总是可以使用强制转换来正确处理事情。由于如果未找到键,运算符就会返回,因此我们最终可以使用来提供我们的默认值。把这些放在一起,这样的事情应该适用于我们的示例数据: AS
SET search_path = db1;
SELECT
json->>'album_name' as album_name,
COALESCE(json->>'favorite_song', 'They all rock!') as favorite_song,
COALESCE((json->>'rating')::int, 99) as rating
FROM
(
SELECT to_json(albums) json
FROM albums
WHERE
band_name = 'Led Zeppelin'
) j
;
复制
album_name favorite_song rating Led Zeppelin Babe I'm Gonna Leave You 5 Led Zeppelin II The Lemon Song 5 Led Zeppelin III Since I've Been Loving You 5
复制
SET search_path = db2;
SELECT
json->>'album_name' as album_name,
COALESCE(json->>'favorite_song', 'They all rock!') as favorite_song,
COALESCE((json->>'rating')::int, 99) as rating
FROM
(
SELECT to_json(albums) json
FROM albums
WHERE
band_name = 'Led Zeppelin'
) j
;
复制
album_name favorite_song rating Led Zeppelin They all rock! 99 Led Zeppelin II They all rock! 99 Led Zeppelin III They all rock! 99
复制
缺点
以这种方法当然也有缺点,我们应该问问“应该吗?” “我们可以吗?”
在缺点中,您可能需要在调用时创建一个表达式索引,以使其在大量数据上具有高性能;这加上需要手动调整数据类型并将返回值强制转换为您期望的适当数据类型,这使得这在实际中是一种荒谬的方法。不要在家里尝试这个。