目录
表
#1楼
#2楼
#3楼
#4楼
#5楼
#6楼
#7楼
#8楼
#9楼
#10楼
#11楼
#12楼
#13楼
#14楼
#15楼
#16楼
#17楼
#18楼
#19楼
函数
1.数学操作符
2.位串操作符
3.数学函数
4.字符串函数
5.数据类型格式化函数
6.日期/时间格式化
7.数值格式化
8.日期/时间操作符
9.日期/时间函数
10.EXTRACT,date_part函数支持的field
11.数组操作符和函数
12.系统信息函数
13.系统管理函数
14.系列号操作函数
用户与权限
索引和约束
为什么唯一性验证还不够 (Why uniqueness validation is not enough)
快速查看为一个或多个列设置唯一索引 (A quick look at setting a unique index for one or more column)
上面的多列迁移问题 (The problem with the multiple column migration above)
常见修复 (The common fix)
正确的解决方法 (The proper fix)
代码说明 (Code explanation)
触发器
数据库扩展
Foreign Data Wrappers是PostgreSQL取得外部数据比较方便的功能扩展。
Foreign Data Wrappers
SQL Databases Wrappers
oracle_fdw
mysql_fdw
tds_fdw
odbc_fdw
NoSQL Databases Wrappers
couchdb_fdw
redis_fdw
File Wrappers
file_fdw
file_text_array_fdw
file_fixed_length_record_fdw
Others
twitter_fdw
ldap_fdw
PGStrom
s3_fdw
www_fdw
Multicorn Foreign Data Wrappers
SQL Database Wrappers
multicorn.sqlalchemyfdw
File Wrappers
muticorn.fsfdw
multicorn.csvfdw
Others
multicorn.rssfdw
如何在PostgreSQL中执行等效于Oracle DESCRIBE TABLE
的命令(使用psql命令)?
DESCRIBE TABLE
的psql等效项是\\d table
。
有关更多详细信息,请参见PostgreSQL手册的psql部分。
您可以使用psql斜杠命令执行此操作:
\d myTable describe table
它也适用于其他对象:
\d myView describe view
\d myIndex describe index
\d mySequence describe sequence
资料来源: faqs.org
试试看(在psql
命令行工具中):
\d+ tablename
有关更多信息,请参见手册 。
除了PostgreSQL方式(\\ d'something'或\\ dt'table'或\\ ds'sequence'等)
SQL标准的方式,如图所示在这里 :
select column_name, data_type, character_maximum_length
from INFORMATION_SCHEMA.COLUMNS where table_name = '';
许多数据库引擎都支持它。
如果要从查询而不是psql获取它,则可以查询目录架构。 这是一个执行此操作的复杂查询:
SELECT
f.attnum AS number,
f.attname AS name,
f.attnum,
f.attnotnull AS notnull,
pg_catalog.format_type(f.atttypid,f.atttypmod) AS type,
CASE
WHEN p.contype = 'p' THEN 't'
ELSE 'f'
END AS primarykey,
CASE
WHEN p.contype = 'u' THEN 't'
ELSE 'f'
END AS uniquekey,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreignkey,
CASE
WHEN p.contype = 'f' THEN p.confkey
END AS foreignkey_fieldnum,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreignkey,
CASE
WHEN p.contype = 'f' THEN p.conkey
END AS foreignkey_connnum,
CASE
WHEN f.atthasdef = 't' THEN d.adsrc
END AS default
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
WHERE c.relkind = 'r'::char
AND n.nspname = '%s' -- Replace with Schema name
AND c.relname = '%s' -- Replace with table name
AND f.attnum > 0 ORDER BY number
;
它非常复杂,但是它确实向您展示了PostgreSQL系统目录的功能和灵活性,并且应该使您逐渐掌握pg_catalog ;-)。 确保更改查询中的%s。 第一个是模式,第二个是表名。
您可以使用星号 \\d *search pattern *
来查找与您感兴趣的搜索模式匹配的表。
您可以使用:
SELECT attname
FROM pg_attribute,pg_class
WHERE attrelid=pg_class.oid
AND relname='TableName'
AND attstattarget <>0;
除了已经找到的命令行\\d+
,您还可以使用info -schema通过info_schema.columns查找信息。
SELECT *
FROM info_schema.columns
WHERE table_schema = 'your_schema'
AND table_name = 'your_table'
使用以下SQL语句
SELECT DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'tbl_name'
AND COLUMN_NAME = 'col_name'
如果替换tbl_name和col_name,它将显示您要查找的特定列的数据类型。
您也可以使用以下查询进行检查
Select * from schema_name.table_name limit 0;
*使用PG admin3
Use this command
\d table name
like
\d queuerecords
Table "public.queuerecords"
Column | Type | Modifiers
-----------+-----------------------------+-----------
id | uuid | not null
endtime | timestamp without time zone |
payload | text |
queueid | text |
starttime | timestamp without time zone |
status | text |
描述表的最佳方法,例如列,类型,列的修饰符等。
\d+ tablename or \d tablename
/ dt是逗号,它列出了数据库中存在的所有表。 使用
/ d命令和/ d +我们可以获取表的详细信息。 该系统将像
* / d table_name(或)\\ d + table_name
查询的这种变化(如其他答案所述)对我有用。
SELECT
COLUMN_NAME
FROM
information_schema.COLUMNS
WHERE
TABLE_NAME = 'city';
此处详细介绍: http : //www.postgresqltutorial.com/postgresql-describe-table/
我为获取表模式制定了以下脚本。
'CREATE TABLE ' || 'yourschema.yourtable' || E'\n(\n' ||
array_to_string(
array_agg(
' ' || column_expr
)
, E',\n'
) || E'\n);\n'
from
(
SELECT ' ' || column_name || ' ' || data_type ||
coalesce('(' || character_maximum_length || ')', '') ||
case when is_nullable = 'YES' then ' NULL' else ' NOT NULL' end as column_expr
FROM information_schema.columns
WHERE table_schema || '.' || table_name = 'yourschema.yourtable'
ORDER BY ordinal_position
) column_list;
在MySQL中 ,DESCRIBE table_name
在PostgreSQL中 ,\\ d table_name
或者,您可以使用以下长命令:
SELECT
a.attname AS Field,
t.typname || '(' || a.atttypmod || ')' AS Type,
CASE WHEN a.attnotnull = 't' THEN 'YES' ELSE 'NO' END AS Null,
CASE WHEN r.contype = 'p' THEN 'PRI' ELSE '' END AS Key,
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid), '\'(.*)\'')
FROM
pg_catalog.pg_attrdef d
WHERE
d.adrelid = a.attrelid
AND d.adnum = a.attnum
AND a.atthasdef) AS Default,
'' as Extras
FROM
pg_class c
JOIN pg_attribute a ON a.attrelid = c.oid
JOIN pg_type t ON a.atttypid = t.oid
LEFT JOIN pg_catalog.pg_constraint r ON c.oid = r.conrelid
AND r.conname = a.attname
WHERE
c.relname = 'tablename'
AND a.attnum > 0
ORDER BY a.attnum
In postgres \d is used to describe the table structure.
e.g. \d schema_name.table_name;
this command will provide you the basic info of table such as, columns, type and modifiers.
If you want more info about table use
\d+ schema_name.table_name;
this will give you extra info such as, storage, stats target and description
为了改进另一个答案的SQL查询(很棒!),这是一个经过修改的查询。 它还包括约束名称,继承信息以及分解为组成部分的数据类型(类型,长度,精度,小数位数)。 它还过滤掉已删除的列(数据库中仍然存在)。
SELECT
n.nspname as schema,
c.relname as table,
f.attname as column,
f.attnum as column_id,
f.attnotnull as not_null,
f.attislocal not_inherited,
f.attinhcount inheritance_count,
pg_catalog.format_type(f.atttypid,f.atttypmod) AS data_type_full,
t.typname AS data_type_name,
CASE
WHEN f.atttypmod >= 0 AND t.typname <> 'numeric'THEN (f.atttypmod - 4) --first 4 bytes are for storing actual length of data
END AS data_type_length,
CASE
WHEN t.typname = 'numeric' THEN (((f.atttypmod - 4) >> 16) & 65535)
END AS numeric_precision,
CASE
WHEN t.typname = 'numeric' THEN ((f.atttypmod - 4)& 65535 )
END AS numeric_scale,
CASE
WHEN p.contype = 'p' THEN 't'
ELSE 'f'
END AS is_primary_key,
CASE
WHEN p.contype = 'p' THEN p.conname
END AS primary_key_name,
CASE
WHEN p.contype = 'u' THEN 't'
ELSE 'f'
END AS is_unique_key,
CASE
WHEN p.contype = 'u' THEN p.conname
END AS unique_key_name,
CASE
WHEN p.contype = 'f' THEN 't'
ELSE 'f'
END AS is_foreign_key,
CASE
WHEN p.contype = 'f' THEN p.conname
END AS foreignkey_name,
CASE
WHEN p.contype = 'f' THEN p.confkey
END AS foreign_key_columnid,
CASE
WHEN p.contype = 'f' THEN g.relname
END AS foreign_key_table,
CASE
WHEN p.contype = 'f' THEN p.conkey
END AS foreign_key_local_column_id,
CASE
WHEN f.atthasdef = 't' THEN d.adsrc
END AS default_value
FROM pg_attribute f
JOIN pg_class c ON c.oid = f.attrelid
JOIN pg_type t ON t.oid = f.atttypid
LEFT JOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = f.attnum
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
WHERE c.relkind = 'r'::char
AND f.attisdropped = false
AND n.nspname = '%s' -- Replace with Schema name
AND c.relname = '%s' -- Replace with table name
AND f.attnum > 0
ORDER BY f.attnum
;
这应该是解决方案:
SELECT * FROM information_schema.columns
WHERE table_schema = 'your_schema'
AND table_name = 'your_table'
1.数学操作符 |
||||
操作符 |
描述 |
例子 |
结果 |
|
/ |
除(整数/整数结果只保留整数位) |
31/31 |
1 |
|
% |
模 |
6 % 4 |
1 |
|
^ |
幂 |
2.0 ^ 3.1 |
8 |
|
|/ |
平方根 |
|/ 25.1 |
5 |
|
||/ |
立方根 |
||/ 27.1 |
3 |
|
! |
阶乘 |
6 ! |
120 |
|
!! |
阶乘 |
!! 6 |
120 |
|
@ |
绝对值 |
|
|
|
& |
按位AND |
92 & 15 |
11 |
|
| |
按位OR |
33 | 3 |
35 |
|
# |
按位XOR |
18 # 5 |
20 |
|
~ |
按位NOT |
~2 |
-2 |
|
<< |
按位左移 |
2 << 4 |
16 |
|
>> |
按位右移 |
9 >> 2 |
2 |
|
2.位串操作符 |
||||
|| |
连接 |
B'10001' || B'012' |
10001011 |
|
& |
按位AND |
B'10001' & B'01102' |
1 |
|
| |
按位OR |
B'10001' | B'01102' |
11101 |
|
# |
按位XOR |
B'10001' # B'01102' |
11100 |
|
~ |
按位NOT |
~ B'10002' |
1110 |
|
<< |
按位左移 |
B'10001' << 4 |
1000 |
|
>> |
按位右移 |
B'10001' >> 3 |
100 |
|
3.数学函数 |
||||
abs(x) |
绝对值 |
abs(-17.4) |
17.4 |
|
cbrt(double) |
立方根 |
cbrt(27.0) |
3 |
|
ceil(double/numeric) |
不小于参数的最小的整数 |
ceil(-42.8) |
-42 |
|
degrees(double) |
把弧度转为角度 |
degrees(0.5) |
28.64788976 |
|
exp(double/numeric) |
自然指数 |
exp(1.0) |
2.718281828 |
|
floor(double/numeric) |
不大于参数的最大整数 |
floor(-42.8) |
-43 |
|
ln(double/numeric) |
自然对数 |
ln(2.0) |
0.693147181 |
|
log(double/numeric) |
10为底的对数 |
log(100.0) |
2 |
|
log(b numeric,x numeric) |
numeric指定底数的对数 |
log(2.0, 64.0) |
6 |
|
mod(y, x) |
取余数 |
mod(9,4) |
1 |
|
pi() |
"π"常量 |
pi() |
3.141592654 |
|
power(a double, b double) |
求a的b次幂 |
power(9.0, 3.0) |
729 |
|
power(a numeric, b numeric) |
求a的b次幂 |
power(9.0, 3.0) |
729 |
|
radians(double) |
把角度转为弧度 |
radians(45.0) |
0.785398163 |
|
random() |
0.0到1.0之间的随机数值 |
random() |
|
|
round(double/numeric) |
圆整为最接近的整数 |
round(42.4) |
42 |
|
round(v numeric, s int) |
圆整为s位小数数字 |
round(42.438,2) |
42.44 |
|
sign(double/numeric) |
参数的符号(-1,0,+1) |
sign(-8.4) |
-1 |
|
sqrt(double/numeric) |
平方根 |
sqrt(2.0) |
1.414213562 |
|
trunc(double/numeric) |
截断(向零靠近) |
trunc(42.8) |
42 |
|
trunc(v numeric, s int) |
截断为s小数位置的数字 |
trunc(42.438,2) |
42.43 |
|
acos(x) |
反余弦 |
|
|
|
asin(x) |
反正弦 |
|
|
|
atan(x) |
反正切 |
|
|
|
atan2(y, x) |
返回弧度角,X轴与(x,y)组成的角 |
|
|
|
cos(x) |
余弦 |
|
|
|
cot(x) |
余切 |
|
|
|
sin(x) |
正弦 |
|
|
|
tan(x) |
正切 |
|
|
|
4.字符串函数 |
||||
string || string |
字串连接 |
'Post' || 'greSQL' |
PostgreSQL |
|
[char_/octet_]length(串) |
求字符串里字符的个数,三种格式 |
|
|
|
array_agg(表达式) |
把一列换成一行 |
|
|
|
ascii(text) |
参数第一个字符的ASCII码 |
ascii('x') |
120 |
|
ASCII(字符串) |
返回最左边字符的ASCII码 |
|
|
|
bit_length(string) |
字串里二进制位的个数 |
bit_length('jose') |
32 |
|
btrim(string text [, characters text]) |
从string开头和结尾删除只包含在characters里(缺省是空白)的字符的最长字串 |
btrim('xyxtrimyyx','xy') |
trim |
|
char_length(string) |
字串中的字符个数 |
char_length('jose') |
4 |
|
chr(int) |
给出ASCII码的字符 |
chr(65) |
A |
|
concat(串1,串2…) |
合并字符串 |
|
|
|
convert(string text, [src_encoding name,] dest_encoding name) |
把字串转换为dest_encoding |
convert( 'text_in_utf8', 'UTF8', 'LATIN1') |
以ISO 8859-1编码表示的text_in_utf8 |
|
convert(string using conversion_name) |
使用指定的转换名字改变编码。 |
convert('PostgreSQL' using iso_8859_1_to_utf8) |
'PostgreSQL' |
|
format |
格式化 |
|
|
|
initcap(text) |
把每个单词的第一个子母转为大写,其它的保留小写。单词是一系列字母数字组成的字符,用非字母数字分隔。 |
initcap('hi thomas') |
Hi Thomas |
|
left/ right(串,整数) |
取字符串的左边/右边整数位 |
|
|
|
length(string text) |
string中字符的数目 |
length('jose') |
4 |
|
lower(string) |
把字串转化为小写 |
lower('TOM') |
tom |
|
lpad(string text, length int [, fill text]) |
通过填充字符fill(缺省时为空白),把string填充为长度length。如果string已经比length长则将其截断(在右边)。 |
lpad('hi', 5, 'xy') |
xyxhi |
|
ltrim(string text [, characters text]) |
从字串string的开头删除只包含characters(缺省是一个空白)的最长的字串。 |
ltrim('zzzytrim','xyz') |
trim |
|
md5(string text) |
计算给出string的MD5散列,以十六进制返回结果。 |
md5('abc') |
|
|
octet_length(string) |
字串中的字节数 |
octet_length('jose') |
4 |
|
overlay(string placing string from int [for int]) |
替换子字串 |
overlay('Txxxxas' placing 'hom' from 2 for 4) |
Thomas |
|
position(substring in string) |
指定的子字串的位置 |
position('om' in 'Thomas') |
3 |
|
repeat(string text, number int) |
重复string number次。 |
repeat('Pg', 4) |
PgPgPgPg |
|
replace(string text, from text, to text) |
把字串string里出现地所有子字串from替换成子字串to。 |
replace('abcdefabcdef', 'cd', 'XX') |
abXXefabXXef |
|
reverse(串) |
把字符串转置 |
|
|
|
rpad(string text, length int [, fill text]) |
通过填充字符fill(缺省时为空白),把string填充为长度length。如果string已经比length长则将其截断。 |
rpad('hi', 5, 'xy') |
hixyx |
|
rtrim(string text [, character text]) |
从字串string的结尾删除只包含character(缺省是个空白)的最长的字 |
rtrim('trimxxxx','x') |
trim |
|
split_part(string text, delimiter text, field int) |
根据delimiter分隔string返回生成的第field个子字串(1 Base)。 |
split_part('abc~@~def~@~ghi', '~@~', 2) |
def |
|
strpos(string, substring) |
声明的子字串的位置。 |
strpos('high','ig') |
2 |
|
substr(string, from [, count]) |
抽取子字串。 |
substr('alphabet', 3, 2) |
ph |
|
substring(string [from int] [for int]) |
抽取子字串 |
substring('Thomas' from 2 for 3) |
hom |
|
substring(string from pattern for escape) |
抽取匹配SQL正则表达式的子字串 |
substring('Thomas' from '%#"o_a#"_' for '#') |
oma |
|
substring(string from pattern) |
抽取匹配 POSIX 正则表达式的子字串 |
substring('Thomas' from '...$') |
mas |
|
to_ascii(text [, encoding]) |
把text从其它编码转换为ASCII。 |
to_ascii('Karel') |
Karel |
|
to_hex(number int/bigint) |
把number转换成其对应地十六进制表现形式。 |
to_hex(9223372036854775807) |
7fffffffffffffff |
|
translate(string text, from text, to text) |
把在string中包含的任何匹配from中的字符的字符转化为对应的在to中的字符。 |
translate('12345', '14', 'ax') |
a23x5 |
|
trim([leading | trailing | both] [characters] from string) |
从字串string的开头/结尾/两边/ 删除只包含characters(缺省是一个空白)的最长的字串 |
trim(both 'x' from 'xTomxx') |
Tom |
|
upper(string) |
把字串转化为大写。 |
upper('tom') |
TOM |
|
5.数据类型格式化函数 |
||||
to_char(timestamp, text) |
把时间戳转换成字串 |
to_char(current_timestamp, 'HH12:MI:SS') |
|
|
to_char(interval, text) |
把时间间隔转为字串 |
to_char(interval '15h 2m 12s', 'HH24:MI:SS') |
|
|
to_char(int, text) |
把整数转换成字串 |
to_char(125, '999') |
|
|
to_char(double precision, text) |
把实数/双精度数转换成字串 |
to_char(125.8::real, '999D9') |
|
|
to_char(numeric, text) |
把numeric转换成字串 |
to_char(-125.8, '999D99S') |
|
|
to_date(text, text) |
把字串转换成日期 |
to_date('05 Dec 2000', 'DD Mon YYYY') |
|
|
to_timestamp(text, text) |
把字串转换成时间戳 |
to_timestamp('05 Dec 2000', 'DD Mon YYYY') |
|
|
to_timestamp(double) |
把UNIX纪元转换成时间戳 |
to_timestamp(200120400) |
|
|
to_number(text, text) |
把字串转换成numeric |
to_number('12,454.8-', '99G999D9S') |
|
|
6.日期/时间格式化 |
||||
|
一天的小时数(01-12) |
|
|
|
|
一天的小时数(01-12) |
|
|
|
|
一天的小时数(00-23) |
|
|
|
|
分钟(00-59) |
|
|
|
|
秒(00-59) |
|
|
|
|
毫秒(000-999) |
|
|
|
|
微秒(000000-999999) |
|
|
|
|
正午标识(大写) |
|
|
|
|
带逗号的年(4和更多位) |
|
|
|
|
年(4和更多位) |
|
|
|
|
年的后三位 |
|
|
|
|
年的后两位 |
|
|
|
|
年的最后一位 |
|
|
|
|
全长大写月份名(空白填充为9字符) |
|
|
|
|
全长混合大小写月份名(空白填充为9字符) |
|||
|
全长小写月份名(空白填充为9字符) |
|
|
|
|
大写缩写月份名(3字符) |
|
|
|
|
缩写混合大小写月份名(3字符) |
|
|
|
|
小写缩写月份名(3字符) |
|
|
|
|
月份号(01-12) |
|
|
|
|
全长大写日期名(空白填充为9字符) |
|
|
|
|
全长混合大小写日期名(空白填充为9字符) |
|||
|
全长小写日期名(空白填充为9字符) |
|
|
|
|
缩写大写日期名(3字符) |
|
|
|
|
缩写混合大小写日期名(3字符) |
|
|
|
|
缩写小写日期名(3字符) |
|
|
|
|
一年里的日子(001-366) |
|
|
|
|
一个月里的日子(01-31) |
|
|
|
|
一周里的日子(1-7;周日是1) |
|
|
|
|
一个月里的周数(1-5)(第一周从该月第一天开始) |
|||
|
一年里的周数(1-53)(第一周从该年的第一天开始) |
|||
7.数值格式化 |
||||
|
带有指定数值位数的值 |
|
|
|
|
带前导零的值 |
|
|
|
|
小数点 |
|
|
|
|
分组(千)分隔符 |
|
|
|
|
尖括号内负值 |
|
|
|
|
带符号的数值 |
|
|
|
|
货币符号 |
|
|
|
|
小数点 |
|
|
|
|
分组分隔符 |
|
|
|
|
在指明的位置的负号(如果数字 < 1) |
|
|
|
|
在指明的位置的正号(如果数字 > 1) |
|
|
|
|
在指明的位置的正/负号 |
|
|
|
8.日期/时间操作符 |
||||
|
date '2001-09-28' + integer '8' |
date '2001-10-06' |
|
|
|
date '2001-09-28' + interval '2 hour' |
timestamp '2001-09-28 01:01' |
|
|
|
date '2001-09-28' + time '03:01' |
timestamp '2001-09-28 03:01' |
|
|
|
interval '1 day' + interval '2 hour' |
interval '1 day 01:01' |
|
|
|
timestamp '2001-09-28 01:00' + interval '24 hours' |
timestamp '2001-09-29 00:01' |
|
|
|
time '01:00' + interval '4 hours' |
time '04:01' |
|
|
|
- interval '24 hours' |
interval '-23:01' |
|
|
|
date '2001-10-01' - date '2001-09-29' |
integer '4' |
|
|
|
date '2001-10-01' - integer '8' |
date '2001-09-25' |
|
|
|
date '2001-09-28' - interval '2 hour' |
timestamp '2001-09-27 23:01' |
|
|
|
time '05:00' - time '03:01' |
interval '02:01' |
|
|
|
time '05:00' - interval '3 hours' |
time '03:01' |
|
|
|
timestamp '2001-09-28 23:00' - interval '24 hours' |
timestamp '2001-09-28 00:01' |
|
|
|
interval '1 day' - interval '2 hour' |
interval '23:01' |
|
|
|
timestamp '2001-09-29 03:00' - timestamp '2001-09-27 12:01' |
interval '1 day 15:01' |
|
|
|
interval '1 hour' * double precision '3.6' |
interval '03:31' |
|
|
|
interval '1 hour' / double precision '1.6' |
interval '00:41' |
|
|
9.日期/时间函数 |
||||
age(timestamp, timestamp) |
减去参数,生成一个使用年、月的"符号化"的结果 |
age('2001-04-10', timestamp '1957-06-13') |
43 years 9 mons 27 days |
|
age(timestamp) |
从current_date减去得到的数值 |
age(timestamp '1957-06-13') |
43 years 8 mons 3 days |
|
current_date |
今天的日期 |
|
|
|
current_time |
现在的时间 |
|
|
|
current_timestamp |
日期和时间 |
|
|
|
date_part(text, timestamp) |
获取子域(等效于extract) |
date_part('hour', timestamp '2001-02-16 20:38:40') |
20 |
|
date_part(text, interval) |
获取子域(等效于extract) |
date_part('month', interval '2 years 3 months') |
3 |
|
date_trunc(text, timestamp) |
截断成指定的精度 |
date_trunc('hour', timestamp '2001-02-16 20:38:40') |
2001-02-16 20:00:00+00 |
|
extract(field from timestamp) |
获取子域 |
extract(hour from timestamp '2001-02-16 20:38:40') |
20 |
|
extract(field from interval) |
获取子域 |
extract(month from interval '2 years 3 months') |
3 |
|
localtime |
今日的时间 |
|
|
|
localtimestamp |
日期和时间 |
|
|
|
now() |
当前的日期和时间(等效于 current_timestamp) |
|
|
|
timeofday() |
当前日期和时间 |
|
|
|
10.EXTRACT,date_part函数支持的field |
||||
CENTURY |
世纪 |
EXTRACT(CENTURY FROM TIMESTAMP '2000-12-16 12:21:13'); |
20 |
|
DAY |
(月分)里的日期域(1-31) |
EXTRACT(DAY from TIMESTAMP '2001-02-16 20:38:40'); |
16 |
|
DECADE |
年份域除以10 |
EXTRACT(DECADE from TIMESTAMP '2001-02-16 20:38:40'); |
200 |
|
DOW |
每周的星期号(0-6;星期天是0) (仅用于timestamp) |
EXTRACT(DOW FROM TIMESTAMP '2001-02-16 20:38:40'); |
5 |
|
DOY |
一年的第几天(1 -365/366) (仅用于 timestamp) |
EXTRACT(DOY from TIMESTAMP '2001-02-16 20:38:40'); |
47 |
|
HOUR |
小时域(0-23) |
EXTRACT(HOUR from TIMESTAMP '2001-02-16 20:38:40'); |
20 |
|
MICROSECONDS |
秒域,包括小数部分,乘以 1,000,000。 |
EXTRACT(MICROSECONDS from TIME '17:12:28.5'); |
28500000 |
|
MILLENNIUM |
千年 |
EXTRACT(MILLENNIUM from TIMESTAMP '2001-02-16 20:38:40'); |
3 |
|
MILLISECONDS |
秒域,包括小数部分,乘以 1000。 |
EXTRACT(MILLISECONDS from TIME '17:12:28.5'); |
28500 |
|
MINUTE |
分钟域(0-59) |
EXTRACT(MINUTE from TIMESTAMP '2001-02-16 20:38:40'); |
38 |
|
MONTH |
对于timestamp数值,它是一年里的月份数(1-12);对于interval数值,它是月的数目,然后对12取模(0-11) |
EXTRACT(MONTH from TIMESTAMP '2001-02-16 20:38:40'); |
2 |
|
QUARTER |
该天所在的该年的季度(1-4)(仅用于 timestamp) |
EXTRACT(QUARTER from TIMESTAMP '2001-02-16 20:38:40'); |
1 |
|
SECOND |
秒域,包括小数部分(0-59[1]) |
EXTRACT(SECOND from TIMESTAMP '2001-02-16 20:38:40'); |
40 |
|
WEEK |
该天在所在的年份里是第几周。 |
EXTRACT(WEEK from TIMESTAMP '2001-02-16 20:38:40'); |
7 |
|
YEAR |
年份域 |
EXTRACT(YEAR from TIMESTAMP '2001-02-16 20:38:40'); |
2001 |
|
11.数组操作符和函数 |
||||
= |
等于 |
ARRAY[1.1,2.1,3.1]::int[] = ARRAY[1,2,4] |
t |
|
<> |
不等于 |
ARRAY[1,2,3] <> ARRAY[1,2,5] |
t |
|
< |
小于 |
ARRAY[1,2,3] < ARRAY[1,2,5] |
t |
|
> |
大于 |
ARRAY[1,4,3] > ARRAY[1,2,5] |
t |
|
<= |
小于或等于 |
ARRAY[1,2,3] <= ARRAY[1,2,4] |
t |
|
>= |
大于或等于 |
ARRAY[1,4,3] >= ARRAY[1,4,4] |
t |
|
|| |
数组与数组连接 |
ARRAY[1,2,3] || ARRAY[4,5,7] |
{1,2,3,4,5,7} |
|
|| |
数组与数组连接 |
ARRAY[1,2,3] || ARRAY[[4,5,6],[7,8,10]] |
{{1,2,3},{4,5,6},{7,8,10}} |
|
|| |
元素与数组连接 |
4 || ARRAY[4,5,6] |
{3,4,5,7} |
|
|| |
元素与数组连接 |
ARRAY[4,5,6] || 8 |
{4,5,6,8} |
|
array_cat(anyarray, anyarray) |
连接两个数组 |
array_cat(ARRAY[1,2,3], ARRAY[4,5]) |
{1,2,3,4,5} |
|
array_append(anyarray, anyelement) |
向一个数组末尾附加一个元素 |
array_append(ARRAY[1,2], 3) |
{1,2,3} |
|
array_prepend(anyelement, anyarray) |
向一个数组开头附加一个元素 |
array_prepend(1, ARRAY[2,3]) |
{1,2,3} |
|
array_dims(anyarray) |
返回一个数组维数的文本表示 |
array_dims(ARRAY[[1,2,3], [4,5,6]]) |
[1:2][1:3] |
|
array_lower(anyarray, int) |
返回指定的数组维数的下界 |
array_lower(array_prepend(0, ARRAY[1,2,3]), 1) |
0 |
|
array_upper(anyarray, int) |
返回指定数组维数的上界 |
array_upper(ARRAY[1,2,3,4], 1) |
4 |
|
array_to_string(anyarray, text) |
使用提供的分隔符连接数组元素 |
array_to_string(ARRAY[1, 2, 3], '~^~') |
1~^~2~^~3 |
|
string_to_array(text, text) |
使用指定的分隔符把字串拆分成数组元素 |
string_to_array('xx~^~yy~^~zz', '~^~') |
{xx,yy,zz} |
|
12.系统信息函数 |
||||
current_database() |
当前数据库的名字 |
|
|
|
current_schema() |
当前模式的名字 |
|
|
|
current_schemas(boolean) |
在搜索路径中的模式名字 |
|
|
|
current_user |
目前执行环境下的用户名 |
|
|
|
inet_client_addr() |
连接的远端地址 |
|
|
|
inet_client_port() |
连接的远端端口 |
|
|
|
inet_server_addr() |
连接的本地地址 |
|
|
|
inet_server_port() |
连接的本地端口 |
|
|
|
session_user |
会话用户名 |
|
|
|
pg_postmaster_start_time() |
postmaster启动的时间 |
|
|
|
user |
current_user |
|
|
|
version() |
PostgreSQL版本信息 |
|
|
|
has_table_privilege(user,table,privilege) |
用户是否有访问表的权限 |
SELECT/INSERT/UPDATE/DELETE/RULE/REFERENCES/TRIGGER |
允许用户在程序里查询对象访问权限的函数 |
|
has_table_privilege(table,privilege) |
当前用户是否有访问表的权限 |
SELECT/INSERT/UPDATE/DELETE/RULE/REFERENCES/TRIGGER |
||
has_database_privilege(user,database,privilege) |
用户是否有访问数据库的权限 |
CREATE/TEMPORARY |
||
has_database_privilege(database,privilege) |
当前用户是否有访问数据库的权限 |
CREATE/TEMPORARY |
||
has_function_privilege(user,function,privilege) |
用户是否有访问函数的权限 |
EXECUTE |
||
has_function_privilege(function,privilege) |
当前用户是否有访问函数的权限 |
EXECUTE |
||
has_language_privilege(user,language,privilege) |
用户是否有访问语言的权限 |
USAGE |
||
has_language_privilege(language,privilege) |
当前用户是否有访问语言的权限 |
USAGE |
||
has_schema_privilege(user,schema,privilege) |
用户是否有访问模式的权限 |
CREAT/USAGE |
||
has_schema_privilege(schema,privilege) |
当前用户是否有访问模式的权限 |
CREAT/USAGE |
||
has_tablespace_privilege(user,tablespace,privilege) |
用户是否有访问表空间的权限 |
CREATE |
||
has_tablespace_privilege(tablespace,privilege) |
当前用户是否有访问表空间的权限 |
CREATE |
||
pg_table_is_visible(table_oid) |
该表/视图是否在搜索路径中可见 |
regclass |
模式可视性查询函数 |
|
pg_type_is_visible(type_oid) |
该类/视图型是否在搜索路径中可见 |
regtype |
||
pg_function_is_visible(function_oid) |
该函数是否在搜索路径中可见 |
regprocedure |
||
pg_operator_is_visible(operator_oid) |
该操作符是否在搜索路径中可见 |
regoperator |
||
pg_opclass_is_visible(opclass_oid) |
该操作符表是否在搜索路径中可见 |
regclass |
||
pg_conversion_is_visible(conversion_oid) |
转换是否在搜索路径中可见 |
regoperator |
||
format_type(type_oid,typemod) |
获取一个数据类型的SQL名称 |
系统表信息函数 |
||
pg_get_viewdef(view_oid) |
为视图获取CREATE VIEW命令 |
|||
pg_get_viewdef(view_oid,pretty_bool) |
为视图获取CREATE VIEW命令 |
|||
pg_get_ruledef(rule_oid) |
为规则获取CREATE RULE命令 |
|||
pg_get_ruledef(rule_oid,pretty_bool) |
为规则获取CREATE RULE命令 |
|||
pg_get_indexdef(index_oid) |
为索引获取CREATE INDEX命令 |
|||
pg_get_indexdef(index_oid,column_no,pretty_bool) |
为索引获取CREATE INDEX命令,如果column_no不为零,则是只获取一个索引字段的定义 |
|||
pg_get_triggerdef(trigger_oid) |
为触发器获取CREATE [CONSTRAINT] TRIGGER |
|||
pg_get_constraintdef(constraint_oid) |
获取一个约束的定义 |
|||
pg_get_constraintdef(constraint_oid,pretty_bool) |
获取一个约束的定义 |
|||
pg_get_expr(expr_text,relation_oid) |
反编译一个表达式的内部形式,假设其中的任何Vars都引用第二个参数指出的关系 |
|||
pg_get_expr(expr_text,relation_oid, pretty_bool) |
反编译一个表达式的内部形式,假设其中的任何Vars都引用第二个参数指出的关系 |
|||
pg_get_userbyid(roleid) |
获取给出的ID的角色名 |
|||
pg_get_serial_sequence(table_name,column_name) |
获取一个serial或者bigserial字段使用的序列名字 |
|||
pg_tablespace_databases(tablespace_oid) |
获取在指定表空间(OID表示)中拥有对象的一套数据库的OID的集合 |
|||
13.系统管理函数 |
||||
current_setting(setting_name) |
当前设置的值 |
查询以及修改运行时配置参数的函数 |
||
set_config(setting_name,new_value,is_local) |
设置参数并返回新值 |
|||
pg_tablespace_size(oid) |
指定OID代表的表空间使用的磁盘空间 |
数据库对象尺寸函数 |
||
pg_tablespace_size(name) |
指定名字的表空间使用的磁盘空间 |
|||
pg_database_size(oid) |
指定OID代表的数据库使用的磁盘空间 |
|||
pg_database_size(name) |
指定名称的数据库使用的磁盘空间 |
|||
pg_relation_size(oid) |
指定OID代表的表或者索引所使用的磁盘空间 |
|||
pg_relation_size(text) |
指定名称的表或者索引使用的磁盘空间。这个名字可以用模式名修饰 |
|||
pg_total_relation_size(oid) |
指定OID代表的表使用的磁盘空间,包括索引和压缩数据 |
|||
pg_total_relation_size(text) |
指定名字的表所使用的全部磁盘空间,包括索引和压缩数据。表名字可以用模式名修饰。 |
|||
pg_size_pretty(bigint) |
把字节计算的尺寸转换成一个人类易读的尺寸单位 |
|||
pg_relation_filenode(relationregclass) |
获取指定对象的文件节点编号(通常为对象的oid值)。 |
数据库对象位置函数 |
||
pg_relation_filepath(relationregclass) |
获取指定对象的完整路径名。 |
|||
14.系列号操作函数 |
||||
nextval(regclass) |
递增序列对象到它的下一个数值并且返回该值。这个动作是自动完成的。即使多个会话并发运行nextval,每个进程也会安全地收到一个唯一的序列值。 |
|||
currval(regclass) |
在当前会话中返回最近一次nextval抓到的该序列的数值。(如果在本会话中从未在该序列上调用过 nextval,那么会报告一个错误。)请注意因为此函数返回一个会话范围的数值,而且也能给出一个可预计的结果,因此可以用于判断其它会话是否执行过nextval。 |
|||
lastval() |
返回当前会话里最近一次nextval返回的数值。这个函数等效于currval,只是它不用序列名为参数,它抓取当前会话里面最近一次nextval使用的序列。如果当前会话还没有调用过nextval,那么调用lastval将会报错。 |
|||
setval(regclass, bigint) |
重置序列对象的计数器数值。设置序列的last_value字段为指定数值并且将其is_called字段设置为true,表示下一次nextval将在返回数值之前递增该序列。 |
|||
setval(regclass, bigint, boolean) |
重置序列对象的计数器数值。功能等同于上面的setval函数,只是is_called可以设置为true或false。如果将其设置为false,那么下一次nextval将返回该数值,随后的nextval才开始递增该序列。 |
Pg权限分为两部分,一部分是“系统权限”或者数据库用户的属性,可以授予role或user(两者区别在于login权限);一部分为数据库对象上的操作权限。对超级用户不做权限检查,其它走acl。对于数据库对象,开始只有所有者和超级用户可以做任何操作,其它走acl。在pg里,对acl模型做了简化,组和角色都是role,用户和角色的区别是角色没有login权限。
可以用下面的命令创建和删除角色,
CREATE ROLE name;
DROP ROLE name;
为了方便,也可以在 shell 命令上直接调用程序 createuser 和 dropuser,这些工具对相应命令提供了封装:
createuser name
dropuser name
数据库对象上的权限有:SELECT,INSERT, UPDATE,DELETE,RULE, REFERENCES,TRIGGER,CREATE, TEMPORARY,EXECUTE,和 USAGE等,具体见下面定义
typedefuint32AclMode; /* a bitmask of privilege bits */
#define ACL_INSERT (1<<0) /* for relations */
#define ACL_SELECT (1<<1)
#define ACL_UPDATE (1<<2)
#define ACL_DELETE (1<<3)
#define ACL_TRUNCATE (1<<4)
#define ACL_REFERENCES (1<<5)
#define ACL_TRIGGER (1<<6)
#define ACL_EXECUTE (1<<7) /* for functions */
#define ACL_USAGE (1<<8) /* for languages, namespaces, FDWs, and
* servers */
#define ACL_CREATE (1<<9) /* for namespaces and databases */
#define ACL_CREATE_TEMP (1<<10) /* for databases */
#define ACL_CONNECT (1<<11) /* for databases */
#define N_ACL_RIGHTS 12 /* 1 plus the last 1<
#define ACL_NO_RIGHTS 0
/* Currently, SELECT ... FOR UPDATE/FOR SHARE requires UPDATE privileges */
#define ACL_SELECT_FOR_UPDATE ACL_UPDATE
我们可以用特殊的名字 PUBLIC 把对象的权限赋予系统中的所有角色。 在权限声明的位置上写 ALL,表示把适用于该对象的所有权限都赋予目标角色。
beigang=# grant all on schema csm_ca to public;
GRANT
beigang=# revoke all on schema csm_ca from public;
REVOKE
beigang=#
每种对象的all权限定义如下:
/*
* Bitmasks defining "all rights" for each supported object type
*/
#define ACL_ALL_RIGHTS_COLUMN (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_REFERENCES)
#define ACL_ALL_RIGHTS_RELATION (ACL_INSERT|ACL_SELECT|ACL_UPDATE|ACL_DELETE|ACL_TRUNCATE|ACL_REFERENCES|ACL_TRIGGER)
#define ACL_ALL_RIGHTS_SEQUENCE (ACL_USAGE|ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_DATABASE (ACL_CREATE|ACL_CREATE_TEMP|ACL_CONNECT)
#define ACL_ALL_RIGHTS_FDW (ACL_USAGE)
#define ACL_ALL_RIGHTS_FOREIGN_SERVER (ACL_USAGE)
#define ACL_ALL_RIGHTS_FUNCTION (ACL_EXECUTE)
#define ACL_ALL_RIGHTS_LANGUAGE (ACL_USAGE)
#define ACL_ALL_RIGHTS_LARGEOBJECT (ACL_SELECT|ACL_UPDATE)
#define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
用户的属性可参见下图:
视图 pg_roles提供访问数据库角色有关信息的接口。 它只是一个 pg_authid 表的公开可读部分的视图,把口令字段用空白填充了。
Table 42-39.pg_roles字段
名字 |
类型 |
引用 |
描述 |
rolname |
name |
|
角色名 |
rolsuper |
bool |
|
有超级用户权限的角色 |
rolcreaterole |
bool |
|
可以创建更多角色的角色 |
rolcreatedb |
bool |
|
可以创建数据库的角色 |
rolcatupdate |
bool |
|
可以直接更新系统表的角色。(除非这个字段为真,否则超级用户也不能干这个事情。) |
rolcanlogin |
bool |
|
可以登录的角色,也就是说,这个角色可以给予初始化会话认证的标识符。 |
rolpassword |
text |
|
不是口令(总是 ********) |
rolvaliduntil |
timestamptz |
|
口令失效日期(只用于口令认证);如果没有失效期,为 NULL |
rolconfig |
text[] |
|
运行时配置变量的会话缺省 |
下面实验验证
先创建一个角色xxx,再创建一个超级用户csm、普通用户csm_ca,csm用户创建一个数据库testdb,在这个数据库里创建一个schema:csm_ca,然后赋予普通用户csm_ca操作数据库testdb里schema:csm_ca里的表的权限。
1
Create role:
testdb=# create role xxx with superuser;
CREATE ROLE
2
Create user:
testdb=# create user csm with superuser password 'csm';
CREATE ROLE
testdb=# create user csm_ca with password 'csm_ca';
CREATE ROLE
testdb=#
3
验证
testdb=# \du
角色列表
-[ RECORD 1 ]--------------------------------------
角色名称 | csm
属性 | 超级用户
成员属于 | {}
-[ RECORD 2 ]--------------------------------------
角色名称 | csm_ca
属性 |
成员属于 | {}
-[ RECORD 3 ]--------------------------------------
角色名称 | postgres
属性 | 超级用户, 建立角色, 建立 DB, Replication
成员属于 | {}
-[ RECORD 4 ]--------------------------------------
角色名称 | xxx
属性 | 超级用户, 无法登录
成员属于 | {}
testdb=# SELECT * FROM pg_roles;
-[ RECORD 1 ]---------+---------
rolname | postgres
rolsuper | t
rolinherit | t
rolcreaterole | t
rolcreatedb | t
rolcreatedblink | t
rolcreatepublicdblink | t
roldroppublicdblink | t
rolcatupdate | t
rolcanlogin | t
rolreplication | t
rolconnlimit | -1
rolpassword | ********
rolvaliduntil |
rolconfig |
oid | 10
-[ RECORD 2 ]---------+---------
rolname | csm
rolsuper | t
rolinherit | t
rolcreaterole | f
rolcreatedb | f
rolcreatedblink | f
rolcreatepublicdblink | f
roldroppublicdblink | f
rolcatupdate | t
rolcanlogin | t
rolreplication | f
rolconnlimit | -1
rolpassword | ********
rolvaliduntil |
rolconfig |
oid | 24598
-[ RECORD 3 ]---------+---------
rolname | csm_ca
rolsuper | f
rolinherit | t
rolcreaterole | f
rolcreatedb | f
rolcreatedblink | f
rolcreatepublicdblink | f
roldroppublicdblink | f
rolcatupdate | f
rolcanlogin | t
rolreplication | f
rolconnlimit | -1
rolpassword | ********
rolvaliduntil |
rolconfig |
oid | 24599
-[ RECORD 4 ]---------+---------
rolname | xxx
rolsuper | t
rolinherit | t
rolcreaterole | f
rolcreatedb | f
rolcreatedblink | f
rolcreatepublicdblink | f
roldroppublicdblink | f
rolcatupdate | t
rolcanlogin | f
rolreplication | f
rolconnlimit | -1
rolpassword | ********
rolvaliduntil |
rolconfig |
oid | 24600
postgres=# \c beigang
You are now connected to database "beigang" as user "csm".
5
Csm用户在beigang里创建schema: csm_ca
beigang=#
beigang=#
beigang=# create schema csm_ca;
CREATE SCHEMA
beigang=#
6
验证模式csm_ca和用户csm_ca
beigang=# \dn
架构模式列表
名称 | 拥有者
--------+----------
csm_ca | csm
dbo | postgres
public | postgres
sys | postgres
(4 行记录)
beigang=# \du
角色列表
角色名称 | 属性 | 成员属于
----------+------------------------------------------+----------
csm | 超级用户 | {}
csm_ca | | {}
postgres | 超级用户, 建立角色, 建立 DB, Replication | {}
xxx | 超级用户, 无法登录 | {}
beigang=#
7
超级用户csm给普通用户csm_ca授予操作schema csm_ca的权限
beigang=# grant all on schema csm_ca to csm_ca;
GRANT
beigang=# grant all on all tables in schema csm_ca to csm_ca;
GRANT
beigang=#
8
pg中组就是role,操作见以下
beigang=# grant xxx to csm_ca;
GRANT ROLE
beigang=# revoke xxx from csm_ca;
REVOKE ROLE
beigang=#
Setting uniqueness validation in rails is something you’ll end up doing quite often. Perhaps, you even already added them to most of your apps. However, this validation only gives a good user interface and experience. It informs the user of the errors preventing the data from being persisted in the database.
在rails中设置唯一性验证是您经常要做的事情。 也许,您甚至已经将它们添加到了大多数应用程序中。 但是,此验证只能提供良好的用户界面和体验。 它向用户通知错误,阻止数据保留在数据库中。
Even with the uniqueness validation, unwanted data sometimes gets saved in the database. For clarity, let’s take a look at a user model shown below:
即使进行了唯一性验证,有时也会将不需要的数据保存在数据库中。 为了清楚起见,让我们看一下下面显示的用户模型:
class User
validates :username, presence: true, uniqueness: true
end
To validate the username column, rails queries the database using SELECT to see if the username already exists. If it does, it prints “Username already exists”. If it doesn’t, it runs an INSERT query to persist the new username in the database.
为了验证用户名列,Rails使用SELECT查询数据库以查看用户名是否已经存在。 如果是这样,它将显示“用户名已存在”。 如果没有,它将运行INSERT查询以将新的用户名保留在数据库中。
When two users are running the same process at the same time, the database can sometimes save the data regardless of the validation constraint and that is where the database constraints (unique index) comes in.
当两个用户同时运行同一进程时,无论验证约束如何,数据库有时都可以保存数据,这就是数据库约束(唯一索引)所在的地方。
If user A and user B are both trying to persist the same username into the database at the same time, rails runs the SELECT query, if the username already exists, it informs both users. However, if the username doesn’t exist in the database, it runs the INSERT query for both users simultaneously as shown in the image below.
如果用户A和用户B都试图同时将同一用户名持久化到数据库中,则Rails将运行SELECT查询,如果用户名已经存在,它将通知两个用户。 但是,如果用户名在数据库中不存在,它将同时为两个用户运行INSERT查询,如下图所示。
Now that you know why the database unique index (database constraint) is important, let’s get into how to set it. It’s quite easy to set database unique index(es) for any column or set of columns in rails. However, some database constraint in rails can be tricky.
现在您已经知道为什么数据库唯一索引(数据库约束)很重要,让我们开始了解如何设置它。 为rails中的任何列或一组列设置数据库唯一索引非常容易。 但是,rails中的某些数据库约束可能很棘手。
This is quite as simple as running a migration. Let’s assume we have a users table with column username and we want to ensure that each user has a unique username. You simply create a migration and input the following code:
这与运行迁移非常简单。 假设我们有一个带有用户名列的用户表,并且我们要确保每个用户都有一个唯一的用户名。 您只需创建一个迁移并输入以下代码:
add_index :users, :username, unique: true
Then you run the migration and that’s it. The database now ensures that no similar usernames are saved in the table.
然后运行迁移,仅此而已。 现在,数据库确保没有类似的用户名保存在表中。
For multiple associated columns, let’s assume we have a requests table with columns sender_id and receiver_id. Similarly, you simply create a migration and input the following code:
对于多个关联的列,假设我们有一个带有表sender_id和receiver_id的请求表。 同样,您只需创建一个迁移并输入以下代码:
add_index :requests, [:sender_id, :receiver_id], unique: true
And that’s it? Uh oh, not so fast.
就是这样吗? 嗯,不是那么快。
The problem is that the ids, in this case, are interchangeable. This means that if you have a sender_id of 1 and receiver_id of 2, the request table can still save a sender_id of 2 and receiver_id of 1, even though they already have a pending request.
问题在于,在这种情况下,这些ID是可互换的。 这意味着,如果sender_id为1,receiver_id为2,则请求表仍可以保存sender_id为2,receiver_id为1,即使它们已经有未决请求。
This problem often happens in a self-referential association. This means both the sender and receiver are users and sender_id or receiver_id is referenced from the user_id. A user with user_id(sender_id) of 1 sends a request to a user with user_id(receiver_id) of 2.
此问题通常发生在自指关联中。 这意味着发送方和接收方都是用户,并且从user_id引用sender_id或receive_id。 user_id(sender_id)为1的用户向user_id(receiver_id)为2的用户发送请求。
If the receiver sends another request again, and we allow it to save in the database, then we have two similar requests from the same two users(sender and receiver || receiver and sender) in the request table.
如果接收方再次发送另一个请求,并且我们允许它保存在数据库中,则在请求表中,来自相同两个用户(发送方和接收方||接收方和发送方)的两个相似请求。
This is illustrated in the image below:
如下图所示:
This problem is often fixed with the pseudo-code below:
此问题通常通过以下伪代码解决:
def force_record_conflict
# 1. Return if there is an already existing request from the sender to receiver
# 2. If not then swap the sender and receiver
end
The problem with this solution is that the receiver_id and sender_id get swapped each time before saving to the database. Hence, the receiver_id column will have to save the sender_id and vice versa.
该解决方案的问题在于,每次将receive_id和sender_id交换一次,然后再保存到数据库中。 因此,receiver_id列将必须保存sender_id,反之亦然。
For example, if a user with sender_id of 1 sends a request to a user with receiver_id of 2, the request table will be as shown below:
例如,如果sender_id为1的用户向receive_id为2的用户发送请求,则请求表如下所示:
This may not sound like an issue but it’s better if your columns are saving the exact data you want them to save. This has numerous advantages. For example, if you need to send a notification to the receiver through the receiver_id, then you’ll query the database for the exact id from the receiver_id column. This already became more confusing the moment you start switching the data saved in your request table.
这听起来似乎不是问题,但最好是您的列要保存要保存的确切数据。 这具有许多优点。 例如,如果您需要通过receiver_id向接收者发送通知,那么您将在数据库中查询receive_id列中的确切ID。 当您开始切换请求表中保存的数据时,这已经变得更加混乱。
This problem can be entirely resolved by talking to the database directly. In this case, I’ll explain using PostgreSQL. When running the migration, you must ensure that the unique constraint checks for both (1,2) and (2,1) in the request table before saving.
通过直接与数据库对话可以完全解决此问题。 在这种情况下,我将说明如何使用PostgreSQL。 运行迁移时,必须确保在保存之前,唯一约束对请求表中的(1,2)和(2,1)进行检查。
You can do that by running a migration with the code below:
您可以通过使用以下代码运行迁移来做到这一点:
class AddInterchangableUniqueIndexToRequests < ActiveRecord::Migration[5.2]
def change
reversible do |dir|
dir.up do
connection.execute(%q(
create unique index index_requests_on_interchangable_sender_id_and_receiver_id on requests(greatest(sender_id,receiver_id), least(sender_id,receiver_id));
create unique index index_requests_on_interchangable_receiver_id_and_sender_id on requests(least(sender_id,receiver_id), greatest(sender_id,receiver_id));
))
end
dir.down do
connection.execute(%q(
drop index index_requests_on_interchangable_sender_id_and_receiver_id;
drop index index_requests_on_interchangable_receiver_id_and_sender_id;
))
end
end
end
end
After creating the migration file, the reversible is to ensure that we can revert our database whenever we must. The dir.up
is the code to run when we migrate our database and dir.down
will run when we migrate down or revert our database.
创建迁移文件后,可逆操作是确保我们可以在需要时还原数据库。 dir.up
是在迁移数据库时运行的代码,而dir.down
将在我们向下迁移或还原数据库时运行。
connection.execute(%q(...))
is to tell rails that our code is PostgreSQL. This helps rails to run our code as PostgreSQL.
connection.execute(%q(...))
告诉Rails我们的代码是PostgreSQL。 这有助于Rails将我们的代码作为PostgreSQL运行。
Since our “ids” are integers, before saving into the database, we check if the greatest and least (2 and 1) are already in the database using the code below:
由于我们的“ id”是整数,因此在保存到数据库之前,我们使用以下代码检查最大和最小(2和1)是否已存在于数据库中:
requests(greatest(sender_id,receiver_id), least(sender_id,receiver_id))
Then we also check if the least and greatest (1 and 2) are in the database using:
然后,我们还使用以下方法检查数据库中最小和最大(1和2)是否存在:
requests(least(sender_id,receiver_id), greatest(sender_id,receiver_id))
The request table will then be exactly how we intend as shown in the image below:
然后,请求表将完全符合我们的预期,如下图所示:
And that’s it. Happy coding!
就是这样。 编码愉快!
1 触发器概述
触发器的功能就是为了解决这类问题而设计的,当你更新或查询某个资料表时会触动触发器,触发器就会照您所设计的流程,同步去插入、更新、删除其他资料,你不再需要重复下达多次的SQL命令就能达成一连串资料的同步处理。
触发器是某个数据库操作发生时被自动调用的函数。可以在INSERT、UPDATE或DELETE操作之前或之后调用触发器。PostgreSQL支持两种类型的触发器,一种是数据行级触发器,另外一种是语句级触发器。对于数据行级的触发器,触发发触发器的语句每操作一个数据行,它就被执行一次。对于语句级的触发器,它只会被执行一次。
创建触发器以前,必须定义触发器使用的函数。这个函数不能有任何参数,它的返回值的类型必须是trigger。函数定义好以后,用命令CREATE TRIGGER创建触发器。多个触发器可以使用同一个函数。
触发器按按执行的时间被分为before触发器和after触发器。语句级的before触发器在语句开始执行前被调用,语句级的after触发器在语句开始执行结束后被调用。数据行级的before触发器在操作每个数据行以前被调用,数据行级的after触发器在操作每个数据行以后被调用。
语句级的触发器应该返回NULL。
行级的before触发器的返回值不同,它对触发触发器的操作的影响也不同。如果它返回NULL, 触发这个触发器的INSERT/UPDATE/DELETE命令不会被执行。如果行级的BEFORE触发器返回非空的值,则INSERT/UPDATE/DELETE命令继续执行。对于UPDATE和INSERT操作触发的行级BEFORE触发器,如果它返回的数据行与更新以后的或被插入的数据行不相同,则以触发器返回的数据行作为新的更新以后的数据行和被插入的数据行。
行级after触发器的返回值总是被忽略,可以返回NULL。
如果同一表上同对同一个事件定义了多个触发器,这些触发器将按它们的名字的字母顺序被触发。对于行级before触发器来说,前一个触发器返回的数据行作为后一个触发器的输入。如果任何一个行级before触发器返回NULL,后面的触发器将停止执行,触发触发器的INSERT/UPDATE/DELETE命令也不会被执行。
行级before触发器一般用于检查和修改将被插入和更新的数据。行级after触发器一般用于将表中被更新的数据记录到其它的表中,或者检查与其它的表中的数据是否是一致的。
before触发器的执行效率比after触发器高,在before触发器和after触发器都能被使用的情况下,应该选择before触发器。
一个触发器在执行的过程中,如果执行了其它的SQL命令,可能会触发其它的触发器,这被称作触发器级联。对于触发器级联的层次,系统没有任何限制,但触发器级联可能会调用前面已经执行过的触发器,从而引起死循环,系统不会检测这种现象,定义触发器的用户应该保证这种现象不会发生。
定义触发器的时候,也可以为它指定参数(在CREATE TRIGGER命令中中指定)。系统提供了特殊的接口来访问这些参数。
触发器在被调用时,系统会自动传递一些数据给它,这些数据包括触发触发器的事件类型(例如INSERT或UPDATE),对于行级触发器,还包括NEW数据行(对于INSERT和 UPDATE触发器)和OLD数据行(对于UPDATE和DELETE触发器)。每种可以用来书写触发器函数的语言都提供了取这些数据的方法。
语句级别的触发器在执行过程中无法查看该语句插入、删除或更新的任何数据行。《PL/pgSQL教程》第8章中有触发器的实例。
还有一种特殊的触发器叫约束触发器,这种触发器的执行时间可以被命令SET CONSTRAINTS控制,详细信息参考《SQL命令手册》对CREATE CONSTRAINT TRIGGER命令的解释。
2 数据可见规则
触发器在执行过程中,如果执行SQL命令访问触发器的父表中的数据,这些SQL命令遵循下面的数据可见规则,这些规则决定它们能否看见触发触发器的操作修改的表中的数据行:
(1)语句级的before触发器在执行过程中,该语句的所有的对表中的数据的更新对它都不可见。语句级的after触发器在执行过程中,该语句的所有的对表中的数据的更新对它都可见。
(2)行级before触发器在执行过程中,前面所有的已经被同一个命令处理的数据行对它是可见的,但触发该触发器的数据行的更新操作的结果(插入、更新或删除)对它是不可见的。行级after触发器在执行过程中,前面所有的已经被同一个命令处理的数据行对它是可见的。
3 事例
可以用PL/pgSQL 来写触发器过程。可以用命令CREATE FUNCTION创建一个触发器过程,这个函数没有任何参数,返回值的类型必须是trigger。使用命令CREATE TRIGGER来创建一个触发器,通过TG_ARGV来传递参数给触发器过程,下面会介绍TG_ARGV的用法。
当一个PL/pgSQL 函数作为一个触发器被调用时,系统自动在最外层的块创建一些特殊的变量。这些变量分别是:
(1)NEW
数据类型是RECORD。对于行级触发器,它存有INSERT或UPDATE操作产生的新的数据行。对于语句级触发器,它的值是NULL。
(2)OLD
数据类型是RECORD。对于行级触发器,它存有被UPDATE或DELETE操作修改或删除的旧的数据行。对于语句级触发器,它的值是NULL。
(3)TG_NAME
数据类型是name,它保存实际被调用的触发器的名字。
(4)TG_WHEN
数据类型是text,根据触发器定义信息的不同,它的值是BEFORE 或AFTER。
(5)TG_LEVEL
数据类型是text,根据触发器定义信息的不同,它的值是ROW或STATEMENT。
(6)TG_OP
数据类型是text,它的值是INSERT、UPDATE或DELETE,表示触发触发器的操作类型。
(7)TG_RELID
数据类型是oid,表示触发器作用的表的oid。
(8)TG_RELNAME
数据类型是name,表示触发器作用的表的名字。它与下面的变量TG_TABLE_NAME的作用是一样的。
(9)TG_TABLE_NAME
数据类型是name,表示触发器作用的表的名字。
(10)TG_TABLE_SCHEMA
数据类型是name,表示触发器作用的表所在的模式。
(11)TG_NARGS
数据类型是integer,表示CREATE TRIGGER命令传给触发器过程的参数的个数。
(12)TG_ARGV[]
数据类型是text类型的数组。表示CREATE TRIGGER命令传给触发器过程的所有参数。下标从0开始。TG_ARGV[0]表示第一个参数,TG_ARGV[1]表示第二个参数,以此类推。 如果下标小于0或大于等于tg_nargs,将会返回一个空值。
触发器函数必须返回一个NULL或者一个记录/数据行类型的变量,这个变量的结构必须与触发器作用的表的结构一样。
对于行级的BEFORE触发器,如果返回NULL,后面的触发器将不会被执行,触发这个触发器的INSERT/UPDATE/DELETE命令也不会执行。如果行级的BEFORE触发器返回非空的值,则INSERT/UPDATE/DELETE命令继续执行。对于UPDATE和INSERT操作触发的行级BEFORE触发器,如果它返回的数据行与更新以后的或被插入的数据行不相同,则以触发器返回的数据行作为新的更新好的数据行和被插入的数据行。
语句级的触发器的返回值和AFTER类型的行级触发器的返回值总是被忽略,没有任何意义。
如果触发器在执行的过程中遇到或者发出了错误,触发触发器的操作将被终止。
下面是一些触发器实例:
(1)使用一个行级BEFORE触发器检查表emp的 被插入或跟新操作完成以后的数据行在列salary上的值是否大于0,列name是否不是空值:
CREATE TABLE emp (
empname text,
salary integer,
last_date timestamp,
last_user text
);
CREATE FUNCTION emp_stamp() RETURNS trigger AS empstamp
BEGIN
-- Check that empname and salary are given
IF NEW.empname IS NULL THEN
RAISE EXCEPTION 'empname cannot be null';
END IF;
IF NEW.salary IS NULL THEN
RAISE EXCEPTION '% cannot have null salary', NEW.empname;
END IF;
-- Who works for us when she must pay for it?
IF NEW.salary < 0 THEN
RAISE EXCEPTION '% cannot have a negative salary', NEW.empname;
END IF;
-- Remember who changed the payroll when
NEW.last_date := current_timestamp;
NEW.last_user := current_user;
RETURN NEW;
END;
empstamp LANGUAGE plpgsql;
CREATE TRIGGER emp_stamp BEFORE INSERT OR UPDATE ON emp
FOR EACH ROW EXECUTE PROCEDURE emp_stamp();
(2)将表emp上被插入、删除和跟新的数据行存到另一个表emp_audit中:
CREATE TABLE emp (
empname text NOT NULL,
salary integer
);
CREATE TABLE emp_audit(
operation char(1) NOT NULL,
stamp timestamp NOT NULL,
userid text NOT NULL,
empname text NOT NULL,
salary integer
);
CREATE OR REPLACE FUNCTION process_emp_audit() RETURNS TRIGGER AS empaudit
BEGIN
--
-- Create a row in emp_audit to reflect the operation performed on emp,
-- make use of the special variable TG_OP to work out the operation.
--
IF (TG_OP = 'DELETE') THEN
INSERT INTO emp_audit SELECT 'D', now(), user, OLD.*;
RETURN OLD;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO emp_audit SELECT 'U', now(), user, NEW.*;
RETURN NEW;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO emp_audit SELECT 'I', now(), user, NEW.*;
RETURN NEW;
END IF;
RETURN NULL; -- result is ignored since this is an AFTER trigger
END;
empaudit LANGUAGE plpgsql;
CREATE TRIGGER emp_audit
AFTER INSERT OR UPDATE OR DELETE ON emp
FOR EACH ROW EXECUTE PROCEDURE process_emp_audit();
(3)这是一个更复杂的例子,表sales_fact存放着表time_dimension的汇总数据,利用表time_dimension上的一个行级BEFORE触发器保持sales_fact和time_dimension中的数据同步。
–
– Main tables - time dimension and sales fact.
–
CREATE TABLE time_dimension (
time_key integer NOT NULL,
day_of_week integer NOT NULL,
day_of_month integer NOT NULL,
month integer NOT NULL,
quarter integer NOT NULL,
year integer NOT NULL
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);
CREATE TABLE sales_fact (
time_key integer NOT NULL,
product_key integer NOT NULL,
store_key integer NOT NULL,
amount_sold numeric(12,2) NOT NULL,
units_sold integer NOT NULL,
amount_cost numeric(12,2) NOT NULL
);
CREATE INDEX sales_fact_time ON sales_fact(time_key);
–
– Summary table - sales by time.
–
CREATE TABLE sales_summary_bytime (
time_key integer NOT NULL,
amount_sold numeric(15,2) NOT NULL,
units_sold numeric(12) NOT NULL,
amount_cost numeric(15,2) NOT NULL
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);
–
– Function and trigger to amend summarized column(s) on UPDATE, INSERT, DELETE.
–
CREATE OR REPLACE FUNCTION maint_sales_summary_bytime() RETURNS TRIGGER AS maintsalessummarybytime
DECLARE
delta_time_key integer;
delta_amount_sold numeric(15,2);
delta_units_sold numeric(12);
delta_amount_cost numeric(15,2);
BEGIN
-- Work out the increment/decrement amount(s).
IF (TG_OP = 'DELETE') THEN
delta_time_key = OLD.time_key;
delta_amount_sold = -1 * OLD.amount_sold;
delta_units_sold = -1 * OLD.units_sold;
delta_amount_cost = -1 * OLD.amount_cost;
ELSIF (TG_OP = 'UPDATE') THEN
-- forbid updates that change the time_key -
-- (probably not too onerous, as DELETE + INSERT is how most
-- changes will be made).
IF ( OLD.time_key != NEW.time_key) THEN
RAISE EXCEPTION 'Update of time_key : % -> % not allowed', OLD.time_key, NEW.time_key;
END IF;
delta_time_key = OLD.time_key;
delta_amount_sold = NEW.amount_sold - OLD.amount_sold;
delta_units_sold = NEW.units_sold - OLD.units_sold;
delta_amount_cost = NEW.amount_cost - OLD.amount_cost;
ELSIF (TG_OP = 'INSERT') THEN
delta_time_key = NEW.time_key;
delta_amount_sold = NEW.amount_sold;
delta_units_sold = NEW.units_sold;
delta_amount_cost = NEW.amount_cost;
END IF;
-- Insert or update the summary row with the new values.
<>
LOOP
UPDATE sales_summary_bytime
SET amount_sold = amount_sold + delta_amount_sold,
units_sold = units_sold + delta_units_sold,
amount_cost = amount_cost + delta_amount_cost
WHERE time_key = delta_time_key;
EXIT insert_update WHEN found;
BEGIN
INSERT INTO sales_summary_bytime (
time_key,
amount_sold,
units_sold,
amount_cost)
VALUES (
delta_time_key,
delta_amount_sold,
delta_units_sold,
delta_amount_cost
);
EXIT insert_update;
EXCEPTION
WHEN UNIQUE_VIOLATION THEN
-- do nothing
END;
END LOOP insert_update;
RETURN NULL;
END;
maintsalessummarybytime LANGUAGE plpgsql;
CREATE TRIGGER maint_sales_summary_bytime
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
FOR EACH ROW EXECUTE PROCEDURE maint_sales_summary_bytime();
INSERT INTO sales_fact VALUES(1,1,1,10,3,15);
INSERT INTO sales_fact VALUES(1,2,1,20,5,35);
INSERT INTO sales_fact VALUES(2,2,1,40,15,135);
INSERT INTO sales_fact VALUES(2,3,1,10,1,13);
SELECT * FROM sales_summary_bytime;
DELETE FROM sales_fact WHERE product_key = 1;
SELECT * FROM sales_summary_bytime;
UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;
(3)
目标:
当表alphas插入新行时,更新titles的alpha_at为NOW()
当表alphas删除行时,更新titles的alpha_at为NULL
1、安装plpgsql语言到数据库
createlang plpgsql DATABASE
2、建立一个返回为trigger的过程
CREATE OR REPLACE FUNCTION after_alphas_id() RETURNS trigger AS BODY
BEGIN
IF( TG_OP=’DELETE’ ) THEN
UPDATE titles SET alpha_at=null WHERE id=OLD.title_id;
ELSE
UPDATE titles SET alpha_at=NOW() WHERE id=NEW.title_id;
END IF;
RETURN NULL;
END;
BODY
LANGUAGE ‘plpgsql’;
3、创建触发器
CREATE TRIGGER after_alphas_id
AFTER INSERT OR DELETE
ON alphas
FOR EACH ROW
EXECUTE PROCEDURE after_alphas_id();
可以取得关系数据库的数据,比如Oracle,MySQL,ODBC.
还可以取得NOSQL数据库的数据。比如CouchDB,Redis
还可以直接访问text文本文件,csv文件。
还可以访问twitter的数据。等等。
可以说利用好这些功能后,我们就会减少很多关联数据的编码。
具体信息参考了wiki。
In 2003, a new extension called SQL/MED ("SQL Management of External Data") was added to the SQL standard. It is a standardized way of handling access to remote objects in SQL databases. In 2011, PostgreSQL 9.1 was released with a great support of this standard.
In a nutshell, you can now use various Foreign Data Wrappers (FDW) to connect a PostgreSQL Server to remote data stores. This page is an incomplete list of the Wrappers available right now. Another fdw list can be found at the PGXN website.
Please keep in mind that most of these wrappers are not officially supported by the PostgreSQL Global Development Group (PGDG) and that some of these projects are still in Beta version. Use carefully!
This extension implements a Foreign Data Wrapper for MySQL. It is supported on PostgreSQL 9.1 and above.
A wrapper for accessing Sybase and Microsoft SQL Server databases.
A wrapper for databases with an ODBC driver, including Oracle, DB2, Microsoft SQL Server, Sybase, Pervasive SQL, IBM Lotus Domino, MySQL, PostgreSQL and desktop database products such as FileMaker and Microsoft Access:
A wrapper for CouchDB
A wrapper for Redis
A CSV files wrapper. Delivered as an official extension of PostgreSQL 9.1
Another CSV wrapper
Fixed-width flat file wrapper
A wrapper fetching text messages from Twitter over the Internet and returning a table
Allows PostgreSQL to query an LDAP server and retrieve data from some pre-configured Organizational Unit
uses GPU devices to accelarate sequential scan on massive amount of records with complex qualifiers.
Reads files located in Amazon S3
Allows to query different web services:
Multicorn is an extension that allows FDWs to be written in Python
This fdw can be used to access data stored in any database supported by the sqlalchemy python toolkit.
This fdw can be used to access data stored in various files, in a filesystem. The files are looked up based on a pattern, and parts of the file's path are mapped to various columns, as well as the file's content itself.
This fdw can be used to access data stored in CSV files. Each column defined in the table will be mapped, in order, against columns in the CSV file.
This fdw can be used to access items from an rss feed.