PostgreSQL 08 数据类型补完

PostgreSQL 08 数据类型补完

继续看一些开发中很实用的数据类型, 看完以后就可以进入到函数等高级内容了, Hibernate也可以尽快搞起了. UUID类型 Array类型 hstore类型 JSON类型 自定义类型 Schema操作 UUID类型 UUID指的是RFC 4122标准中规定的128位的二进制码, 通常用32位16

继续看一些开发中很实用的数据类型, 看完以后就可以进入到函数等高级内容了, Hibernate也可以尽快搞起了.

  1. UUID类型
  2. Array类型
  3. hstore类型
  4. JSON类型
  5. 自定义类型
  6. Schema操作

UUID类型

UUID指的是RFC 4122标准中规定的128位的二进制码, 通常用32位16进制数表示, 并且在特定的位置用减号连接, 如下是一些UUID的例子:

40e6215d-b5c6-4896-987c-f30f3678f608
6ecd8c99-4036-403d-bf84-cf8400f67836
3f333df6-90a4-4fda-8dd3-9485d27cee36

在分布式环境下, UUID要比使用SERIAL更能确保数据的唯一性.

在Java中使用UUID, 简单的做法是直接使用UUID库:

public static void main(String[] args) {

    UUID uuid = UUID.randomUUID();

    System.out.println(uuid);

}

结果就会打印出一个UUID, 比如 0623b7fb-f975-4217-bb93-0f604313f97a. 在PgSQL中, 可以使用一个插件来生成UUID.

剩下就是在使用Hibernate的时候, 来试验一下这个新的数据类型吧.

Array类型

ARRAY类型很重要, 在PgSQL中每一个数据类型, PgSQL都会创建一个对应的数组类型, 甚至自定义一个数据类型的同时, PgSQL也会在后台创建对应的数组类型.

数组类型采取的是C系风格, 即在数据类型后加上[], 创建一个表带有数组类型如下:

CREATE TABLE contacts (
   id serial PRIMARY KEY,
   name VARCHAR (100),
   phones TEXT []
);

插入数组的时候, 使用特殊的 ARRAY 关键字, 然后用方括号括住数组中的每一个值:

INSERT INTO contacts (name, phones)
VALUES
(
    'John Doe',
    ARRAY [ '(408)-589-5846',
        '(408)-589-5555' ]
);

之后查询, 可以得到如下结果:

id   name      phones
1    John Doe  {(408)-589-5846,(408)-589-5555}

如果不使用ARRAY[], 也可以使用{}大括号:

INSERT INTO contacts (name, phones)
VALUES
   (
      'Lily Bush',
      '{"(408)-589-5841"}'
   ),
   (
      'William Gate',
      '{"(408)-589-5842","(408)-589-58423"}'
   );

要注意的是, 单引号括在大括号外边, 内部是用双引号括住字符串类型.

在查询或者取出数组的时候, 使用索引即可, 比如:

UPDATE contacts
SET phones [ 2 ] = '(408)-589-5843'
WHERE
   ID = 3;

这是更新其中的一个元素, 也可以直接更新整个字段, 这个时候就无须使用索引, 当然其后的类型也要正确.

数组的一个特点是, 可以将其当成一个结果集, 使用ANY, SOME之类进行引用:

SELECT
   name,
   phones
FROM
   contacts
WHERE
   '(408)-589-5555' = ANY (phones);

unnest()函数可以将数组展开成一列, 比如:

SELECT
   name,
   unnest(phones)
FROM
   contacts;

结果中一个name对应的数组有几个值, 就会被展开成几行.

hstore类型

这个需要安装扩展, 被包含在contrib包中, 在VPS上运行:

yum search postgresql12

可以找到PgSQL的相关安装包:

postgresql12-debuginfo.x86_64 : Debug information for package postgresql12
postgresql12.x86_64 : PostgreSQL client programs and libraries
postgresql12-contrib.x86_64 : Contributed source and binaries distributed with PostgreSQL
postgresql12-devel.x86_64 : PostgreSQL development header files and libraries
postgresql12-docs.x86_64 : Extra documentation for PostgreSQL
postgresql12-libs.x86_64 : The shared libraries required for any PostgreSQL clients
postgresql12-llvmjit.x86_64 : Just-in-time compilation support for PostgreSQL
postgresql12-odbc.x86_64 : PostgreSQL ODBC driver
postgresql12-plperl.x86_64 : The Perl procedural language for PostgreSQL
postgresql12-plpython.x86_64 : The Python procedural language for PostgreSQL
postgresql12-plpython3.x86_64 : The Python3 procedural language for PostgreSQL
postgresql12-pltcl.x86_64 : The Tcl procedural language for PostgreSQL
postgresql12-server.x86_64 : The programs needed to create and run a PostgreSQL server
postgresql12-test.x86_64 : The test suite distributed with PostgreSQL

要安装的其实也就是contrib, server和posgresql12.x86_64之前都安装过了, 一个是服务器, 一个是客户端.

yum install postgresql12-contrib.x86_64

安装完之后, 扩展的文件都有了, 但是扩展不是自动启用的, 而是要在每个数据库中执行创建扩展的命令, 要使用hstore就需要执行:

CREATE EXTENSION hstore;

没有报错就说明安装扩展完成, 扩展仅仅对使用了这个命令的数据库生效, 这样可以有效的隔离不同的数据库, 避免因装了太多扩展互相污染.

通过PgAdmin4查看, 可以看到dvdrental中的Extension中增加了hstore.

hstore类似于一个Map类型, 里边存放一个一个的键值对, 创建一个表然后插入数据的方式如下:

CREATE TABLE books (
   id serial primary key,
   title VARCHAR (255),
   attr hstore
);

INSERT INTO books (title, attr)
VALUES
   (
      'PostgreSQL Tutorial',
      '"paperback" => "243",
      "publisher" => "postgresqltutorial.com",
      "language"  => "English",
      "ISBN-13"   => "978-1449370000",
       "weight"    => "11.2 ounces"'
   );

INSERT INTO books (title, attr)
VALUES
(
    'PostgreSQL Cheat Sheet',
    '
"paperback" => "5",
"publisher" => "postgresqltutorial.com",
"language"  => "English",
"ISBN-13"   => "978-1449370001",
"weight"    => "1 ounces"'
);

可以看到, 用一个单引号包围住以=>分割的字符串键值对. 如果直接查询整个字段, 是如下结果:

ISBN-13 => 978-1449370000, weight => "11.2 ounces", paperback => 243, publisher => postgresqltutorial.com, language => English

如果要查询其中的一个键对应的值, 采取如下写法:

SELECT
       title, attr -> 'ISBN-13' AS isbn
FROM
    books;

这会在attr这个hstore对象中, 寻找键为 'ISBN-13' 的值, 然后列名叫做ISBN, 返回结果集.

用在其他地方也一样, 只要记住 attr -> 'key' 代表了一个值.

如果要向hstore中添加一个键, 使用如下语句:

UPDATE books
SET attr = attr || '"freeshipping"=>"yes"' :: hstore;

注意这里使用了字符串连接符, 然后加上了一个键值对, 之后显式指明了数据类型为 hstore.

更新一个键也类似, 只要键存在就是更新:

UPDATE books
SET attr = attr || '"freeshipping"=>"yes"' :: hstore;

删除的语法有些特别:

UPDATE books
SET attr = delete(attr, 'freeshipping');

然后是几个特别的用法:

  1. WHERE attr ? 'publisher';, 表示选出所有attr属性中键名包含publisher的元组
  2. WHERE attr @> '"weight"=>"11.2 ounces"' :: hstore;, 这个表示选出attr中存在这个键值对的元组
  3. WHERE attr ?& ARRAY [ 'language', 'weight' ];, 这个表示选出键同时包含language和weight两个键的元组.

之后是一系列函数:

  1. akeys(),获取一个hstore对象内所有的键
  2. skeys(),将键拆开成序列
  3. avals(),获取一个hstore对象内所有的值
  4. svals(),将值拆开成序列
  5. hstore_to_json(),将hstore转换成JSON, 超级好用的函数
  6. each(),将hstore拆开成键值, 然后按行显示

最后一个函数用法如下:

SELECT
   title,
   (EACH(attr) ).*
FROM
   books;

JSON类型

JSON类型感觉是以后常用的一个类型, 也要仔细来看看, 创建JSON类型的列如下:

CREATE TABLE orders (
   ID serial NOT NULL PRIMARY KEY,
   info json NOT NULL
);

info就是json类型的列, 之后插入数据的时候, 可以插入标准的JSON字符串:

INSERT INTO orders (info)
VALUES
   (
      '{ "customer": "Lily Bush", "items": {"product": "Diaper","qty": 24}}'
   ),
   (
      '{ "customer": "Josh William", "items": {"product": "Toy Car","qty": 1}}'
   ),
   (
      '{ "customer": "Mary Clark", "items": {"product": "Toy Train","qty": 2}}'
   );

如果要取出来的时候, PgSQL提供了两个操作符, ->用于取对象, ->>用于取出字符串. 写法都是 列名 ->/->> 属性名 的写法. 这两个操作符的区别在于是否可以对嵌套的对象继续执行操作:

-- 选出JSON中customer对应的值, 结果是一个JSON对象
SELECT info -> 'customer' as name FROM orders;
-- 选出JSON中items对应的值,结果依然是一个JSON对象
SELECT info -> 'items' as item FROM orders;
-- 验证上一个查询的结果, 可以对对象继续取属性, 返回字符串
SELECT info -> 'items' ->> 'product' as product FROM orders;

-- 这行会报错! 选出JSON中items对应的值, 返回字符串, 不能对字符串进行进一步取值操作
SELECT info ->> 'items' -> 'product' as product FROM orders;

知道了如何从JSON中取值后, 这个表达式就可以用在任意地方了, 比如WHERE或者其他子句中. 取出的值可以转换成其他类型进行比较, 现在还没有学CAST函数, 但是可以先看一个例子:

SELECT
   info ->> 'customer' AS customer,
   info -> 'items' ->> 'product' AS product
FROM
   orders
WHERE
   CAST (
      info -> 'items' ->> 'qty' AS INTEGER
   ) = 2

这个例子选出所有items的数量为2的客户名称和产品名称.

对于一个JSON, 就可以认为是一个元组, 在其上选一个属性, 就和从普通关系中选一列一样, 可以进行对应的操作, 比如聚集函数:

SELECT
    MIN (
            CAST (
                            info -> 'items' ->> 'qty' AS INTEGER
                )
        ),
    MAX (
            CAST (
                            info -> 'items' ->> 'qty' AS INTEGER
                )
        ),
    SUM (
            CAST (
                            info -> 'items' ->> 'qty' AS INTEGER
                )
        ),
    AVG (
            CAST (
                            info -> 'items' ->> 'qty' AS INTEGER
                )
        )

FROM
    orders;

来看一些操作JSON的函数:

  1. json_each(), 可以将一个JSON展开成键值对, 展开的各个结果依然是JSON对象, 仅展开第一层. 需要的话可以再对获取的结果继续调用.
  2. json_each_text(), 上一个函数的字符串版本.
  3. json_object_keys(), 展开并返回所有键, 一般搭配其他函数针对单个JSON对象使用.
  4. json_typeof(), 返回某个键对应的值的类型, 参数必须是一个JSON对象, 不能是字符串. 结果是object就表示是一个JSON对象. 例如
    SELECT json_typeof(info -> 'items' -> 'qty') FROM orders;
    结果是number类型.

官网的JSON函数页面还有更多函数.

自定义类型

自定义类型涉及后边很多函数等相关的内容, 还没有学会. 这里先记录下来.

创建自定义有两种:

  1. 创建一个自定义的单个类型, 使用 CREATE DOMAIN 新类型名称 AS 已知类型 约束; 语句, 很明显创建了一个域对应的类型, 这个实际上看上去像一个别名.
  2. 创建复合类型, 使用CREATE TYPE. 详情见下.

创建的类型, 仅在对应的schema中生效. 如果创建DOMAIN类型, 通过PgADmin4可以看到当前Schema下的Domains中新增了一个内容. 而新创建的TYPE复合类型, 则是在Types对象下.

看两个从网站上抄的例子:

CREATE DOMAIN contact_name AS VARCHAR NOT NULL CHECK (value !~ '\s');

CREATE TABLE mail_list (
    id serial PRIMARY KEY,
    first_name contact_name,
    last_name contact_name,
    email VARCHAR NOT NULL
);

创建了一个域类型和使用这个类型的表, !~操作符是PgSQL中不匹配正则表达式的意思, 也就是不能有空白.

创建一个复合类型和返回此类型结果的函数:

CREATE TYPE film_summary AS (
    film_id INT,
    title VARCHAR,
    release_year YEAR
);

CREATE OR REPLACE FUNCTION get_film_summary (f_id INT)
    RETURNS film_summary AS
$$
SELECT
    film_id,
    title,
    release_year
FROM
    film
WHERE
    film_id = f_id ;
$$
LANGUAGE SQL;

这个函数接受一个INT类型的参数, 然后到film中查找与参数相等的ID, 然后返回film_summary类型, film_summary复合类型中的三个字段由函数中的查询结果填充.

Schema操作

Schema的操作也不外乎增删改, 简明列出如下:

  1. 可以显式指定schema_name.object_name来使用schema中的对象(不仅仅是表).
  2. SELECT current_schema();,获得当前schema名称.
  3. SHOW search_path;,查看当前默认寻找路径.
  4. SET search_path TO schema_name1, schema_name2...;,设置搜索路径, 设置了之后就可以不显式写schema路径
  5. GRANT USAGE/CRUD ON SCHEMA schema_name TO user_name;,授权, schema必须先授予USAGE权限, 才能让其他权限发挥作用
  6. CREATE SCHEMA [IF NOT EXISTS] schema_name;,创建Schema
  7. CREATE SCHEMA [IF NOT EXISTS] AUTHORIZATION user_name;,创建一个与user_name同名的schema, 且这个用户拥有schema权限.
  8. ALTER SCHEMA schema_name RENAME TO new_name;,修改schema名称
  9. ALTER SCHEMA schema_name OWNER TO { new_owner | CURRENT_USER | SESSION_USER};,修改schema所属用户, 用户这里还分SESSION_USER, 先暂时不讨论.
  10. DROP SCHEMA [IF EXISTS] schema_name [ CASCADE | RESTRICT ];,删除schema, 最后一个级联默认是RESTRICT, 即仅在schema为空的情况下删除. CASCADE会删除schema中所有对象和依赖这些对象的内容, 但可以在schema不为空的情况下删除schema.

到目前为止, 就把PgSQL操作的部分基本上都看完了, 还剩下几大部分: 索引, 触发器, 视图, 以及函数, 还有现在已经用不到的存储过程.

这几部分在纯操作层面简单看了一下, 也不会很难. 现在应该是可以进军Hibernate了.

LICENSED UNDER CC BY-NC-SA 4.0
Comment