【问题标题】:Insert multiple rows in SQL Server table with single INSERT / single triggering of trigger使用单次 INSERT/单次触发触发器在 SQL Server 表中插入多行
【发布时间】:2021-08-15 20:32:21
【问题描述】:

我想要什么

为了能够在 SQL Server 中使用 python 向表中插入多行,并且只有在插入所有行时才触发我的 INSTEAD OF INSERT 触发器。

背景

我的表上有一个INSTEAD OF INSERT 触发器,它对以前看不见的数据执行操作。

通常的操作模式是周期性地,一次插入多行。

当我从 python 插入时(使用pyodbc),如果我使用:

cursor.executemany('INSERT INTO table_name VALUE (?, ?, ...)', list_of_lists)

每行都会触发触发器 - 这不是我想要的。

我目前正在使用:

sql = 'INSERT INTO table_name VALUES (...),(...)...;'  # string built with format-strings
cursor.execute(sql)

理论上,这让我可以使用 SQL 注入,但我的环境已被锁定,因此不是真正的问题。
另外,我不知道查询字符串的最大限制是多少。 我尝试使用the method in this post 找出限制,但我得到一个空结果。所以我不确定是没有限制还是未知。

我一次插入的行数预计不会太疯狂(不超过 1-2k 行,少数列)。所以我不认为我会达到极限,但我不想以后感到惊讶。

然后有BULK INSERT 选项和FIRE_TRIGGERS 选项,但这需要将我的数据作为文件上传到运行SQL Server 的机器。这是我想避免的复杂性。

有更好的选择还是我目前的解决方案可以?

【问题讨论】:

  • 你考虑过使用表类型参数吗?
  • @Larnu 不,我没有。我的 SQL-fu 很弱。那会是什么样子?
  • @DavidBrowne-Microsoft 不正确。我在我的答案中演示了使用一个,并且在我引用的答案中有另一个答案确认添加了版本支持。

标签: python sql-server triggers pyodbc


【解决方案1】:

似乎您可以使用表类型参数来执行此操作。我会注意到我的 Python 不是很好,所以我不知道您是否可以在不使用存储过程的情况下 做到这一点。

不管怎样,首先让我们创建一个示例表:

CREATE TABLE dbo.SomeTable (ID int IDENTITY(1,1),
                            SomeString varchar(10),
                            SomeInt int,
                            SomeDate date,
                            SomeGUID uniqueidentifier);
GO

然后是样本类型。我故意省略了SomeGUID

CREATE TYPE dbo.SomeType AS table (SomeString varchar(10),
                                   SomeInt int,
                                   SomeDate date);

然后您需要创建一个接受上述TYPE 作为参数的过程。在下面,我为SomeGUID 定义了整个数据集的静态值,以证明所有行都插入到对最终结果集中的 proc 的一次调用中:

CREATE OR ALTER PROC dbo.SomeProc @SomeData dbo.SomeType READONLY AS
BEGIN

    DECLARE @UID uniqueidentifier = NEWID();
    
    INSERT INTO dbo.SomeTable (SomeString, SomeInt, SomeDate, SomeGUID)
    SELECT SomeString,
           SomeInt,
           SomeDate,
           @UID
    FROM @SomeData;
END;
GO

现在是蟒蛇。为此,我编写了下面的简单脚本,参考this answer 寻求帮助。我已经包含了完整的工作解决方案的整个脚本,但显然有些不适用于您的脚本,并且您可能没有使用环境文件:

#!/usr/bin/env python3
#Import the bare minimums for this to work
import pyodbc, os
from dotenv import load_dotenv

#Get the details from my environemental file
load_dotenv()
SQLServer = os.getenv('SQL_SERVER')
SQLDatabase = os.getenv('SQL_DATABASE')
SQLLogin = os.getenv('SQL_LOGIN')
SQLPassword = os.getenv('SQL_PASSWORD')

#Create the full connection string
SQLConnString = 'Driver={ODBC Driver 17 for SQL Server};Server=' + SQLServer + ';Database=' + SQLDatabase + ';UID='+ SQLLogin +';PWD=' + SQLPassword

#Define the data that is going to be inserted into the table.
MyData = [('abc',1,'20200527'),('def',2,'20210527')]
#Turn it into a tuple. explained in https://stackoverflow.com/a/61156422/2029983
params = (MyData,) 

#And then execute the procedure, passing params as the parameters; which is a table
with pyodbc.connect(SQLConnString,timeout=20) as sqlConn:
    with sqlConn.cursor() as cursor:
        cursor.execute('EXEC dbo.SomeProc ?;', params)
        sqlConn.commit()

然后,当我运行 SELECT * FROM dbo.SomeTable 时,我得到以下数据:

ID SomeString SomeInt SomeDate SomeGUID
1 abc 1 2020-05-27 945a3656-54ee-47a2-962c-7a34c7f50635
2 def 2 2021-05-27 945a3656-54ee-47a2-962c-7a34c7f50635

【讨论】:

  • SELECT 查询应该在没有存储过程的情况下工作。您只是发送带有参数的 TSQL 批处理。不过,仍然必须创建表格类型。
  • 但是,我个人不知道如何将数据定义到 TYPE 中,例如仅使用 SELECT @DavidBrowne-Microsoft .cursor.execute('INSERT INTO dbo.SomeTable(SomeString,SomeInt,SomeDate) SELECT * FROM ?;', params) 是行不通的。
  • 有趣。在 .NET 中,您必须在客户端的 SqlParameter 中指定表类型。也许 pyodbc 正在查看存储过程参数以确定表类型。
  • 这就是我的假设,@DavidBrowne-Microsoft。我没有对上述确认进行跟踪;如果我的好奇心变得更好,我可能会稍后。
  • 是的,exactly 是做什么的,@DavidBrowne-Microsoft。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多