我必须使用表变量参数调用MS SQLServer存储过程。
/* Declare a variable that references the type. */
DECLARE @TableVariable AS [AList];
/* Add data to the table variable. */
INSERT INTO @TableVariable (val) VALUES ('value-1');
INSERT INTO @TableVariable (val) VALUES ('value-2');
EXEC [dbo].[sp_MyProc]
@param = @TableVariable
在SQL Sv Management工作室中运行良好。我在python中使用PyOdbc尝试了以下内容:
cursor.execute("declare @TableVariable AS [AList]")
for a in mylist:
cursor.execute("INSERT INTO @TableVariable (val) VALUES (?)", a)
cursor.execute("{call dbo.sp_MyProc(@TableVariable)}")
出现以下错误:错误42000:必须声明表变量。变量不能在不同的执行步骤中存活。 我也尝试过:
sql = "DECLARE @TableVariable AS [AList]; "
for a in mylist:
sql = sql + "INSERT INTO @TableVariable (val) VALUES ('{}'); ".format(a)
sql = sql + "EXEC [dbo].[sp_MyProc] @param = @TableVariable"
cursor.execute(sql)
出现以下错误:无结果。以前的SQL不是查询。
没有机会了sql = sql + "{call dbo.sp_MyProc(@TableVariable)}"
有人知道如何使用Pyodbc处理这个问题吗?
答案 0 :(得分:4)
我相信这个错误与sql忘记表变量无关。我最近经历过这个问题,问题是如果SP还返回受影响事物的计数,pyodbc不知道如何从存储过程中获取结果集。
在我的情况下,解决这个问题的方法是在SP的开头简单地设置“SET NOCOUNT”。
我希望这会有所帮助。
答案 1 :(得分:1)
现在问题的根源是SQL Server variable具有定义的批次的范围。对cursor.execute的每次调用都是一个单独的批处理,即使他们在同一笔交易中。
有几种方法可以解决这个问题。最直接的方法是重写Python代码,以便将所有内容作为单个批处理发送。 (我在我的测试服务器上对此进行了测试,只要您添加了set nocount,或者使用nextset在中间结果上执行步骤,它就可以正常工作。)
更间接的方法是重写过程以查找临时表而不是表变量,然后只创建并填充临时表而不是表变量。未在存储过程中创建的temp table具有在其中创建的会话的范围。
答案 2 :(得分:0)
我不确定这是否有效且我无法测试它,因为我没有MS SQL Server,但你是否尝试过在一个语句中执行所有内容:
cursor.execute("""
DECLARE @TableVariable AS [AList];
INSERT INTO @TableVariable (val) VALUES ('value-1');
INSERT INTO @TableVariable (val) VALUES ('value-2');
EXEC [dbo].[sp_MyProc] @param = @TableVariable;
""");
答案 3 :(得分:0)
我有同样的问题,但这里的答案都没有解决。我无法使“SET NOCOUNT ON”工作,我也无法使用表变量进行单个批处理操作。什么工作是分两批使用临时表,但它整天都找到了正确的语法。下面的代码在第一批中创建并填充临时表,然后在第二批中,它使用数据库名称执行存储过程,然后在存储过程名称之前使用两个点。此语法对于避免错误“找不到存储过程'x'非常重要。(2812)(SQLExecDirectW))”。
def create_incidents(db_config, create_table, columns, tuples_list, upg_date):
"""Executes trackerdb-dev mssql stored proc.
Args:
config (dict): config .ini file with mssqldb conn.
create_table (string): temporary table definition to be inserted into 'CREATE TABLE #TempTable ()'
columns (tuple): columns of the table table into which values will be inserted.
tuples_list (list): list of tuples where each describes a row of data to insert into the table.
upg_date (string): date on which the items in the list will be upgraded.
Returns:
None
"""
sql_create = """IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
DROP TABLE #TempTable;
CREATE TABLE #TempTable ({});
INSERT INTO #TempTable ({}) VALUES {};
"""
columns = '"{}"'.format('", "'.join(item for item in columns))
# this "params" variable is an egregious offense against security professionals everywhere. Replace it with parameterized queries asap.
params = ', '.join([str(tupl) for tupl in tuples_list])
sql_create = sql_create.format(
create_table
, columns
, params)
msconn.autocommit = True
cur = msconn.cursor()
try:
cur.execute(sql_create)
cur.execute("DatabaseName..TempTable_StoredProcedure ?", upg_date)
except pyodbc.DatabaseError as err:
print(err)
else:
cur.close()
return
create_table = """
int_column int
, name varchar(255)
, datacenter varchar(25)
"""
create_incidents(
db_config = db_config
, create_table = create_table
, columns = ('int_column', 'name', 'datacenter')
, cloud_list = tuples_list
, upg_date = '2017-09-08')
存储过程使用IF OBJECT_ID('tempdb..#TempTable') IS NULL
语法来验证是否已创建临时表。如果有,则程序从中选择数据并继续。如果尚未创建临时表,则proc将中止。这会强制存储过程使用在存储过程本身之外但在同一会话中创建的#TempTable的副本。 pyodbc会话一直持续到游标或连接关闭,而pyodbc创建的临时表具有整个会话的范围。
IF OBJECT_ID('tempdb..#TempTable') IS NULL
BEGIN
-- #TempTable gets created here only because SQL Server Management Studio throws errors if it isn't.
CREATE TABLE #TempTable (
int_column int
, name varchar(255)
, datacenter varchar(25)
);
-- This error is thrown so that the stored procedure requires a temporary table created *outside* the stored proc
THROW 50000, '#TempTable table not found in tempdb', 1;
END
ELSE
BEGIN
-- the stored procedure has now validated that the temporary table being used is coming from outside the stored procedure
SELECT * FROM #TempTable;
END;
最后,请注意“tempdb”不是占位符,就像我第一次看到它时所想的那样。 “tempdb”是一个实际的MS SQL Server数据库系统对象。
答案 4 :(得分:0)
设置connection.autocommit = True
并仅使用cursor.execute()
一次,而不是多次。您传递给cursor.execute()
的SQL字符串必须包含所有3个步骤:
这三个步骤之间不需要分号。
这是一个功能齐全的演示。我不理会参数传递,因为它无关紧要,但记录下来也可以正常工作。
SQL安装程序(提前执行)
CREATE TYPE dbo.type_MyTableType AS TABLE(
a INT,
b INT,
c INT
)
GO
CREATE PROCEDURE dbo.CopyTable
@MyTable type_MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT * INTO MyResultTable FROM @MyTable
END
python
import pyodbc
CONN_STRING = (
'Driver={SQL Server Native Client 11.0};'
'Server=...;Database=...;UID=...;PWD=...'
)
class DatabaseConnection(object):
def __init__(self, connection_string):
self.conn = pyodbc.connect(connection_string)
self.conn.autocommit = True
self.cursor = self.conn.cursor()
def __enter__(self):
return self.cursor
def __exit__(self, *args):
self.cursor.close()
self.conn.close()
sql = (
'DECLARE @MyTable type_MyTableType'
'\nINSERT INTO @MyTable VALUES'
'\n(11, 12, 13),'
'\n(21, 22, 23)'
'\nEXEC CopyTable @MyTable'
)
with DatabaseConnection(CONN_STRING) as cursor:
cursor.execute(sql)
如果要将SQL分散到对cursor.execute()
的多个调用中,则需要使用一个临时表。请注意,在这种情况下,您仍然需要connection.autocommit = True
。
答案 5 :(得分:0)
正如Timothy所指出的,要抓住的地方是使用nextset()。
我发现的是,当您执行execute()多语句查询时,pyodbc会检查(是否存在语法错误)并仅执行批处理中的第一个语句,而不执行整个批处理,除非您明确指定nextset()。
说您的查询是:
ClientBuilder/WebTarget)
您的结果是:
cursor.execute('select 1 '
'select 1/0')
print(cursor.fetchall())
,但是只要您通过命令指示它进一步移至语法上错误的部分,则该批处理:
[(1, )]
有它:
cursor.nextset()
因此解决了我在多语句查询中使用变量表时遇到的问题。