【问题标题】:Using SQL Server stored procedures from Python (pyodbc)从 Python 使用 SQL Server 存储过程 (pyodbc)
【发布时间】:2023-11-22 12:14:02
【问题描述】:

我有一个存储过程,代码:

DECLARE @RC int 
DECLARE @id varchar(13) 
DECLARE @pw varchar(13) 
DECLARE @depart varchar(32) 
DECLARE @class varchar(12) 
DECLARE @name varchar(12) 
DECLARE @birthday varchar(10) 
DECLARE @grade int 
DECLARE @subgrade int 
SELECT @id = 'test' 
SELECT @pw = '12345' 
SELECT @depart = 'none' 
SELECT @class = 'GM' 
SELECT @name = 'name' 
SELECT @birthday = 'None' 
SELECT @grade = 3 
SELECT @subgrade = 2 
EXEC @RC = [my_database].[dbo].[my_table] @id, @pw, @depart, @class, @name, @birthday, @grade, @subgrade 
DECLARE @PrnLine nvarchar(4000) 
PRINT 'Stored Procedure: my_database.dbo.my_table' 
SELECT @PrnLine = ' Return Code = ' + CONVERT(nvarchar, @RC)

如何使用此过程进行原始 sql 查询来创建帐户? 我正在使用烧瓶和 pyodbc。

【问题讨论】:

    标签: python sql-server pyodbc


    【解决方案1】:

    来自pyodbc documentation

    要立即调用存储过程,请使用数据库可识别的格式或使用ODBC call escape format 将调用传递给execute 方法。 (然后,ODBC 驱动程序将重新格式化调用以匹配给定的数据库。)

    对于 SQL Server,您可以使用如下内容:

    # SQL Server format
    cursor.execute("exec sp_dosomething(123, 'abc')")
    
    # ODBC format
    cursor.execute("{call sp_dosomething(123, 'abc')}")
    

    所以调用你的程序

    id_ = 'test' 
    pw = '12345' 
    depart = 'none' 
    class_ = 'GM' 
    name = 'name' 
    birthday = 'None' 
    grade = 3 
    subgrade = 2 
    
    sql = 'exec [my_database].[dbo].[my_table](?, ?, ?, ?, ?, ?, ?, ?)'
    values = (id_, pw, depart, class_, name, birthday, grade, subgrade)
    
    cursor.execute(sql, (values))
    

    【讨论】:

    • 这里有个问题:sqlalchemy.exc.ProgrammingError: (ProgrammingError) ('42000', "[42000] [Microsoft][ODBC SQL Server Driver][SQL Server]'@P1'附近的语法不正确. (102) (SQLExecDirectW)") 'exe my_procedure(?, ?, ?)' ('5215125', '125151', '31231')
    • 这看起来像是存储过程中的语法错误。 @P1 是真实的一部分吗?
    • 谢谢,sql = 'exec [my_database].[dbo].[my_table] ?, ?, ?, ?, ?, ?, ?, ?'工作正常(不带括号)。
    • 如果名称中没有空格,则没有括号有效,如果有空格,则需要括号.....
    • 大家好,如果我的存储过程有一个SELECT IDENT_CURRENT('TABLE')作为ID,如何获取返回值,我想获取ID。
    【解决方案2】:

    接受的答案没有解决从存储过程中捕获返回值的问题,可以这样做:

    id_ = 'test' 
    pw = '12345' 
    depart = 'none' 
    class_ = 'GM' 
    name = 'name' 
    birthday = 'None' 
    grade = 3 
    subgrade = 2 
    
    sql = """\
    SET NOCOUNT ON;
    DECLARE @RC int;
    EXEC @RC = [my_database].[dbo].[my_sp] ?, ?, ?, ?, ?, ?, ?, ?;
    SELECT @RC AS rc;
    """
    values = (id_, pw, depart, class_, name, birthday, grade, subgrade)
    cursor.execute(sql, values)
    rc = cursor.fetchval()  # pyodbc convenience method similar to cursor.fetchone()[0]
    

    【讨论】:

      【解决方案3】:

      不要忘记在存储过程中设置 SET NOCOUNT ON。

      【讨论】:

      • 这实际上帮助我们处理了 SP 不工作的问题。我仍然不明白这是一个因素
      【解决方案4】:

      为了清晰起见,Gord 的另一种答案是使用 OUTPUT 和命名参数(将在存储过程中定义)。

      id_ = 'test' 
      pw = '12345' 
      depart = 'none' 
      class_ = 'GM' 
      name = 'name' 
      birthday = 'None' 
      grade = 3 
      subgrade = 2 
      
      sql = """\
      DECLARE @RC int;
      EXEC [my_database].[dbo].[my_sp] @RC OUTPUT, @id_=?, @pw=?, @depart=?, @class_=?, @name=?, @birthday=?, @grade=?, @subgrade=?;
      SELECT @RC AS rc;
      """
      values = (id_, pw, depart, class_, name, birthday, grade, subgrade)
      cursor.execute(sql, values)
      rc = cursor.fetchval()
      

      【讨论】:

        【解决方案5】:

        通过你的连接初始化一个游标,可以直接调用sp如下

        sql = " exec your_SP @codemp = ?, @fecha = ? "
        prm = (dict['param1'], dict['param2'])
        cursor.execute(qry, params)
        

        【讨论】:

          【解决方案6】:

          在到处搜索此解决方案后,它找不到简化版本。所有结果似乎都使这本应该很容易做到的事情变得过于复杂。这是我的解决方案。

           import pyodbc
           import pandas as pd
           import datetime as d
          
          
            conn = pyodbc.connect('Driver=;'
                            'Server=;'
                            'Database=;'
                            'UID=;'
                            'PWD=;')
          
          
               # define parameters to be passed in and out
          
               quarter_date = d.date(year=2020, month=10, day=1)
          
               SQL = r'exec TERRITORIES_SP @quarterStart = ' + "'" + str(quarter_date) + "'"
          
               print(SQL)
          
               try:
                   cursor = conn.cursor()
                    cursor.execute(SQL)
                    cursor.close()
                    conn.commit()
              finally:
                    conn.close()
          

          【讨论】:

          • 通过使用动态 SQL,您将面临 SQL 注入漏洞。
          • 谢谢。有什么更好的方法?
          • 类似sql = r'exec TERRITORIES_SP @quarterStart = ?' 后跟cursor.execute(sql, str(quarter_date))。我承认在这种情况下 SQL 注入风险很低,但将动态 SQL 推广为“简化”解决方案仍然是一种糟糕的形式。
          【解决方案7】:

          对于 MSSQL,正确的格式是:

          SQL = 'exec sp_UpdateUserGoogleAuthenticated ''?'', ''?'''
          

          尝试在 SQL 查询窗口中运行 MSSQL 中的存储过程,每次都会失败,并且 () 围绕?分数。如果您转义单引号,它将允许变量中包含空格。

          【讨论】: