暂无图片
暂无图片
3
暂无图片
暂无图片
暂无图片

[译] 您知道PostgreSQL能做这八件着迷事儿么

原创 多米爸比 2022-04-28
1044

原文标题:8 Fascinating Things You Probably Didn’t Know PostgreSQL Can Do!
原文地址:https://www.enterprisedb.com/blog/8-cool-interesting-facts-things-postgresql-can-do
原文作者:thom-brown
翻译:多米爸比

本文为在Postgres中执行任务提供有用的提示和快捷方式。

  1. 整行引用
  2. where比较列合并
  3. With硬编码表
  4. 自定义配置参数
  5. 布尔值列可以独立写
  6. 不费代价转换列数据类型
  7. 查找数据行的归属分区
  8. 表即类型

一、整行引用

您是否尝试过运行以下语句?

SELECT my_table FROM my_table;

这看起来可能很奇怪,但它所做的是将所有列作为行类型返回单列。您为什么要这样做?好吧,您很可能已经以这种方式引用了该表:

SELECT table_a.column, table_b.column
FROM table_a
INNER JOIN table_b ON table_a.id = table_b.aid;

它的作用是引用整行,然后只要求单列显示每个表,这只是常见的情况。

再看下面这个示例:

SELECT data, backup_data
FROM data
FULL JOIN backup_data ON data = backup_data
WHERE data IS NULL OR backup_data IS NULL;

这里我们有一个表和它的备份。如果我们想查看它们之间的差异,或者是因为我们想查看自备份以来发生了什么变化,或者想查看我们是否丢失了任何可能需要从备份中恢复的历史行,该怎么办?

为快速演示,我们将创建表并插入三行:

postgres=# CREATE TABLE data (id serial, person text, country text);
CREATE TABLE
postgres=# INSERT INTO data (person, country)
  VALUES ('Tim','France'),('Dieter','Germany'),('Marcus','Finland');
INSERT 0 3

现在,让我们创建一个相同的表副本并将相同的数据复制到其中:

postgres=# CREATE TABLE backup_data (id serial, person text, country text);
CREATE TABLE
postgres=# INSERT INTO backup_data SELECT * FROM data;
INSERT 0 3

我们希望两张表数据有所不同,因此我们从原始表中删除一行并添加新行到备份表中:

postgres=# DELETE FROM data WHERE id = 2;
DELETE 1
postgres=# INSERT INTO data (person, country)
  VALUES ('Roberto','Italy');
INSERT 0 1

最后,让我们看看如果我们运行查询来比较表会发生什么:

postgres=# SELECT data, backup_data
postgres-# FROM data
postgres-# FULL JOIN backup_data ON data = backup_data
postgres-# WHERE data IS NULL OR backup_data IS NULL;
       data        |    backup_data  
-------------------+--------------------
                   | (2,Dieter,Germany)
 (4,Roberto,Italy) |
(2 rows)

我们可以在这里看到backup_data表包含数据表中缺少的行,反之亦然。

还有这个:

postgres=# SELECT to_jsonb(data) FROM data;
                   to_jsonb                   
-----------------------------------------------------
 {"id": 1, "person": "Tim", "country": "France"}
 {"id": 3, "person": "Marcus", "country": "Finland"}
 {"id": 4, "person": "Roberto", "country": "Italy"}
(3 rows)

我们将所有数据转换为JSON!

二、where比较列合并

这是一个非常方便的技巧,可使查询更短且更易于阅读。

假设我们有以下查询:

SELECT country, company, department
FROM suppliers
WHERE country = 'Australia'
  AND company = 'Skynet'
  AND department = 'Robotics';

我们可以去掉那些AND:

SELECT country, company, department
FROM suppliers
WHERE (country, company, department) = ('Australia','Skynet','Robotics');

我们还可以使用IN来满足OR条件。如果我们调整原始查询:

SELECT country, company, department
FROM suppliers
WHERE department = 'Robotics'
AND (
  (country = 'Australia'
    AND company = 'Skynet')
OR
  (country = 'Norway'
    AND company = 'Nortech')
);

我们可以将其缩短为:

SELECT country, company, department
FROM suppliers
WHERE department = 'Robotics'
  AND (country, company) IN (('Australia','Skynet'),('Norway','Nortech'));

三、With硬编码表

假设您只有对数据库及其表的读取权限,但您有一小部分数据要用于连接现有表。

SELECT station, time_recorded, temperature
FROM weather_stations;

    station     |    time_recorded    | temperature
----------------+---------------------+-------------
 Biggin_Hill_14 | 2020-02-02 13:02:44 |        22.4
 Reigate_03     | 2020-02-02 16:05:12 |        20.9
 Aberdeen_06    | 2020-02-02 15:52:49 |         8.5
 Madrid_05      | 2020-02-02 14:05:27 |        30.1
(4 rows)

我们想知道每个车站的温暖程度,所以我们可以构造这个查询:

SELECT station, 
  CASE
    WHEN temperature <= 0 THEN 'freezing'
    WHEN temperature < 10 THEN 'cold'
    WHEN temperature < 18 THEN 'mild'
    WHEN temperature < 30 THEN 'warm'
    WHEN temperature < 36 THEN 'hot'
    WHEN temperature >= 36 THEN 'scorching'
  END AS temp_feels
FROM weather_stations;

如果我们设置一个伪表来包含所有这些信息,我们可以添加更多模拟的数据,并且为了更容易使用,我们可以将它放在一个公共表表达式中:

WITH temp_ranges (temp_range, feeling, colour) AS (
  VALUES
    ('(,0]'::numrange, 'freezing', 'blue'),
    ('( 0,10)'::numrange, 'cold', 'white'),
    ('[10,18)'::numrange, 'mild', 'yellow'),
    ('[18,30)'::numrange, 'warm', 'orange'),
    ('[30,36)'::numrange, 'hot', 'red'),
    ('[36,)'::numrange, 'scorching', 'black')
)

SELECT ws.station, tr.feeling, tr.colour
FROM weather_stations ws
INNER JOIN temp_ranges tr ON ws.temperature <@ tr.temp_range;

注意:不熟悉范围类型的人可能会被“numrange”值和数据类型混淆。它是范围类型中的一种,用于数值范围的类型。圆括号表示排他;方括号表示包括在内。因此,’(0,10]’ 表示“从0开始但不包括0,直到并包括10”。缺失值如果是第一个值表示之前的任何值,缺失值如果是第二个值表示之后的任何值。

四、自定义配置参数

Postgres有广泛的参数,允许您配置数据库系统的各个方面,但您也可以添加自己的参数并调用任何您想要的参数,只要您给它们自己的配置类。

例如,您可以将其添加到 postgresql.conf:

config.cluster_type = 'staging'

然后使用 SHOW 命令访问它。

postgres=# SHOW config.cluster_type;
 config.cluster_type
---------------------
 staging
(1 row)

请注意,这些设置不会出现在pg_settings中,也不会由SHOW ALL输出。

那么为什么我们能够做到这一点呢?为什么我们不能在不提供配置前缀的情况下做到这一点?在 PostgreSQL 9.2 之前,有一个名为custom_variable_classes的设置,它获取了一个类列表,这些类可以被扩展用于他们自己的设置。如果您想在postgresql.conf 中配置它,您需要将该扩展的类添加到列表中。但是,此要求在较新的版本中已删除,您不再需要显式声明它们。只有内置的配置参数没有前缀,所以任何自定义的参数都需要前缀,否则将不被接受。

正如您在上面的示例中所看到的,当您想要提供有关集群的某种元数据时,这可能会很方便。

五、布尔值列可以独立写

您可能编写了如下查询:

SELECT user, location, active
FROM subscriptions
WHERE active = true;

您知道您不需要那个“= true”吗?可以这样写:

WHERE active

这是因为布尔值不需要与另一个布尔值进行比较,因为表达式无论如何都会返回 true 或 false。如果想取反的,可以写:

WHERE NOT active

读起来也更好。

六、不费代价转换列数据类型

通常,当更改包含现有数据表列的类型时,必须重写整个表以将数据存储在其新数据类型中。但在很多情况下,可以避免这一情况发生。

以下语句是如何找到所有这些:

SELECT
  castsource::regtype::text,
  array_agg(casttarget::regtype ORDER BY casttarget::regtype::text) casttargets
FROM pg_cast
WHERE castmethod = 'b'
GROUP BY 1
ORDER BY 1;

该语句将返回一个相对较小的类型列表以及它们可以转换为的类型集,因为它们是“binary compatible”(二进制亲和的)。在此列表中,您将看到text、xml、char 和 varchar都是可以互换的–它们是相同存储的二进制格式。因此,如果您在文本列中有一个包含XML数据的表,请随意转换它而不会受到影响(请注意,如果您的数据中有无效的XML,Postgres将禁止它并告诉您)。

七、查找数据行的归属分区

您可能将数据拆分为不同的分区,但是当您选择行时,如果您想知道每行来自哪个分区表怎么办?这很简单:只需将 tableoid::regclass 添加到您的 SELECT 子句中。例如:

postgres=# SELECT tableoid::regclass, * FROM customers;
   tableoid   | id  |      name      |     country    | subscribed
--------------+-----+----------------+----------------+------------
 customers_de |  23 | Hilda Schumer  | Germany        | t
 customers_uk | 432 | Geoff Branshaw | United Kingdom | t
 customers_us | 815 | Brad Moony     | USA            | t
(3 rows)

这是有效的,因为tableoid 是一个隐藏的系统列,您只需要显式选择才能看到它。它返回该行所属表的 OID(对象标识符)。如果将其转换为regclass 类型,它将返回表名。

八、表即类型

您没听错。每当创建一个表时,您也同时创建了一个新类型:

CREATE TABLE books (isbn text, title text, rrp numeric(10,2));

我们可以在创建另一个表时使用此表类型,或者作为函数参数或返回类型:

CREATE TABLE personal_favourites (book books, movie movies, song songs);

然后,您将在其中输入信息:

INSERT INTO personal_favourites (book)
  VALUES (('0756404746','The Name of the Wind',9.99));

或者:

INSERT INTO personal_favourites (book.isbn, book.title, book.rrp)
  VALUES ('0756404746','The Name of the Wind',9.99);

要从表值中获取单个值,您可以从列中选择列:

SELECT (book).isbn, (book).title, (book).rrp
FROM personal_favourites;

而且,正如我在“整行引用”中提到的,您可以将整行转换为JSON,它会以您希望的方式返回所有内容:

postgres=# SELECT jsonb_pretty(to_jsonb(personal_favourites))
FROM personal_favourites;
             	jsonb_pretty            	 
----------------------------------------------
 {                                       	+
 	"book": {                           	+
     	"rrp": 9.99,                    	+
     	"isbn": "0756404746",           	+
     	"title": "The Name of the Wind" 	+
 	},                                  	+
 	"song": {                           	+
     	"album": "Grace",               	+
     	"title": "This is our Last Goodbye",+
     	"artist": "Jeff Buckley"        	+
 	},                                  	+
 	"movie": {                          	+
     	"title": "Magnolia",            	+
     	"studio": "New Line Cinema",    	+
     	"release_date": "2000-03-24"    	+
 	}                                   	+
 }

可以使用这种功能为JSON 数据创建模式,以实现类似NoSQL 的功能,但数据具有已定义的结构。

但是等等,如果我想存储和查询所有我最喜欢的书籍、歌曲和电影,而不仅仅是一个呢?

这也可以。任何类型,包括表,都可以通过在数据类型名称后添加 [] 转换为数组。与其重新创建表,不如将列转换为数组类型,然后添加另一本书:

ALTER TABLE personal_favourites
  ALTER COLUMN book TYPE books[] USING ARRAY[book];

ALTER TABLE personal_favourites
  ALTER COLUMN movie TYPE movies[] USING ARRAY[movie];

ALTER TABLE personal_favourites
  ALTER COLUMN song TYPE songs[] USING ARRAY[song];

我们将在 book 数组中添加另一本书:

UPDATE personal_favourites
  SET book = book || ('1408891468','Jonathan Strange and Mr Norrell',7.99)::books;

现在我们的结果如下所示:

postgres=# SELECT jsonb_pretty(to_jsonb(personal_favourites))
FROM personal_favourites;
                  	jsonb_pretty                 	 
--------------------------------------------------------
 {                                                 	+
 	"book": [                                     	+
     	{                                         	+
         	"rrp": 9.99,                          	+
         	"isbn": "0756404746",                 	+
         	"title": "The Name of the Wind"       	+
     	},                                        	+
     	{                                         	+
         	"rrp": 7.99,                          	+
         	"isbn": "1408891468",                 	+
         	"title": "Jonathan Strange and Mr Norrell"+
     	}                                         	+
 	],                                            	+
 	"song": [                                     	+
     	{                                         	+
         	"album": "Grace",                     	+
         	"title": "This is our Last Goodbye",  	+
         	"artist": "Jeff Buckley"              	+
     	}                                         	+
 	],                                            	+
 	"movie": [                                    	+
     	{                                         	+
         	"title": "Magnolia",                  	+
         	"studio": "New Line Cinema",          	+
         	"release_date": "2000-03-24"          	+
     	}                                         	+
 	]                                             	+
 }

book的值现在包含book对象数组,并且我们的查询没有任何更改。

我希望这些技巧可以帮助您从Postgres 中获得更多价值!

保持联系

从2019年12月开始写第一篇文章,分享的初心一直在坚持,本人现在组建了一个PG乐知乐享交流群,欢迎关注我文章的小伙伴进群吹牛唠嗑,交流技术,互赞文章。

123.png

如果群二维码失效可以加我微信。

456.png

最后修改时间:2022-10-23 10:00:52
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
1人已赞赏
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论