插入空表时出现 psycopg2 UniqueViolation 错误

Posted

技术标签:

【中文标题】插入空表时出现 psycopg2 UniqueViolation 错误【英文标题】:psycopg2 UniqueViolation error while inserting in empty table 【发布时间】:2019-06-07 12:26:26 【问题描述】:

假设我有这样定义的用户表:

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 端做同样的事情。但我真的很想保留它。也许我遗漏了一些明显的东西,但我已经花了几个小时在上面,但仍然不知道它为什么会发生。如果有人可以帮助我找到它,那将非常有帮助。谢谢!

【问题讨论】:

从错误中,看起来可能有多个用户使用 kjhmgfd@* 。你能检查源数据吗? @Jeremy 是的,就是这样。谢谢 【参考方案1】:

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

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

【讨论】:

以上是关于插入空表时出现 psycopg2 UniqueViolation 错误的主要内容,如果未能解决你的问题,请参考以下文章

使用 psycopg2 在 python 中执行查询时出现“ProgrammingError:在或附近出现语法错误”

在 Windows 上安装 psycopg2 时出错

在 unix 上为 python 3.5 安装 psycopg2 包

使用 Python 在 Flask 中创建端点时出现问题 [重复]

在 Mac OS 10.6 上安装 psycopg2 时出错

当尝试使用 psycopg2 模块通过 python 连接到 redshift 时,会显示以下错误