在空表中插入时psycopg2 UniqueViolation错误

时间:2019-06-07 12:26:26

标签: python python-3.x postgresql psycopg2

假设我的用户表定义如下:

CREATE TABLE user (
    id SERIAL UNIQUE,
    email character varying(254) NOT NULL UNIQUE,
    username character varying(254) NOT NULL UNIQUE
)

并以这种方式触发它:

CREATE OR REPLACE FUNCTION insert_username() RETURNS                       
TRIGGER AS $insert_username$                                               
    BEGIN                                                                  
        NEW.username := SPLIT_PART(NEW.email, '@', 1);                     
        RETURN NEW;                                                        
    END;                                                                   
$insert_username$ LANGUAGE plpgsql;                                        
CREATE TRIGGER insert_username BEFORE INSERT OR UPDATE                     
ON user FOR EACH ROW EXECUTE                           
PROCEDURE insert_username();

然后使用psycopg2尝试用来自另一个数据库的数据填充该表。这是我制作的迁移模块中的代码片段以及负责此操作的查询字符串:

from psycopg2.extras import execute_values
from data_migration.database import cursor

query = 'INSERT INTO user("id", "email") VALUES %s'
values = [[1, 'example1@email.com'], [2, 'example2@email.com']]

execute_values(cursor, query, values)

在我实施此触发器之前,它一直按预期工作。现在,当我从postgres shell插入单行或使用psycopg2时,它仍然可以正常工作,但是即使在表完全为空的情况下,execute_values也失败,并给我UniqueViolation错误:

UniqueViolation: duplicate key value violates unique constraint "user_username_key"
DETAIL:  Key (username)=(kjhmgfd) already exists.

围绕它有更多的字段和代码,当然,我可以摆脱此触发器,并在python端执行相同的操作。但是我真的很想保留它。也许我遗漏了一些明显的东西,但是我花了几个小时而仍然不知道为什么会发生。如果有人可以帮助我找到答案,那将非常有帮助。谢谢!

1 个答案:

答案 0 :(得分:0)

似乎这是我对源数据的关注不足。那里有一些用户具有相同的本地部分,但电子邮件中的域不同。感谢Jeremy指出。最终以这种方式修改了触发器:

CREATE OR REPLACE FUNCTION insert_username() RETURNS                            
TRIGGER AS $insert_username$                                                    
    DECLARE                                                                     
        counter INTEGER := 0;                                                   
        new_username VARCHAR(256);                                              
        attempt_username VARCHAR(256);                                          
        is_exists BOOLEAN;                                                      
    BEGIN                                                                       
        CREATE OR REPLACE FUNCTION is_username_exists(un VARCHAR(256))          
            RETURNS BOOLEAN AS $$                                               
            DECLARE                                                             
                is_row_exists BOOLEAN;                                          
            BEGIN                                                               
                SELECT EXISTS(SELECT * FROM user WHERE username=un) INTO is_row_exists;                          
                RETURN is_row_exists;                                           
            END;                                                                
        $$ LANGUAGE plpgsql;                                                    

        new_username := SPLIT_PART(NEW.email, '@', 1);                          
        attempt_username := new_username;                                       

        LOOP                                                                    
            SELECT is_username_exists(attempt_username) INTO is_exists;         
            EXIT WHEN is_exists IS FALSE;                                       
            counter := counter + 1;                                             
            attempt_username := new_username || counter;                        
        END LOOP;                                                               

        NEW.username := attempt_username;                                       

        RETURN NEW;
    END;                                                                        
$insert_username$ LANGUAGE plpgsql;                                             
CREATE TRIGGER insert_username BEFORE INSERT OR UPDATE                          
ON user FOR EACH ROW EXECUTE PROCEDURE insert_username();