tags:

views:

67

answers:

2

Hi,

I have two tables. One is items, and one istags. Query's here.

The idea is to create a search box to search through the items and then narrow down the search by selecting tags. (Some kind of faceted search).

After fiddling around with mysql tutorials i came up with this:

SELECT `items`.`id`, `items`.`name`, `items`.`pay`, `items`.`desc`
FROM `items`LEFT JOIN (`tags`) ON (`tags`.`item_id`=`items`.`id`) 
WHERE (
    (`tags`.`type`='food' AND `tags`.`name`='pizza')
    OR (`tags`.`type`='drinks' AND `tags`.`name`='lemonade')
    --And so on for every tag
)
ORDER BY `pay` DESC LIMIT 0 , 30

(I removed the full-text search in the query because it isn't relevant right now)

It works pretty good, but I have two problems:

  1. If the user doesn't select any tags, every item that has more then 1 tag attached to it shows up more then 1 time
  2. If the user selects two tags, food=>pizza and drinks=>lemonade by example, every item with pizza or lemonade or both will show up, I only want the items that have BOTH those tags attached to it.

Is it possible to solve these problems with a different mysql query? Or should I fix these problems in the php code. (Or am I doing this totally wrong, and is there a much better way to use tags?)

Hopefully I made myself clear.

Gr,

Bas

+1  A: 

Try WHERE tag.id IN (SUBQUERY)

SELECT `items`.`id`, `items`.`name`, `items`.`pay`, `items`.`desc`
FROM `items`  WHERE items.id IN (SELECT DISTINCT item_id
   FROM tags
   WHERE (
    (`tags`.`type`='food' AND `tags`.`name`='pizza')
    OR (`tags`.`type`='drinks' AND `tags`.`name`='lemonade'))
    --And so on for every tag
)
ORDER BY `pay` DESC LIMIT 0 , 30
MindStalker
This solves the problem with cloned results, but when the user selects more then 1 tag, it doesn't return the items that have both tags. Instead it just returns every item with one of the selected tags. Any ideas?
Bas
If you only want items with pizza AND lemonade just change your OR to an AND. This goes for the vicatcu's method as well, no need to count how many tags if you want them all.
MindStalker
That won't work. Every tag/type combination has his own row. So SELECT DISTINCT item_id FROM tags WHERE ( (`tags`.`type`='food' AND `tags`.`name`='pizza') AND (`tags`.`type`='drinks' AND `tags`.`name`='lemonade'))Won't return any results at all because one row can't hold two values of type and name.
Bas
+2  A: 

Throw in the keyword DISTINCT after SELECT?

SELECT DISTINCT `items`.`id`, `items`.`name`, `items`.`pay`, `items`.`desc` 
FROM `items`LEFT JOIN (`tags`) ON (`tags`.`item_id`=`items`.`id`)  
WHERE ( 
    (`tags`.`type`='food' AND `tags`.`name`='pizza') 
    OR (`tags`.`type`='drinks' AND `tags`.`name`='lemonade') 
    --And so on for every tag 
) 
ORDER BY `pay` DESC LIMIT 0 , 30 

As for your second issue... your logic is just wrong. If I understand you correctly you want to group by items.id and then reject the items that don't come back with the same number of rows as the number of selected tags... maybe something like:

SELECT DISTINCT `items`.`id`, `items`.`name`, `items`.`pay`, `items`.`desc` 
    FROM `items`LEFT JOIN (`tags`) ON (`tags`.`item_id`=`items`.`id`)  
    WHERE ( 
        (`tags`.`type`='food' AND `tags`.`name`='pizza') 
        OR (`tags`.`type`='drinks' AND `tags`.`name`='lemonade') 
        --And so on for every tag 
    ) 
    GROUP BY `items`.`id` 
    HAVING COUNT(*) = 2 
    ORDER BY `pay` DESC LIMIT 0 , 30 

... and replace the 2 in HAVING COUNT(*) = 2 with the number of tags you are trying to concurrently match ...

vicatcu
Thanks, the last query worked perfectly!
Bas
Btw, is this good mysql practice?
Bas
That's a really hard question to answer... as far as performance goes it should be fine so long as you set up the appropriate indexes... my opinion is that if it works correctly consistently and is 'fast enough' for your application, go with it.
vicatcu