2

I am struggling to insert None values into the date column of a PostgreSQL database from python using PygreSQL v5.0.6.

Some code:

def _update_traits_db(self, code, date_start, date_end, unit):
    sql = ("Insert into traits (code, date_start, date_end, unit) "
           "VALUES ('%s', '%s', '%s', '%s') "
           "ON CONFLICT (code) DO UPDATE "
           "SET date_start = excluded.date_start, date_end = excluded.date_end, unit = excluded.unit "
           % (code, date_start, date_end, unit))
    try:
        self._connect()
        self._cur.execute(sql)
        self._con.commit()
        self._close()
    except Exception as e:
        self._close()
        raise e

There are a couple issues I am facing, the biggest being the the possibility of None values for date_end and unit, the first being a date causing SQL errors like:

ERROR: invalid input syntax for type date: "None"
LINE 1: ...d, unit) VALUES ('AWGHT', '2003-01-29T23:00:00Z', 'None', 'N... ^ If I replace the none value with a hardcoded NULL then it works but from reading around I figured it should be handled py PyGreSQL automatically converting None to NULL but I can't get that to work.

A second issue is with None values in the unit column, this is supposed to be a string but None is now stored in the database where it ideally would be a NULL value. I have tried removing the quotes from the around the '%s' for unit in the query vut that only causes SQL error on the None value.

I am fairly new to Python and PostgreSQL so there are many potential places i might have messed up so all suggestions are more than welcome.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Kevin
  • 21
  • 1
  • 5
  • You don't need the `'`s around the placeholders... just use `%s` by itself... eg: `values (%s, %s, %s, %s)` - the engine will already correctly escape strings, None will be converted to null etc... – Jon Clements Nov 13 '18 at 14:19
  • Tried this now, but am getting an error on the date due to the date format i believe, the date input is this " 2003-01-29T23:00:00Z" so i get ERROR: syntax error at or near "T23" – Kevin Nov 13 '18 at 14:23
  • So your date is a string then? If you make it a `datetime` object instead - it'll work fine. – Jon Clements Nov 13 '18 at 14:40
  • Have converted the date strings to datetime objects and that works and can be inserted into the database, I cant find a way to convert a "None" variable to a datetime though and for some reason its still not converted into NULL when i try inserting it to the database. Same thing goes for the "Unit" value. – Kevin Nov 13 '18 at 15:13
  • After removing the quotes around the values like @JonClements sugested i also get an error on the code column. "ERROR: column "testCode" does not exist" so it does not seem to escape the strings on its own. – Kevin Nov 13 '18 at 15:15
  • Is it definitely a `None` object and not a string containing "None"... eg `type(obj)` gives you a `NoneType` – Jon Clements Nov 13 '18 at 15:16
  • print(type(date_end)) prints this: when theres no value for date_end – Kevin Nov 13 '18 at 15:21
  • Okay... I'd have thought the adaptor should handle the `None` -> `NULL` just fine... although it's not listed as a conversion at http://www.pygresql.org/contents/pg/adaptation.html. I have only ever used psycopg2 when dealing with postgres. – Jon Clements Nov 13 '18 at 15:30
  • I have never used psycopg2, have just started with python and postrgreSQL so i just jumped at the first thing i found that seemed straight forward. Is psycopg2 a better library to to use? – Kevin Nov 13 '18 at 15:34
  • it's the one that's most widely used by various frameworks and in production systems... – Jon Clements Nov 13 '18 at 15:42
  • Excellent, I will give that a go then. Thanks a lot for your time anyhow and hopefully it will be easier to get working properly with psycopg2! – Kevin Nov 13 '18 at 15:46
  • you're welcome... it's probably overkill for now, but you might find it worthwhile looking at https://www.sqlalchemy.org/ - if you want to move to ORM stuff later you can, but you can still use it as a wrapper around the database adapters and execute raw queries as you're doing now if needs be (but can also use it to construct queries programatically if needs be as well) – Jon Clements Nov 13 '18 at 15:51
  • ahh... I'd missed that you had a `%` in there for interpolation - Parfait's given you the answer - everything should just work with that :) – Jon Clements Nov 13 '18 at 16:02

1 Answers1

1

Simply use parameterization, the industry standard to separate SQL code from data values, and not string interpolation which looks similar since the placeholder %s is used in both. With this approach, None should resolve as NULL. In fact, PygreSQL docs even warns users on the practice:

Warning
Remember to never insert parameters directly into your queries using the % operator. Always pass the parameters separately.

Consider following adjustment using unquoted %s placeholders (see docs) with values later binded in cursor.execute() call:

def _update_traits_db(self, code, date_start, date_end, unit):
    # PREPARED STATEMENT (NO DATA VALUES INTERPOLATED)
    sql =  """INSERT INTO traits (code, date_start, date_end, unit)
              VALUES (%s, %s, %s, %s)
              ON CONFLICT (code) DO UPDATE
              SET date_start = excluded.date_start, 
                  date_end = excluded.date_end, 
                  unit = excluded.unit
           """
    try:
        self._connect()
        # BIND PARAMETERS WITH TUPLE OF VALUES
        self._cur.execute(sql, (code, date_start, date_end, unit))
        self._con.commit()

    except Exception as e:
        raise e

    finally:
        self._close()
Parfait
  • 104,375
  • 17
  • 94
  • 125
  • That did it! Thanks a lot to both of you! Seems i had a misunderstanding on how the parameterization in python really worked, this makes a lot more sense. Glad to have that cleared up, thanks! – Kevin Nov 13 '18 at 16:16
  • Great to hear. Yes, this `cursor.execute(query, params)` approach is consistent across most Python DB-APIs (cxOracle, ibmdb, pymssql, pyodbc, pymysql, psycopg2, sqlite3) per Python's [PEP 249](https://www.python.org/dev/peps/pep-0249/) SQL standard. Also, parameterization is an industry-wide best practice not just in Python but any app layer language (Java, C#, PHP, Perl, etc.) running SQL. Happy coding! – Parfait Nov 13 '18 at 16:23