个人身份代码由一串表示个人出生日期的数字、一个个性化字符串和一个控制字符组成。
示例:150600A905P
• 150600 = Date of birth
• A = the character in the middle
• 905 = the individualized string
• P = Control character
控制字符可以是数字或字母。 计算公式是将出生日期和个性化字符串组成的九位字符串的值除以31,然后除法的余数确定控制字符。余数为0->30,对应的控制字符为[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, H, J, K、L、M、N、P、R、S、T、U、V、W、X、Y]
中间的字符:中间字符的约定是:
• 1800 年代的出生年份 → +(加号), • 1900 年代的出生年份 → -(减号)和 Y、X、W、V、U(大写字母) • 2000 年代的出生年份 → A、B、C、D、E、F(大写字母)。
如何在 Postgres 中为志愿者表创建检查约束,并使用在插入新志愿者时验证志愿者 ID 的函数。如果满足以下条件,则 ID 有效:
- Length = 11 characters
- The 7th character (separator) is one of the following: +, -, A, B, C, D, E, F, X, Y, W, V, U
- The correct control character is used
约束没有按预期发挥作用。我的逻辑有什么问题吗?
-- 创建一个函数来计算控制字符
CREATE OR REPLACE FUNCTION calculate_control_character(volunteer_id VARCHAR)
RETURNS CHAR AS $$
DECLARE
dob_individualized VARCHAR(9);
dob VARCHAR(6);
mid_character CHAR(1);
control_character CHAR(1);
remainder INT;
BEGIN
-- Extract date of birth and individualized string from the volunteer ID
dob := SUBSTRING(volunteer_id FROM 1 FOR 6);
dob_individualized := SUBSTRING(volunteer_id FROM 7 FOR 3);
-- Determine the mid-character based on the year of birth
CASE
WHEN dob BETWEEN '000001' AND '999999' THEN mid_character := '+';
WHEN dob BETWEEN '00A001' AND '99F999' THEN mid_character := '-';
ELSE mid_character := '';
END CASE;
-- Concatenate the date of birth and individualized string
dob := dob || dob_individualized;
-- Calculate the remainder
remainder := CAST(dob AS INT) % 31;
-- Determine the control character
CASE remainder
WHEN 10 THEN control_character := 'A';
WHEN 11 THEN control_character := 'B';
WHEN 12 THEN control_character := 'C';
WHEN 13 THEN control_character := 'D';
WHEN 14 THEN control_character := 'E';
WHEN 15 THEN control_character := 'F';
WHEN 16 THEN control_character := 'H';
WHEN 17 THEN control_character := 'J';
WHEN 18 THEN control_character := 'K';
WHEN 19 THEN control_character := 'L';
WHEN 20 THEN control_character := 'M';
WHEN 21 THEN control_character := 'N';
WHEN 22 THEN control_character := 'P';
WHEN 23 THEN control_character := 'R';
WHEN 24 THEN control_character := 'S';
WHEN 25 THEN control_character := 'T';
WHEN 26 THEN control_character := 'U';
WHEN 27 THEN control_character := 'V';
WHEN 28 THEN control_character := 'W';
WHEN 29 THEN control_character := 'X';
WHEN 30 THEN control_character := 'Y';
ELSE control_character := CAST(remainder AS CHAR(1));
END CASE;
RETURN control_character;
END;
$$ LANGUAGE plpgsql;
-- 为 Volunteer 表添加检查约束
ALTER TABLE Volunteer
ADD CONSTRAINT CHK_ValidVolunteerID CHECK (
LENGTH(ID) = 11 AND
SUBSTRING(ID FROM 7 FOR 1) IN ('+', '-', 'A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y', 'W', 'V', 'U') AND
SUBSTRING(ID FROM 11 FOR 1) = calculate_control_character(ID)
);
您要求在插入之前验证列值,如果失败则返回某种错误,在这种情况下,您可以创建一个检查约束,该约束调用自定义函数来验证volunteer_id: 你的桌子看起来有点像:
CREATE TABLE volunteer (
id SERIAL PRIMARY KEY,
volunteer_id TEXT NOT NULL,
CONSTRAINT chk_volunteer_id CHECK (validate_volunteer_id(volunteer_id))
);
如果表已经存在则:
ALTER TABLE volunteer
ADD CONSTRAINT chk_volunteer_id CHECK (validate_volunteer_id(volunteer_id));
在运行此命令之前,您必须检查所有现有数据是否已满足条件,否则更改语句将失败。
现在关于自定义功能:
CREATE OR REPLACE FUNCTION validate_volunteer_id(vol_id TEXT)
RETURNS BOOLEAN AS $$
DECLARE
dob TEXT;
individualized_string TEXT;
control_character TEXT;
separator CHAR;
nine_digit_string BIGINT;
remainder INTEGER;
valid_control_characters CHAR[];
BEGIN
IF LENGTH(vol_id) <> 11 THEN
RETURN FALSE;
END IF;
dob := SUBSTRING(vol_id FROM 1 FOR 6);
separator := SUBSTRING(vol_id FROM 7 FOR 1);
individualized_string := SUBSTRING(vol_id FROM 8 FOR 3);
control_character := SUBSTRING(vol_id FROM 11 FOR 1);
IF separator NOT IN ('+', '-', 'A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y', 'W', 'V', 'U') THEN
RETURN FALSE;
END IF;
nine_digit_string := (dob || individualized_string)::BIGINT;
remainder := nine_digit_string % 31;
valid_control_characters := ARRAY['0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'H', 'J', 'K', 'L',
'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y'];
IF control_character <> valid_control_characters[remainder + 1] THEN
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
现在,当您尝试插入新行时,它将由 validate_volunteer_id 函数进行验证。如果volunteer_id不符合条件,插入将失败并出现约束违规错误。
希望这是您正在寻找的
您可以修复您的功能(用
!!!
标记的错误):
CREATE OR REPLACE FUNCTION pg_temp.org(volunteer_id VARCHAR)
RETURNS CHAR AS $$
DECLARE
dob_individualized VARCHAR(9);
dob VARCHAR(9); -- VARCHAR(6) was too short !!!
mid_character CHAR(1);
control_character CHAR(1);
remainder INT;
BEGIN
-- Extract date of birth and individualized string from the volunteer ID
dob := SUBSTRING(volunteer_id FROM 1 FOR 6);
dob_individualized := SUBSTRING(volunteer_id FROM 8 FOR 3); -- offset 7 was wrong !!!
-- Determine the mid-character based on the year of birth
CASE
WHEN dob BETWEEN '000001' AND '999999' THEN mid_character := '+';
WHEN dob BETWEEN '00A001' AND '99F999' THEN mid_character := '-';
ELSE mid_character := '';
END CASE;
-- Concatenate the date of birth and individualized string
dob := dob || dob_individualized;
-- Calculate the remainder
remainder := CAST(dob AS INT) % 31;
-- Determine the control character
CASE remainder
WHEN 10 THEN control_character := 'A';
WHEN 11 THEN control_character := 'B';
WHEN 12 THEN control_character := 'C';
WHEN 13 THEN control_character := 'D';
WHEN 14 THEN control_character := 'E';
WHEN 15 THEN control_character := 'F';
WHEN 16 THEN control_character := 'H';
WHEN 17 THEN control_character := 'J';
WHEN 18 THEN control_character := 'K';
WHEN 19 THEN control_character := 'L';
WHEN 20 THEN control_character := 'M';
WHEN 21 THEN control_character := 'N';
WHEN 22 THEN control_character := 'P';
WHEN 23 THEN control_character := 'R';
WHEN 24 THEN control_character := 'S';
WHEN 25 THEN control_character := 'T';
WHEN 26 THEN control_character := 'U';
WHEN 27 THEN control_character := 'V';
WHEN 28 THEN control_character := 'W';
WHEN 29 THEN control_character := 'X';
WHEN 30 THEN control_character := 'Y';
ELSE control_character := CAST(remainder AS CHAR(1));
END CASE;
RETURN control_character;
END;
$$ LANGUAGE plpgsql;
但大部分只是噪音。归结为:
CREATE OR REPLACE FUNCTION calculate_control_character(volunteer_id text)
RETURNS text
LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT
RETURN ('[0:30]={0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,H,J,K,L,M,N,P,R,S,T,U,V,W,X,Y}'::text[])
[(left($1, 6) || substring($1, 8, 3))::int % 31];
基本上根据用公式计算的位置来选择数组元素。
使用标准 SQL 函数的简短语法。参见:
添加适当的功能标签。在这种情况下:
IMMUTABLE
,PARALLEL SAFE
,STRICT
。前两个对于性能至关重要!参见:
Postgres 数组默认从 1 开始。模运算符
%
返回从 0 开始的值。您可以添加 1,或者使用自定义索引数组(更便宜)。参见:
不要使用数据类型
char(n)
。参见:
CHECK
约束也便宜一点,更短:
ALTER TABLE volunteer
ADD CONSTRAINT chk_validvolunteerid CHECK (
length(id) = 11
AND substring(id, 7, 1) = ANY ('{+,-,A,B,C,D,E,F,X,Y,W,V,U}')
AND right(id, 1) = calculate_control_character(id)
);
您可以甚至直接使用我的压缩函数中的表达式,而根本不需要创建函数。
无论哪种方式,都要记住记录你在做什么以及为什么这样做。