tags:

views:

76

answers:

4

Im revising my auto-complete style search script. The site has 2 categories... movies and tv shows. Currently, the auto-complete query string looks like this:

SELECT * FROM movies WHERE mov_title LIKE '%" . $queryString . "%' AND mov_status = 1 AND mov_incomplete = 0  ORDER BY mov_type, mov_title LIMIT 10

The problem with this is if there are more than 10 matches with mov_type = 1 (since it orders it by type first), there wont be any mov_type = 2 records, that would make it into the results.

Is there a way for it to do 5 records from mov_type 1, and then 5 records from mov_type 2. The only way I figured you can do it is, run 2 queries and use UNION to put them together.

I always want to have 10 results if possible... and if a search yields 1 movie (mov_type = 1) and 14 tv shows (mov_type = 2), using UNION this will yield 6 records (since I would do LIMIT 5 on each query), instead of 10 (1 movie, and 9 tv shows).

Any other way I can do this?

+1  A: 

This is pretty huge, and would probably perform better if you did some of it in the application layer, but something like this should do it:

select * from ( 
    SELECT * 
    FROM movies 
    WHERE mov_type=1
     and mov_title LIKE '%" . $queryString . "%' 
     AND mov_status = 1 
     AND mov_incomplete = 0  
    LIMIT 5

    UNION ALL

    SELECT * 
    FROM movies 
    WHERE mov_type=2
     and mov_title LIKE '%" . $queryString . "%' 
     AND mov_status = 1 
     AND mov_incomplete = 0  
    LIMIT 5

    UNION ALL 

    SELECT * 
    FROM movies 
    WHERE mov_type=1
     and mov_title LIKE '%" . $queryString . "%' 
     AND mov_status = 1 
     AND mov_incomplete = 0  
     AND id not in (
      SELECT id
      FROM movies 
      WHERE mov_type=1
       and mov_title LIKE '%" . $queryString . "%' 
       AND mov_status = 1 
       AND mov_incomplete = 0  
      LIMIT 5
     )
    LIMIT 5

    UNION ALL 

    SELECT * 
    FROM movies 
    WHERE mov_type=2
     and mov_title LIKE '%" . $queryString . "%' 
     AND mov_status = 1 
     AND mov_incomplete = 0  
     AND id not in (
      SELECT id
      FROM movies 
      WHERE mov_type=2
       and mov_title LIKE '%" . $queryString . "%' 
       AND mov_status = 1 
       AND mov_incomplete = 0  
      LIMIT 5
     )
    LIMIT 5
) a
LIMIT 10

It would be a good idea to move some of this business logic into a view to avoid so much repetition, e.g.:

create view ActiveMovies as
SELECT * 
FROM movies 
WHERE mov_status = 1 
    AND mov_incomplete = 0

Then it would look more like:

select * from (    
    SELECT * 
    FROM ActiveMovies 
    WHERE mov_type=1
        and mov_title LIKE '%" . $queryString . "%' 
    LIMIT 5

    UNION ALL

    SELECT * 
    FROM ActiveMovies 
    WHERE mov_type=2
        and mov_title LIKE '%" . $queryString . "%' 
    LIMIT 5

    UNION ALL 

    SELECT * 
    FROM ActiveMovies 
    WHERE mov_type=1
        and mov_title LIKE '%" . $queryString . "%' 
        AND id not in (
            SELECT id
            FROM ActiveMovies 
            WHERE mov_type=1
                and mov_title LIKE '%" . $queryString . "%' 
            LIMIT 5
        )
    LIMIT 5

    UNION ALL 

    SELECT * 
    FROM ActiveMovies 
    WHERE mov_type=2
        and mov_title LIKE '%" . $queryString . "%' 
        AND id not in (
            SELECT id
            FROM ActiveMovies 
            WHERE mov_type=2
                and mov_title LIKE '%" . $queryString . "%' 
            LIMIT 5
        )
    LIMIT 5
) a
LIMIT 10
RedFilter
This looks like it'll actually work. My only complaint would be the use of 'not in, but considering that the subquery returns at most 5 items, it's not bad. I would probably take the first two 'selects' and make them 'withs' instead, to make it clearer that they're being reused in the later portion.
krdluzni
+1  A: 

Why not:

SELECT * FROM (
    SELECT * FROM movies 
    WHERE mov_title LIKE '%" . $queryString . "%' 
    AND mov_status = 1 
    AND mov_incomplete = 0  
    ORDER BY mov_title
    LIMIT 10
) AS a
ORDER BY a.mov_type, a.mov_title

Subselects make this a much simpler problem.

Daniel Pryden
This query makes no attempt to return the top 5 from each category, it just returns the first ten records sorted by title, so you might get 10 movies when there are TV shows that should be returned as well.
RedFilter
Worse: It actually orders by the type, so if there's 10 matching films, you're guaranteed not to get any shows.
krdluzni
+1  A: 

Just run two queries, one for each category, and have both return 10 items. Then in your application code merge them into a list based on the number or items returned.

krdluzni
A: 

something like this should do it:

SET @num:=0; SELECT *, IF (mov_type=1, @num:=@num+2, 11) AS num 
FROM movies 
WHERE mov_title LIKE '%" . $queryString . "%' AND mov_status = 1 AND mov_incomplete = 0 
ORDER BY num, mov_type, mov_title LIMIT 10

edit: Actually it's probably not so efficient, but you can combine it with the union query, should be faster:

SET @num:=0;
SELECT *, IF (mov_type=1, @num:=@num+2, 11) AS num FROM (
    SELECT *
    FROM movies 
    WHERE mov_type=1
        and mov_title LIKE '%" . $queryString . "%' 
        AND mov_status = 1 
        AND mov_incomplete = 0  
    LIMIT 10

    UNION ALL

    SELECT * 
    FROM movies 
    WHERE mov_type=2
        and mov_title LIKE '%" . $queryString . "%' 
        AND mov_status = 1 
        AND mov_incomplete = 0  
    LIMIT 10
) ORDER BY num LIMIT 10;
Pomyk