基于复杂算法创建CHECK约束

问题描述 投票:0回答:3

个人身份代码由一串表示个人出生日期的数字、一个个性化字符串和一个控制字符组成。
示例: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

约束没有按预期发挥作用。我的逻辑有什么问题吗?

sql postgresql database-design plpgsql check-constraints
3个回答
0
投票

-- 创建一个函数来计算控制字符

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)
    );

0
投票

您要求在插入之前验证列值,如果失败则返回某种错误,在这种情况下,您可以创建一个检查约束,该约束调用自定义函数来验证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不符合条件,插入将失败并出现约束违规错误。

希望这是您正在寻找的


0
投票

功能

您可以修复您的功能(用

!!!
标记的错误):

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)
);

可以甚至直接使用我的压缩函数中的表达式,而根本不需要创建函数。

无论哪种方式,都要记住记录你在做什么以及为什么这样做。

© www.soinside.com 2019 - 2024. All rights reserved.