The fluent select() builder from the last two lessons is the everyday tool, but sometimes you already know the exact SQL you want to run — a fixed query with no dynamic joins or conditions to assemble. For that case, Drupal gives you a more direct route: Connection::query(), which runs a literal SQL string with placeholders, safely.
What you'll learn in this lesson
- How to run a literal SQL string with
Connection::query() - Why placeholders are non-negotiable even in a "static" query
- When a static query is the right choice over the dynamic builder
A real static query
From the Drupal Examples project's own documentation comment:
// SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
\Drupal::database()->query(
"SELECT * FROM {dbtng_example} WHERE uid = :uid and name = :name",
[':uid' => 0, ':name' => 'John']
)->execute();
Two details make this a genuinely Drupal-flavored static query rather than plain PDO:
Curly braces around the table name
Notice {dbtng_example}, not just dbtng_example. Drupal wraps table names in curly braces specifically so its database layer can rewrite them at query time — this is what makes Drupal's multisite table-prefixing feature work transparently, even inside a hand-written SQL string.
Named placeholders, never string concatenation
:uid and :name are placeholders, resolved from the second array argument — not interpolated into the string. This is the one rule that must never be broken: the moment you concatenate a variable directly into a SQL string ("WHERE name = '$name'"), you've opened a SQL injection hole, "static" query or not. The placeholder syntax is what keeps this method just as safe as the dynamic query builder.
select() builder from the previous lessons is the better fit, since building that logic inside a raw string gets unreadable fast.Quick check: would
\Drupal::database()->query("SELECT * FROM {dbtng_example} WHERE uid = " . $uid)be safe if$uidcame from user input? (No — that's direct string concatenation, not a placeholder, so it's exactly the SQL injection pattern this lesson warns against, regardless of which method name you called it through.)
Key takeaways
Connection::query()runs a literal SQL string — the right tool when the query shape is genuinely fixed.- Wrap table names in curly braces (
{table_name}) so Drupal's multisite prefixing still applies inside raw SQL. - Always use named placeholders (
:name) with a parameters array — never concatenate variables into the SQL string, ever. - Reach for the dynamic
select()builder instead the moment your query needs conditional logic.
Coming up next
You've now read data three different ways. The last piece of the Database API puzzle is making sure multiple writes succeed or fail together — that's what transactions are for, up next.