1

Consider these two tables stored in a MySQL database. The first stores a list of movies and the second stores metadata about each movie. Rather than adding 'category' and 'actor' fields to the movies table, they are stored as key/value pairs in the movie_attributes table, as each movie may have more than one category or actor attributed to it.

movies

id    name
-----------
0     hot rod
1     star wars

movie_attributes

id    movie_id    key      value
----------------------------------------
0     0           genre    comedy
1     0           genre    stupid
2     0           actor    andy samberg
3     0           actor    harrison ford
4     0           actor    chester tam
5     1           genre    adventure
6     1           actor    harrison ford

What would be the most efficient way to query the movies based on a search criteria that allows the user to select multiple genres and multiple actors? If the user adds more than one category to their search, I would like the results to include movies from both categories (but only those videos that also match the actors selected.)

For example, this pseudo-query:

select * from movies where (genre = 'comedy' OR genre = 'adventure') AND (actor = 'harrison ford')

would return this:

id    name
-----------
0     hot rod
1     star wars

Currently, my solution uses a mess of subqueries and it's awfuly slow, as my database contains more than two attribute key types per movie (genres, actors, actresses, tags, etc..) and it's terribly slow. Here's the real query that's equivalent to the example pseudo-query above:

select * from movies
    WHERE (select count(*) from movie_attributes 
           WHERE key='genre'
           AND (value='comedy' 
                OR value='adventure'))>0
    AND
          (select count(*) from movie_attributes 
           WHERE key='actor' 
           AND (value='harrison ford'))>0

Is there a more efficient way to achieve the same results using joins or some other SQL voodoo magic I'm unfamiliar with? Thanks!

mcarpenter
  • 47
  • 6

2 Answers2

0

See if this one helps:

SELECT m.* FROM `movie_attributes` ma
JOIN `movies` m ON (ma.movie_id=m.id)
WHERE 
  (`key` = 'genre' and `value` in ('comedy', 'adventure'))
OR (`key` = 'actor' and `value` in ('harrison ford'))

Secondly, make sure, there is an index on the fields key, value in movie_attributes

Dan Soap
  • 10,114
  • 1
  • 40
  • 49
  • 1
    The key can't be 'genre' and 'actor' at the same time, so your query will never return anything. You need to join to the movies table twice. I'm also not really sure why you used a left join instead of an inner join. – Josh Jun 16 '11 at 21:55
0
SELECT *
FROM movies 
WHERE EXISTS(
  SELECT NULL 
  FROM movie_attributes
  WHERE movies.id = movie_attributes.movie_id AND
        key = 'genre' AND value in ('comedy', 'adventure')
) AND 
EXISTS(
  SELECT NULL 
  FROM movie_attributes
  WHERE movies.id = movie_attributes.movie_id AND
        key = 'actor' AND value = 'harrison ford'
)

I would however suggest against this kind of design, see @n8wrl answer.

Community
  • 1
  • 1
Magnus
  • 45,362
  • 8
  • 80
  • 118