views:

188

answers:

6

*UPDATE, seems like the answer was given, but the SQL query his erroring out.. can anyone help? See the first answer, i posted the problem there.

So to put this simply. I have 3 tables. An "item" table and a "tag" table. Then I also have an "item_tag" table which ties the 2 together.

I want to make a query that lists all the items that have particular tags assigned to it. So I would like the query to list all items that have tag x and tag y applied to it.

This is what I have come up with so far.. except that this will list any that match either tag id 148 or tag id 152. If I make it say "AND" it shows no results.

SELECT *
FROM (`item`)
RIGHT OUTER JOIN `item_tag` ON `item`.`id` = `item_tag`.`fk_item_id`
WHERE `item_tag`.`fk_tag_id` = "152" OR `item_tag`.`fk_tag_id` = "148"
GROUP BY `item`.`id`
+1  A: 
select * from item, item_tag a, item_tag b
where item.id = a.fk_item_id and a.fk_tag_id = 148 and
item.id = b.fk_item_id and b.fk_tag_id = 152;
Paul Tomblin
Nope. He *has* to have both tags assigned to the item.
Franz
Yeah, I fixed it now.
Paul Tomblin
And what if he has more than two tags?
Franz
Sorry. I'm not trying to be a badass, I'm not smarter than you right now... ;)
Franz
I read the question as only needing two tags. If he needs more than two, I'd have to think about it a bit.
Paul Tomblin
A: 

select * from item where item.id = itemtag.fk_item_id and item_tag.fk_tag_id = "152" or item_tag.fk_tag_id = "148" group by item.id

ennuikiller
Doesn't it have to be `AND` now?
Franz
+2  A: 

You can get ids of the items that have all of the tags you want using this query:

SELECT fk_item_id
FROM item_tag
WHERE fk_tag_id IN (5,10,15)
GROUP BY fk_item_id
HAVING COUNT(*) = 3

And then just

SELECT * 
FROM item 
WHERE id 
IN 
(
    SELECT fk_item_id
    FROM item_tag
    WHERE fk_tag_id IN (5,10,15)
    GROUP BY fk_item_id
    HAVING COUNT(*) = 3
)

You just have to modify the ids and the 3 which is the count of those ids.

When your table does not have UNIQUE constraint (it should have) and there can be the same tags in particular item you should modify the query to this:

SELECT * 
FROM item 
WHERE id 
IN 
(
    SELECT fk_item_id
    FROM ( SELECT DISTINCT fk_item_id, fk_tag_id FROM item_tag ) someAlias
    WHERE fk_tag_id IN (5,10,15)
    GROUP BY fk_item_id
    HAVING COUNT(*) = 3
)
Lukasz Lysik
Looks like that's it.
Franz
`HAVING COUNT(DISTINCT *)` is safer, in case a tag could be related more than once.
OMG Ponies
Yes, you're right. Corrected.
Lukasz Lysik
what does "HAVING COUNT(DISTINCT *) mean? also.. I assume the "IN" is the same as an having "OR"'s in the "WHERE" statement?
Roeland
just ran into a problem with the query. check screenshot.. doesnt realy explain the error thougH :(http://sensenich.bythepixel.com/error.gifif i take out the "having count(disticnt) line the query runs.
Roeland
The problem was with `DISTINCT`. When you remove it it work. However if in your table one item can be tagged more than once with the same tag you can make one more SELECT. I will edit my answer.
Lukasz Lysik
in all reality a tag should only be applied to an item at most once. it wouldt make sense to apply the tag 'red' to a item more then once. my backend currently doesnt realy allow it to happen.. but if a user clicks to fast, or refreshes to quickly the same tag could be added to one item. is there a database way to prevent this?
Roeland
Yes, you can add `UNIQUE` constraint. Like this `ALTER TABLE item_tag ADD UNIQUE(fk_item_id,fk_tag_id)`. It won't allow to appear the same pair (item, tag) twice.
Lukasz Lysik
Or you could make the `tag_id` and `fk_item_id` columns the primary key - a composite key.
OMG Ponies
+2  A: 

I put system in the tem table and programing language in tags. Using this query you have this result:

SELECT *
FROM (`item`)
INNER JOIN `item_tag` ON `item`.`id` = `item_tag`.`fk_item_id`
INNER JOIN `tag` ON `item_tag`.`fk_tag_id` = `tag`.`id`
WHERE `tag`.`desc` = 'Java' or `tag`.`desc` = 'C++'

Result:

1, 'Sistem A', 1, 1, 1, 'Java'
1, 'Sistem A', 1, 3, 3, 'C++'
2, 'Sistem B', 2, 1, 1, 'Java'
2, 'Sistem B', 2, 3, 3, 'C++'

In this case, system A and System B use Java and C++. To simplify use distinct clause

SELECT distinct item.desc
FROM (`item`)
INNER JOIN `item_tag` ON `item`.`id` = `item_tag`.`fk_item_id`
INNER JOIN `tag` ON `item_tag`.`fk_tag_id` = `tag`.`id`
WHERE `tag`.`desc` = 'Java' or `tag`.`desc` = 'C++'

Result:

System A
System B
Mário Celso Teixeira
+2  A: 

Using JOINs:

 SELECT it.fk_item_id
   FROM ITEM i
   JOIN ITEM_TAG it1 ON it1.fk_item_id = i.id
                    AND it1.fk_tag_id = 148
   JOIN ITEM_TAG it2 ON it2.fk_item_id = i.id
                    AND it2.fk_tag_id = 152

Using GROUP BY/HAVING COUNT:

  SELECT it.fk_item_id
    FROM ITEM_TAG it
   WHERE it.fk_tag_id IN (148, 152)
GROUP BY it.fk_item_id
  HAVING COUNT(*) = 2

Caveat emptor:
The GROUP BY/HAVING COUNT version of the query is dependent on your data model having a composite key, unique or primary, defined for the two columns involved (fk_item_id and fk_tag_id). If this is not in place, the database will not stop duplicates being added. If duplicate rows are possible in the data, this version can return false positives because an item_id could have 2 associations to the tag_id 148 - which would satisfy the HAVING COUNT(*) = 2.

OMG Ponies
I'd prefer the method using JOIN, compare with http://stackoverflow.com/questions/621884/database-development-mistakes-made-by-appdevelopers "5. Favouring aggregation over joins"
VolkerK
@VolkerK: I lean towards JOINs myself, but the GROUP BY has been more popularly voted in the past on SO :/
OMG Ponies
+2  A: 

I am hoping that this will model your tables ( you mention 3 tables but I only have 2 in this )

-- drop table item ;
-- drop table item_tag ;

create table item (
    id int not null auto_increment 
    , primary key ( id )
);
create table item_tag (
    fk_item_id int not null
,   fk_tag_id int not null
);

insert into item values ( 1 );
insert into item values ( 2 );
insert into item values ( 3 );

insert into item_tag values ( 1, 148 );
insert into item_tag values ( 1, 152 );

insert into item_tag values ( 2, 148 );

insert into item_tag values ( 3, 152 );

select i.id, a.fk_tag_id, b.fk_tag_id
from item i, item_tag a, item_tag b
where i.id = a.fk_item_id 
  and i.id = b.fk_item_id 
  and a.fk_tag_id = 148 
  and b.fk_tag_id = 152 
;

Produces the output

+----+-----------+-----------+
| id | fk_tag_id | fk_tag_id |
+----+-----------+-----------+
|  1 |       148 |       152 | 
+----+-----------+-----------+
1 row in set (0.00 sec)
Philip Schlump
That limits him to two equal tags, too.
Franz