The Python Oracle

NULL safe inequality comparisons in SQL Alchemy?

This video explains
NULL safe inequality comparisons in SQL Alchemy?

--

Become part of the top 3% of the developers by applying to Toptal
https://topt.al/25cXVn

--

Music by Eric Matyas
https://www.soundimage.org
Track title: Peaceful Mind

--

Chapters
00:00 Question
02:51 Accepted answer (Score 12)
05:02 Answer 2 (Score 0)
05:25 Thank you

--

Full question
https://stackoverflow.com/questions/2166...

Question links:
[http://gerrit.beaker-project.org/#/c/277.../]: http://gerrit.beaker-project.org/#/c/277.../

Answer 1 links:
[sqlalchemy custom operator]: http://docs.sqlalchemy.org/en/latest/cor...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #sqlalchemy

#avk47



ACCEPTED ANSWER

Score 12


There's a few ways to plug an alternate operator in there, as well as creating a custom operator, but the most public/mainstream way to get at what happens when __ne__() is invoked is at the type level:

from sqlalchemy import TypeDecorator, type_coerce, String, or_

class NullComparisons(TypeDecorator):
    impl = String

    class comparator_factory(TypeDecorator.Comparator):
        def __ne__(self, other):
            expr = type_coerce(self.expr, String)
            return or_(expr == None, expr != other)

so that will do the OR thing:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
class Thing(Base):
    __tablename__ = 'thing'

    id = Column(Integer, primary_key=True)
    data = Column(NullComparisons(50))
print(Thing.data != 'hi')

gives us:

thing.data IS NULL OR thing.data != :param_1

then for the PG/MySQL operator, what we really should have is the ability to link @compiles to operators directly. But that hook isn't present right now, so with more effort than it ideally should require, we can make a custom column element to handle it:

from sqlalchemy import TypeDecorator, type_coerce, String
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import BinaryExpression
import operator
class IsDistinctFrom(BinaryExpression):
    pass

@compiles(IsDistinctFrom, "postgresql")
def pg_is_distinct_from(element, compiler, **kw):
    return "%s IS DISTINCT FROM %s" % (
                    compiler.process(element.left, **kw),
                    compiler.process(element.right, **kw),
                )

@compiles(IsDistinctFrom, "mysql")
def mysql_is_distinct_from(element, compiler, **kw):
    return "%s <=> %s" % (
                    compiler.process(element.left, **kw),
                    compiler.process(element.right, **kw),
                )

class AdvancedNullComparisons(TypeDecorator):
    impl = String

    class comparator_factory(TypeDecorator.Comparator):
        def __ne__(self, other):
            expr = type_coerce(self.expr, String)
            # this step coerces a literal into a SQL expression,
            # this can be done without the private API here but the private
            # function does the most thorough job, this could also be made
            # public
            other = self._check_literal(expr, operator.ne, other)
            return IsDistinctFrom(self.expr, other, operator.ne)

then we can try that out:

from sqlalchemy import Column, Integer
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Thing(Base):
    __tablename__ = 'thing'

    id = Column(Integer, primary_key=True)
    data = Column(AdvancedNullComparisons(50))

from sqlalchemy.dialects import postgresql, mysql
print(Thing.data != 'hi').compile(dialect=postgresql.dialect())
print(Thing.data != 'hi').compile(dialect=mysql.dialect())

gives us:

thing.data IS DISTINCT FROM %(param_1)s
thing.data <=> %s



ANSWER 2

Score 0


Use customize operator may help: sqlalchemy custom operator

eg:

outerjoin(A, B.outlet_id.op('<=>')(A.outlet_id))