Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

So rufen Sie eine gespeicherte Prozedur mit SQLAlchemy auf, die einen benutzerdefinierten Tabellenparameter erfordert

Es gibt einen Treiber, der TVPs wirklich unterstützt:Pytds . Es wird nicht offiziell unterstützt, aber es gibt eine Dialektimplementierung eines Drittanbieters dafür:sqlalchemy-pytds . Wenn Sie sie verwenden, können Sie Ihre gespeicherte Prozedur folgendermaßen aufrufen:

In [1]: engine.execute(DDL("CREATE TYPE [dbo].[StringTable] AS TABLE([strValue] [nvarchar](max) NULL)"))
Out[1]: <sqlalchemy.engine.result.ResultProxy at 0x7f235809ae48>

In [2]: engine.execute(DDL("CREATE PROC test_proc (@pArg [StringTable] READONLY) AS BEGIN SELECT * FROM @pArg END"))
Out[2]: <sqlalchemy.engine.result.ResultProxy at 0x7f2358027b70>

In [3]: arg = ['Name One', 'Name Two']

In [4]: import pytds

In [5]: tvp = pytds.TableValuedParam(type_name='StringTable',
   ...:                              rows=((x,) for x in arg))

In [6]: engine.execute('EXEC test_proc %s', (tvp,))
Out[6]: <sqlalchemy.engine.result.ResultProxy at 0x7f294e699e10>

In [7]: _.fetchall()
Out[7]: [('Name One',), ('Name Two',)]

Auf diese Weise können Sie potenziell große Datenmengen als Parameter übergeben:

In [21]: tvp = pytds.TableValuedParam(type_name='StringTable',
    ...:                              rows=((str(x),) for x in range(100000)))

In [22]: engine.execute('EXEC test_proc %s', (tvp,))
Out[22]: <sqlalchemy.engine.result.ResultProxy at 0x7f294c6e9f98>

In [23]: _.fetchall()[-1]
Out[23]: ('99999',)

Wenn Sie andererseits einen Treiber verwenden, der TVPs nicht unterstützt, können Sie eine Tabellenvariable deklarieren , fügen Sie die Werte ein und übergeben Sie das als Argument zu Ihrem Verfahren:

In [12]: engine.execute(
    ...:     """
    ...:     DECLARE @pArg AS [StringTable];
    ...:     INSERT INTO @pArg VALUES {placeholders};
    ...:     EXEC test_proc @pArg;
    ...:     """.format(placeholders=",".join(["(%s)"] * len(arg))),
    ...:     tuple(arg))
    ...:     
Out[12]: <sqlalchemy.engine.result.ResultProxy at 0x7f23580f2908>

In [15]: _.fetchall()
Out[15]: [('Name One',), ('Name Two',)]

Beachten Sie, dass Sie keine executemany-Methoden verwenden können, da Sie sonst die Prozedur für jeden Tabellenwert separat aufrufen werden. Deshalb werden die Platzhalter manuell konstruiert und die Tabellenwerte als einzelne Argumente übergeben. Es ist darauf zu achten, dass keine Argumente direkt in die Abfrage formatiert werden, sondern die richtige Anzahl an Platzhaltern für die DB-API. Zeilenwerte sind auf maximal 1000 .

Es wäre natürlich schön, wenn der zugrunde liegende DB-API-Treiber eine angemessene Unterstützung für Tabellenwertparameter bieten würde, aber zumindest konnte ich keinen Weg für pymssql finden, das FreeTDS verwendet. Ein Verweis auf TVPs auf der Mailingliste macht deutlich, dass sie nicht unterstützt werden. Die Situation ist nicht viel besser für PyODBC .

Haftungsausschluss:Ich habe MS SQL Server noch nie wirklich verwendet.