简体   繁体   中英

limiting results without using a subquery

I have 2 tables :

  • business (id,name)
  • categories (id,name,business_id)

How do i output all categories in the database with 5 businesses in each category ?

For example, if there are 3 categories in the db, then I want :

category 1
--------------
business 1
business 2
business 3
business 4
business 5

category 2
---------------
business 6
business 7
business 8
business 9
business 10

category 3
------------------
business 11
business 12
business 13
business 14
business 15

The only way I know how to do it is fetch all the categories from db and then programmatically loop through each of them and fire another query to get all businesses in each category.

Any other way of doing it ?

You could use rank method but this involve subquery.

 select id,business_name,cat_name,cat_id from
(SELECT *,@i := CASE WHEN ( @temp_bid <> m.id ) THEN 1                      
            ELSE     @i+1      
            END AS rank ,
            @temp_bid:=m.id as dlset 
 FROM 
 ( Select a.id as id,a.name as business_name,b.name as cat_name,b.id as cat_id
   from business a left outer join categories b on a.id=b.business_id ) m)k,
 (SELECT @i:=0)i,(SELECT @i2:=0)i2
where rank<=5
 ORDER BY business_name,rank desc;

SQL FIDDLE HERE.

You can do this with a correlated subquery in the WHERE clause:

select b.name, c.id
from categories c join
     business b
     on c.business_id = b.id
where c.id in (select c2.id
               from categories c2
               where c2.business_id = c.business_id
               order by rand()
               limit 5
              )

In other database that support ranking functions, this is much simpler.

If the limit isn't supported in this subquery, then you have to do it with a self-join. Yuck, yuck, yuck:

with bc as (
     select b.name, c.id
     from categories c join
          business b
          on c.business_id = b.id
   )
select bc.name, bc.id
from bc join
     bc bcprev
     on bc.name = bcprev.name and
        bcprev.id <= bc.id
group by bc.name, bc.id
having count(*) <= 5

This doesn't get 5 random categories. Instead, it gets the five with the lowest ids.

EDIT BY ROSS SMITH:

The above query returns an error in MySQL 5.5.25, but the following works:

select bc.name, bc.id
from
  (
     select b.name, c.id
     from categories c join
          business b
          on c.business_id = b.id
   ) bc join
   (
     select b.name, c.id
     from categories c join
          business b
          on c.business_id = b.id
   ) bcprev
     on bc.name = bcprev.name and
        bcprev.id <= bc.id
group by bc.name, bc.id
having count(*) <= 5

and seems to return this expected result.

See http://sqlfiddle.com/#!2/ea78a/9

Wouldn't it be better if you stored the category-business relationship in a separate table? If so, then you can use this query:

SELECT
    c_name,
    b_name
FROM
(
    SELECT
        c.name AS c_name,
        b.name AS b_name,
        (@row := IF(@last = cat_id, @row + 1, 1)) AS row,
        @last := cat_id
    FROM
        (SELECT @row := 0, @last = '') a,
        cat_bus cb
    INNER JOIN
        cat c ON c.id = cb.cat_id
    INNER JOIN
        bus b ON b.id = cb.bus_id
    ORDER BY
        c.name,
        b.name
) d 
WHERE
    d.row <= 5
ORDER BY
    c_name,
    b_name  

See sqlfiddle.com for the result.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM