【问题标题】:SQLAlchemy INSERT stmt with OUTPUT return multiple rowsSQLAlchemy INSERT stmt with OUTPUT 返回多行
【发布时间】:2021-12-19 04:23:56
【问题描述】:

当我使用 sqlalchemy 插入多行时,我没有得到插入的 id 值。

例如,当我在 MS SQL Server 中执行普通 SQL 时,它会在结果集中显示 2 行(我插入的记录...1,2)。

INSERT INTO dbo.emp (id, fname, lname, dob)    
OUTPUT inserted.id     
VALUES (1, 'Test1','Rec1','01/01/1990'), (2, 'Test2','Rec2','01/01/1990')    

当我使用下面的代码在 sqlalchemy 中执行相同的查询时,打印语句只显示值 2(不是 1)。看起来结果集已插入最后一行。下面的代码有什么问题吗?

try:    
    sql = """\    
    INSERT INTO dbo.emp (id, fname, lname, dob)   
    OUTPUT inserted.id    
    VALUES (:id, :fname, :lname, :dob)    
    """    
    stmt = text(sql)    
    data = [    
         {'id': 1, 'fname':'Test1', 'lname':'rec1', 'dob':'05/05/2000'}    
        ,{'id': 2, 'fname': 'Test2', 'lname': 'rec2', 'dob': '05/05/2001'}    
    ]    
    result = conn.execute(stmt, data)    
    for id in result:    
        print(id)    
    
    conn.commit()    
except exc.IntegrityError as e:    
    print('Code3 - Integrity error raised',  e)    
    conn.rollback()    
except exc.SQLAlchemyError as e:    
    print('Code3 - Something else went wrong', e)     
    conn.rollback()

【问题讨论】:

    标签: python sql-server sqlalchemy pyodbc


    【解决方案1】:

    对于像字典列表这样的多行参数数据,SQLAlchemy 使用 pyodbc 的 .executemany() 方法,该方法不适合从 OUTPUT 子句返回多个结果。

    如果您使用的是 SQL Server 2016+,您可以使用 OPENJSON 并传递表示多行数据的单个字符串来获取结果:

    import json
    
    import sqlalchemy as sa
    
    engine = sa.create_engine("mssql+pyodbc://@mssqlLocal")
    
    with engine.begin() as conn:
        conn.exec_driver_sql("DROP TABLE IF EXISTS emp")
        conn.exec_driver_sql(
            "CREATE TABLE emp ("
                "id int identity primary key, "
                "fname nvarchar(50), "
                "lname nvarchar(50)"
            ")"
        )
    
    sql = """\
    INSERT INTO emp (fname, lname)
    OUTPUT inserted.id
    SELECT fname, lname FROM 
    OPENJSON(:json_str)
        WITH (
            fname nvarchar(50) '$.fname',
            lname nvarchar(50) '$.lname'
        )
    """
    data = [
        {"fname": "Homer", "lname": "Simpson"},
        {"fname": "Ned", "lname": "Flanders"},
    ]
    with engine.begin() as conn:
        results = conn.execute(
            sa.text(sql), {"json_str": json.dumps(data)}
        ).fetchall()
        print(results)  # [(1,), (2,)]
    

    对于旧版本的 SQL Server,您可以将行数据上传到临时表,然后使用

    INSERT INTO emp (fname, lname)
    OUTPUT inserted.id
    SELECT fname, lname FROM #temp_table
    

    【讨论】:

    • 嗨,Gord,当我们在 SQL Management Studio 中执行查询时,它显示 2 行带有 id 值。而且,它显示 2 行受影响(插入)。当我打印 results.rowcount 时,它显示 -1(而不是 2)。但无论如何,你的 json 方法确实插入并取回了 2 个 ids 值,这很棒。
    • 那是因为executemany() 没有使用 TVC(复合 VALUES 子句)构建 INSERT 语句。它使用一个简单的 VALUES 子句并(有效地)多次执行该语句。
    【解决方案2】:

    我很确定 conn.execute 会运行两次插入命令。看,您的 sql 命令是一个数据行的插入,因此它不会转换为:

    INSERT INTO dbo.emp (id, fname, lname, dob)    
    OUTPUT inserted.id     
    VALUES (1, 'Test1','Rec1','01/01/1990'), (2, 'Test2','Rec2','01/01/1990')   
    

    相反,插入的 2 个命令是连续运行的,而 sqlalchemy 似乎只保留了第二个结果。 (我试图深入研究 sqlalchemy 代码,但找不到它发生的地方)。

    如果你真的想发挥 sqlalchemy 的优势,我建议至少使用 sqlalchemy 查询语言,让 sqlalchemy 构造你的 sql 命令。查找 SQL 表达式语言教程以开始使用。

    【讨论】:

    • 嗨 Petr,我尝试了 sql exrpession...它没有显示我插入的 2 个 id 值。但它确实插入了 2 行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多