0

In the project's design schema, one product may have many images.

Now I want to select n images from products with the situation:

  • If n products were defined, select 1 image from each.
  • Else select more images from each product limit n

Do we also need a PHP side action to reach the goal?

The schema is as expected:

product (id, title, ...)
image (id, product_id, filename, ...)

I cannot even think of a query like this, that's why I haven't unfortunately tried anything.

The query should look like this:

SELECT * FROM image ..with..those..hard..conditions.. LIMIT n
Mohammad Naji
  • 5,372
  • 10
  • 54
  • 79

2 Answers2

3

If I understand it well -- you need n images. If possible from different products. Otherwise, several images from the same product is acceptable as fallback.

From now, the only solution I could think of, is to build a temporary table, with numbered rows such as there will be one "image" of each product at the "top" -- and filed with the rest of the images.

Once that table is build, your query is just a SELECT ... LIMIT n.

This will perform horribly -- and if you choose that solution of something inspired -- you should consolidate the image table off-line or on schedule basis.

see http://sqlfiddle.com/#!2/81274/2

--
-- test values
--
create table P (id int, title char(20));
insert into P values
  (1, "product 1"),
  (2, "product 2"),
  (3, "product 3");

create table I (pid int, filename char(40));
insert into I values
   (1, "image p1-1"),
   (1, "image p1-2"),
   (3, "image p3-1"),
   (3, "image p3-2"),
   (3, "image p3-3");

--
-- "ordered" image table
--
create table T(n int primary key auto_increment not null,
                         filename char(20));


--
-- consolidate images (once in a while)
--
delete from T;
insert into T(filename)
    select filename from I group by pid;
insert into T(filename)
    select filename from I order by rand();

--
-- do the actual query
--
select * from T limit n;

EDIT Here is a completely different idea. Not using a consolidation table/view -- so this might be seen as better:

http://sqlfiddle.com/#!2/57ea9/4

select distinct(filename) from
    (select 1 as p, filename from I group by pid
     union (select 2 as p, filename from I order by rand() limit 3)) as T
order by p limit 3

The key point here is I don't have to really "number" the rows. Only to keep track of which rows are coming from the first SELECT. That is the purpose of p. I set both LIMIT clause to the same value for simplicity. I don't think you have to "optimize" that part since the benefit would be very small -- and ORDER BY RAND() is so terrible that you don't have to think about "performances" here ;)

Please note I don't have fully tested this solution. Let me know if there is some corner cases (well.. any case) that don't work.

Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
  • Thank you very much Sylvian, for both the solution and of course the implementation you did. I really appreciate your nice and perfect answer. Love SO's great community and I hope I be able to answer other guys problems like you did to me. – Mohammad Naji Jul 29 '13 at 13:51
  • @MohammadNaji The answer from GeorgesDupéron below gave me a different idea. See the edit if you need ;) – Sylvain Leroux Jul 29 '13 at 16:55
  • Your version using `union` is much cleaner to read than mine :) , congratulations! Note however that as you say, `order by rand()` is terrible: `select x from mytable order by rand() limit 3` will fetch and order randomly all the rows in `mytable`, and *then* will take the first three (so if `mytable` has 1 millon rows, it will be quite slow). – Suzanne Soy Jul 29 '13 at 17:35
  • 1
    @GeorgesDupéron Thinking a little bit more about it, I would stick with my former solution if the OP application has the classic profile "insert occasionally, select often". Concerning the terrible `ORDER BY RAND()` and how to get rid of it, this a a classic question on SO: http://stackoverflow.com/questions/1244555/how-can-i-optimize-mysqls-order-by-rand-function just to provide a ... hum ... *random* link ;) – Sylvain Leroux Jul 29 '13 at 17:46
1

http://sqlfiddle.com/#!2/32198/2/0

create view T as select (select filename from I where pid = id order by filename limit 1) as singleImage from P having singleImage is not null;

select * from (
  select singleImage from T
  union all (select filename from I where filename not in
    (select singleImage from T) order by rand() limit 5)
  ) as MoreThanN limit 5;

If your N is rather small, you may benefit from my technique for selecting random rows from large tables: although it is intended for selecting a single row, it could be adapted to select a few random rows relatively easily.

Here's the SQL with Sylvain Leroux's examples:

-- Test values
create table P (id int, title char(20));
insert into P values
  (1, "product 1"),
  (2, "product 2"),
  (3, "product 3");

create table I (pid int, filename char(40));
insert into I values
   (1, "image p1-1"),
   (1, "image p1-2"),
   (3, "image p3-1"),
   (3, "image p3-2"),
   (3, "image p3-3"),
   (3, "image p3-4"),
   (3, "image p3-5");

-- View to avoid repeating the query
create view T as select (select filename from I where pid = id order by filename limit 1) as singleImage from P having singleImage is not null;

-- Query
select * from (
  select singleImage from T
  union all (select filename from I where filename not in
    (select singleImage from T) order by rand() limit 5)
  ) as MoreThanN limit 5;
Community
  • 1
  • 1
Suzanne Soy
  • 3,027
  • 6
  • 38
  • 56
  • First, I thought about `UNION` too -- but *nothing* guarantee any particular order for the returned rows. As far as I know, the rows from the second `select` might appears "first". That's why I had to use a temporary table to "number" the rows (and doing *two* separate `INSERT ... SELECT...` to ensure that the first select rows are "numbered" first)... – Sylvain Leroux Jul 29 '13 at 16:38
  • ... but Georges you gave me an other idea. See the edit in my answer – Sylvain Leroux Jul 29 '13 at 16:55
  • @SylvainLeroux I thought union all would preserve the order of the results, but it seems I was wrong -- for mysql at least, didn't check for other engines. – Suzanne Soy Jul 29 '13 at 17:29