1

I am trying to do something like this in Python,

SQLCommand = ("Delete From %s where [Date] >= %s and [Date] <= %s", (calendar_table_name, required_starting_date, required_ending_date))

cursor.execute(SQLCommand)

calendar_table_name is a string variable

required_starting_date is a datetime variable

required_ending_date is a datetime variable

Trying this gives me an error:

The first argument to execute must be a string or unicode query.

Tried this and it gives me the same error:

SQLCommand = ("Delete From " +  calendar_table_name + " where [Date] >= %s and [Date] <= %s", ( required_starting_date, required_ending_date))

cursor.execute(SQLCommand)

Edit:

type(required_ending_date)

Out[103]: pandas._libs.tslibs.timestamps.Timestamp


type(required_starting_date)

Out[103]: pandas._libs.tslibs.timestamps.Timestamp

This works in SSMS for me,

  delete from [table_test] where [Date] >= '2007-01-01' and [Date] <= '2021-01-01';

Update :- This is the code, that I am trying with

Delete_SQLCommand =  f"Delete FROM [{calendar_table_name}] WHERE [Date]>=? And [Date]<=?"
params = (required_starting_date, required_ending_date)

required_starting_date & required_ending_date are of "TimeStamp" formats

calendar_tbl_connection = pyodbc.connect(driver=driver, server=required_server, database=database_name,
                     trusted_connection='yes')   
calendar_tbl_cursor = calendar_tbl_connection.cursor()
calendar_tbl_cursor.execute(Delete_SQLCommand,params)
calendar_tbl_connection.commit
calendar_tbl_connection.close()
CodingDawg
  • 2,598
  • 7
  • 49
  • 94
  • 2
    You cannot parameterize table names. `From %s where` is not valid in this case. You need to use string formatting to make dynamic table names, then pass the parameters for the rest of the query – roganjosh Aug 23 '19 at 17:02
  • You haven't shown us how this sql command is _used_. Show us the call to `execute()`. – John Gordon Aug 23 '19 at 17:06
  • Right, so the edit suggests that you're passing a `datetime` object and not a string, and either the column is configured to take TEXT values (or similar) or your connection library won't do the conversion to ISO format – roganjosh Aug 23 '19 at 17:06
  • 1
    @tgikal That's how you get SQL injection attacks. Don't do it that way. – John Gordon Aug 23 '19 at 17:09
  • @tgikal they definitely did not mean that – roganjosh Aug 23 '19 at 17:09
  • @tgikal Tried what you mentioned and it gives me this error on cursor.execute command - ('42000', "[42000] [Microsoft][ODBC SQL Server Driver][SQL Server]Incorrect syntax near '00'. (102) (SQLExecDirectW)") – CodingDawg Aug 23 '19 at 17:12
  • 1
    Make `SQLCommand` be just the string portion, i.e. `SQLCommand = "Delete From ..."` . Then call execute with the arguments: `cursor.execute(SQLCommand, (arg1, arg2))` – John Gordon Aug 23 '19 at 17:13
  • @roganjosh I guess that would really depend on what filter is done on `required_starting_date` and `required_ending_date` before the query. – tgikal Aug 23 '19 at 17:13
  • @Siddharth please do _not_ do what they suggested. Your attempt, albeit currently broken, is much better practice. Please print the `type` of the parameters you are passing – roganjosh Aug 23 '19 at 17:13
  • @tgikal assume _every filter fails_ and do it the correct way, all the time. Not only is it more secure, but it has better syntax anyway and will be faster – roganjosh Aug 23 '19 at 17:14
  • I'm not sure where these suggestions are going. The error states that the parameter should be a string and it's not, so the first port of call should be checking the type of the parameters that are being inserted into the query – roganjosh Aug 23 '19 at 17:16
  • Look for libraries that support (or hope your library supports) safe dynamic construction of SQL queries instead of using string formatting. See http://initd.org/psycopg/docs/sql.html#module-psycopg2.sql for an example. – chepner Aug 23 '19 at 17:19
  • You write that "required_starting_date is a datetime variable", but the exception message indicates that it's in fact a pandas Timestamp object. Also you should tell us which database adaptor library you are using. I've added the `pyodbc` tag, since you mentioned that in a comment, but you should edit the question to add relevant information about the libraries you are using (pandas, pyodbc). That way we can reproduce your problem and suggest solutions that might be specific to those libraries. [mcve] – Håken Lid Aug 23 '19 at 18:09

3 Answers3

1

You don't say which library you are using for SQL access, but here is a safe example using psycopg.

from psycopg2 import sql

cmd = sql.SQL("delete from {} where date >= %s and date <= %s")
table_name = sql.Identifier(calendar_table_name)
cur.execute(
    cmd.format(table_name),
    [required_starting_date, required_ending_date]
)

Note that this is not str.format being called, but SQL.format. The library ensures that calendar_table_name is a proper column name, and SQL.format ensures that it is correctly incorporated into your command template before in order to produce a valid parameterized query.


Failing proper library support, you would need to do some sort of dynamic query generation. It should be a restricted sort, though, the more restricted the better. The safest way would be to start with a lookup table of hard-coded queries:

queries = {
  'name1': 'delete from name1 where ... ',
  'name2': 'delete from name2 where ...',
}

This way, you can't construct a query for an arbitrary table name, only select a pre-constructed query.

The second would be to wrap the constructor in a function that checks for a valid table name first. For example,

def generate_query(table_name):
    if table_name not in ['name1', 'name2', ...]:
        raise ValueError("Invalid table name")

    return "delete from {} where ...".format(table_name)
chepner
  • 389,128
  • 51
  • 403
  • 529
  • I am using pyodbc – CodingDawg Aug 23 '19 at 17:26
  • @Siddarth: This might still work, python has a standardized database connector api. If pyodbc follows it, `cursor.execute` should work much the same as with pyscopg2 (postgres). – Håken Lid Aug 23 '19 at 17:29
  • 1
    @HåkenLid The classes provided by `psycopg2.sql` are not part of the DBAPI. – chepner Aug 23 '19 at 17:30
  • Ok. So even if the cursor.execute method works fine, the equivalent of `sql.Identifier` and `sql.SQL` in your answer might be missing from pyodbc? It would be possible to just use string formatting, I suppose. But then you would have to be careful that the table name variable is safe. I found a relevant question, but the highest answer there looks like it could expose a sql injection vulnerability: [Passing table name as a parameter in pyodbc](https://stackoverflow.com/questions/6669009/passing-table-name-as-a-parameter-in-pyodbc). – Håken Lid Aug 23 '19 at 17:43
  • 1
    @HåkenLid you can get a reference list of the schema of your database and check any variable input against that list ("does this 'table' actually exist?") before execution. – roganjosh Aug 23 '19 at 17:45
1

pyodbc has no problem dealing with pandas' Timestamp values as inputs to a proper parameterized query:

# test data
calendar_table_name = "#calendar_table"
crsr.execute(f"CREATE TABLE [{calendar_table_name}] ([Date] date)")
crsr.execute(f"INSERT INTO [{calendar_table_name}] VALUES ('2019-08-22'),('2019-08-24')")
df = pd.DataFrame(
    [(datetime(2019, 8, 23, 0, 0), datetime(2019, 8, 25, 0, 0))],
    columns=['required_starting_date', 'required_ending_date'])
required_starting_date = df.iloc[0][0]
required_ending_date = df.iloc[0][1]
print(type(required_starting_date))  # <class 'pandas._libs.tslibs.timestamps.Timestamp'>

# test
sql = f"DELETE FROM [{calendar_table_name}] WHERE [Date]>=? AND [Date]<=?"
params = (required_starting_date, required_ending_date)
crsr.execute(sql, params)
cnxn.commit()

#verify
rows = crsr.execute(f"SELECT * FROM [{calendar_table_name}]").fetchall()
print(rows)  # [(datetime.date(2019, 8, 22), )]
Gord Thompson
  • 98,607
  • 26
  • 164
  • 342
0

You have 3 (at least) different problems with this code:

  1. You are using pandas Timestamp types instead of the expected python datetime types. Roganosh answer explains that
  2. You are mixing sql identifiers (table name) with sql values (date). You can only pass values as parameters to cursor.execute. see chepner's answer.
  3. You are calling cursor.execute with incorrect arguments.

cursor.execute expects two arguments. Since your SQLCommand variable is a tuple, you can use * to unpack the query string and the variables into two arguments when calling cursor.execute.

SQLCommand = (
    "DELETE FROM table_name WHERE date >= %s", 
    (datetime.date(2019, 08, 23),) 
)

cursor.execute(*SQLCommand)

Note that you can't pass sql identfiers such as table names as parameters to the cursor.execute method. The Python Database API Specification does not specify how to construct queries with dynamic identifiers (for example column or table names).

Håken Lid
  • 18,252
  • 8
  • 40
  • 58
  • This only works for query parameters, not the table name. The DBAPI itself does not support full dynamic generation of SQL commands. – chepner Aug 23 '19 at 17:20
  • @chepner: You are right. There are two problems with the code in the question. My answer addresses why they get the error "The first argument to execute must be a string or unicode query." in this case. I've edited the answer to make it clearer that parameters (the second argument to `cursor.execute` must be values, not identifiers such as table names. – Håken Lid Aug 23 '19 at 17:25