Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comment appeler une procédure stockée avec SQLAlchemy qui nécessite un paramètre de table de type défini par l'utilisateur

Il existe un pilote qui prend vraiment en charge les TVP :Pytds . Il n'est pas officiellement pris en charge, mais il existe une implémentation de dialecte tiers :sqlalchemy-pytds . En les utilisant, vous pouvez appeler votre procédure stockée comme suit :

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',)]

De cette façon, vous pouvez transmettre de grandes quantités de données en tant que paramètres :

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',)

Si, par contre, vous utilisez un pilote qui ne prend pas en charge les TVP, vous pouvez déclarer une variable de table , insérez les valeurs et transmettez-le comme argument à votre procédure :

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',)]

Notez que vous ne pouvez pas utiliser de méthodes executemany, ou vous finirez par appeler la procédure pour chaque valeur de table séparément. C'est pourquoi les espaces réservés sont construits manuellement et les valeurs de table transmises en tant qu'arguments individuels. Il faut veiller à ne pas formater d'arguments directement dans la requête, mais plutôt le nombre correct d'espaces réservés pour l'API DB. Les valeurs des lignes sont limitées à un maximum de 1000 .

Ce serait bien sûr bien si le pilote DB-API sous-jacent fournissait un support approprié pour les paramètres de table, mais au moins je ne pouvais pas trouver un moyen pour pymssql, qui utilise FreeTDS. Une référence aux TVP sur la liste de diffusion indique clairement qu'ils ne sont pas pris en charge. La situation n'est pas beaucoup mieux pour PyODBC .

Avis de non-responsabilité :je n'ai jamais vraiment utilisé MS SQL Server auparavant.