Hi,
thanks for your insights.
We're on MySQL / MyISAM, so there're no real foreign key constraints
anyway...
As long as our comparator works - and it seems to be "right" way - we're
fine I guess until the scheme will be refactored more completely
Am Dienstag, 8. November 2016 16:12:34 UTC+1 schrieb Mike Bayer:
>
>
>
> On 11/08/2016 08:00 AM, Sebastian Eckweiler wrote:
> > Hi there,
> >
> > I'm having an issue when building queries against a relationship using a
> > custom primaryjoin.
> > The issue can be reproduced with a slightly modified Users/Address model
> > as taken from the docs:
> >
> > |
> >
> > fromsqlalchemyimportInteger,ForeignKey,String,Column
> > fromsqlalchemy.ext.declarativeimportdeclarative_base
> > fromsqlalchemy.ormimportrelationship
> >
> > Base=declarative_base()
> >
> > classUser(Base):
> > __tablename__ ='user'
> > id =Column(Integer,primary_key=True)
> > name =Column(String)
> >
> > address_id =Column(Integer,ForeignKey('address.id'))
> > boston_address =relationship("BostonAddress",
> >
> > primaryjoin="and_(User.address_id==Address.id, "
> > "Address.city=='Boston')")
> >
> > classAddress(Base):
> > __tablename__ ='address'
> > __mapper_args__ ={'polymorphic_on':'type',
> > 'polymorphic_identity':'any'}
> >
> > id =Column(Integer,primary_key=True)
> > type =Column(String,primary_key=True)
> >
> > street =Column(String)
> > city =Column(String)
> > state =Column(String)
> > zip =Column(String)
> >
> > classBostonAddress(Address):
> >
> > __mapper_args__ ={'polymorphic_on':'type',
> > 'polymorphic_identity':'Boston'}
> >
> > |
> >
> > The actual application uses more than one Address subclass obviously -
> > but the above suffices for the example.
> > When trying to query for "Users not having a boston_address" I'd use
> >
> > |
> > User.boston_address ==None
> > |
> >
> > as a where clause, which however turns out be become:
> >
> > |
> > "user".address_id IS NULL AND 'Boston'IS NULL
> > |
> >
> > which obvisouly won't yield any results - I'm not sure though, whether
> > this is a bug or simply due to the maybe unusual model.
> > I've now implemented a dedicated comparator for this type of
> > relationship which seems to work.
> > But still I'm wondering if there's an easier way to fix this, e.g. by
> > annotating parts of the join condition as "do not touch"...?
>
>
> well, this is a many-to-one so the usual comparison tries to cut out the
> "Address" table totally for the "address == None" equation, which
> doesn't work here. We can make it not consider Address.city as one of
> the columns to "null out" like this:
>
> primaryjoin="and_(User.address_id==remote(Address.id), "
> "Address.city=='Boston')")
>
> and then the expression comes out as:
>
> "user".address_id IS NULL AND address.city = :city_1
> {u'city_1': 'Boston'}
>
>
> which also does not work, because then in a query you get:
>
> SELECT "user".id AS user_id, "user".name AS user_name, "user".address_id
> AS user_address_id
> FROM "user", address
> WHERE "user".address_id IS NULL AND address.city = :city_1
>
> If we are to use this expression as written to produce a user with no
> boston_address, the only way to do this "magically" is to use has():
>
>
> print Session().query(User).filter(~User.boston_address.has())
>
> SELECT "user".id AS user_id, "user".name AS user_name, "user".address_id
> AS user_address_id
> FROM "user"
> WHERE NOT (EXISTS (SELECT 1
> FROM address
> WHERE "user".address_id = address.id AND address.city = :city_1 AND
> address.type IN (:type_1)))
>
> Where you'll notice that has() is also smart enough to get the
> polymorphic discriminator in there.
>
> The relational quirk this model is tripping over is that a BostonAddress
> would have a primary key that includes "Boston" in the type but you are
> not including this in the schema for your User table, that is,
> User.address_id == 5 is ambiguous, it could point to address (5,
> 'Denver') or (5, 'Boston'). In fact a strict database like Postgresql
> will disallow such a structure from being created, because this is not a
> unique key:
>
> (psycopg2.ProgrammingError) there is no unique constraint matching
> given keys for referenced table "address"
> [SQL: '\nCREATE TABLE "user" (\n\tid SERIAL NOT NULL, \n\tname
> VARCHAR, \n\taddress_id INTEGER, \n\tPRIMARY KEY (id), \n\tFOREIGN
> KEY(address_id) REFERENCES address (id)\n)\n\n']
>
> If we used a better relational form like this:
>
> class User(Base):
> __tablename__ = 'user'
> id = Column(Integer, primary_key=True)
> name = Column(String(50))
>
> address_id = Column(Integer)
> address_type = Column(String)
> boston_address = relationship("BostonAddress")
> __table_args__ = (
> ForeignKeyConstraint(
> ['address_id', 'address_type'],
> ['address.id', 'address.type']),
> )
>
> now we see the original User.boston_address == None works smoothly (note
> no custom primaryjoin needed):
>
> "user".address_id IS NULL AND "user".address_type IS NULL
>
>
> The way you're doing it is fine if you are stuck with that model, but
> the need to create a custom comparator is warranted since you're moving
> away from a generic foreign key constraint model.
>
>
>
>
>
>
>
>
>
> >
> > Cheers
> > Sebastian
> >
> >
> >
> > --
> > SQLAlchemy -
> > The Python SQL Toolkit and Object Relational Mapper
> >
> > http://www.sqlalchemy.org/
> >
> > To post example code, please provide an MCVE: Minimal, Complete, and
> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full
> > description.
> > ---
> > You received this message because you are subscribed to the Google
> > Groups "sqlalchemy" group.
> > To unsubscribe from this group and stop receiving emails from it, send
> > an email to [email protected] <javascript:>
> > <mailto:[email protected] <javascript:>>.
> > To post to this group, send email to [email protected]
> <javascript:>
> > <mailto:[email protected] <javascript:>>.
> > Visit this group at https://groups.google.com/group/sqlalchemy.
> > For more options, visit https://groups.google.com/d/optout.
>
--
SQLAlchemy -
The Python SQL Toolkit and Object Relational Mapper
http://www.sqlalchemy.org/
To post example code, please provide an MCVE: Minimal, Complete, and Verifiable
Example. See http://stackoverflow.com/help/mcve for a full description.
---
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.