Kürzlich ging auf X ein Artikel herum, der sinngemäß behauptete, CSS sei eine Datenbank. Der Artikel selbst wirkte auf mich eher wie AI-generierter Slop, und natürlich ist CSS keine Datenbank.
Aber der Gedanke hat mich trotzdem nicht losgelassen.
Denn SQL und CSS haben tatsächlich etwas Wesentliches gemeinsam: Beide sind deklarative Sprachen. Wir beschreiben also vor allem, was wir haben wollen, und überlassen einem System die Entscheidung, wie es zu diesem Ergebnis kommt.
Und trotzdem kenne ich viele Backend-Entwickler, die ohne Probleme komplexe SQL-Abfragen schreiben, bei CSS aber schon bei der Frage verzweifeln, warum ein Element fünf Pixel zu breit ist.
Mich eingeschlossen.
Warum eigentlich?
SQL und CSS sind zunächst erstaunlich ähnlich#
Nehmen wir eine etwas interessantere SQL-Abfrage — das teuerste Produkt der jeweils letzten Order aller aktiven Kunden:
SELECT DISTINCT ON ("c"."id") "c"."name", "p"."name" AS "product_name", "oi"."quantity", "p"."price"
FROM "customer" AS "c"
JOIN "order" AS "o" ON "o"."customer_id" = "c"."id"
JOIN "order_item" AS "oi" ON "oi"."order_id" = "o"."id"
JOIN "product" AS "p" ON "p"."id" = "oi"."product_id"
WHERE "c"."status" = 'ACTIVE'
AND "o"."order_date" = (SELECT MAX("o2"."order_date")
FROM "order" AS "o2"
WHERE "o2"."customer_id" = "c"."id")
ORDER BY "c"."id", "p"."price" DESC;Ob die Query performant ist oder nicht, soll hier nicht Thema sein. Mein Punkt ist, ich beschreibe nicht, wie die Datenbank durch ihre Datenstrukturen laufen soll. Ich sage nur, welche Daten ich haben möchte.
Die Datenbank entscheidet selbst, ob sie beispielsweise einen Index verwendet, einen Sequential Scan ausführt oder welche Reihenfolge bei Joins sinnvoll ist.
Bei CSS sieht es zunächst ähnlich aus:
.orders:has(.order[data-status="pending"]) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
gap: var(--space-m, 1rem);
container-type: inline-size;
}
.orders > .customer.active .order:last-child .most-expensive {
font-weight: bold;
font-size: calc(1rem + 0.25cqi);
}
@container (min-width: 48rem) {
.orders > .customer.active .order:last-child .most-expensive {
grid-column: span 2;
}
}Auch hier beschreibe ich nicht, wie der Browser durch den DOM-Baum laufen soll. Sondern ich formuliere eine Regel:
Für alle Elemente, auf die dieser Selektor zutrifft, soll diese Eigenschaft gelten.
Man kann sogar einige grobe Analogien bilden:
| SQL | CSS |
|---|---|
| Tabelle / Relation | DOM |
WHERE | Selektor |
| Join-Bedingung | Beziehungen zwischen Elementen im Selektor |
| Result Set | Menge passender Elemente |
| Query Planner | Style- und Layout-Engine |
| deklarative Abfrage | deklarative Style-Regel |
Natürlich ist diese Analogie begrenzt. CSS ist kein relationales Abfragesystem und SQL keine Layout-Sprache.
Aber beide verlangen vom Entwickler eine ähnliche Umstellung gegenüber imperativen Programmiersprachen:
Beschreibe das gewünschte Ergebnis, nicht den Algorithmus der dorthin führt.
Warum fühlt sich SQL dann für viele Backend-Entwickler so viel kontrollierbarer an?
SQL ist meistens erstaunlich lokal#
Bei einer normalen SQL-Abfrage kann ich relativ lokal darüber nachdenken, was passiert.
Wenn ich
WHERE "status" = 'ACTIVE'durch
WHERE "status" IN ('ACTIVE', 'PENDING')ersetze, ist die Wirkung ziemlich offensichtlich.
Natürlich kann die Abfrage kompliziert werden. Es können zehn Tabellen beteiligt sein, Common Table Expressions, Window Functions, Subqueries und Aggregationen. Aber die Logik, die das Result Set bestimmt, steht zu einem großen Teil vor mir.
CSS funktioniert anders. Nehmen wir eine scheinbar harmlose Eigenschaft wie:
display: flex;Damit ändere ich nicht einfach nur eine sichtbare Eigenschaft eines Elements. Ich verändere gleichzeitig den Layout-Kontext seiner Kinder.
Oder:
position: relative;Das Element sieht möglicherweise exakt genauso aus wie vorher. Gleichzeitig kann es aber zum Bezugspunkt eines ganz anderen, absolut positionierten Elements werden.
Auch overflow, transform, contain oder bestimmte Layout-Eigenschaften können Auswirkungen darauf haben,
wie andere Elemente berechnet oder dargestellt werden.
CSS-Semantik ist, anders als die von SQL, häufig leider alles andere als lokal.
Bei CSS kommt nach dem Selektor erst das eigentliche Problem#
Vielleicht führt gerade der Vergleich mit SQL zu einem typischen Missverständnis.
Bei SQL ist die Auswahl der Daten normalerweise bereits ein wesentlicher Teil des Ergebnisses.
Bei CSS beantwortet ein Selektor dagegen nur die erste Frage:
Auf welche Elemente trifft diese Regel zu?
Danach geht es erst los.
Für eine Property können mehrere Regeln gelten. Also muss die Cascade bestimmen, welche gewinnt. Dazu kommen unter anderem Herkunft, Cascade Layers, Specificity und Reihenfolge.
Dann gibt es Vererbung.
Dann müssen relative Werte berechnet werden.
Und schließlich muss der Browser das Layout bestimmen, wobei Größen und Positionen wiederum von Parent-Elementen, Geschwistern, Inhalt, Fonts, Viewport, Flexbox, Grid oder Container Queries abhängen können.
Sehr grob läuft CSS also durch etwas wie:
Selection
↓
Cascade
↓
Inheritance
↓
Computed Values
↓
Layout
↓
RenderingDas ist deutlich mehr als:
Finde alle Elemente mit der Klasse
customer.
Der Selektor ist eher das CSS-Pendant zum WHERE.
Er ist noch nicht die eigentliche Berechnung des Ergebnisses.
Aber ist SQL wirklich so lokal?#
Bis hierher könnte man zu dem einfachen Schluss kommen:
SQL ist gut strukturiert und lokal, CSS ist voller versteckter Abhängigkeiten.
Ganz so einfach ist es aber nicht.
Denn wenn ich darüber nachdenke, was ich normalerweise meine, wenn ich sage, dass ich “SQL kann”, dann blende ich einen ziemlich großen Teil dessen aus, was in realen Datenbanken passieren kann.
Nehmen wir einen Trigger.
Ich führe aus:
UPDATE "customer"
SET "status" = 'CANCELLED'
WHERE "id" = 123;Wenn ich nur dieses Statement betrachte, scheint vollkommen klar zu sein, was passiert.
Aber vielleicht existiert auf "customer" ein Trigger.
- Der schreibt einen Audit-Eintrag.
- Vielleicht aktualisiert er noch eine andere Tabelle.
- Vielleicht feuert dadurch ein weiterer Trigger.
- Oder eine Funktion wird aufgerufen, die noch ganz andere Dinge tut.
Plötzlich habe ich exakt das Problem, das mich bei CSS nervt:
Ich ändere hier etwas – und irgendwo anders passiert etwas, das an dieser Stelle überhaupt nicht sichtbar ist.
SQL kann also durchaus nichtlokal werden.
When SQL Starts to Feel Like CSS#
Trigger sind dafür wahrscheinlich das offensichtlichste Beispiel.
Aber nicht das einzige.
- Foreign Keys mit
ON DELETE CASCADEkönnen beim Löschen eines Datensatzes weitere Datensätze löschen. - Views können eine erhebliche Menge Logik hinter einem scheinbar einfachen Objekt verstecken.
- Functions können innerhalb einer Query weitere Logik ausführen.
Generated Values, Constraints und andere Datenbankmechanismen beeinflussen ebenfalls das Verhalten eines Statements, ohne vollständig im Statement selbst sichtbar zu sein.
Und dann gibt es Stored Procedures. Spätestens dort verlassen wir häufig sogar die rein deklarative Welt.
Oracle verwendet dafür beispielsweise PL/SQL, PostgreSQL unter anderem PL/pgSQL. Dazu kommen Variablen, Bedingungen, Schleifen, Exceptions und expliziter Kontrollfluss.
Das Interessante daran ist:
Wenn Backend-Entwickler sagen, sie könnten gut mit SQL umgehen, meinen sie meistens nicht automatisch, dass sie auch 20.000 Zeilen PL/SQL mit Stored Procedures und verschachtelten Triggerketten für übersichtliche, einfach verständliche Software halten.
Eher im Gegenteil.
Trigger haben in vielen Entwicklungsteams gerade deshalb einen schlechten Ruf, weil sie Verhalten verstecken.
Vielleicht sind Backend-Entwickler also gar nicht so tolerant gegenüber Nichtlokalität, wie der Vergleich mit CSS zunächst vermuten lässt.
Wir nennen die problematischen Teile nur nicht mehr unbedingt “SQL”.
Vielleicht vergleichen wir das nette SQL mit dem bösen CSS#
Damit wird der Vergleich etwas unfair.
Wenn wir an SQL denken, denken wir meistens an so etwas:
SELECT ...
FROM ...
WHERE ...Wenn wir an CSS-Probleme denken, denken wir dagegen an eine große Anwendung mit hunderten Komponenten, Browser-Defaults,
mehreren Stylesheets, Vererbung, Cascade, Flexbox, Grid, Responsive Design und einer Regel,
die vor drei Jahren jemand mit !important versehen hat.
Wir vergleichen also möglicherweise:
kleines, deklaratives SQL
mit:
einem komplexen CSS-System.
Der fairere Vergleich wäre vielleicht:
eine einzelne SQL-Query mit einer einzelnen CSS-Regel
und:
eine große Datenbank mit Views, Constraints, Functions, Stored Procedures und Triggern mit einer großen CSS-Codebasis.
Und plötzlich liegen die beiden Welten gar nicht mehr so weit auseinander.
- In beiden Fällen kann eine lokale Änderung von Regeln beeinflusst werden, die an einer anderen Stelle definiert wurden.
- In beiden Fällen muss man einen größeren Kontext kennen, um das Ergebnis sicher vorherzusagen.
- Und in beiden Fällen beginnt man irgendwann, Werkzeuge zu benutzen, um herauszufinden, was eigentlich passiert.
Bei SQL ist es dann beispielsweise EXPLAIN ANALYZE.
Bei CSS sind es die Browser Developer Tools mit Computed Styles, Layout-Ansichten und der Frage, welche Regel eigentlich gerade gewinnt.
Trotzdem gibt es einen wichtigen Unterschied#
Ganz auflösen lässt sich der Unterschied zwischen SQL und CSS damit aber nicht.
Eine SQL-Abfrage beschreibt im Wesentlichen eine Operation auf Daten.
Für einen gegebenen Datenbankzustand hat sie eine definierte Bedeutung. Der Query Planner darf völlig unterschiedliche Strategien verwenden, solange dabei dasselbe Resultat herauskommt.
Ob PostgreSQL einen Index Scan oder Sequential Scan verwendet, sollte das fachliche Ergebnis meiner Query nicht verändern.
Der Query Planner ist damit hauptsächlich ein Performance-Problem.
Bei CSS ist die “Ausführung” viel enger mit dem eigentlichen Ergebnis verbunden.
Die Größe eines Elements kann beispielsweise davon abhängen, wie viel Platz seine Geschwister benötigen. Diese wiederum hängen von ihrem Inhalt ab. Der Inhalt hängt vom verwendeten Font ab. Und ein anderer Container kann über eine Container Query wiederum andere Styles aktivieren.
Das Layout selbst ist Teil des Ergebnisses.
CSS beschreibt damit nicht einfach eine Abfrage auf einem DOM-Baum. Es beschreibt ein System von Regeln und Abhängigkeiten, aus denen der Browser einen konkreten visuellen Zustand berechnen muss.
Insbesondere moderne Layout-Systeme wie Flexbox und Grid fühlen sich deshalb teilweise eher wie Constraint-Programming an.
Ich sage beispielsweise sinngemäß:
Dieses Element darf wachsen, dieses darf schrumpfen, jenes soll mindestens so groß wie sein Inhalt sein, die Spalten sollen den verfügbaren Platz aufteilen und ab einer bestimmten Container-Größe gelten andere Regeln.
Und der Browser löst dieses System für mich.
SQL hat eine Escape Hatch#
Dazu kommt noch ein Unterschied, den ich besonders interessant finde.
Wenn das deklarative Modell einer Datenbank unbequem wird, gibt es fast immer eine Fluchtmöglichkeit.
- Ich kann eine Stored Procedure schreiben.
- Ich bekomme Variablen.
- Ich bekomme
IF. - Ich bekomme Schleifen.
Im Extremfall kann ich also sinngemäß sagen:
Vergiss die elegante relationale Lösung. Ich programmiere das jetzt einfach.
Innerhalb von CSS gibt es diese Möglichkeit nicht.
Natürlich kann ich JavaScript einsetzen. Aber dann habe ich CSS verlassen.
CSS selbst zwingt mich erstaunlich konsequent dazu, das Problem weiterhin deklarativ auszudrücken.
Moderne CSS-Features wie Custom Properties, calc(), Grid, Container Queries oder :has() machen die Sprache immer mächtiger.
Sie verwandeln sie aber nicht in eine klassische imperative Programmiersprache.
Vielleicht ist genau das ein Grund, warum CSS für Entwickler mit imperativem Hintergrund manchmal so frustrierend ist.
SQL lässt uns irgendwann aus dem deklarativen Paradigma aussteigen. CSS nicht.
Können Backend-Entwickler wirklich besser SQL als CSS?#
Damit komme ich zu meiner ursprünglichen Frage zurück.
Warum können so viele Backend-Entwickler SQL, aber angeblich kein CSS?
Vielleicht stimmt die Frage schon nicht.
Die meisten Backend-Entwickler beherrschen wahrscheinlich einen relativ gutmütigen Ausschnitt von SQL:
SELECT,JOINundWHERE- Aggregationen mit
GROUP BY INSERT,UPDATEundDELETE
Das entspricht ungefähr dem Teil der Sprache, bei dem Ursache und Wirkung noch recht gut lokal nachvollziehbar sind.
Bei Stored-Procedures, Triggern, CTE-Queries, Window Functions steigen die meisten Entwickler aber aus.
Sobald also eine Datenbank voller Trigger, versteckter Seiteneffekte und umfangreicher PL/SQL- oder PL/pgSQL-Programme steckt, reagieren viele Entwickler erstaunlich ähnlich wie auf ein großes Legacy-CSS-Stylesheet:
Wo kommt dieser Wert jetzt her?
Warum passiert das?
Wer hat diese Regel eingebaut?
Was geht kaputt, wenn ich das ändere?
Vielleicht ist CSS also gar nicht grundsätzlich schlechter verständlich als SQL.
Vielleicht erreichen wir bei CSS nur viel schneller den Punkt, an dem deklarative Regeln miteinander interagieren.
Und welche Sprache ist nun besser?#
Damit bleibt die Frage aus dem Titel.
SQL or CSS — Which Is the Better Language?
Natürlich ist die Frage unsinnig. Beide lösen völlig unterschiedliche Probleme.
Aber wenn ich trotzdem vergleichen müsste, würde ich SQL einen Vorteil bei der lokalen Verständlichkeit geben. Eine einzelne Query lässt sich meistens recht isoliert betrachten. Ein einzelner CSS-Block hängt dagegen schneller von seiner Umgebung ab.
CSS ist dafür beeindruckend konsequent deklarativ.
Ein Browser kann dasselbe Stylesheet auf einem Smartphone, einem Notebook oder einem riesigen Bildschirm anwenden, auf Inhalte reagieren, deren Länge beim Schreiben des CSS niemand kannte, verschiedene Fonts berücksichtigen und daraus ein Layout berechnen.
Würde man all das imperativ programmieren wollen, wäre der Code vermutlich erheblich schlimmer als das CSS, über das wir uns beschweren.
Und vielleicht ist das die eigentliche Erkenntnis aus dem Vergleich:
CSS ist möglicherweise nicht deshalb schwierig, weil es eine schlechte Programmiersprache ist.
Es ist schwierig, weil wir häufig versuchen, es wie eine Programmiersprache zu lesen, während der Browser eigentlich ein System von Regeln und Constraints löst.
SQL hat uns längst daran gewöhnt, dem Computer zu sagen, was wir wollen.
Bei CSS fällt es uns offenbar schwerer, ihm auch zuzutrauen, herauszufinden, wie es dahin kommt.
Dieser Artikel ist mit Unterstützung von KI entstanden.
