Načrtovanje in razvoj spletnih aplikacij

Delni izpis podatkovne zbirke in navigacija

Kadar je v tabeli veliko zapisov, jih običajno ne prikažemo vseh hkrati. Namesto tega jih razdelimo na več strani. Tak postopek imenujemo delni izpis ali paginacija.

Delni izpis izboljša preglednost strani in zmanjša količino podatkov, ki jih mora aplikacija prikazati naenkrat. Uporabnik se med posameznimi deli rezultatov premika s povezavami, kot so prejšnja stran, naslednja stran ali številke strani.

Pomni: Delni izpis temelji na treh podatkih: številu zapisov na stran, začetnem odmiku in skupnem številu vseh zapisov.

Pri paginaciji je pomembno, da so rezultati urejeni z ukazom ORDER BY. Tako uporabnik pri prehodu med stranmi dobi predvidljiv in stabilen vrstni red zapisov.

Vsebina strani

Osnovna pravila

Pri delnem izpisu iz celotnega nabora zapisov prikažemo samo določen del. Zato moramo poznati število zapisov na stran in začetni odmik.

  • Za delni izpis uporabimo LIMIT in OFFSET.
  • LIMIT določa, koliko zapisov prikažemo.
  • OFFSET določa, koliko začetnih zapisov preskočimo.
  • Skupno število vseh zapisov pridobimo z COUNT(*).
  • Število strani izračunamo iz skupnega števila zapisov in števila zapisov na stran.
  • Začetni odmik mora biti veljavno nenegativno celo število.
  • Rezultate uredimo z ORDER BY.
  • Pri izpisu vrednosti uporabimo htmlspecialchars().

Pozor: Vrednost parametra start prihaja iz URL naslova, zato jo moramo vedno preveriti. Če je negativna, neveljavna ali previsoka, jo prilagodimo na smiselno vrednost.

Delni izpis zapisov oziroma paginacija

Paginacija pomeni, da uporabniku prikažemo le določen del zapisov, na primer 5 ali 10 zapisov na posamezni strani. Tako je izpis preglednejši in hitrejši.

Pri paginaciji običajno potrebujemo:

  • koliko zapisov želimo prikazati na eni strani,
  • od katerega zapisa naprej želimo začeti izpis,
  • skupno število vseh zapisov v tabeli.

Osnovna sintaksa za delni izpis je:

SELECT *
FROM imeTabele
ORDER BY idStolpec
LIMIT steviloZapisov OFFSET zacetek;

Za izračun paginacije potrebujemo še skupno število zapisov:

SELECT COUNT(*) AS total
FROM imeTabele;

Na podlagi števila vseh zapisov izračunamo, koliko strani potrebujemo, katera stran je trenutna in ali lahko uporabniku pokažemo povezavo na prejšnjo ali naslednjo stran.

Pomni: Če je na strani prikazanih 5 zapisov, potem ima prva stran OFFSET 0, druga stran OFFSET 5, tretja stran pa OFFSET 10.

Osnovni primer z mysqli

Spodnji zgled prikaže 5 zapisov na stran in uporablja parameter start za določitev začetnega odmika.

<?php
define('DB_SERVER', 'localhost');
define('DB_USER', 'uporabnik');
define('DB_PASS', 'skritoGeslo');
define('DB_NAME', 'knjiznica');

$connection = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_NAME);

if (!$connection) {
    die(
        'Povezava s podatkovno zbirko ni vzpostavljena: ' .
        mysqli_connect_error() .
        ' (' . mysqli_connect_errno() . ')'
    );
}

$limit = 5;
$start = $_GET['start'] ?? 0;

if (filter_var($start, FILTER_VALIDATE_INT) === false || (int)$start < 0) {
    $start = 0;
}
$start = (int)$start;

// Skupno število zapisov
$countResult = mysqli_query($connection, "SELECT COUNT(*) AS total FROM knjige");
$countRow = mysqli_fetch_assoc($countResult);
$totalRecords = (int)$countRow['total'];

// Delni izpis zapisov
$stmt = mysqli_prepare(
    $connection,
    "SELECT ID_knjige, Priimek_avtorja, Ime_avtorja, Naslov, Strani, Cena, Leto
     FROM knjige
     ORDER BY ID_knjige
     LIMIT ? OFFSET ?"
);

mysqli_stmt_bind_param($stmt, 'ii', $limit, $start);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

while ($row = mysqli_fetch_assoc($result)) {
    echo htmlspecialchars($row['Naslov']) . '<br>';
}

mysqli_stmt_close($stmt);
mysqli_close($connection);
?>

Pri mysqli uporabimo mysqli_query() za pridobivanje skupnega števila zapisov, za delni izpis pa pripravljen stavek z vrednostma LIMIT in OFFSET.

Osnovni primer s PDO

Tudi z vmesnikom PDO lahko pripravimo delni izpis in navigacijo med stranmi.

<?php
$streznik = 'localhost';
$baza = 'knjiznica';
$uporabnik = 'uporabnik';
$geslo = 'skritoGeslo';

try {
    $pdo = new PDO("mysql:host=$streznik;dbname=$baza;charset=utf8mb4", $uporabnik, $geslo);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $limit = 5;
    $start = $_GET['start'] ?? 0;

    if (filter_var($start, FILTER_VALIDATE_INT) === false || (int)$start < 0) {
        $start = 0;
    }
    $start = (int)$start;

    $stmtCount = $pdo->query('SELECT COUNT(*) AS total FROM knjige');
    $countRow = $stmtCount->fetch(PDO::FETCH_ASSOC);
    $totalRecords = isset($countRow['total']) ? (int)$countRow['total'] : 0;

    $stmt = $pdo->prepare(
        "SELECT ID_knjige, Priimek_avtorja, Ime_avtorja, Naslov, Strani, Cena, Leto
         FROM knjige
         ORDER BY ID_knjige
         LIMIT :limit OFFSET :start"
    );
    $stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
    $stmt->bindValue(':start', $start, PDO::PARAM_INT);
    $stmt->execute();

    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    foreach ($rows as $row) {
        echo htmlspecialchars($row['Naslov']) . '<br>';
    }
}
catch (PDOException $e) {
    echo 'Napaka pri delnem izpisu: ' . $e->getMessage();
}
?>

Pri PDO vrednosti :limit in :start vežemo kot cela števila z PDO::PARAM_INT. Tako jasno določimo, da gre za številski vrednosti.

Izračun strani pri paginaciji

Pri paginaciji moramo poleg poizvedbe izračunati še nekaj dodatnih vrednosti, da lahko uporabniku pravilno prikažemo navigacijo.

  • skupno število zapisov pridobimo z ukazom COUNT(*),
  • število strani izračunamo iz skupnega števila zapisov in velikosti strani,
  • trenutno stran izračunamo iz začetnega odmika,
  • pripravimo povezave za prejšnjo in naslednjo stran,
  • če uporabnik vnese previsok začetni odmik, ga prilagodimo na zadnji smiselni blok zapisov.
$limit = 5;
$totalRecords = 23;

$totalPages = (int)ceil($totalRecords / $limit);

Če imamo 23 zapisov in na eni strani prikažemo 5 zapisov, potrebujemo 5 strani.

$currentPage = (int)floor($start / $limit) + 1;

Če je $start enak 10 in je $limit enak 5, je trenutna stran tretja stran.

Pozor: Pri deljenju skupnega števila zapisov z velikostjo strani uporabimo ceil(), ker tudi nepopolna zadnja stran šteje kot samostojna stran.

Pri delnem izpisu uporabniku običajno prikažemo povezave za premikanje med stranmi. Pri tem moramo izračunati ustrezne vrednosti parametra start.

$previousStart = max(0, $start - $limit);
$nextStart = $start + $limit;

Povezava na naslednjo stran je smiselna samo, če naslednji začetni odmik ne preseže skupnega števila zapisov.

if ($nextStart < $totalRecords) {
    echo '<a href="14_delni-izpis.php?start=' . $nextStart . '">Naslednja</a>';
}

Povezave na številke strani lahko pripravimo z zanko:

for ($page = 1; $page <= $totalPages; $page++) {
    $pageStart = ($page - 1) * $limit;
    echo '<a href="14_delni-izpis.php?start=' . $pageStart . '">' . $page . '</a> ';
}

Pomni: Pri navigaciji je dobro označiti trenutno stran. Tako uporabnik lažje razume, kateri del rezultatov trenutno gleda.

Primerjava pristopov mysqli in PDO

Oba pristopa omogočata delni izpis s preverjenimi vrednostmi za LIMIT in OFFSET. Razlikujeta se predvsem v načinu priprave poizvedbe in branju rezultatov.

Korak mysqli PDO
Preverjanje odmika filter_var() filter_var()
Štetje zapisov mysqli_query() $pdo->query()
Priprava delnega izpisa mysqli_prepare() $pdo->prepare()
Parametra ?, ? :limit, :start
Branje rezultatov mysqli_fetch_assoc() $stmt->fetchAll()

Aplikacija Knjige

📘Aplikacija Knjige

V priloženi aplikaciji Knjige je delni izpis izveden v datoteki 14_delni-izpis.php. Na posamezni strani se prikaže po 5 zapisov, začetni odmik pa se prebere iz parametra start. Če je vrednost neveljavna ali negativna, se nastavi na 0.

Aplikacija najprej z ukazom SELECT COUNT(*) AS total FROM knjige prešteje vse zapise, nato pa z ukazom LIMIT :limit OFFSET :start pridobi le trenutni del rezultatov. Če je začetni odmik previsok, ga prilagodi na zadnji možni blok zapisov.

Nato izračuna še trenutno stran, skupno število strani ter vrednosti za povezavi nazaj in naprej. Na strani se izpišejo tudi aktivne številke strani in preglednica zapisov. Če zapisov ni, se pokaže posebno obvestilo.

Primer: aplikacija Knjige – 14_delni-izpis.php
  1. Določimo, koliko zapisov želimo prikazati na eni strani.
  2. Iz URL parametra preberemo začetni odmik.
  3. Preverimo, ali je začetni odmik veljavno nenegativno celo število.
  4. Z ukazom COUNT(*) pridobimo skupno število zapisov.
  5. Če je odmik previsok, ga prilagodimo na zadnji smiseln blok zapisov.
  6. Z ukazoma LIMIT in OFFSET pridobimo trenutni del rezultatov.
  7. Izračunamo trenutno stran, skupno število strani ter povezavi za nazaj in naprej.
  8. Na strani izpišemo tabelo zapisov in navigacijo med stranmi.
  9. Pri izpisu vrednosti uporabimo htmlspecialchars().

Pozor: Pri paginaciji moramo obravnavati tudi primere, ko v tabeli ni zapisov. Takrat ne prikazujemo nepotrebne navigacije med stranmi, ampak jasno obvestilo.

Priporočila

  • Za delni izpis uporabi LIMIT in OFFSET.
  • Za skupno število zapisov uporabi COUNT(*).
  • Rezultate vedno uredi z ORDER BY.
  • Parameter start preveri kot nenegativno celo število.
  • Če je odmik previsok, ga prilagodi na zadnji smiseln blok zapisov.
  • Uporabniku prikaži trenutno stran in skupno število strani.
  • Pri številkah strani označi trenutno aktivno stran.
  • Pri izpisu podatkov uporabi htmlspecialchars().

Pogoste napake

  • Parameter start ni preverjen.
  • Začetni odmik je negativen.
  • Začetni odmik je večji od števila zapisov in ni prilagojen.
  • Manjka ukaz COUNT(*), zato navigacija ni pravilno izračunana.
  • Ukaza LIMIT in OFFSET sta uporabljena brez ORDER BY.
  • Trenutna stran ni jasno označena.
  • Pri prazni tabeli se še vedno prikazuje navigacija med stranmi.
  • Pri izpisu vrednosti manjka htmlspecialchars().

Pri delnem izpisu mora biti izračun strani usklajen s skupnim številom zapisov. Napačen izračun lahko povzroči prazne strani, nepravilne povezave ali ponavljanje istih zapisov.