Stodge wrote: > Thanks. That doesn't quite work. Based on my data, the following > should (and does) work because it only returns document id=1, which > only has these two tags: > > tag_list = ['my document', 'source code'] > session.query(Document).\ > filter(Document.tags.any(Tag.tag.in_([t for t in tag_list]))).\ > filter(~Document.tags.any(~Tag.tag.in_([t for t in tag_list]))) > > The following should return no records, as there is no document that > has only these tags. Instead it returns document id=2, which only has > the tag 'random stuff': > > tag_list = ['my document', 'source code', 'random stuff'] > session.query(Document).\ > filter(Document.tags.any(Tag.tag.in_([t for t in tag_list]))).\ > filter(~Document.tags.any(~Tag.tag.in_([t for t in tag_list]))) > >
Try this query instead: tag_list = ['my document', 'source code', 'random stuff'] q = session.query(Document) for t in tag_list: q = q.filter(Document.tags.any(Tag.tag == t)) q = q.filter(~Document.tags.any(~Tag.tag.in_(tag_list))) Alternatively, if your database supports aggregating row sets into arrays/strings (e.g. PostgreSQL supports ARRAY(SELECT ...) to collect row sets into an array, MySQL and others have GROUP_CONCAT() that I believe you can use for this purpose), you may prefer a different technique. I will show an example with PostgreSQL and ARRAY(). subq = session.query(Tag.tag) subq = subq.join(document_tags) subq = subq.filter(document_tags.c.document_id == Document.id) subq = subq.order_by(Tag.tag.asc()) subq = subq.correlate(Document) subq = subq.subquery() q = session.query(Document) q = q.filter(func.array(subq) == sorted(tag_list)) q = q.correlate(s) # May not be needed This should generate SQL like: SELECT <Document columns> FROM document WHERE ARRAY( SELECT tag.tag FROM tag JOIN document_tags ON document_tags.tag_id = tag.id WHERE document_tags.document_id = document.id ORDER BY tag.tag ASC ) = %(array_1)s where array_1 would be ['my document', 'random stuff', 'source code']. Note how both the subquery and array_1 have to be sorted. I tend to prefer this type of query since its complexity doesn't grow as you add more tags in your search criteria. -Conor > > On Mar 19, 10:15 am, Michael Bayer <mike...@zzzcomputing.com> wrote: > >> select document.* from document join tags on document.id=tags.document_id >> where tags.tag='foo' and tags.tag='bar' and tags.tag=.... >> >> am I missing something ? that would return no rows in most cases. >> >> if you want to find documents that have an exact list of tags, you'd have to >> do something like the IN query we started with, and additionally ensure no >> extra tags remain. >> >> like: >> >> sess.query(Document).\ >> filter(Document.tags.any(Tag.id.in_([t.id for t in >> tag_list])).\ >> filter(~Document.tags.any(~Tag.id.in_([t.id for t in >> tag_list])) >> >> On Mar 19, 2010, at 8:31 AM, Stodge wrote: >> >> >>> Now we're getting somewhere: >>> >>> expressions = [] >>> for tag in tag_list: >>> expressions += [Tag.tag==tag] >>> documents = >>> session.query(Document).join(Document.tags).filter(and_(*expressions)) >>> >>> Thanks to a Storm example I found. :) >>> >>> On Mar 19, 8:12 am, Stodge <sto...@gmail.com> wrote: >>> >>>> Ok so far I have this: >>>> >>>> expressions = [] >>>> for tag in tag_list: >>>> expressions += session.query(Document).filter(Tag.tag==tag) >>>> documents = >>>> session.query(Document).join(Document.tags).filter(and_(*expressions)) >>>> >>>> Doesn't work but it's progress! :) >>>> >>>> On Mar 18, 2:37 pm, Stodge <sto...@gmail.com> wrote: >>>> >>>>> Thanks that worked beautifully. >>>>> >>>>> On a similar note, how would I match documents with only the tags that >>>>> I specify in the list? My naive attempt is: >>>>> >>>>> for tag in tag_list: >>>>> session.query(Document).join(Document.tags).filter_by(tag=tag) >>>>> >>>>> But that doesn't work. >>>>> >>>>> On Mar 15, 10:54 am, "Michael Bayer" <mike...@zzzcomputing.com> wrote: >>>>> >>>>>> Stodgewrote: >>>>>> >>>>>>> I have two classes with a third table: >>>>>>> >>>>>>> document_tags = Table('document_tags', metadata, >>>>>>> Column('document_id', Integer, ForeignKey('documents.id')), >>>>>>> Column('tag_id', Integer, ForeignKey('tags.id')) >>>>>>> ) >>>>>>> >>>>>>> class Document(Base): >>>>>>> __tablename__ = 'documents' >>>>>>> >>>>>>> id = Column(Integer, primary_key=True) >>>>>>> title = Column(String) >>>>>>> filename = Column(String) >>>>>>> tags = relation('Tag', secondary=document_tags, backref='tags') >>>>>>> >>>>>>> def __init__(self, title, filename): >>>>>>> self.title = title >>>>>>> self.filename = filename >>>>>>> >>>>>>> class Tag(Base): >>>>>>> __tablename__ = 'tags' >>>>>>> >>>>>>> id = Column(Integer, primary_key=True) >>>>>>> tag = Column(String) >>>>>>> >>>>>>> def __init__(self, tag): >>>>>>> self.tag = tag >>>>>>> >>>>>>> I want to find all documents with tags in a given list of tags: >>>>>>> >>>>>>> documents = >>>>>>> session.query(Document).filter(Document.tags.in_(tag_list)) >>>>>>> >>>>>>> except I get the familiar message that the "in_()" operator is not >>>>>>> currently implemented for many-to-one-relations. >>>>>>> >>>>>>> I've searched and found some alternatives but I can't get any to work. >>>>>>> Is there an easy example that will make this work? Thanks >>>>>>> >>>>>> if the error message says "many-to-one" then that's a bug. Your relation >>>>>> is many-to-many. >>>>>> >>>>>> in this case the syntactically easiest method is to use any(). >>>>>> Document.tags.any(Tag.id.in_([t.id for t in tag_list])). >>>>>> >>>>>> A join could be more performant, which would be: >>>>>> >>>>>> query.join(Document.tags).filter(Tag.id.in_([t.id for t in tag_list])) -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalch...@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.