Comment puis-je appeler des procédures stockées du serveur SQL avec sqlAlchemy?
Les moteurs et les connexions ont une méthode execute()
que vous pouvez utiliser pour les instructions sql arbitraires, tout comme les sessions. Par exemple:
results = sess.execute('myproc ?, ?', [param1, param2])
Vous pouvez utiliser outparam()
pour créer des paramètres de sortie si vous en avez besoin (ou pour les paramètres de liaison, utilisez bindparam()
avec l'option isoutparam=True
)
contexte : j'utilise flask-sqlalchemy avec MySQL et sans ORM-mapping. Habituellement, j'utilise:
# in the init method
_db = SqlAlchemy(app)
#... somewhere in my code ...
_db.session.execute(query)
L'appel de procédures stockées n'est pas pris en charge dès le départ: le callproc
n'est pas générique, mais spécifique au connecteur mysql.
Pour les procédures stockées sans paramètres, il est possible d'exécuter une requête comme
_db.session.execute(sqlalchemy.text("CALL my_proc(:param)"), param='something')
comme d'habitude. Les choses se compliquent quand vous avez out params ...
Une façon d'utiliser les paramètres est d'accéder au connecteur sous-jacent via engine.raw_connection()
. Par exemple:
conn = _db.engine.raw_connection()
# do the call. The actual parameter does not matter, could be ['lala'] as well
results = conn.cursor().callproc('my_proc_with_one_out_param', [0])
conn.close() # commit
print(results) # will print (<out param result>)
C'est bien puisque nous pouvons accéder au paramètre out, MAIS cette connexion n'est pas gérée par la session flask. Cela signifie qu'elle ne sera pas validée/abandonné comme avec les autres requêtes gérées ... (problématique uniquement si votre procédure a un effet secondaire).
Enfin, j'ai fini par faire ceci:
# do the call and store the result in a local mysql variabl
# the name does not matter, as long as it is prefixed by @
_db.session.execute('CALL my_proc_with_one_out_param(@out)')
# do another query to get back the result
result = _db.session.execute('SELECT @out').fetchone()
Le result
sera un Tuple avec une valeur: le paramètre out. Ce n'est pas idéal, mais le moins dangereux: si une autre requête échoue pendant la session, l'appel de procédure sera également annulé (restauration).
Exécutez simplement l'objet de procédure créé avec func
:
from sqlalchemy import create_engine, func
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite://', echo=True)
print engine.execute(func.upper('abc')).scalar() # Using engine
session = sessionmaker(bind=engine)()
print session.execute(func.upper('abc')).scalar() # Using session
En supposant que vous avez déjà créé une session avec sessionmaker (), vous pouvez utiliser la fonction suivante:
def exec_procedure(session, proc_name, params):
sql_params = ",".join(["@{0}={1}".format(name, value) for name, value in params.items()])
sql_string = """
DECLARE @return_value int;
EXEC @return_value = [dbo].[{proc_name}] {params};
SELECT 'Return Value' = @return_value;
""".format(proc_name=proc_name, params=sql_params)
return session.execute(sql_string).fetchall()
Vous pouvez maintenant exécuter votre procédure stockée 'MyProc' avec des paramètres simplement comme ça:
params = {
'Foo': foo_value,
'Bar': bar_value
}
exec_procedure(session, 'MyProc', params)
La façon la plus simple d'appeler une procédure stockée dans MySQL à l'aide de SQLAlchemy est d'utiliser la méthode callproc
de Engine.raw_connection()
. call_proc
nécessitera le nom de la procédure et les paramètres requis pour la procédure stockée appelée.
def call_procedure(function_name, params):
connection = cloudsql.Engine.raw_connection()
try:
cursor = connection.cursor()
cursor.callproc(function_name, params)
results = list(cursor.fetchall())
cursor.close()
connection.commit()
return results
finally:
connection.close()
Par besoin désespéré d'un de mes projets, j'ai écrit une fonction qui gère les appels de procédure stockée.
Voici:
import sqlalchemy as sql
def execute_db_store_procedure(database, types, sql_store_procedure, *sp_args):
""" Execute the store procedure and return the response table.
Attention: No injection checking!!!
Does work with the CALL syntax as of yet (TODO: other databases).
Attributes:
database -- the database
types -- Tuple of strings of SQLAlchemy type names.
Each type describes the type of the argument
with the same number.
List: http://docs.sqlalchemy.org/en/rel_0_7/core/types.html
sql_store_procudure -- string of the stored procedure to be executed
sp_args -- arguments passed to the stored procedure
"""
if not len(types) == len(sp_args):
raise ValueError("types Tuple must be the length of the sp args.")
# Construch the type list for the given types
# See
# http://docs.sqlalchemy.org/en/latest/core/sqlelement.html?highlight=expression.text#sqlalchemy.sql.expression.text
# sp_args (and their types) are numbered from 0 to len(sp_args)-1
type_list = [sql.sql.expression.bindparam(
str(no), type_=getattr(sql.types, typ)())
for no, typ in Zip(range(len(types)), types)]
try:
# Adapts to the number of arguments given to the function
sp_call = sql.text("CALL `%s`(%s)" % (
sql_store_procedure,
", ".join([":%s" % n for n in range(len(sp_args))])),
bindparams=type_list
)
#raise ValueError("%s\n%s" % (sp_call, type_list))
with database.engine.begin() as connection:
return connection.execute(
sp_call,
# Don't do this at home, kids...
**dict((str(no), arg)
for (no, arg) in Zip(range(len(sp_args)), sp_args)))
except sql.exc.DatabaseError:
raise
Il fonctionne avec la syntaxe CALL, donc MySQL devrait fonctionner comme prévu. MSSQL utilise EXEC à la place de call et une petite syntaxe différente, je suppose. Donc, le rendre indépendant du serveur dépend de vous, mais ne devrait pas être trop difficile.
Une autre solution:
query = 'call Procedure ('+"'"+@param1+"'"+','+"'"+@param2+"'"+','+"'"+@param3+"'"+')'
sqlEngine = sqlalchemy.create_engine(jdbc)
conn = sqlEngine.connect()
df = pd.read_sql(query,conn,index_col=None)