NULL-Test mit PyMySQL

Die SQL-Syntax unterscheidet syntaktisch bei Vergleichen mit realen Werten und NULL:

SELECT * FROM tabelle WHERE spalte=0
SELECT * FROM tabelle WHERE spalte IS NULL

Wenn Sie in einem Python-Script mit PyMySQL auf eine MySQL- oder MariaDB-Datenbank zugreifen und dabei Datensätzen selektieren möchten, deren Inhalt je nach Parameter auch NULL sein darf, führt das zu Problemen:

import pymysql.cursors
conn = pymysql.connect(...)
with conn.cursor() as cur:
    sql = 'SELECT * FROM tabelle WHERE spalte = %s'
    cur.execute(sql, (0))      # OK
    cur.execute(sql, (None))   # liefert einen SQL-Syntaxfehler
    ...

Lösung

Die naheliegende Lösung besteht darin, das SQL-Statement eben in Abhängigkeit vom betreffenden Parameter zu variieren, also:

if value == None:
    sql = 'SELECT * FROM tabelle WHERE spalte IS %s'
else:    
    sql = 'SELECT * FROM tabelle WHERE spalte = %s'

In einem Projekt musste ich zuletzt aber eine Menge derartiger SELECT-Abfragen zusammenstellen, bei denen es jeweils viele Parameter gab, die alle NULL sein konnten. Der obige Ansatz wird dann unübersichtlich. Ich habe mir deswegen eine kleine Funktion programmiert, an die ich ein Dictionary mit Spaltennamen und Werten übergeben kann. Die Funktion erzeugt daraus eine Zeichenkette mit dem WHERE-Teil des SQL-Statements.

dict = { 'spalte1': 123, 'spalte2': 'abc', 'spalte3': None }
sql = 'SELECT FROM tabelle ' + buildWhere(conn, dict)

# erzeugt eine Zeichenkette der Form
# WHERE col1 = 'val1' AND col2 = 'val2' etc.
# mit   col1 IS NULL für Werte, die None sind
def buildWhere(conn, dict):
    result = "WHERE "
    for colname, value in dict.items():
        if value == None:
            result += colname + ' IS NULL AND '
        else:
            result += colname + ' = ' + conn.escape(value) + ' AND '

    return result[:-4]  # letztes 'AND ' eliminieren

Die Methode escape stellt sicher, dass das auch dann funktioniert, wenn die Werte im Dictionary Anführungszeichen oder andere problematische Zeichen enthalten.

Ergänzung 21.12.2018

Wenn die zu vergleichenden Daten (betrifft nur Zeichenketten) das Zeichen % enthalten, glaubt PyMySQL bei der Verarbeitung des SQL-Kommandos, an dieser Stelle seien Parameter einzusetzen. Abhilfe: Ersetzen Sie das Prozentzeichen durch zwei derartige Zeichen, also:

def buildWhere(conn, dict):
    ...
    result = result.replace('%', '%%')
    return result[:-4]  # letztes 'AND ' eliminieren

Quellen: https://stackoverflow.com/questions/3037581, http://mysql-python.sourceforge.net/MySQLdb.html#functions-and-attributes