Calling MSSQL stored procedure from SqlAlchemy
I remember this giving me grief before too. From memory either session.execute()
or connection.execute()
worked for me. There's also a callproc()
method, which is probably the right way to go. http://docs.sqlalchemy.org/en/latest/core/connections.html
Also, I've had issues in the past with MSSQL which seemed to be due to something asynchronous happening where the method was returning before the procedure was finished, which resulted in unpredictable results on the database. I found that putting a time.sleep(1)
(or whatever the appropriate number is) right after the call fixed this.
How do I execute a MySQL stored procedure in a sqlalchemy scoped session to return a single result set of data (for flask web app)?
SQLAlchemy doesn't, as far as I know, support calling stored procedures directly. The docs recommend using the raw DB-API connection's callproc method.
The connection can be accessed from the engine; it can also be accessed via a session, but this is still goes through the engine.
The example code below shows both methods. Note that the method of accessing the result of calling the procedure may vary depending on the parameters and the connector used - see this answer for some examples.
import mysql.connector
import sqlalchemy as sa
from sqlalchemy import orm
# Setup the database
DATA = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
DDL1 = """\
CREATE TABLE IF NOT EXISTS test_table (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
a INT,
b INT,
c INT)
"""
DDL2 = """\
CREATE PROCEDURE IF NOT EXISTS test_procedure (IN p1 INT)
BEGIN
SELECT a, b, c FROM test_table
WHERE a > p1;
END
"""
DML1 = """DELETE FROM test_table"""
DML2 = """INSERT INTO test_table (a, b, c) VALUES (%s, %s, %s)"""
CALL1 = """CALL test_procedure(:param)"""
conn = mysql.connector.connect(database='test')
cur = conn.cursor()
cur.execute(DDL1)
cur.execute(DDL2)
cur.execute(DML1)
for row in DATA:
cur.execute(DML2, row)
conn.commit()
conn.close()
# Call the procedure
engine = sa.create_engine('mysql+mysqlconnector:///test')
Session = orm.scoped_session(orm.sessionmaker(autocommit=False, autoflush=False, bind=engine))
session = Session()
raw_conn = session.connection().engine.raw_connection()
cur = raw_conn.cursor()
cur.callproc('test_procedure', [1])
print('Using session')
for result in cur.stored_results():
print(result.fetchall())
Session.remove()
print('Using engine directly')
raw_conn = engine.raw_connection()
cur = raw_conn.cursor()
cur.callproc('test_procedure', [1])
for result in cur.stored_results():
print(result.fetchall())
One final observation: it seems to me that the raw connection accessed via the session is outside of the session's transaction context, so changes made using the session might not be visible to changes made using the connection, depending on transaction isolation settings.
Launch SQL stored procedures from python with sqlalchemy?
Finally solved my problem with the following function :
def execute_stored_procedure(engine, procedure_name):
res = {}
connection = engine.raw_connection()
try:
cursor = connection.cursor()
cursor.execute("EXEC "+procedure_name)
cursor.close()
connection.commit()
res['status'] = 'OK'
except Exception as e:
res['status'] = 'ERROR'
res['error'] = e
finally:
connection.close()
return res
why the stored procedure called from sqlalchemy is not working but calling from workbench is working?
I haven't called stored procs from SQLAlchemy, but it seems possible that this could be within a transaction because you're using the session. Perhaps calling db.session.commit()
at the end would help?
If that fails, SQLAlchemy calls out calling stored procs here. Perhaps try their method of using callproc
. Adapting to your use-case, something like:
connection = db.session.connection()
try:
cursor = connection.cursor()
cursor.callproc("escalatelobalarm", [clientid])
results = list(cursor.fetchall())
cursor.close()
connection.commit()
finally:
connection.close()
How correctly execute a MSSQL stored procedure with parameters in Python
Write your SQL command text using the "named" paramstyle, wrap it in a SQLAlchemy text()
object, and pass the parameter values as a dict:
import pandas as pd
import sqlalchemy as sa
connection_uri = "mssql+pyodbc://@mssqlLocal64"
engine = sa.create_engine(connection_uri)
# SQL command text using "named" paramstyle
sql = """
SET NOCOUNT ON;
SET ARITHABORT ON;
EXEC dbo.breakfast @name = :name_param, @food = :food_param;
"""
# parameter values
param_values = {"name_param": "Gord", "food_param": "bacon"}
# execute query wrapped in SQLAlchemy text() object
df = pd.read_sql_query(sa.text(sql), engine, params=param_values)
print(df)
"""
column1
0 Gord likes bacon for breakfast.
"""
Python sqlalchemy and mySQL stored procedure always returns 0 (out param only)
With the help of this answer I found the following solution that worked for me.
a) Working solution using engine.raw_connection()
and cursor.callproc
:
def call_procedure(engine, function_name):
connection = engine.raw_connection()
try:
cursor = connection.cursor()
cursor.callproc(function_name, [0])
cursor.execute(f"""SELECT @_{function_name}_0""")
results = cursor.fetchone() ## returns a tuple e.g. (285,)
rows_affected = results[0]
cursor.close()
connection.commit()
logger.info(f"Running procedure {function_name} success!")
return rows_affected
except Exception as e:
logger.error(f"Running procedure {function_name} failed!")
logger.exception(e)
return None
finally:
connection.close()
And with this answer I found this solution also:
b) Instead of using a raw connection, this worked as well:
def call_procedure(engine, function_name, params=None):
try:
with engine.begin() as db_conn:
db_conn.execute(f"""CALL {function_name}(@out)""")
results = db_conn.execute('SELECT @out').fetchone() ## returns a tuple e.g. (285,)
rows_affected = results[0]
logger.debug(f"Running procedure {function_name} success!")
return rows_affected
except Exception as e:
logger.error(f"Running procedure {function_name} failed!")
logger.exception(e)
return None
finally:
if db_conn: db_conn.close()
If there are any advantages or drawbacks of using one of these methods over the other, please let me know in a comment.
How to call stored procedure with SQLAlchemy that requires a user-defined-type Table parameter
There is a driver that really supports TVPs: Pytds. It's not officially supported, but there's a 3rd party dialect implementation for it: sqlalchemy-pytds. Using them you could call your stored procedure like so:
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',)]
This way you can pass potentially large amounts of data as params:
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',)
If on the other hand you're using a driver that does not support TVPs, you could declare a table variable, insert the values, and pass that as the argument to your procedure:
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',)]
Note that you cannot use any executemany methods, or you'll end up calling the procedure for each table value separately. That is why the placeholders are constructed manually and the table values passed as individual arguments. Care must be taken not to format any arguments directly in to the query, but the correct amount of placeholders for the DB-API instead. Row values are limited to a maximum of 1000.
It'd of course be nice, if the underlying DB-API driver provided proper support for table valued parameters, but at least I could not find a way for pymssql, which uses FreeTDS. A reference to TVPs on the mailing list makes it clear that they're not supported. The situation is not much better for PyODBC.
Disclaimer: I've not really used MS SQL Server before.
SQLAlchemy stored procedure is not working in Postgres
As noted in a comment to the question, psycopg2 does not like the commit;
in the SP. If you remove that then this will work with SQLAlchemy:
import sqlalchemy as sa
from sqlalchemy.orm import Session
engine = sa.create_engine("postgresql://scott:tiger@192.168.0.199/test")
with Session(engine) as session, session.begin():
session.execute(
sa.text("CALL addcity (:param1, :param2)"),
{"param1": "One", "param2": "Two"},
)
# automatically commits when the context manager exits (assuming no errors)
Related Topics
No Matching Distribution Found for Tkinter
How to Retrieve Data from Dynamic Table - Selenium Python
Matplotlib: Attributeerror: 'Axessubplot' Object Has No Attribute 'Add_Axes'
How to Run an .Ipynb Jupyter Notebook from Terminal
Find the Longest Substring in Alphabetical Order
How to Do This Horizontally Instead of Vertically in Python
Pyspark Replace All Values in Dataframe With Another Values
How to Redeem Nitro Gifts Automatically With Discord.Py (Self-Bot)
Using Opencv to Overlay Transparent Image Onto Another Image
Python Pandas Count the Number of Occurances Inside Lists in a Column
Python: Using Doctests for Classes
Pyspark Data Frame Converting False and True to 0 and 1
Sharing a Complex Object Between Processes
Maximum Characters That Can Be Stuffed into Raw_Input() in Python
How to Append Two Bytes in Python
How to Add Thousand Separator to Numbers in Python Pandas Dataframe