MySQL LIMIT in spremenljivke

2 minute read

Trenutno se ukvarjam s popolno predelavo nekega velikega novičarskega portala. Na prvi strani je kar nekaj sekcij, ki morajo prikazovati zadnje novice iz izbranih kategorij. Ker je člankov v bazi trenutno okoli 180.000, bi rešitev v stilu SELECT * FROM clanki ORDER BY datum DESC ne bila prav idealna. Na pomoč seveda priskočijo materialized views, se pa kmalu pojavi problem. Za vsako sekcijo moram izbrati kategorije in število člankov, ki jih vsebuje. Ob vsakem dodajanju, spremembi ali brisanju članka, bom izvedel trigger, ki bo preveril, če je potrebno katerega of materializiranih viewjev na novo kreirati. Po potrebi se bo izvedla naslednja procedura:

CREATE PROCEDURE find_grouped(
      IN cat_id INT,
      IN cat_limit INT
   )
BEGIN
   DELETE
      FROM
         cat_articles
      WHERE
         category_id = cat_id;
   INSERT
      INTO
         cat_articles
         (category_id, article_id)
      SELECT
            cat_id, articles.id
         FROM
            articles
         WHERE
            articles.category_id = cat_id
         ORDER BY
            articles.datum DESC
         LIMIT
            cat_limit;
END;

Proceduro sem poenostavil, dejansko stanje je namreč takšno, da so lahko v eni sekciji članki iz večih kategorij, nastavitve, katere kategorije vključiti in koliko člankov zajeti, se prebere iz nekaj tabel z nastavitvami in ne podaja v parametrih procedure, kot v našem primeru. Ravno tako se, zaradi optimizacije poizvedbe, iz nastavitev prebere tudi, za koliko dni nazaj objavljenih člankov naj se v poizvedbi analizira. Te stvari sicer za naš problem niso pomembne, ker pa zelo zakomplicirajo poizvedbo, sem jih, zaradi lažjega razumevanja, iz procedure brez slabe vesti izpustil.

No, kje se pojavi problem? Problem je LIMIT, ki ne sprejema spremenljivk, kasnejše branje manuala to tudi potrdi:

User variables may be used in contexts where expressions are allowed. This does not currently include contexts that explicitly require a literal value, such as in the LIMIT clause of a SELECT statement, or the IGNORE N LINES clause of a LOAD DATA statement.

Super, kaj pa zdaj? Stric Google ve vse, poglejmo, če je kdo že rešil problem. So ga, na kar nekaj načinov. Eden od njih je uporaba prepared statements, drugega bom pa v nadaljevanju opisal. Ta rešitev se mi je zdela sicer lepša kot prva, pa še vseeno kar grda. Ampak deluje, jebiga.

CREATE PROCEDURE find_grouped(
      IN cat_id INT,
      IN cat_limit INT
   )
BEGIN
   DELETE
      FROM
         cat_articles
      WHERE
         category_id = cat_id;
   SET @__sequence = 0;
   INSERT
      INTO
         cat_articles
         (category_id, article_id)
      SELECT
            cat_id, id
         FROM
            (
            SELECT
                  (@__sequence:=@__sequence+1) AS rownum,
                  articles.id
               FROM
                  articles
               WHERE
                  articles.category_id = cat_id
               ORDER BY
                  articles.datum DESC
            ) AS articles
         WHERE
            rownum <= cat_limit;
END;

Kaj smo naredili? Umetno smo ustvarili nek row number counter in rezultat omejili le na prvih cat_limit vrstic. Grda ali ne tako zelo grda rešitev? Mogoče celo lepa? Presodite sami. Je pa zaenkrat, po mojem mnenju, še najboljši workaround za omenjeni problem.

Pozna kdo boljšega in lepšega? Vesel ga bom, ponoči namreč zaradi tega slabo spim.

Updated: