SQL Fundamentals: DDL, DML & DQL
Before Fabric, Snowflake, Databricks, or BigQuery, there's one skill every one of them assumes: SQL. This series starts at the fundamentals and builds to procedural SQL and cross-platform syntax — all runnable locally, no server required.
SQL has three pillars. Everything else in this series is built on them:
- DDL (Data Definition Language) —
CREATE,ALTER,DROP: defines schema structure. - DML (Data Manipulation Language) —
INSERT,UPDATE,DELETE: writes data into that structure. - DQL (Data Query Language) —
SELECT: retrieves it back out. - A view is a saved
SELECT— DDL that defines a query, not a table. - Every constraint (
PRIMARY KEY,FOREIGN KEY,CHECK) is enforced by the engine, not your application code.
Why SQL Is Still the First Skill
Every platform in this series — Snowflake, Databricks, BigQuery, Fabric, Redshift — speaks SQL as its primary interface. The dashboards, the dbt models, the ad-hoc analysis: all SQL underneath. Learn the language once, and the platform becomes a detail. That's the practical reason this series exists as a reference and not just an introduction: an analyst who is fluent in the concepts below can sit down in front of any of these five platforms and be productive within the hour, translating unfamiliar keywords rather than re-learning how relational data works.
DDL: Defining Structure
CREATE TABLE with NOT NULL, UNIQUE, DEFAULT, CHECK, and FOREIGN KEY constraints does most of the data-quality work before a single row is ever inserted — the engine simply refuses a row that violates them. Each constraint answers a different question: NOT NULL says this column can never be empty; UNIQUE says no two rows may share this value; DEFAULT supplies a value when none is given; CHECK enforces an arbitrary condition (like price >= 0); and FOREIGN KEY ties a column to another table's primary key so an order can never point at a customer that doesn't exist. Getting these right at table-creation time is far cheaper than catching the same bad data downstream in a report.
SQL · DDL
CREATE TABLE customers (
id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
membership_level VARCHAR(10) NOT NULL DEFAULT 'basic'
CHECK (membership_level IN ('basic', 'premium', 'vip')),
created_date DATE NOT NULL DEFAULT (date('now'))
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_date DATE NOT NULL DEFAULT (date('now')),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','shipped','delivered','cancelled')),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
DQL: Retrieving Data with SELECT
SELECT is the query every analyst writes dozens of times a day, and its five clauses always run in the same logical order regardless of how they're typed: FROM (which table), WHERE (which rows), GROUP BY (collapse into groups, covered in Part 3), SELECT (which columns/expressions), then ORDER BY (final sort) and LIMIT (cap the row count, Part 12). Knowing this order explains a common beginner confusion: you can't reference a column alias from SELECT inside the same query's WHERE, because WHERE logically runs before SELECT has computed anything.
SQL · a plain SELECT, clause by clause
SELECT name, city, membership_level -- 4. which columns
FROM customers -- 1. which table
WHERE membership_level = 'vip' -- 2. which rows
ORDER BY name ASC -- 5. final sort
LIMIT 10; -- 6. cap the results
DML: Writing Data, Safely
The one hard rule that carries through every module in this series: preview every UPDATE/DELETE with a SELECT using the identical WHERE clause first. Omitting WHERE touches every row in the table — there's no "undo" once it commits. INSERT adds new rows; UPDATE modifies specific columns on existing rows that match a WHERE; DELETE removes whole rows that match a WHERE. All three are permanent the moment they're committed, which is exactly why the preview-first habit matters more here than almost anywhere else in SQL.
SQL · safe DML
-- 1. Preview exactly which rows will be affected
SELECT id, customer_id, order_date, status
FROM orders
WHERE status = 'cancelled';
-- 2. Only then run the DELETE, with the identical WHERE.
-- Children before parents, or a FOREIGN KEY will reject the delete:
DELETE FROM order_items WHERE order_id = 40;
DELETE FROM orders WHERE status = 'cancelled' AND id = 40;
-- An UPDATE with no WHERE touches every row in the table:
UPDATE customers SET membership_level = 'vip' WHERE id IN (9, 14, 21);
Views: A Saved Query
CREATE VIEW is DDL that defines a SELECT, not a table — the underlying rows are never duplicated or stored separately; the query behind the view simply re-runs, in full, every single time someone selects from it. That makes a view useful for exactly two things: hiding a complex, multi-table JOIN behind a simple name that reads like a table, and giving a metric a single, consistent definition so five different analysts don't each write a slightly different "active customer" query and get five different numbers. A view is always as fresh as the underlying tables, at the cost of re-computing the whole query every time — Part 9 picks this up again to contrast plain views against materialized views, which trade that freshness for speed by storing the result physically.
SQL · view
CREATE VIEW customer_order_totals AS
SELECT
c.id AS customer_id,
c.name AS customer_name,
c.membership_level,
COUNT(o.id) AS order_count,
COALESCE(SUM(oi.quantity * oi.unit_price), 0) AS lifetime_spend
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
LEFT JOIN order_items oi ON oi.order_id = o.id
GROUP BY c.id, c.name, c.membership_level;
-- Query it exactly like a table:
SELECT * FROM customer_order_totals
ORDER BY lifetime_spend DESC
LIMIT 5;
DDL defines the shape; DML fills it; DQL reads it back out.
The takeaway: SQL fundamentals aren't a warm-up before the "real" platform skills — they're the one skill that transfers unchanged across every platform this series covers.
Runnable code for this part: sql-for-data-platforms on GitHub — Module 1 of 14.
← Back to Publications