1

I have a problem here, I wonder if it is possible to do this in just one query instead of having the server do several queries.

Example of database

So here's the thing, what I want is, in each category, to search all stores belonging to that category within a limit of 24 stores. That is, imagining that I have 5 categories registered, I will search 24 stores belonging to that category, which in the conclusion I will have a total of 120 stores in the query result.

The following code is an example, but this will only fetch the first 24 stores of any category without iterating through each category.

SELECT *
FROM categories c
    LEFT JOIN (SELECT * FROM stores_categories LIMIT 24 OFFSET 0) sc ON sc.id_category = c.id
WHERE type = 'STORE' OR type = 'ALL';

Someone sent me this and it is very similar to my problem, How to SELECT the newest four items per category?, It is kind of like that, but I would like to be able to limit and with offset to make pages and not just the first 24 most recent in each category.

The example code from the link I was given:

SELECT sc1.*
FROM stores_categories sc1
    LEFT OUTER JOIN stores_categories sc2 ON (sc1.id_category = sc2.id_category AND sc1.id_store < sc2.id_store)
GROUP BY sc1.id_store
HAVING COUNT(*) < 24
ORDER BY sc1.id_category;

And the database server which one I'm is the version 5.5.64-MariaDB. An fiddle example where is possible to make tests with same values from my database. https://www.db-fiddle.com/f/jcowJL9S4FQXqKg2yMa8kf/0

Rick James
  • 135,179
  • 13
  • 127
  • 222
0rangeFox
  • 55
  • 1
  • 9
  • Sample data must be provided as textual CREATE TABLE + INSERT INTO scripts or as online fiddle. They must be accompanied with desired output for this source data and detailed explanations. *I'm using is MariaDB with version 5.6.* Show `SELECT VERSION();` output. – Akina Nov 26 '21 at 06:23
  • Look for more examples in the tag I added. – Rick James Nov 28 '21 at 06:57

1 Answers1

1

It could make use of a calculated row_number per category.

--
-- Emulated row_number via variables
-- 
SELECT sc.id, id_store
, cat.name AS cat_name
, cat.type AS cat_type
, rn
FROM
(
  SELECT sc.*
  , @rn := CASE 
           WHEN @cat_id = sc.id_category
           THEN @rn + 1 ELSE 1
           END AS rn
  , @cat_id := sc.id_category as cat_id
  FROM stores_categories sc
  CROSS JOIN (SELECT @cat_id:=0, @rn:=0) vars
  ORDER BY sc.id_category, sc.id_store DESC, sc.id DESC
) sc
LEFT JOIN categories cat 
  ON cat.id = sc.id_category
WHERE rn BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC, sc.id DESC

Or

--
-- Grouped self-join
-- 
SELECT sc.id, sc.id_store
, cat.name AS cat_name
, cat.type AS cat_type
, COUNT(sc2.id_store) AS cnt
FROM stores_categories sc
LEFT JOIN stores_categories sc2
  ON sc2.id_category = sc.id_category 
 AND sc2.id_store >= sc.id_store
LEFT JOIN categories cat 
  ON cat.id = sc.id_category
GROUP BY sc.id
HAVING cnt BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC;

Or

--
-- Correlated sub-query
-- 
SELECT sc.id, id_store
, cat.name AS cat_name
, cat.type AS cat_type
, ( select count(*)
    from stores_categories sc2
    where sc2.id_category = sc.id_category 
      and sc2.id_store >= sc.id_store
  ) as rn
FROM stores_categories AS sc
LEFT JOIN categories cat 
  ON cat.id = sc.id_category
HAVING rn BETWEEN 1 AND 24
ORDER BY sc.id_category, sc.id_store DESC;

Demo on db<>fiddle here

LukStorms
  • 28,916
  • 5
  • 31
  • 45
  • And then how do I do pagination? I do an calculation where I get the page number and multiply it by 24 and change it in the part where it says "HAVING cnt/rn BETWEEN X + 1 AND Y"? – 0rangeFox Dec 03 '21 at 12:32
  • Don't know best practises for pagination. So just a guess. Something like `rn BETWEEN 1+(@page*24) AND (@page+1)*24`. Where the variable starts at 0. Or calculate 2 variables. Then `rn BETWEEN @startnum AND @endnum` – LukStorms Dec 03 '21 at 12:52
  • 1
    Yes that's what I did, apparently it seems to be working fine. Thanks for the help! – 0rangeFox Dec 03 '21 at 13:55
  • Ok! Btw, which of the 3 methods do you feel is the fastest? I'm guessing method 1, but I can't test it against lots of data. – LukStorms Dec 03 '21 at 14:45
  • 1
    I opted for the "Grouped Self-Join" (2nd Option) because visually it is cleaner and looks the most correct way. Anyway, I benchmarked it anyway, and the results were these, In total I have 629 entries and their runtime was: First option: 1s 390ms Second option: 1s 281ms Third option: 1s 556ms – 0rangeFox Dec 03 '21 at 16:14
  • I see, thx for the response. 629 is relatively small. So performance wise either is fine at the moment. – LukStorms Dec 03 '21 at 16:27