Gå til indhold
Jan Engelbrecht Pederseb Logo

SQL til MariaDB med PHP-integration

Et komplet kursus fra grundlæggende database-opbygning og CRUD-operationer til avancerede joins, transaktioner og sikker PHP-integration med PDO.

SQL til MariaDB med PHP-integration

Et komplet kursus fra grundlæggende database-opbygning og CRUD-operationer til avancerede joins, transaktioner og sikker PHP-integration med PDO.

Del 1: Introduktion, relationelle databaser og opsætning

Del 2: Data Definition Language (DDL) – Databasestrukturen

Del 3: Data Manipulation Language (DML) & CRUD i PHP

Del 4: Datarelationer og Joins (Niveau: Øvet)

Del 5: Aggregering, gruppering og avancerede funktioner

Del 6: Avanceret SQL, transaktioner og ydeevne (Niveau: Avanceret)

Del 1: Introduktion, relationelle databaser og opsætning

Dette modul giver dig en grundlæggende forståelse for datalagring og klæder dig på til at forbinde din PHP-kode med MariaDB.

Lektion 1: Hvad er MariaDB og SQL?

Introduktion til relationelle databaser (RDBMS)
En relationel database organiserer data i tabeller, der minder om regneark. Hver tabel består af:

  • Kolonner (Columns): Definerer datatypen (f.eks. id, navn, oprettet_at).
  • Rækker (Rows): Selve dataindlæggene (f.eks. en specifik bruger).

Det "relationelle" betyder, at tabeller kan kobles sammen. En tabel over ordrer kan f.eks. have en reference til et unikt ID i tabellen brugere.

Hvad er SQL?
SQL (Structured Query Language) er det universelle standardsprog, vi bruger til at kommunikere med databasen. Uanset om du vil oprette en tabel, hente data eller slette en række, skriver du en SQL-forespørgsel (query).

MariaDB vs. MySQL

  • Historie: Da MySQL blev opkøbt af Oracle, opstod der bekymring for, om det ville forblive open-source. Skaberen af MySQL valgte derfor at "forke" (kopiere) kildekoden og startede MariaDB.
  • Kompatibilitet: MariaDB er skabt til at være en direkte erstatning (drop-in replacement) for MySQL. De bruger samme syntaks, porte og drivere.
  • Fordele: MariaDB er ofte hurtigere, open-source drevet af fællesskabet og indeholder avancerede funktioner og stor ydeevne.

Lektion 2: Opsætning og dit første databasetjek

For at køre MariaDB lokalt bruger vi XAMPP, som indeholder en fuld Apache-webserver og en MariaDB-database.

  1. Åbn XAMPP Control Panel.
  2. Klik på Start ud for Apache og MySQL (XAMPP kalder stadig MariaDB-modulet for MySQL i kontrolpanelet).
  3. Åbn din browser og gå til: http://localhost/phpmyadmin/

Dette er phpMyAdmin – et visuelt værktøj til at administrere din MariaDB-database.

Opret en database via SQL-fanen
I phpMyAdmin kan du klikke på fanen SQL øverst og køre din allerførste forespørgsel for at oprette en database til dette kursus:

CREATE DATABASE kursus_db CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
            

utf8mb4 sikrer, at databasen understøtter alle sprog samt emojis.

Lektion 3: Forbindelse til MariaDB med PHP (PDO)

Når du skal snakke med en database fra PHP, har du to indbyggede muligheder: mysqli og PDO.

Funktion / Egenskab mysqli PDO (PHP Data Objects)
DatabaseunderstøttelseKun MySQL og MariaDB.Over 12 forskellige databaser (MySQL, MariaDB, PostgreSQL, SQLite, m.fl.).
KodestilBåde procedurel og objektorienteret.Altid ren objektorienteret (Moderne standard).
Navngivne parametreIkke understøttet i prepared statements.Understøttet (Gør koden markant nemmere at læse og vedligeholde).

Det professionelle forbindelsesscript
Opret en fil i din projektmappe, der hedder db_forbindelse.php. Indsæt følgende kode. Dette er den mest sikre og moderne måde at oprette en databaseforbindelse i PHP:

<?php
// db_forbindelse.php

$host    = 'localhost';
$db      = 'kursus_db';
$user    = 'root'; // Standard XAMPP brugernavn
$pass    = '';     // Standard XAMPP adgangskode (tom)
$charset = 'utf8mb4';

// DSN (Data Source Name) definerer databasetype og lokation
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";

// Konfiguration af PDO-indstillinger
$options = [
    // 1. Kast fejl som genkendelige Exceptions, der kan fanges i en try-catch blok
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    // 2. Returner altid data som associative arrays (nøgle/værdi) som standard
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    // 3. Brug ægte prepared statements i databasen frem for PHP-emulering (Vigtigt for sikkerhed)
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    // Vi opretter en ny instans af PDO-klassen
    $pdo = new PDO($dsn, $user, $pass, $options);
    
    // Fjern denne linje i et rigtigt produktionsmiljø, 
    // men brug den nu for at bekræfte, at det virker:
    echo "Succes! Forbindelsen til MariaDB er oprettet.";

} catch (\PDOException $e) {
    // Hvis forbindelsen fejler (f.eks. forkert kodeord), fanges fejlen her
    // stop scriptet og vis en kontrolleret fejlbesked uden følsomme serverdetaljer
    die("Databasefejl: Kunne ikke oprette forbindelse. " . $e->getMessage());
}
            

Sådan tester du det

  1. Placer db_forbindelse.php i din htdocs/kursus/ mappe.
  2. Gå til http://localhost/kursus/db_forbindelse.php i din browser.
  3. Du bør se teksten: Succes! Forbindelsen til MariaDB er oprettet.

Del 2: Databasestrukturen (DDL)

Dette modul dækker de SQL-kommandoer, der styrer arkitekturen og tabellerne i din database – altså fundamentet, før vi overhovedet begynder at indsætte data.

Lektion 4: Oprettelse af tabeller (CREATE TABLE)

Når du designer en tabel, skal du på forhånd definere, hvilken type data hver enkelt kolonne må indeholde. MariaDB har en række faste datatyper, der sikrer, at databasen forbliver hurtig og struktureret.

De vigtigste datatyper i MariaDB

Datatype Beskrivelse Typisk brugsscenarie
INTHeltal (positive og negative).ID'er, antal, priser i ører.
VARCHAR(X)Tekststreng med variabel længde op til X tegn (maks. 65.535).Navne, e-mails, adgangskoder (hashes).
TEXTStore tekstmængder uden fast længdegrænse.Blogindlæg, produktbeskrivelser.
BOOLEANSandt eller falsk (gemmes internt som 1 eller 0).er_aktiv, har_betalt.
TIMESTAMPDato og præcist klokkeslæt.oprettet_at, sidst_opdateret.

Nøgler og attributter (Regelsæt)
Når vi opretter kolonner, kan vi give dem særlige regler:

  • PRIMARY KEY: Fortæller at denne kolonne er unik for hver række (oftest et ID). Der må kun være én primærnøgle pr. tabel.
  • AUTO_INCREMENT: Fortæller MariaDB, at den automatisk skal lægge 1 til ID'et, hver gang en ny række oprettes (1, 2, 3...).
  • NOT NULL: Feltet skal udfyldes. Det må ikke være tomt.
  • DEFAULT: Sætter en standardværdi, hvis der ikke angives noget (f.eks. DEFAULT CURRENT_TIMESTAMP).

PHP-eksempel: Automatisering af tabel-oprettelse
I professionelle projekter laver man ofte et "installationsscript", som opretter alle tabellerne automatisk.
Opret filen installer.php og indsæt følgende kode (læg mærke til, hvordan vi inkluderer vores eksisterende $pdo forbindelse):

<?php
// installer.php
require_once 'db_forbindelse.php'; // Henter vores $pdo instans

// Vi skriver vores SQL-kode til at oprette en brugertabel
$sql = "CREATE TABLE IF NOT EXISTS brugere (
    id INT AUTO_INCREMENT PRIMARY KEY,
    navn VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    er_aktiv BOOLEAN DEFAULT 1,
    oprettet_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";

try {
    // Da vi ændrer på selve database-strukturen og ikke sender brugerinput, 
    // kan vi bruge $pdo->exec() direkte
    $pdo->exec($sql);
    echo "<br>Succes! Tabellen 'brugere' er oprettet eller findes i forvejen.";
} catch (PDOException $e) {
    die("<br>Kunne ikke oprette tabel: " . $e->getMessage());
}
            

Lektion 5: Ændring og sletning af struktur (ALTER, DROP)

Når din applikation udvikler sig, får du brug for at rette i dine tabeller. Det gør vi med kommandoen ALTER TABLE.

1. Tilføj en kolonne
Hvis du f.eks. vil tilføje en fødselsdato-kolonne til din eksisterende brugertabel:

ALTER TABLE brugere ADD fodselsdato DATE NULL;
            

2. Slet en kolonne
Hvis du vil fjerne en kolonne igen, fordi den ikke længere bruges:

ALTER TABLE brugere DROP COLUMN fodselsdato;
            

3. Slet en hel tabel (DROP TABLE)
Hvis du vil slette en tabel fuldstændig (inklusive alt data indeni den!), bruger vi DROP TABLE. Det er en destruktiv kommando, så vi bruger næsten altid IF EXISTS for at undgå fejlbeskeder, hvis tabellen allerede er væk.

<?php
// slet_tabel.php
require_once 'db_forbindelse.php';

$sql = "DROP TABLE IF EXISTS brugere;";

try {
    $pdo->exec($sql);
    echo "<br>Tabellen 'brugere' er nu slettet permanent.";
} catch (PDOException $e) {
    echo "Fejl under sletning: " . $e->getMessage();
}
            

Del 3: Data Manipulation Language (DML) & CRUD i PHP

Dette modul lærer dig at manipulere data permanent i din MariaDB-database via PHP på en fuldstændig sikker og professionel måde.

Lektion 6: Indsættelse og opdatering med Prepared Statements

Når du modtager data fra en bruger (f.eks. via en HTML-formular), må du aldrig indsætte variablerne direkte i din SQL-streng (f.eks. VALUES ('$navn')). En hacker kan indtaste ondsindet SQL-kode i dit tekstfelt og slette hele din database. Dette kaldes SQL Injection.
Løsningen er Prepared Statements. Vi indsætter midlertidige pladsholdere (som :navn) i vores SQL-kode. Derefter beder vi PDO om at "binde" de rigtige værdier på separat, så MariaDB udelukkende fortolker dem som uskadelig tekst.

1. CREATE (Indsæt data med INSERT)

<?php
// opret_bruger.php
require_once 'db_forbindelse.php';

// Data der typisk kommer fra en $_POST-formular
$navn = "Anna Hansen";
$email = "anna@example.dk";

// SQL med navngivne pladsholdere (:navn og :email)
$sql = "INSERT INTO brugere (navn, email) VALUES (:navn, :email);";

try {
    // 1. Forbered forespørgslen (Prepare)
    $stmt = $pdo->prepare($sql);
    
    // 2. Kør forespørgslen og send data med i et array (Execute)
    $stmt->execute([
        'navn'  => $navn,
        'email' => $email
    ]);
    
    // Hent det autogenererede ID på den bruger, der lige er oprettet
    $nytId = $pdo->lastInsertId();
    echo "<br>Bruger oprettet med succes! ID er: " . $nytId;

} catch (PDOException $e) {
    echo "<br>Kunne ikke oprette bruger: " . $e->getMessage();
}
            

2. UPDATE (Opdater data)
Hvis du vil ændre informationer på en eksisterende række, bruger vi UPDATE kombineret med en WHERE-betingelse. Glemmer du WHERE, opdaterer du samtlige rækker i hele databasen!

<?php
// opdater_bruger.php
require_once 'db_forbindelse.php';

$brugerId = 1;
$nytNavn = "Anna P. Hansen";

$sql = "UPDATE brugere SET navn = :navn WHERE id = :id;";

try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        'navn' => $nytNavn,
        'id'   => $brugerId
    ]);
    
    // rowCount() fortæller, hvor mange rækker der blev ændret i databasen
    echo "<br>Antal opdaterede rækker: " . $stmt->rowCount();
} catch (PDOException $e) {
    echo "Fejl under opdatering: " . $e->getMessage();
}
            

Lektion 7: Sletning af data (DELETE)

Når du skal slette data, bruger vi DELETE FROM. Ligesom med UPDATE er det livsvigtigt at bruge en WHERE-betingelse, medmindre du bevidst ønsker at tømme hele tabellen.

<?php
// slet_bruger.php
require_once 'db_forbindelse.php';

$sletId = 1;

$sql = "DELETE FROM brugere WHERE id = :id;";

try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['id' => $sletId]);
    
    echo "<br>Brugeren er nu slettet fra systemet.";
} catch (PDOException $e) {
    echo "Fejl under sletning: " . $e->getMessage();
}
            

Tip: Hvis du vil tømme en hel tabel lynhurtigt og nulstille AUTO_INCREMENT tælleren til 1, bruger du kommandoen: TRUNCATE TABLE brugere; (Dette kræver ikke prepared statements, da der ikke indgår brugervariabler).

Lektion 8: Dataudtræk og filtrering (SELECT)

For at hente data ud af databasen bruger vi SELECT. I PHP bruger vi to forskellige metoder til at modtage resultaterne:

  • fetch(): Henter én enkelt række (godt hvis du søger efter f.eks. et unikt ID eller e-mail).
  • fetchAll(): Henter alle matchende rækker i et stort array (godt til lister).

Hent og udskriv en liste over alle aktive brugere
Her bruger vi avancerede SQL-sorteringer og filtreringer:

  • WHERE: Filtrerer så vi kun får de aktive (er_aktiv = 1).
  • ORDER BY: Sorterer listen alfabetisk efter navn (ASC = A-Å, DESC = Å-A).
  • LIMIT: Sætter en max-grænse for, hvor mange resultater vi vil have op på skærmen (godt til paginering).
<?php
// liste_brugere.php
require_once 'db_forbindelse.php';

$sql = "SELECT id, navn, email, oprettet_at 
        FROM brugere 
        WHERE er_aktiv = :status 
        ORDER BY navn ASC 
        LIMIT 5;";

try {
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['status' => 1]);
    
    // Da vi forventer flere brugere, bruger vi fetchAll()
    $brugere = $stmt->fetchAll();

} catch (PDOException $e) {
    die("Fejl ved hentning af data: " . $e->getMessage());
}
?>

<!DOCTYPE html>
<html lang="da">
<head>
    <meta charset="UTF-8">
    <title>Brugerliste</title>
</head>
<body>
    <h2>Aktive Medlemmer</h2>

    <?php if (empty($brugere)): ?>
        <p>Der blev ikke fundet nogen aktive brugere i databasen.</p>
    <?php else: ?>
        <table border="1" cellpadding="10" cellspacing="0">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Navn</th>
                    <th>E-mail</th>
                    <th>Oprettet</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($brugere as $bruger): ?>
                    <tr>
                        <td><?= htmlspecialchars((string)$bruger['id']) ?></td>
                        <td><?= htmlspecialchars($bruger['navn']) ?></td>
                        <td><?= htmlspecialchars($bruger['email']) ?></td>
                        <td><?= htmlspecialchars($bruger['oprettet_at']) ?></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    <?php endif; ?>
</body>
</html>
            

Bemærk: Vi pakker altid databaseværdierne ind i htmlspecialchars(), når vi udskriver dem i HTML, for at beskytte siden mod Cross-Site Scripting (XSS).

Del 4: Datarelationer og Joins

Dette modul dækker databasenormalisering, opsætning af fremmednøgler og brugen af JOIN-kommandoer i MariaDB koblet med PHP.

Lektion 9: Databasenormalisering og relationer

I en professionel database må vi ikke gentage data unødigt. Hvis en bruger skriver fem blogindlæg, skal vi ikke gemme brugerens navn og e-mail ud for hvert eneste indlæg. Det ville skabe dataredundans. I stedet deler vi data op i to tabeller (brugere og indlaeg) og opretter en relation imellem dem.

1. Én-til-mange-relationen (1:N)
Dette er den mest udbredte relation. Én bruger kan skrive mange indlæg, men et indlæg tilhører kun én specifik bruger.

  • Primary Key (Primærnøgle): Det unikke ID i modertabellen (id i tabellen brugere).
  • Foreign Key (Fremmednøgle): En kolonne i børnetabellen, der refererer direkte til primærnøglen i modertabellen (bruger_id i tabellen indlaeg).

2. Oprettelse af tabeller med Foreign Keys
Vi bruger attributten ON DELETE CASCADE for at sikre dataintegritet. Det betyder: Hvis en bruger slettes fra databasen, sletter MariaDB automatisk alle brugerens tilhørende blogindlæg, så vi ikke efterlader "forældreløse" rækker.
Kør følgende SQL i phpMyAdmin for at oprette den relaterede tabel indlaeg:

CREATE TABLE indlaeg (
    id INT AUTO_INCREMENT PRIMARY KEY,
    bruger_id INT NOT NULL,
    titel VARCHAR(255) NOT NULL,
    indhold TEXT NOT NULL,
    oprettet_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    -- Oprettelse af selve relationen
    FOREIGN KEY (bruger_id) REFERENCES brugere(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            

Lektion 10: Kombinering af tabeller med Joins

Når vi skal hente data ud af databasen til vores PHP-applikation, vil vi typisk gerne vise en liste over blogindlæg sammen med navnet på den forfatter, der har skrevet det. Til det formål bruger vi JOIN.
De to mest anvendte typer af Joins er:

  • INNER JOIN: Henter kun rækker, hvor der er et perfekt match i begge tabeller. Hvis et indlæg ikke har et gyldigt bruger_id, eller en bruger ingen indlæg har, udelades de.
  • LEFT JOIN: Henter alt fra den venstre tabel (f.eks. alle indlæg), uanset om der findes et matchende forfatter-id i den højre tabel eller ej.

PHP-eksempel: Hent artikler og forfatternavn på én gang
Her bygger vi en forespørgsel, der smelter de to tabeller sammen i databasen, før data sendes til PHP. Læg mærke til, hvordan vi bruger aliasser (f.eks. i.titel og b.navn) for at holde koden ren og overskuelig.

<?php
// blog_oversigt.php
require_once 'db_forbindelse.php';

// SQL med INNER JOIN
// i står for 'indlaeg', b står for 'brugere'
$sql = "SELECT i.id, i.titel, i.oprettet_at, b.navn AS forfatter 
        FROM indlaeg i
        INNER JOIN brugere b ON i.bruger_id = b.id
        ORDER BY i.oprettet_at DESC;";

try {
    // Da denne query ikke modtager direkte variabler fra en bruger-formular,
    // kan vi køre den direkte med $pdo->query() i stedet for prepare()
    $stmt = $pdo->query($sql);
    $indlaegListe = $stmt->fetchAll();

} catch (PDOException $e) {
    die("Databasefejl: Kunne ikke hente blogindlæg. " . $e->getMessage());
}
?>

<!DOCTYPE html>
<html lang="da">
<head>
    <meta charset="UTF-8">
    <title>Blog Oversigt</title>
</head>
<body style="font-family: sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem;">

    <h2>Seneste Blogindlæg</h2>

    <?php if (empty($indlaegListe)): ?>
        <p>Der er endnu ikke skrevet nogen blogindlæg.</p>
    <?php else: ?>
        <?php foreach ($indlaegListe as $indlaeg): ?>
            <article style="border-bottom: 1px solid #ccc; padding-bottom: 1.5rem; margin-bottom: 1.5rem;">
                <h3 style="margin-bottom: 0.5rem;">
                    <?= htmlspecialchars($indlaeg['titel']) ?>
                </h3>
                <small style="color: #666;">
                    Skrevet af: <strong><?= htmlspecialchars($indlaeg['forfatter']) ?></strong> 
                    den <?= htmlspecialchars($indlaeg['oprettet_at']) ?>
                </small>
            </article>
        <?php endforeach; ?>
    <?php endif; ?>

</body>
</html>
            

Del 5: Aggregering, gruppering og avancerede funktioner

Dette modul dækker matematiske samlefunktioner, datagruppering og brugen af MariaDB's indbyggede tekst- og datofunktioner koblet med PHP.

Lektion 11: Aggregeringsfunktioner og gruppering

Aggregeringsfunktioner tager en hel række af værdier og regner dem om til én enkelt værdi (f.eks. en sum eller et gennemsnit).

De 5 primære aggregeringsfunktioner

  • COUNT(): Tæller antallet af rækker.
  • SUM(): Lægger alle tal i en kolonne sammen.
  • AVG(): Beregner gennemsnittet (Average).
  • MIN(): Finder den mindste værdi.
  • MAX(): Finder den største værdi.

Gruppering med GROUP BY og filtrering med HAVING
Hvis du vil se, hvor mange blogindlæg hver enkelt bruger har skrevet, skal vi parre COUNT() med kommandoen GROUP BY.
Hvis du efterfølgende vil filtrere i resultatet (f.eks. kun vise de brugere, der har skrevet mere end 2 indlæg), kan du ikke bruge WHERE, fordi WHERE kører før beregningen er lavet. I stedet bruger vi HAVING.

PHP-eksempel: Generering af en forfatterstatistik
Her henter vi en oversigt, der tæller indlæg pr. forfatter, og viser det direkte i et simpelt dashboard.

<?php
// statistik.php
require_once 'db_forbindelse.php';

// SQL der tæller indlæg pr. bruger, sorterer med flest indlæg øverst, 
// og filtrerer så vi kun ser brugere med mindst 1 indlæg
$sql = "SELECT b.navn, COUNT(i.id) AS antal_indlaeg 
        FROM brugere b
        LEFT JOIN indlaeg i ON b.id = i.bruger_id
        GROUP BY b.id
        HAVING antal_indlaeg >= 1
        ORDER BY antal_indlaeg DESC;";

try {
    $stmt = $pdo->query($sql);
    $statistik = $stmt->fetchAll();
} catch (PDOException $e) {
    die("Fejl under generering af statistik: " . $e->getMessage());
}
?>

<!DOCTYPE html>
<html lang="da">
<head>
    <meta charset="UTF-8">
    <title>Forfatter Statistik</title>
</head>
<body style="font-family: sans-serif; max-width: 600px; margin: 2rem auto;">

    <h2>Forfatter Aktivitet Dashboard</h2>
    
    <table border="1" cellpadding="10" cellspacing="0" style="width: 100%; border-collapse: collapse;">
        <thead style="background-color: #f2f2f2;">
            <tr>
                <th>Forfatter Navn</th>
                <th>Antal Skrevne Indlæg</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($statistik as $række): ?>
                <tr>
                    <td><?= htmlspecialchars($række['navn']) ?></td>
                    <td><strong><?= htmlspecialchars((string)$række['antal_indlaeg']) ?></strong> indlæg</td>
                </tr>
            <?php endforeach; ?>
        </tbody>
    </table>

</body>
</html>
            

Lektion 12: MariaDB indbyggede funktioner

MariaDB har hundredvis af indbyggede funktioner, som kan manipulere tekst, datoer og tider direkte i din forespørgsel.

1. Tekstfunktioner

  • CONCAT(str1, str2, ...): Smelter flere tekststrenge sammen.
  • UPPER(str) / LOWER(str): Gør tekst til store eller små bogstaver.
-- Eksempel: Henter navnet i store bogstaver og smelter det sammen med e-mailen
SELECT UPPER(navn) AS navn_store_bogstaver, CONCAT('Kontakt: ', email) AS kontakt_info FROM brugere;
            

2. Dato- og tidsfunktioner
Datohåndtering er essentielt i webudvikling (f.eks. til at finde nye ordrer, slette gamle logs eller tjekke fødselsdage).

  • NOW(): Returnerer det nuværende præcise tidspunkt (YYYY-MM-DD HH:MM:SS).
  • CURDATE(): Returnerer den nuværende dato (uden klokkeslæt).
  • DATE_SUB(dato, INTERVAL enhed): Trækker tid fra en dato (genialt til tidsintervaller).

PHP-eksempel: Hent indlæg fra de sidste 7 dage
Her bruger vi DATE_SUB til automatisk at filtrere indlæg, der er oprettet inden for den seneste uge.

<?php
// seneste_uge.php
require_once 'db_forbindelse.php';

// Hent indlæg hvor oprettelsesdatoen er større end eller lig med nuværende tid minus 7 dage
$sql = "SELECT titel, oprettet_at 
        FROM indlaeg 
        WHERE oprettet_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
        ORDER BY oprettet_at DESC;";

try {
    $stmt = $pdo->query($sql);
    $nyeIndlaeg = $stmt->fetchAll();
} catch (PDOException $e) {
    die("Databasefejl: " . $e->getMessage());
}

// Nu kan $nyeIndlaeg udskrives i HTML på samme måde som i tidligere lektioner
            

Del 6: Avanceret SQL, transaktioner og ydeevne

Dette modul dækker underforespørgsler, uomstødelig sikkerhed via databasetransaktioner samt performanceoptimering (indeksering).

Lektion 13: Subqueries (Underforespørgsler) og Views

1. Subqueries (Forespørgsler inde i forespørgsler)
Nogle gange har du brug for at hente data baseret på en dynamisk beregning, du ikke kender på forhånd. Her kan du placere en SELECT-forespørgsel inde i en anden SELECT-forespørgsel.

-- Eksempel: Hent alle brugere, der har skrevet flere indlæg end gennemsnittet
SELECT navn FROM brugere 
WHERE id IN (
    SELECT bruger_id FROM indlaeg 
    GROUP BY bruger_id 
    HAVING COUNT(id) > (SELECT COUNT(id) / COUNT(DISTINCT bruger_id) FROM indlaeg)
);
            

2. Views (Virtuelle tabeller)
Hvis du har en gigantisk SQL-query med mange JOINs og funktioner, som du skal bruge 20 forskellige steder i dit PHP-projekt, kan du gemme forespørgslen direkte i MariaDB som et View. Det fungerer som en virtuel tabel, du kan lave almindelige SELECT-kald imod.

-- Kør dette i phpMyAdmin for at gemme en kompleks query under navnet 'v_blog_detaljer'
CREATE VIEW v_blog_detaljer AS
SELECT i.id, i.titel, b.navn AS forfatter, COUNT(i.id) AS forfatter_total_indlaeg
FROM indlaeg i
INNER JOIN brugere b ON i.bruger_id = b.id
GROUP BY i.id;
            

Nu bliver dit PHP-kode ekstremt simpelt og letlæseligt:

// I PHP skal du nu bare kalde dit View som en helt almindelig tabel:
$stmt = $pdo->query("SELECT * FROM v_blog_detaljer WHERE forfatter = 'Anna Hansen';");
            

Lektion 14: Transaktioner (ACID-principper)

Forestil dig, at du bygger en webshop eller et banksystem. En bruger køber en vare. Du skal gøre to ting i databasen:

  1. Trække penge fra kundens saldo (UPDATE kunder ...).
  2. Trække 1 fra varens lagerbeholdning (UPDATE produkter ...).

Hvis serveren mister strømmen præcis efter trin 1, men før trin 2, har du trukket kundens penge uden at opdatere lageret. Data er nu korrupt.
Løsningen er transaktioner. En transaktion sikrer, at en række SQL-kald enten lykkes 100% sammen, eller overhovedet ikke udføres (alt rulles tilbage).

<?php
// transaktion_ordre.php
require_once 'db_forbindelse.php';

$kundeId = 4;
$produktId = 12;
$pris = 299;

try {
    // 1. Fortæl MariaDB at vi starter en lukket transaktion
    $pdo->beginTransaction();

    // 2. SQL-kald A: Træk pengene fra kunden
    $stmtA = $pdo->prepare("UPDATE kunder SET saldo = saldo - :pris WHERE id = :id;");
    $stmtA->execute(['pris' => $pris, 'id' => $kundeId]);

    // 3. SQL-kald B: Træk varen fra lageret
    $stmtB = $pdo->prepare("UPDATE produkter SET lager = lager - 1 WHERE id = :id;");
    $stmtB->execute(['id' => $produktId]);

    // 4. Hvis vi er nået hertil uden fejl, gemmes alle ændringer permanent i databasen
    $pdo->commit();
    echo "Ordre gennemført! Databasen er opdateret korrekt.";

} catch (Exception $e) {
    // Hvis blot ÉT af SQL-kaldene fejler, rammer vi denne catch-blok.
    // Vi ruller ALT tilbage, så ingen ændringer gemmes (Database-salvation!)
    $pdo->rollBack();
    echo "Ordre fejlede. Alle ændringer er rullet tilbage: " . $e->getMessage();
}
            

Lektion 15: Indeksering og optimering (Performance)

Når du skriver SELECT * FROM brugere WHERE email = 'test@test.dk';, skal MariaDB som standard løbe samtlige rækker igennem fra top til bund for at finde matchet. Hvis du har 2 millioner brugere, tager det lang tid.
Et indeks (INDEX) fungerer som et stikordsregister i bunden af en tyk bog. Databasen opretter en sorteret søgestruktur i baggrunden, så den kan finde rækken med det samme (et opslag falder typisk fra flere sekunder til få millisekunder).

1. Oprettelse af et indeks
Du bør sætte et indeks på de kolonner, du meget ofte bruger i dine WHERE-betingelser eller i dine JOINs.

-- Gør søgninger på e-mail lynende hurtige (Unique sikrer samtidig at to ikke kan have samme e-mail)
CREATE UNIQUE INDEX idx_bruger_email ON brugere(email);

-- Sæt almindeligt indeks på en kolonne der ofte søges eller sorteres efter
CREATE INDEX idx_indlaeg_oprettet ON indlaeg(oprettet_at);
            

2. Analysér din kode med EXPLAIN
Hvis du har en langsom forespørgsel, kan du sætte ordet EXPLAIN foran din SQL-kode i phpMyAdmin:

EXPLAIN SELECT * FROM brugere WHERE email = 'anna@example.dk';
            

MariaDB vil nu returnere en tabel, der forklarer dens søgestrategi. Hold øje med kolonnen type (hvis der står ALL, betyder det fuld tabelscanning – her mangler du et indeks) og key (fortæller hvilket indeks databasen rent faktisk brugte).

Afslutning på kurset: SQL & MariaDB med PHP

Stort tillykke! Du har nu fuldført hele forløbet og mestrer professionel databasehåndtering til MariaDB. Du har bevæget dig hele vejen fra at forbinde med PHP via PDO, til sikkert at manipulere data (CRUD), strukturere komplekse datarelationer (Joins) og sikre dine transaktioner mod fejl.

Du har nu de nødvendige værktøjer til at bygge sikre, skalerbare og lynhurtige datadrevne webapplikationer!

Klar til at bygge datadrevne løsninger?

Brug din nye viden om SQL og PHP til at skabe sikre og dynamiske webapplikationer med MariaDB.