PostgreSQL最常用数据类型-重点说明自增主键处理

简介

PostgreSQL提供了非常丰富的数据类型,我们平常使用最多的基本就3类:

  1. 数字类型
  2. 字符类型
  3. 时间类型

这篇文章重点介绍这3中类型,因为对于高并发项目还是推荐:尽量使用简单类型,把运算和逻辑放在应用中,而不是数据库上。

如果有离线数据分析处理、做报表等低并发的业务应用,可以多了解一点PostgreSQL的其他类型,作为知识面的拓展,可以在主导设计表的时候有更丰富和灵活的选择,其他类型的详细信息可以参考官方文档。

数字类型

数字类型概览

类型字节长度说明范围
smallint2 字节2字节整数,int2-32768 到 +32767
integer4 字节4字节整数,int,int4-2147483648 到 +2147483647
bigint8 字节8字节大整数,int8-9223372036854775808 到 +9223372036854775807
numeric可变长用户指定的精度,精确小数点前 131072 位;小数点后 16383 位
decimal可变长等价于numeric
real4 字节单精度浮点数,float46 位十进制数字精度
double precision8 字节双精度浮点数,float815 位十进制数字精度
smallserial2 字节自增的小范围整数1 到 32767
serial4 字节自增整数1 到 2147483647
bigserial8 字节自增的大范围整数1 到 9223372036854775807

PostgreSQL没有像MySQL的tinynit这样的1字节整型,如果需要就用smaillint吧,不要用bytea,bytea需要额外的空间,并且要转。

numeric decimal money

postgresql的numeric和decimal等价:numeric可以兼容oracle,decimal可以兼容MySQL。

numeric(precision, scale),numeric(12,4)表示可以存储12位数字,其中小数4位,整数8位(12-4=8)。

存储数据时,整数不能超过8(precision-scale)位,超过8位报错,小数超过4(scale)位会四舍五入。

-- money只保留2位小数
CREATE TABLE price (
    id SERIAL PRIMARY KEY,
    var_val VARCHAR(100),
    price money
);

insert into price(var_val,price) values('123456789.123456789',123456789.123456789);
select * from price;

PostgreSQL money类型

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    var_val VARCHAR(100),
    numeric_val NUMERIC(12, 4),
    decimal_val decimal(12, 4)
);

-- 整数超过8位出错

-- ERROR:  A field with precision 12, scale 4 must round to an absolute value less than 10^8.numeric field overflow 

-- ERROR:  numeric field overflow
-- SQL state: 22003
-- Detail: A field with precision 12, scale 4 must round to an absolute value less than 10^8.
insert into orders(var_val,numeric_val,decimal_val) values('123456789.123456789',123456789.123456789,123456789.123456789);

PostgreSQL numeric整数部分不能超

小数位的处理方式,采用四舍五入截断:

-- 运行插入,实际存储值12345678.1235
insert into orders(var_val,numeric_val,decimal_val) values('12345678.123456789',12345678.123456789,12345678.123456789);

PostgreSQL numeric小数部分自动截断

sequence

聊数据库有一个绕不开的话题,自增主键。

对于PostgreSQL聊自增主键之前,我们得先了解一下sequence。

创建sequence与参数

参数说明
INCREMENT步长,每次增加多少
START初始值
MINVALUE最小值
MAXVALUE最大值
CACHE缓存多少,就是一次生成多个缓存起来,默认值1
NO CYCLE表示不循环

创建sequence,名字通常是:表名_字段名_seq

CREATE SEQUENCE IF NOT EXISTS public.user_id_seq
    INCREMENT 1
    START 1
    MINVALUE 1
    MAXVALUE 2147483647
    CACHE 1
    NO CYCLE;

PostgreSQL sequence

调用sequence

使用nextval来触发sequence,让它自增。

-- 让xxx_zzz_seq这个sequence自增
select nextval('xxx_zzz_seq');

-- 调用之后,current value自动递增
select nextval('user_id_seq') as r;

表字段关联sequence

alter table table_name alter column id set DEFAULT nextval('xxx_zzz_seq')

可以看到,本质上是表字段设置一个默认值,这个默认值是一个函数nextval,它的参数是sequence的名称。

sequence的其他操作

--删除序列
drop sequence xxx_zzz_seq;

--重置序列
alter sequence xxx_zzz_seq restart with 1;

--修改序列(修改序列的最小值和最大值)
alter sequence sequence_name MINVALUE new_min_value MAXVALUE new_max_value;

查看sequence信息

sequence的信息可以在pg_sequences表查看。

select * from pg_sequences;

自增主键

MySQL习惯了为表添加自增主键,PostgreSQL想要同样的效果可以使用serial。

其实MySQL和PostgreSQL自增主键的实现完全不一样。

PostgreSQL的serial更像一个语法糖,实际上是通过sequence实现。

serial语法糖

serial不是SQL标准的语法,所以PostgreSQL推荐还是使用标准语法来实现:

-- 创建序列
CREATE SEQUENCE IF NOT EXISTS user_id_seq
    INCREMENT 1
    START 1
    MINVALUE 1
    MAXVALUE 2147483647
    CACHE 1
    OWNED BY "user".id;
 
-- 创建表格,并为id给定默认值,nextval相当于一个函数
CREATE TABLE user (
    id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('user_id_seq'),
    name VARCHAR(100)
);
 
-- 为序列赋予所有权,这个可以省略,因为创建sequence的时候已经指定了
ALTER SEQUENCE user_id_seq OWNED BY user.id;

注意:sequence只是自增,主键还是要使用PRIMARY KEY关键字。

语法糖不标准,但是简洁啊

手动创建sequence看起来就非常麻烦,所以serial该用还得用,反正最后还是转换了。

不信,可以看:

CREATE TABLE seq_serial (
    id serial NOT NULL PRIMARY KEY,
    name VARCHAR(100),
    age smallint
);

转换之后:

CREATE TABLE IF NOT EXISTS public.seq_serial
(
    id integer NOT NULL DEFAULT nextval('seq_serial_id_seq'::regclass),
    name character varying(100) COLLATE pg_catalog."default",
    age smallint,
    CONSTRAINT seq_serial_pkey PRIMARY KEY (id)
)

PostgreSQ identity default

serial的问题

serial做自增主键有一个问题,就是如果显式的指定了某个自增值之后,肯定会冲突。

插入2个,没有显式指定值,没有问题:

INSERT INTO seq_serial(name, age) VALUES ('allen', 22);
INSERT INTO seq_serial(name, age) VALUES ('bob', 99);

显式指定id值为3,然后再不指定id就会冲突:

INSERT INTO seq_serial(id,name, age) VALUES (3,'alice', 30);

-- ERROR:  Key (id)=(3) already exists.duplicate key value violates unique constraint "seq_serial_pkey" 
-- ERROR:  duplicate key value violates unique constraint "seq_serial_pkey"
-- SQL state: 23505
-- Detail: Key (id)=(3) already exists.
INSERT INTO seq_serial(name, age) VALUES ('albert', 25);

因为指定id值的时候,关联的sequence并没有更新。

所以使用serial的时候,一定不要在显式的指定自增主键的值了。

和serial有相同问题的还有:GENERATED BY DEFAULT AS IDENTITY,本质也是sequence。

CREATE TABLE user_default (
 id bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
 name varchar(30),
 age smallint
);

转换之后:

CREATE TABLE IF NOT EXISTS public.user_default
(
    id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
    name character varying(30) COLLATE pg_catalog."default",
    age smallint,
    CONSTRAINT user_default_pkey PRIMARY KEY (id)
)

这个没有看到nextval,其实还是sequence,只是变成隐式的了:

PostgreSQ sequence隐式转换

更优雅的自增主键

我们前面说了serial的问题,有显式指定值,从而主键冲突的情况,如何避免呢?

可以使用:GENERATED ALWAYS AS IDENTITY

CREATE TABLE user_always (
 id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
 name varchar(30),
 age smallint
);
INSERT INTO user_always(name, age) VALUES ('allen', 22);
INSERT INTO user_always(name, age) VALUES ('bob', 99);

不能显式指定值:

-- ERROR:  Column "id" is an identity column defined as GENERATED ALWAYS.cannot insert a non-DEFAULT value into column "id" 

-- ERROR:  cannot insert a non-DEFAULT value into column "id"
-- SQL state: 428C9
-- Detail: Column "id" is an identity column defined as GENERATED ALWAYS.
-- Hint: Use OVERRIDING SYSTEM VALUE to override.
INSERT INTO user_always(id,name, age) VALUES (3,'alice', 30);

我们可以看到如果显式指定值,就会提示我们,不能插入非默认值。

PostgreSQL identity只能默认主键

如果一定要显式指定,怎么操作,使用DEFAULT参数自动生成,不能使用自定义值。

INSERT INTO userb(id,name, age) VALUES (DEFAULT,'alice', 30);

无符号整型

postgresql没有无符号类型,可以通过check检查,虽然不能再数据范围上改变,但是,可以约束字段。

CREATE TABLE products (
    id int not null primary key generated always as identity,
    name varchar(50),
    price numeric CHECK (price > 0)
);
-- ERROR:  Failing row contains (1, 羽绒服, -1).new row for relation "products" violates check constraint "products_price_check" 

-- ERROR:  new row for relation "products" violates check constraint "products_price_check"
-- SQL state: 23514
-- Detail: Failing row contains (1, 羽绒服, -1).
insert into products(name,price) values('羽绒服',-1);

PostgreSQL check检查

字符类型

字符类型基本说明

类型说明
char(n)固定长度,最多10485760字节(1GB),默认长度1
bpchar(n)等价于char(n)
varchar(n)可变长度长度,最多10485760字节(1GB)
character(n)等价于char(n)
character varying(n)等价于varchar(n)
text无长度限制

推荐使用char、varchar,这个是SQL的标准类型,看起来也更简洁一些。

char是固定长度,使用空格填充,所以也叫bpchar(blank pad char)

对于一些固定长度的字段非常合适,例如md5、手机号码等。

-- true char比较的时候只会比较值,统计长度时、转换为其他类型时候也会删除填充的空格
select 'aa'::char(20)='aa'::char(10) as r1,
length('aa'::char(10)) as r2,
,length('aa'::char(10)::varchar(3)) as r3;

PostgreSQL char数据类型

字符编码与排序

通常我们是不会关系编码和排序相关信息,因为使用默认的设置就可以。

但是,我们最好了解一下,避免碰到问题两眼一抹黑。

在PostgreSQL中有3个概念比较重要:

  1. encoding:这个好理解,就是字符编码
  2. collate:字符串排序规则
  3. ctype:字符分类,定义什么是字符,大小写是否相等

PostgreSQL中可以在创建表的时候和做比较操作的时候指定collate:

CREATE TABLE user_collate (
    a text COLLATE "my_collation",
    b text COLLATE "my_collation"
);
SELECT a < ('what 王德发' COLLATE "my_collation") FROM user;

注意:和MySQL不同,上面collate不是单独collate,而是指collation的name,collation是包含collate和ctype。

可以通过下面语句创建。

CREATE COLLATION collation_name (
    LC_COLLATE = 'zh_CN.UTF-8',
    LC_CTYPE = 'zh_CN.UTF-8'
);

创建完成,可以通过下面的语句查看:

SELECT * from pg_collation;

如果我们没有指定,默认使用的是default,就是创建数据库上指定的值,如:

CREATE DATABASE db_name WITH ENCODING 'UTF8' LC_COLLATE='C' LC_CTYPE='C' TEMPLATE=template0;

-- 修改
update pg_database set datcollate='en_US.utf8',datctype='en_US.utf8' where datname='db_name';
CREATE TABLE IF NOT EXISTS basic_file_info
(
    fid bigint NOT NULL,
    report_type numeric(3,0),
    business_type character varying(255) COLLATE pg_catalog."default",
    area character varying(50) COLLATE pg_catalog."default",
    file_name character varying(300) COLLATE pg_catalog."default",
    CONSTRAINT basic_file_info_pkey PRIMARY KEY (fid)
)

可以通过下面语句来查看:

SELECT * from pg_database;

MySQL的collate就包含了排序和是否忽略大小写相关的内容:

CREATE TABLE user (
  id int NOT NULL,
  name varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8_general_ci COMMENT='user';

测试不同collation对字段排序的影响:

CREATE COLLATION zhutf8 (
    LC_COLLATE = 'zh_CN.UTF-8',
    LC_CTYPE = 'zh_CN.UTF-8'
);

CREATE COLLATION enuft8 (
    LC_COLLATE = 'en_US.UTF-8',
    LC_CTYPE = 'en_US.UTF-8'
);

CREATE TABLE user_collate (
    id serial not null primary key,
    a text COLLATE "zhutf8",
    b text COLLATE "enuft8"
);

insert into user_collate(a,b) values
('a','a'),
('A','A'),
('b','b'),
('B','B'),
('bca','bca'),
('一二三','一二三'),
('一二三b','一二三b'),
('王德发','王德发');
select * from user_collate order by a;
select * from user_collate order by b;

PostgreSQL collate对排序影响

我们可以通过COLLATION来创建一些特殊的比较规则,如:

  1. 让大写字母排前面(通常默认是使用ascii码排序,小写字母会排前面)
  2. 忽略大小写
  3. 忽略数字和标点符号
-- 忽略口音和大小写
CREATE COLLATION ignore_accent_case (provider = icu, deterministic = false, locale = 'und-u-ks-level1');
SELECT 'Å' = 'A' COLLATE ignore_accent_case; -- true
SELECT 'z' = 'Z' COLLATE ignore_accent_case; -- true

-- 大写字母排前面
CREATE COLLATION upper_first (provider = icu, locale = 'und-u-kf-upper');
SELECT 'B' < 'b' COLLATE upper_first; -- true

-- 数字当数字比较、忽略标点符号
CREATE COLLATION num_ignore_punct (provider = icu, deterministic = false, locale = 'und-u-ka-shifted-kn');
SELECT 'id-45' < 'id-123' COLLATE num_ignore_punct; -- true
SELECT 'w;x*y-z' = 'wxyz' COLLATE num_ignore_punct; -- true
SELECT 'id-45' < 'id-123' COLLATE num_ignore_punct as r1,
'id-45' < 'id-123' as r2,
'45' < '123' COLLATE num_ignore_punct as r3,
'45' < '123' as r4;

PostgreSQL 定制排序规则

前面几个例子,可以参考官方文档:ICU-CUSTOM-COLLATIONS

关于字符集、比较和本地化的更多内容,也可以参考:

collation

字符集支持

本地化-locale涉及日期、时间、数字、货币等

时间日期时间戳

基本类型说明

PostgreSQL有非常丰富的时间相关类型。

类型说明
timestamp(n) without time zone不带时区的时间戳,8字节
timestamp(n) with time zone带时区的时间戳,8字节
date日期,4字节
time(n) without time zone不带时区的时间,8字节
time(n) with time zone带时区的时间,12字节
interval时间间隔,16字节

上面除了date的精度是1天,其他类型的最大精度都是微秒(1/1000000秒)

interval

interval支持ISO8601格式,P后面表示日期,T后面表示日期。

interval类型可以使用Java的Duration来存储。

select '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::interval as r1,
'P1Y2M3DT4H5M6S'::interval as r2;

interval最常用来做计算而不是存储,例如:

select now(),now() - interval '1 day' as subday,
now() + interval '1 week' as addweek,now() - interval '1 month' as submonth,now()+interval '1 year' as addyear;
 
--加1年1月1天1时1分1秒
select now() + interval '1 year 1 month 1 day 1 hour 1 min 1 sec';

关于时间的计算和函数,可以参数:PostgreSQL常用时间函数与时间计算提取示例说明

时区

查看设置时区:

show timezone;

SET TIMEZONE ='Australia/Sydney';

时区转换:

SELECT localtimestamp as lt,current_timestamp as ct,
timezone('Australia/Sydney',current_timestamp) as sydney_time,
timezone('America/New_York',current_timestamp) as newyork_time;

timestamptz

timestamp(n) with time zone可以简写为timestamptz,这个类型可以通过下面的语句查询:

SELECT typname, typlen FROM pg_type WHERE typname like 'timestamp%';
CREATE TABLE ts_tz_n (
    id serial not null primary key,
    ts TIMESTAMPTZ default current_timestamp, 
    ts0 TIMESTAMPTZ(0) default current_timestamp,
    ts1 TIMESTAMPTZ(1) default current_timestamp,
    ts2 TIMESTAMPTZ(2) default current_timestamp,
    ts3 TIMESTAMPTZ(3) default current_timestamp,
    ts4 TIMESTAMPTZ(4) default current_timestamp,
    ts5 TIMESTAMPTZ(5) default current_timestamp,
    ts6 TIMESTAMPTZ(6) default current_timestamp,
    ts7 TIMESTAMPTZ(7) default current_timestamp
);

PostgreSQL 时间戳精度

timestamptz最大精度是6位,到微秒,所以超过6位会提示如下的警告,并自动修正为6位。

WARNING: TIMESTAMP(7) WITH TIME ZONE precision reduced to maximum allowed, 6

如果没有指定精度,那么默认也是6位精度,到微秒。

timestamptz可以使用Java的OffsetDateTime来存储。

timestamp

timestamp和timestamptz的区别在于:timestamp不带时区,可以看做是MySQL的datetime类型。

CREATE TABLE ts_n (
    id serial not null primary key,
    ts TIMESTAMP default current_timestamp, 
    ts0 TIMESTAMP(0) default current_timestamp,
    ts1 TIMESTAMP(1) default current_timestamp,
    ts2 TIMESTAMP(2) default current_timestamp,
    ts3 TIMESTAMP(3) default current_timestamp,
    ts4 TIMESTAMP(4) default current_timestamp,
    ts5 TIMESTAMP(5) default current_timestamp,
    ts6 TIMESTAMP(6) default current_timestamp,
    ts7 TIMESTAMP(7) default current_timestamp
);

timestamp可以使用Java的Timestamp来存储。

timestamp和timestamptz怎么选?

没有国际化的需求无脑选timestamp就对了。

有国际化的也可以用timestamp,把转换操作放在应用层就可以。

时间戳的疑问

有朋友可能会有疑问,我平时看到的时间戳就是1732523307、或者1732523307672这样的一个数字,他为什么会有时区呢?

我们先来看一下时间戳的定义:

时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数

所以,我们经常看到的类似于1732523307这样的10位数字,的确是时间戳,这个是unix的时间戳。

实际在数据库和编程语言中,还有一个epoch概念,toEpochSecond就是10位秒时间戳,toEpochMilli就是13位毫秒时间戳。

在PostgreSQL中我们可以通过extract来获取:

-- 1732523307.672621
SELECT extract(epoch from now()) as r1;

我们可以看到PostgreSQL非常严谨,时间戳定义为秒,它的精度是微秒,它就用小数来表示。

根据时间戳的定义,我们知道不同时区同一时间的时间戳相同,我再此刻生成一个时间戳,不管是在北京生成的,还是在纽约生成的,都是相同的数字。

那为什么timestamptz还有时区,这不是很奇怪么?

timestamptz只是表示时间戳,它有自己的结构,转换为epoch还是等于10位的整数时间戳。

带时区,主要是为了转换显示为本地时间,因为我们数据库查看的时候,通常不会查看一个整数,而是本地化之后的时间。

这样如果有国际化的需求,就能一眼看出其中的时间差别。

timestamp与整型时间戳互转

最高精度到微秒:

select extract(epoch from now()) as r,
floor(extract(epoch from now())) as r1,
floor(extract(epoch from now())*1000) as r2,
extract(epoch from now())*1000000 as r3;

PostgreSQL 时间戳转unix时间戳

to_timestamp接收的参数是10位秒,获取到的是一个带时区的时间戳timestamptz:

select to_timestamp(1732610870057034 / 1000000) as r1,
to_timestamp(1732610870057 / 1000) as r2,
to_timestamp(1732610870) as r3;

unix时间戳转PostgreSQL时间戳

有朋友肯定在想,精确到微秒,那用微秒级时间戳来做自增主键,那不得能支持10w并发(秒后6位10万级)。

理论上是这样,但实际上可能有点差距,可以看一下下面的SQL:

do $$
declare
	i int;
begin
	for i in 1..100000000 loop
		if i % 10000000 = 0 then
			raise notice '%',extract(epoch from current_timestamp);
		end if;
	end loop;
end;
$$

执行时间肯定超过微秒,但输出时间并没有变化:

PostgreSQL内部调用时间可能有缓存
不太熟悉上面存储过程的朋友可以看一下:PostgreSQL存储过程

bit

类型说明
bit(n)固定长度,默认长度1
varbit(n)可变长度位
bit varying(n)可变长度位,等价于varbit(n)

注意:bit的n是精确值,就是设置n为3,那么必须插入3位值,不能少也不能多,不像char少了会用空格填充。

CREATE TABLE table_bit(col_bit bit(3), col_varbit varbit(5));

-- B表示这是一个二进制串
INSERT INTO table_bit VALUES (B'101', B'00');

-- ERROR:  bit string length 2 does not match type bit(3) 
-- SQL state: 22026
INSERT INTO table_bit VALUES (B'10', B'101');

-- 可以强转为3位,在尾部填充0
INSERT INTO table_bit VALUES (B'10'::bit(3), B'101');

bit是2进制位,一个比较典型的应用场景是开关。

比如,我有一条数据的校验可能有20个校验类型,每个校验类型都有1个开关,怎么设计呢?

当然,可以设置20个字段来分别表示,但是,不够优雅。

我们可以用一个bit(20)的字段来表示,1表示开启,0表示关闭,这样可以通过位运算,可以非常方便知道哪些校验开启了,哪些校验关闭了。

CREATE TABLE check_bit(id serial not null primary key,switch bit(20));

INSERT INTO check_bit(switch) VALUES (B'00000000000000000101');
INSERT INTO check_bit(switch) VALUES (B'00000000000000000001');
INSERT INTO check_bit(switch) VALUES (B'00000000000000000100');

这种设计也可以查询开启指定校验的数据有哪些。

通过位运算的方式:

例如,最后1位表示数据正确性校验,现在想要知道哪些数据开启了数据正确性校验,就可以通过下面的方式查询:

select * from check_bit where (switch & B'00000000000000000001') = B'00000000000000000001';

PostgreSQL 位运算

这种查询方式,会在数据库上做大量的运算,所以,这种查询最好确保有其他的条件能先过滤掉大部分数据。

bytea(二进制数据)

输入输出

一提到二进制数据,很多朋友就想到文件、图片之类的资源。

很多设计也这么做,例如工作流引擎Camunda,就把流程图资源保存到数据库。

通常不建议把文件之类的资源放在数据库中,Camunda这样设计是因为它是框架,为了管理避免数据丢失了。

bytea按字节存储,PostgreSQL接受2中输入方式:

  1. hex:16进制
  2. escape:转义字符

输出方式也支持上面2种方式,可以配置:

-- 输出为16进制
SET bytea_output = 'hex'-- 输出为转义字符
SET bytea_output = 'escape';

escape转义是\八进制(进制对应关系可以参考后面的ASCII表):

PostgreSQL bytea输出模式

Java对应类型处理

create table bytea_table(
    id serial not null primary key,
    bin_data bytea
);

插入:

@Test
void insert() throws SQLException {
    String url = "jdbc:postgresql://127.0.0.1:5432/postgres";
    try (Connection connection = DriverManager.getConnection(url, "root", "")) {
        assert connection != null;
        String sql = "INSERT INTO bytea_table(bin_data) VALUES (?)";

        PreparedStatement ps = connection.prepareStatement(sql);
        byte[] data = "hello world".getBytes(StandardCharsets.UTF_8);
        ps.setBytes(1, data);
        ps.executeUpdate();
        ps.close();
    }
}

查看:

@Test
void query() throws SQLException {
    String url = "jdbc:postgresql://127.0.0.1:5432/postgres";
    try (Connection connection = DriverManager.getConnection(url, "root", "")) {
        assert connection != null;
        String sql = "select bin_data from bytea_table";
        PreparedStatement ps = connection.prepareStatement(sql);
        ResultSet rs = ps.executeQuery();
        while (rs.next()){
            System.out.println(new String(rs.getBytes(1)));
        }
    }
}

PostgreSQL bytea输出示例

网络地址

PostgreSQL提供了处理ip和mac地址的类型,可以非常方便计算。

可能我们很少用这些ip来参与计算,但是可以作为小工具来用,还是非常方便。

例如,计算子网掩码、网络地址等。

类型说明
cidr7字节(ipv4)、19字节(ipv6)
inet7字节(ipv4)、19字节(ipv6)
macaddr6字节,MAC地址
macaddr88字节,MAC地址
CREATE TABLE ip_table (
    id serial not null PRIMARY KEY,
    ip_net inet,
    ip_cidr cidr
);

INSERT INTO ip_table (ip_net,ip_cidr) VALUES ('192.168.0.1','192.168.0.1');
INSERT INTO ip_table (ip_net,ip_cidr) VALUES ('192.168.0.0/24','192.168.0.0/24');
INSERT INTO ip_table (ip_cidr) VALUES ('128.1.2');
INSERT INTO ip_table (ip_cidr) VALUES ('128.1');
INSERT INTO ip_table (ip_cidr) VALUES ('128');
-- inet类型必须是完整的ip或者网络
-- INSERT INTO ip_table (ip_net) VALUES ('128');

SELECT * FROM ip_table;
select broadcast('192.168.1.5/24') as 广播地址,
host('192.168.1.5/24') as 主机地址,
hostmask('192.168.23.20/30') as 主机掩码,
masklen('192.168.1.5/24') as 掩码长度,
netmask('192.168.1.5/24') as 子网掩码,
network('192.168.1.5/24') as 网络地址,
family(inet '::1') ip4或ip6,
inet_same_family(inet '192.168.1.5/24', inet '::1') as 是否是相同网络族,
inet_merge('192.168.1.5/24', '192.168.2.5/24') as 包含两个给定网络的最小网络;

PostgreSQL 网络地址函数

-- 192.168.1.7这个ip是否属于192.168.1/24这个网络
select inet '192.168.1.7' << inet '192.168.1/24' as r;

-- 192.168.1/24网络是否包含或者等于192.168.1/25网络
select inet '192.168.1/25' <<= inet '192.168.1/24' as r;
select ~ inet '192.168.1.6' as op_异或,
inet '192.168.1.6' & inet '0.0.0.255' as op_与,
inet '192.168.1.6' | inet '0.0.0.255' as op_或;

很多时候我们查看的时候只知道ip和网络掩码,并不知道网络长度,怎么计算网络地址呢?

network函数显然不行了,我们可以自己手动计算(ip & 子网掩码):

select network('10.185.33.8') as r, 
inet '255.255.252.0' & inet '10.185.33.8' as r2;

更多可以参考官方文档:网络地址与函数

枚举

枚举有用吗?

当然有用,例如一个订单状态,可能好多地方都会使用,我们如果用枚举约束,就比较出现未知类型。

但是,枚举最大的问题在于,它是我们自定义的类型,很多框架是不支持,都需要我们自己单独处理。

所以,需要考虑迁移维护和兼容问题。

基本操作(创建、修改、添加)

-- 创建枚举
CREATE TYPE cup_enum as ENUM ('中杯', '大杯', '特大杯', '超大杯');

-- 重命名枚举
ALTER TYPE cup_enum RENAME TO cup_enum_all;

-- 添加枚举值
ALTER TYPE cup_enum ADD VALUE '超超大杯';

-- 指定添加位置
ALTER TYPE cup_enum ADD VALUE '超超大杯' BEFORE '超大杯';
ALTER TYPE cup_enum ADD VALUE '超超大杯' AFTER '超大杯';

-- 删除枚举
DROP TYPE cup_enum;

查看枚举(第1个、最后1个、枚举范围)

-- 查看第1个最后1个
select enum_first(null::cup_enum),
enum_first('超大杯'::cup_enum),
enum_last(null::cup_enum),
enum_last('大杯'::cup_enum);

-- 查看枚举值
SELECT enum_range(null::cup_enum) as r1,
enum_range(null::cup_enum) as r2,
enum_range('大杯'::cup_enum,'超大杯'::cup_enum) as r3;

SELECT UNNEST(enum_range(null::cup_enum)) as r1;

PostgreSQL 查看枚举

插入

CREATE TABLE enum_table (
    id serial not null primary key,
    cup cup_enum
);
INSERT INTO enum_table(cup) VALUES ('大杯');
INSERT INTO enum_table(cup) VALUES ('特大杯');
INSERT INTO enum_table(cup) VALUES ('超大杯');

不能插入枚举中不存在的值:

-- ERROR:  invalid input value for enum cup_enum: "无敌大杯"
-- LINE 1: INSERT INTO enum_table(cup) VALUES ('无敌大杯');
-- SQL state: 22P02
-- Character: 37
INSERT INTO enum_table(cup) VALUES ('无敌大杯');

查询

select min(cup),max(cup) from enum_table;

SELECT * FROM enum_table WHERE cup = '特大杯';

PostgreSQL 枚举查询

查看枚举信息

select * from pg_enum;

-- Psql中可以使用
\dT+ enum_name

枚举Java类型处理

在pgAdmin、psql中我们插入枚举值用字符串没有问题。

但是在jdbc中我们插入枚举值,直接用string有问题。

@Test
void insert() throws SQLException {
    String url = "jdbc:postgresql://127.0.0.1:5432/postgres";
    Connection connection = DriverManager.getConnection(url,"postgres","");
    assert connection != null;
    String sql = "INSERT INTO enum_table(cup) VALUES (?)";

    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1,"特大杯");
    ps.executeUpdate();
    ps.close();
    connection.close();
}

大概会报下面的这个错误:

org.postgresql.util.PSQLException: ERROR: column "cup" is of type cup_enum but expression is of type character varying
  建议:You will need to rewrite or cast the expression.
  位置:37

我们需要使用setObject,类型使用java.sql.Types.OTHER

@Test
void insert() throws SQLException {
    String url = "jdbc:postgresql://127.0.0.1:5432/postgres";
    Connection connection = DriverManager.getConnection(url,"postgres","");
    assert connection != null;
    String sql = "INSERT INTO enum_table(cup) VALUES (?)";

    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setObject(1,"特大杯", Types.OTHER);
    ps.executeUpdate();
    ps.close();
    connection.close();
}

PostgreSQL的一些表

表名说明
pg_tables可以查看数据库中的表
pg_type可以查看PostgreSQL的数据类型
pg_database可以查看PostgreSQL的所有数据库
pg_namespace可以查看PostgreSQL的所有命名空间,命名空间是对表、索引函数等的抽象
pg_tablespace可以查看PostgreSQL的所有表空间,表空间是对磁盘数据的抽象,一个表空间可以有多个磁盘位置,\db
information_schema.tables类似于MySQL的information_schema.tables
information_schema.columns类似于MySQL的information_schema.columns
pg_enum枚举信息表
select * from pg_type;

select * from pg_tables;

表空间再多说2句:

PostgreSQL默认有2个表空间:

  1. pg_default:创建数据库默认表空间,对应的目录PGDATA/base
  2. pg_global:共享系统表如pg_database、pg_tables等,对应的目录PGDATA/global
SELECT d.oid,d.datname,t.oid,t.spcname from pg_database d join pg_tablespace t on d.dattablespace=t.oid;

通常我们使用默认表空间就可以了,如果有需要可以下吗方式创建

-- 创建表空间
CREATE TABLESPACE appdatas LOCATION 'F:\PostgreSQL17\data\appdatas';
-- 给用户赋权
GRANT CREATE ON TABLESPACE appdatas TO user_name;

附录-ASCII

Bin (二进制)Oct (八进制)Dec (十进制)Hex (十六进制)缩写/字符解释
0000 00000000x00NUL(null)空字符
0000 00010110x01SOH(start of headline)标题开始
0000 00100220x02STX (start of text)正文开始
0000 00110330x03ETX (end of text)正文结束
0000 01000440x04EOT (end of transmission)传输结束
0000 01010550x05ENQ (enquiry)请求
0000 01100660x06ACK (acknowledge)收到通知
0000 01110770x07BEL (bell)响铃
0000 100001080x08BS (backspace)退格
0000 100101190x09HT (horizontal tab)水平制表符
0000 1010012100x0ALF (NL line feed, new line)换行键
0000 1011013110x0BVT (vertical tab)垂直制表符
0000 1100014120x0CFF (NP form feed, new page)换页键
0000 1101015130x0DCR (carriage return)回车键
0000 1110016140x0ESO (shift out)不用切换
0000 1111017150x0FSI (shift in)启用切换
0001 0000020160x10DLE (data link escape)数据链路转义
0001 0001021170x11DC1 (device control 1)设备控制1
0001 0010022180x12DC2 (device control 2)设备控制2
0001 0011023190x13DC3 (device control 3)设备控制3
0001 0100024200x14DC4 (device control 4)设备控制4
0001 0101025210x15NAK (negative acknowledge)拒绝接收
0001 0110026220x16SYN (synchronous idle)同步空闲
0001 0111027230x17ETB (end of trans. block)结束传输块
0001 1000030240x18CAN (cancel)取消
0001 1001031250x19EM (end of medium)媒介结束
0001 1010032260x1ASUB (substitute)代替
0001 1011033270x1BESC (escape)换码(溢出)
0001 1100034280x1CFS (file separator)文件分隔符
0001 1101035290x1DGS (group separator)分组符
0001 1110036300x1ERS (record separator)记录分隔符
0001 1111037310x1FUS (unit separator)单元分隔符
0010 0000040320x20(space)空格
0010 0001041330x21!叹号
0010 0010042340x22"双引号
0010 0011043350x23#井号
0010 0100044360x24$美元符
0010 0101045370x25%百分号
0010 0110046380x26&和号
0010 0111047390x27单引号
0010 1000050400x28(开括号
0010 1001051410x29)闭括号
0010 1010052420x2A*星号
0010 1011053430x2B+加号
0010 1100054440x2C,逗号
0010 1101055450x2D-减号/破折号
0010 1110056460x2E.句号
0010 1111057470x2F/斜杠
0011 0000060480x300字符0
0011 0001061490x311字符1
0011 0010062500x322字符2
0011 0011063510x333字符3
0011 0100064520x344字符4
0011 0101065530x355字符5
0011 0110066540x366字符6
0011 0111067550x377字符7
0011 1000070560x388字符8
0011 1001071570x399字符9
0011 1010072580x3A:冒号
0011 1011073590x3B;分号
0011 1100074600x3C<小于
0011 1101075610x3D=等号
0011 1110076620x3E>大于
0011 1111077630x3F?问号
0100 00000100640x40@电子邮件符号
0100 00010101650x41A大写字母A
0100 00100102660x42B大写字母B
0100 00110103670x43C大写字母C
0100 01000104680x44D大写字母D
0100 01010105690x45E大写字母E
0100 01100106700x46F大写字母F
0100 01110107710x47G大写字母G
0100 10000110720x48H大写字母H
0100 10010111730x49I大写字母I
010010100112740x4AJ大写字母J
0100 10110113750x4BK大写字母K
0100 11000114760x4CL大写字母L
0100 11010115770x4DM大写字母M
0100 11100116780x4EN大写字母N
0100 11110117790x4FO大写字母O
0101 00000120800x50P大写字母P
0101 00010121810x51Q大写字母Q
0101 00100122820x52R大写字母R
0101 00110123830x53S大写字母S
0101 01000124840x54T大写字母T
0101 01010125850x55U大写字母U
0101 01100126860x56V大写字母V
0101 01110127870x57W大写字母W
0101 10000130880x58X大写字母X
0101 10010131890x59Y大写字母Y
0101 10100132900x5AZ大写字母Z
0101 10110133910x5B[开方括号
0101 11000134920x5C|反斜杠
0101 11010135930x5D]闭方括号
0101 11100136940x5E^脱字符
0101 11110137950x5F_下划线
0110 00000140960x60`开单引号
0110 00010141970x61a小写字母a
0110 00100142980x62b小写字母b
0110 00110143990x63c小写字母c
0110 010001441000x64d小写字母d
0110 010101451010x65e小写字母e
0110 011001461020x66f小写字母f
0110 011101471030x67g小写字母g
0110 100001501040x68h小写字母h
0110 100101511050x69i小写字母i
0110 101001521060x6Aj小写字母j
0110 101101531070x6Bk小写字母k
0110 110001541080x6Cl小写字母l
0110 110101551090x6Dm小写字母m
0110 111001561100x6En小写字母n
0110 111101571110x6Fo小写字母o
0111 000001601120x70p小写字母p
0111 000101611130x71q小写字母q
0111 001001621140x72r小写字母r
0111 001101631150x73s小写字母s
0111 010001641160x74t小写字母t
0111 010101651170x75u小写字母u
0111 011001661180x76v小写字母v
0111 011101671190x77w小写字母w
0111 100001701200x78x小写字母x
0111 100101711210x79y小写字母y
0111 101001721220x7Az小写字母z
0111 101101731230x7B{开花括号
0111 110001741240x7C
0111 110101751250x7D}闭花括号
0111 111001761260x7E~波浪号
0111 111101771270x7FDEL (delete)删除

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927936.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

做异端中的异端 -- Emacs裸奔之路4: 你不需要IDE

确切地说&#xff0c;你不需要在IDE里面编写或者阅读代码。 IDE用于Render资源文件比较合适&#xff0c;但处理文本&#xff0c;并不划算。 这的文本文件&#xff0c;包括源代码&#xff0c;配置文件&#xff0c;文档等非二进制文件。 先说说IDE带的便利: 函数或者变量的自动…

ospf协议(动态路由协议)

ospf基本概念 定义 OSPF 是典型的链路状态路由协议&#xff0c;是目前业内使用非常广泛的 IGP 协议之一。 目前针对 IPv4 协议使用的是 OSPF Version 2 &#xff08; RFC2328 &#xff09;&#xff1b;针对 IPv6 协议使用 OSPF Version 3 &#xff08; RFC2740 &#xff09;。…

【热门主题】000072 分布式数据库:开启数据管理新纪元

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

Python 3 教程第33篇(MySQL - mysql-connector 驱动)

Python MySQL - mysql-connector 驱动 MySQL 是最流行的关系型数据库管理系统&#xff0c;如果你不熟悉 MySQL&#xff0c;可以阅读我们的 MySQL 教程。 本章节我们为大家介绍使用 mysql-connector 来连接使用 MySQL&#xff0c; mysql-connector 是 MySQL 官方提供的驱动器。…

ENSP IPV6-over-IPV4

IPv6是网络层协议的第二代标准协议&#xff0c;一个IPv6地址同样可以分为网络前缀和主机ID两个部分。 可以将IPV4的网络看成IPV6的承载网&#xff0c;只有IPv4网络是连通的&#xff0c;则IPv6网络才有可能连通。所以配置的时候需要先配置IPv4网络的路由功能&#xff0c;再配IP…

《数据挖掘:概念、模型、方法与算法(第三版)》

嘿&#xff0c;数据挖掘的小伙伴们&#xff01;今天我要给你们介绍一本超级实用的书——《数据挖掘&#xff1a;概念、模型、方法与算法》第三版。这本书是数据挖掘领域的经典之作&#xff0c;由该领域的知名专家编写&#xff0c;系统性地介绍了在高维数据空间中分析和提取大量…

53 基于单片机的8路抢答器加记分

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 首先有三个按键 分别为开始 暂停 复位&#xff0c;然后八个选手按键&#xff0c;开机显示四条杠&#xff0c;然后按一号选手按键&#xff0c;数码管显示&#xff13;&#xff10;&#xff0c;这…

从零开始写游戏之斗地主-网络通信

在确定了数据结构后&#xff0c;原本是打算直接开始写斗地主的游戏运行逻辑的。但是突然想到我本地写出来之后&#xff0c;也测试不了啊&#xff0c;所以还是先写通信模块了。 基本框架 在Java语言中搞网络通信&#xff0c;那么就得请出Netty这个老演员了。 主要分为两个端&…

Logistic Regression(逻辑回归)、Maximum Likelihood Estimatio(最大似然估计)

Logistic Regression&#xff08;逻辑回归&#xff09;、Maximum Likelihood Estimatio&#xff08;最大似然估计&#xff09; 逻辑回归&#xff08;Logistic Regression&#xff0c;LR&#xff09;逻辑回归的基本思想逻辑回归模型逻辑回归的目标最大似然估计优化方法 逻辑回归…

数据类型.

数据类型分类 数值类型 tinyint类型 以tinyint为例所有数值类型默认都是有符号的&#xff0c;无符号的需要在后面加unsignedtinyint的范围在-128~127之间无符号的范围在0~255之间(类比char) create database test_db; use test_db;建表时一定要跟着写上属性 mysql> creat…

IDEA使用HotSwapHelper进行热部署

目录 前言JDK1.8特殊准备DECVM安装插件安装与配置参考文档相关下载 前言 碰到了一个项目&#xff0c;用jrebel启动项目时一直报错&#xff0c;不用jrebel时又没问题&#xff0c;找不到原因&#xff0c;又不想放弃热部署功能 因此思考能否通过其他方式进行热部署&#xff0c;找…

机器学习算法(六)---逻辑回归

常见的十大机器学习算法&#xff1a; 机器学习算法&#xff08;一&#xff09;—决策树 机器学习算法&#xff08;二&#xff09;—支持向量机SVM 机器学习算法&#xff08;三&#xff09;—K近邻 机器学习算法&#xff08;四&#xff09;—集成算法 机器学习算法&#xff08;五…

【Electron学习笔记(四)】进程通信(IPC)

进程通信&#xff08;IPC&#xff09; 进程通信&#xff08;IPC&#xff09;前言正文1、渲染进程→主进程&#xff08;单向&#xff09;2、渲染进程⇌主进程&#xff08;双向&#xff09;3、主进程→渲染进程 进程通信&#xff08;IPC&#xff09; 前言 在Electron框架中&…

GateWay使用手册

好的&#xff0c;下面是优化后的版本。为了提高可读性和规范性&#xff0c;我对内容进行了结构化、简化了部分代码&#xff0c;同时增加了注释说明&#xff0c;便于理解。 1. 引入依赖 在 pom.xml 中添加以下依赖&#xff1a; <dependencies><!-- Spring Cloud Gate…

【Go 基础】channel

Go 基础 channel 什么是channel&#xff0c;为什么它可以做到线程安全 Go 的设计思想就是&#xff1a;不要通过共享内存来通信&#xff0c;而是通过通信来共享内存。 前者就是传统的加锁&#xff0c;后者就是 channel。也即&#xff0c;channel 的主要目的就是在多任务间传递…

C# 解决【托管调试助手 “ContextSwitchDeadlock“:……】问题

文章目录 一、遇到问题二、解决办法 一、遇到问题 托管调试助手 “ContextSwitchDeadlock”:“CLR 无法从 COM 上下文 0x56e81e70 转换为 COM 上下文 0x56e81d48&#xff0c;这种状态已持续 60 秒。拥有目标上下文/单元的线程很有可能执行的是非泵式等待或者在不发送 Windows …

Spring AI 框架介绍

Spring AI是一个面向人工智能工程的应用框架。它的目标是将Spring生态系统的设计原则&#xff08;如可移植性和模块化设计&#xff09;应用于AI领域&#xff0c;并推广使用pojo作为AI领域应用的构建模块。 概述 Spring AI 现在(2024/12)已经支持语言&#xff0c;图像&#xf…

使用Grafana K6来测测你的系统负载能力

背景 近期我们有个号称会有很高很高并发的系统要上线&#xff0c;为了测试一下自己开发的系统的负载能力&#xff0c;准备了点海克斯科技&#xff0c;来看看抗不抗的住。 之前笔者写过用Apache JMeter进行压力测试的文章&#xff08;传送门&#x1f449;&#xff1a;https://…

32 从前序与中序遍历序列构造二叉树

32 从前序与中序遍历序列构造二叉树 32.1 从前序与中序遍历序列构造二叉树解决方案 class Solution { public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {return buildTreeHelper(preorder, inorder, 0, 0, inorder.size() - 1)…

【C++boost::asio网络编程】有关异步读写api的笔记

异步读写api 异步写操作async_write_someasync_send 异步读操作async_read_someasync_receive 定义一个Session类&#xff0c;主要是为了服务端专门为客户端服务创建的管理类 class Session { public:Session(std::shared_ptr<asio::ip::tcp::socket> socket);void Conn…