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]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
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.