2

I can successfully connect to SQL Server Management Studio from my jupyter notebook with this script :

from sqlalchemy import create_engine
import pyodbc 
import csv
import time
import urllib

params = urllib.parse.quote_plus('''DRIVER={SQL Server Native Client 11.0};
                                    SERVER=SV;
                                    DATABASE=DB;
                                    TRUSTED_CONNECTION=YES;''')

engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params)

Just for an example, the following script perfectly works :

engine.execute("delete from table_name_X")

However, I failed to get the following script to work. For information, it works when I execute its adaptation in SQL Server Management Studio :

cde = 5
reportDate = df.loc[df.index[0],'Report Date'] # when you execute reportDate it returns 2019-11-15 00:00:00

req = "DELETE table_name_Y "
req+= "WHERE code = " + str(cde)
req+= " AND report_date = '" + str(reportDate.strftime('%Y-%m-%d')) + "'"

engine.execute(req)

According to the error message, there is a problem with the conversion of a varchar to a datetime, which created a value out of range. However, independently executed, the script str(reportDate.strftime('%Y-%m-%d')) works.

Could you please help me to understand why this previous script does not work ?

7
  • could you please print out req ? and add the error message too ! Commented Dec 12, 2019 at 10:51
  • "DELETE table_name_Y WHERE code = 5 AND report_date = '2019-11-15'" Commented Dec 12, 2019 at 10:53
  • reportDate = 2019-11-15 00:00:00 produces "SyntaxError: invalid syntax" Commented Dec 12, 2019 at 10:54
  • Indeed, I edited my question, thank you Commented Dec 12, 2019 at 10:55
  • 2
    Using string formatting to pass values to SQL queries is error prone as you've found out and can lead to SQL injection. Use placeholders in the query instead and pass the arguments to execute() separately. That being said, you'll still probably have to first convert the pandas timestamp to a datetime. Commented Dec 12, 2019 at 12:16

1 Answer 1

2

As @Ilja mentions in the comments to the question, you really should not be using dynamic SQL to construct your statement. It is error-prone and potentially unsafe. If you use a proper parameterized query many of your issues will simply go away.

For what it's worth, this works for me:

import pandas as pd
import sqlalchemy as sa

# ...

with engine.begin() as conn:    
    # set up test environment
    conn.execute(sa.text("CREATE TABLE #table_name_Y (code int, report_date date)"))
    conn.execute(sa.text("INSERT INTO #table_name_Y (code, report_date) VALUES (5, '2019-11-15')"))
    # verify test environment
    result = conn.execute(sa.text("SELECT * FROM #table_name_Y")).fetchall()
    print(result)  # [(5, datetime.date(2019, 11, 15))]
    
    # test code
    df = pd.DataFrame([(5, datetime.datetime(2019, 11, 15),), ], columns=['code', 'Report Date'])
    cde = int(df.loc[df.index[0],'code'])
    print(type(cde))  # <class 'int'>
    reportDate = df.loc[df.index[0],'Report Date']
    print(type(reportDate))  # <class 'pandas._libs.tslibs.timestamps.Timestamp'>
    sql = sa.text("DELETE FROM #table_name_Y WHERE code = :p0 AND report_date = :p1")
    params = {'p0': cde, 'p1': reportDate}
    conn.execute(sql, params)
    
    # verify outcome
    result = conn.execute(sa.text("SELECT * FROM #table_name_Y")).fetchall()
    print(result)  # []
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for your help. Based on your answer, I tried to execute the following script : sql = sqlalchemy.text("DELETE FROM #table_name_Y WHERE code = :p0 AND report_date = :p1") params = {'p0': cde, 'p1': reportDate} engine.execute(sql, params) And I have the following error : (pyodbc.ProgrammingError) ('Invalid parameter type. param-index=0 param-type=numpy.int64', 'HY105') [SQL: 'DELETE FROM #table_name_Y WHERE code = ? AND report_date = ?'] [parameters: (5, '2019-11-15')] (Background on this error at: sqlalche.me/e/f405) Do you know why ?
... because the example code in your question is misleading. cde = 5 produces an int, and that's (apparently) not what it actually is. I have updated my answer.
Thanks. I still have another issue, I am going to try to understand why
It works ! Many thanks. I needed to remove the '#' in DELETE FROM #table_name_Y. Is there a specific reason why you used a # ?
In SQL Server, tables whose name starts with # are local temporary tables. Tables whose name starts with ## are global temporary tables.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.