Search code examples
sqlmany-to-many

Many-to-many SQL selection


I'm currently writing a tag cloud system.

So I wrote the following sql schema :

CREATE TABLE bookmark_model_bookmark (
    id INTEGER NOT NULL, 
    link VARCHAR(255), 
    title VARCHAR(140), 
    description TEXT, 
    PRIMARY KEY (id)
);

CREATE TABLE bookmark_model_tag (
    id INTEGER NOT NULL, 
    name VARCHAR(20), 
    PRIMARY KEY (id)
);

CREATE TABLE bookmark_tag (
    bookmark_model_bookmark_id INTEGER NOT NULL, 
    bookmark_model_tag_id INTEGER NOT NULL, 
    PRIMARY KEY (bookmark_model_bookmark_id, bookmark_model_tag_id), 
    CONSTRAINT bookmark_model_bookmark_tags_fk FOREIGN KEY(bookmark_model_bookmark_id) REFERENCES bookmark_model_bookmark (id), 
    CONSTRAINT bookmark_model_tag_bookmarks_fk FOREIGN KEY(bookmark_model_tag_id) REFERENCES bookmark_model_tag (id)
);

And fill it with a small amount of data :

SELECT * FROM bookmark_model_bookmark;

1|http://braindead.fr|braindead|
2|http://example.fr|example|example text

SELECT * FROM bookmark_model_tag;

1|test
2|braindead
3|example

SELECT * FROM bookmark_tag;

1|1
1|2
2|1
2|3

In more readable terms :

  • Bookmark #1 named braindead refer to http://braindead.fr/ and have two tags : "test" and "braindead".
  • Bookmark #2 named example refer to http://example.fr/ and have to tags "test" and "example".

So the next step of my development is to select all the tag linked to bookmarks which are tagged with a list of selected tag.

For example is I pass in parameter "test" it must return "braindead" AND "example" because both are tagged with test tag.

But if I pass in parameter "test" and "braindead", only "braindead" must be returned by the query because it's the only bookmark tagged with "test" AND "braindead".

SELECT *
FROM bookmark_model_tag AS tag
INNER JOIN bookmark_tag ON (bookmark_tag.bookmark_model_tag_id = tag.id)
WHERE bookmark_tag.bookmark_model_bookmark_id IN ( /* Here my subquery */ );

But I can't manage to find out the subquery. I have start with something like this :

SELECT bookmark.id
FROM bookmark_model_bookmark AS bookmark
INNER JOIN bookmark_tag ON (bookmark.id = bookmark_tag.bookmark_model_bookmark_id)
WHERE /* what to write here ? */

Just to be precise, input parameter of my query is an array of tags id.

So here I'm stuck.

EDIT

I'm using an ORM (sqlalchemy) for build my queries, and it's build queries which looks like this :

SELECT tag.id AS tag_id, tag.name AS tag_name, count(tag.id) AS count_1 
FROM tag, bookmark_tag, (
  SELECT bookmark.id AS id 
  FROM bookmark, bookmark_tag 
  WHERE bookmark.id = bookmark_tag.bookmark_id 
  AND bookmark_tag.tag_id IN (1, 2) 
  GROUP BY bookmark.id 
  HAVING count(bookmark.id) = 2) AS anon_1 
WHERE tag.id = bookmark_tag.tag_id 
AND bookmark_tag.bookmark_id = anon_1.id 
AND tag.id NOT IN (1, 2) 
GROUP BY tag.id

Solution

  • Not sure if this is still an issue but the query should look something like this:

    DECLARE @array TABLE(
        tag varchar(50)
    )
    INSERT INTO @array
    VALUES('test')
    INSERT INTO @array
    VALUES('braindead')
    
    select * from bookmark_model_bookmark
    where id in(
        select bookmark_model_bookmark_id FROM bookmark_tag x INNER JOIN bookmark_model_tag t ON t.id = x.bookmark_model_tag_id
        where t.name in (select tag from @array)
        group by bookmark_model_bookmark_id
        having bookmark_model_bookmark_id >= (select count(0) from @array))
    

    In the example above i've used a table but you running any sort of split on a comma delimited string input would end up in a similar position. Let me know if this was what you were looking for or if ive got the wrong end of the stick??? Thanks,