views:

47

answers:

3

I have two tables (notes and tags). Tags has a foreign key to notes. There may be several tag records to a single note record.

I'm trying to select only the notes that contain all of the desired tags.

SELECT notes.*, tags.* FROM notes LEFT JOIN tags ON notes.id = tags.note_id 
WHERE {my note contains all three tags I would like to search on}

Using WHERE tag.name IN ('fruit','meat','vegetable') will bring back all the notes that have a "fruit", "meat", OR "vegetable" tag. I only want to return notes that have all three "fruit", "meat", AND "vegetable" tags.

I'm ok to bring back multiple records (the query above would yield a record for each tag).

I need help with my where clause. Is it possible to do this without a sub-select?

A: 

If it is not too late, wouldn't it be better to have a NoteTag table - so you will have notes, tags, notetag tables and you can use simple queries and AND operator to find what you want ?)

Dani
For this purpose, notes and tags (with a foreign key to notes) is sufficient, but for the sake of argument, having a separate xref table would still present the same problem.select all notes (and respective tags) that have match all tags queried by.
Nate Tallman
+1  A: 

Without a subselect, as per request:

SELECT  notes.*
FROM    notes
JOIN    tags
ON      tag.note = notes.id
        AND tag.name IN ('fruit','meat','vegetable')
GROUP BY
        notes.id
HAVING  COUNT(*) = 3

More efficient method would be:

SELECT  notes.*
FROM    (
        SELECT  to.note
        FROM    tags to
        WHERE   to.name = 'meat'
        AND     EXISTS
                (
                SELECT  NULL
                FROM    tags ti
                WHERE   ti.note = to.note
                        AND to.name IN ('fruit', 'vegetable')
                LIMIT 1, 1
                )
        ) t
JOIN    notes
ON      note.id = t.note

The trick here is to put the search on the most selective tag ('meat' in my example) on the first place.

Quassnoi
This is close.I would like to also bring back the tags associated with the note (all tags, not just the ones searched on).SELECT notes.*, tags.* ...This really should yield multiple records per note, depending on how many tags there are.
Nate Tallman
+1  A: 

Assuming tags(note_id, tag) is declared UNIQUE or PK, then you can use:

SELECT note_id, COUNT(tag) FROM tags
WHERE tag IN ('fruit', 'vegetable', 'meat')
GROUP BY note_id
HAVING COUNT(tag) >= 3

Further answer based on OP's comment below. To get all tags for the records that match:

SELECT * FROM tags
INNER JOIN
(
SELECT note_id, COUNT(tag) FROM tags
WHERE tag IN ('fruit', 'vegetable', 'meat')
GROUP BY note_id
HAVING COUNT(tag) >= 3
) search_results
ON search_results.note_id = tags.note_id
Larry Lustig
But I still need to be able to return all tags associated with the note. This query would return 1 record per note, when there should be as many records per note as there are tags.
Nate Tallman
Just join the solution above back to the tags table to get one record per tag.
Larry Lustig
I added the SQL to do that JOIN into the original answer.
Larry Lustig