Module 1, Session 3. Master standalone SELECT, expressions, aliases, psql catalog navigation, the LIMIT safety net, DDL table architecture, and DML row modification.
Open in Present mode · Live class schedule · Try the playground
Jo class mein bola, yahan nukte mein likha hai.
What I said in class, written here as key points.
From writing your first queries with SELECT, expressions, and psql exploration, to building durable relational structures with DDL and breathing life into tables with DML.
Mastering standalone SELECT evaluation, psql database reconnaissance, DDL table architecture, and DML data manipulation.
Declarative queries vs manual ledger chaos at St. Xavier's.
Evaluating arithmetic, dates, system functions, and AS labels.
\dn, \dt, \d inspection and preventing terminal crashes with LIMIT.
The 3 languages of SQL and the intentional all-TEXT strategy.
Physical containers and logical department namespace partitions.
Idempotent IF NOT EXISTS, schema mutations, and DROP vs TRUNCATE.
Why empty DDL structures need living rows of data.
Multi-row inserts, RETURNING, and the danger of unconstrained updates.
Hands-on student enrollment drills + 10 master interview scenarios.
A two-minute bridge from foundational theory to practical database manipulation.
SELECT b.title, s.student_name, l.due_date FROM library.lending l JOIN library.books b ON l.book_id = b.book_id JOIN library.students s ON l.student_id = s.student_id WHERE b.subject = 'Physics' AND s.grade = 12;
Every physical object in Mrs. Sharma's library directly corresponds to an essential PostgreSQL relational primitive:
| Physical Library Object | SQL Relational Equivalent | Production Database Example |
|---|---|---|
| 🏛️ The Library Building | Database | retailmart |
| 🗂️ Departmental Book Shelves | Schema | college, sales |
| 📖 A Bound Register Book | Table | college.students |
| 📝 One Handwritten Line Entry | Row (Record / Tuple) | Student '1', 'Aarav' |
| 🏷️ Register Column Headers | Column (Attribute) | first_name, email |
| 🔍 Asking Mrs. Sharma for a Book | SQL Query (SELECT) | SELECT * FROM college.students; |
FROM clause to pull rows from a table? How can we query before loading RetailMart?”
SELECT evaluates scalar math, string transforms, and system introspection instantly in memory.
SELECT 42 AS answer; SELECT 25 * 4 AS calculation; SELECT version();
SELECT without a table first; it will become your primary testing and debugging superpower.An SQL query tells the database what you want — not how to get it. The engine figures out the execution plan.
When you go to a restaurant, you order: "Butter Paneer with 2 Garlic Naans."
You do not tell the chef: "Walk 4 steps left, open the fridge, pick 200g cottage cheese, heat the pan to 180°C, stir clockwise for 3 minutes."
SELECT 'Hello, SQL!';
No table needed. No FROM clause. SQL can evaluate expressions standalone — this is the superpower you'll use in this session.
-- Numeric constant
SELECT 42;
-- Text string literal
SELECT 'Hello, TheShiraverse!';'text' for strings. Double quotes "col" for identifiers only.-- Multiplication, division, addition
SELECT 25 * 4;
SELECT 100 / 4;
SELECT 10 + 5 * 2; -- PEMDAS (20)-- Multiple expressions at once
SELECT
100 AS base,
100 * 0.18 AS gst,
100 * 1.18 AS total;-- Engine diagnostics & timestamps
SELECT version();
SELECT current_database();
SELECT current_date, current_time;Time to get your hands dirty! Standalone arithmetic, string manipulation, system introspection, and safe table inspection across 4 challenge gates.
SELECT is the most versatile SQL command on Earth. It functions as a calculator, a clock, a system inspector, and a data retriever without needing a table.
SELECT 100 * 1.18 AS calculator;
SELECT current_timestamp AS clock;
SELECT version() AS system_inspector;
PostgreSQL waits patiently for the semicolon before executing. Forgetting it is the #1 beginner mistake — your query appears to hang, but it's just waiting for termination!
-- Correct termination:
SELECT 42;
-- What happens without semicolon in psql:
retailmart=# SELECT 42
retailmart-# ; -- The dash (# -> -#) indicates continuation line!
retailmart=# means ready for a new statement. retailmart-# means PostgreSQL is still waiting for the statement to terminate with ;.
retailmart=# SELECT 1 + 1
retailmart-#
retailmart-# [Pressing Enter repeatedly]
retailmart-# Nothing happens! Cursor just moves down!SELECT 1 + 1 and pressing Enter. Nothing happens. The cursor just moves down. Beginners panic thinking PostgreSQL crashed or froze.retailmart-# ;
?column?
----------
2
(1 row)
retailmart=# SELECT 1 + 1; — the semicolon tells PostgreSQL: "I'm done, compile and execute this now!"Two essential production habits: renaming output streams cleanly with AS and using PostgreSQL as a rapid testing scratchpad.
Output Renaming vs Table Mutation: An alias renames the column header in your query output — it never alters or renames the underlying table data on disk.
-- Without alias: column header defaults to 'current_date'
SELECT current_date;
-- With alias: column header cleanly displays as 'today'
SELECT current_date AS today;
The AS keyword is technically optional (SELECT current_date today; works), but ALWAYS write AS explicitly in production code.
AS causes catastrophic bugs when a comma is deleted: SELECT first_name last_name FROM users; accidentally aliases first_name as last_name instead of fetching two columns!
Your Engineering Swiss Army Knife: Most tutorials claim every SELECT requires FROM. In PostgreSQL, you can evaluate standalone expressions without querying any table.
-- Fast math without opening Python or a calculator:
SELECT 100 * 1.18 AS price_with_tax;
-- Test string/date functions before touching production tables:
SELECT UPPER('theshiraverse') AS preview;
-- Generate instant mock sequences on the fly:
SELECT generate_series(1, 5) AS sample_seq;
SELECT first!
The essential command-line playbook for exploring databases, inspecting catalogs, and querying safely without a GUI.
Confirm your environment, list cluster databases, and switch active connections instantly:
-- Check active DB, user, host & port:
retailmart=# \conninfo
-- List all databases on the cluster:
retailmart=# \l
-- Switch database connection:
retailmart=# \c retailmart
\l, \c) are client meta-commands — never add a semicolon. SQL statements (SELECT) are sent to the engine and must end with ;.
Discover schemas, inspect relations, and avoid the empty public trap:
-- List all 16 schema namespaces:
retailmart=# \dn
-- Inspect tables in specific schema:
retailmart=# \dt sales.*
-- Dump all 55 tables across all schemas:
retailmart=# \dt *.*
\dt only searches the public schema. In enterprise databases partitioned into domain schemas, \dt appears empty. Always run \dt *.*!
Describe columns, types, and primary keys, then sample rows with qualified names:
-- Describe table columns & datatypes:
retailmart=# \d sales.orders
-- Extended details (indexes, storage):
retailmart=# \d+ sales.orders
-- ALWAYS qualify with schema + LIMIT:
SELECT * FROM sales.orders LIMIT 10;
SELECT * FROM sales.orders; against massive enterprise tables dumps gigabytes of raw data, freezing your terminal. Always cap with LIMIT 10.
\q
Exit terminal (or Ctrl+D)
\x auto
Expanded view (flips wide tables into vertical records)
\timing
Execution stopwatch (tracks ms per query)
\? / \h
Built-in manuals (\? client / \h SQL)
postgresql://prod-db:5432/retailmart. Tell us what schemas we have and inspect the structure of sales.orders.”
SELECT * FROM orders; (error: no schema), then SELECT * FROM sales.order_items; with no LIMIT: 3,75,202 rows flood the screen and the terminal crawls.
\dn → \dt sales.* → \d sales.orders → SELECT * FROM sales.orders LIMIT 5;.
-- 1. List schemas: retailmart=# \dn -- 2. Inspect tables in schema: retailmart=# \dt sales.* -- 3. Check columns & datatypes: retailmart=# \d sales.orders -- 4. Sample safely: SELECT * FROM sales.orders LIMIT 5;
Type every psql command manually in your terminal. Never copy-paste — tactile muscle memory is what saves you under pressure.
Close all notes. Write from memory: (a) list schemas (\dn), (b) list tables (\dt), (c) describe (\d), (d) LIMIT 5 query.
Explain to a peer: "Why does SELECT * FROM orders fail without sales.orders? Why does LIMIT save the server buffer?"
CREATE, ALTER, DROP. Never buys groceries.INSERT, UPDATE, DELETE. Cannot break walls.SELECT. Changes nothing.-- 1. ARCHITECT (DDL): Build empty room CREATE TABLE home.living_room ( sofa_id TEXT, color TEXT ); -- 2. BUILDER (DML): Put furniture inside INSERT INTO home.living_room VALUES ('1', 'Navy Blue'); -- 3. RESIDENT (DQL): Look around the room SELECT * FROM home.living_room;
Clear division of responsibilities: who queries the data, who builds the structure, and who modifies the records.
Primary Verb: SELECT
What It Touches: Reads data rows into memory. Alters zero bits on disk storage.
Who Uses It: Analysts, Product Managers, BI Developers, Data Scientists.
-- Inspect customer directory:
SELECT customer_id, first_name, email
FROM customers.customers
LIMIT 10;
SELECT.
Primary Verbs: CREATE, ALTER, DROP, TRUNCATE
What It Touches: Schema metadata, table definitions, columns, data types, and primary keys.
Who Uses It: Data Engineers, DBAs, System Architects.
-- Define table blueprint:
CREATE TABLE college.students (
student_id TEXT,
first_name TEXT,
email TEXT
);
Primary Verbs: INSERT, UPDATE, DELETE
What It Touches: Rows inside existing tables. Populates, modifies values, and purges rows.
Who Uses It: Backend Developers, Application APIs, ETL Pipelines.
-- Populate row inside table:
INSERT INTO college.students
VALUES ('1', 'Aarav', 'aarav@edu');
-- Mutate row with condition:
UPDATE college.students
SET email = 'aarav.s@edu'
WHERE student_id = '1';
TRUNCATE deletes all data rows from a table, it is classified as DDL. DML (DELETE) scans rows one-by-one and fires triggers. TRUNCATE bypasses row scanning entirely — it drops the underlying disk storage segment and re-initializes an empty table structure in 1 millisecond at the engine metadata level.
Why we intentionally typed every column as TEXT in today's session: structure before constraints.
In this session, every table we create will declare its columns as TEXT. Every ID ('1'), price ('999'), date ('2026-09-29'), and boolean ('true') is treated as a string literal.
CREATE TABLE college.students (
student_id TEXT, -- Treated as '1', '2', etc.
first_name TEXT,
email TEXT,
date_of_birth TEXT, -- Stored as '2004-03-15'
is_active TEXT -- Stored as 'true' / 'false'
);
VALUES ('1', 'Aarav', 'true');
Getting halted by ERROR: invalid input syntax for integer: "abc" while learning basic table syntax completely kills momentum.
Your entire brainpower stays focused on table naming, column order, schema qualification (college.students), and multi-row operations.
You will soon feel the pain when sorting numbers stored as text ('10' comes before '2'!), creating the ultimate "aha!" moment for strict data types in the next session.
INT, NUMERIC, TIMESTAMPTZ) and constraints will introduce mathematical indexing and ironclad validation.
The two top layers of the relational hierarchy: purchasing the plot of land, then building organized domain folders.
A database is the top-level container on the server cluster. Everything lives inside it: schemas, tables, views, and data. Before building rooms, you purchase the plot of land:
-- Create a brand new database plot:
CREATE DATABASE college_db;
-- Verify on cluster & connect:
retailmart=# \l
retailmart=# \c college_db
CREATE DATABASE cannot run inside a transaction block (BEGIN ... COMMIT). It must execute as an autonomous standalone cluster command.
A schema is a namespace — a logical directory grouping related tables. Without schemas, all 55 RetailMart tables would sit in one chaotic unindexed pile in public:
-- Create domain folder for academic data:
CREATE SCHEMA IF NOT EXISTS college;
-- Create domain folder for supply chain:
CREATE SCHEMA IF NOT EXISTS supply;
-- Verify schemas inside database:
college_db=# \dn
sales away from payroll) • 2. Role Security (grant permissions per schema) • 3. Multi-tenancy (isolate clients).
Defining durable structural schemas: columns, datatypes, and namespaces across domains.
CREATE TABLE college.students (
student_id TEXT,
first_name TEXT,
last_name TEXT,
email TEXT,
date_of_birth TEXT,
is_active TEXT
);CREATE TABLE college.courses (
course_id TEXT,
course_name TEXT,
credits TEXT,
department TEXT
);CREATE TABLE shop.products (
product_id TEXT,
product_name TEXT,
category TEXT,
unit_price TEXT,
stock_quantity TEXT
);CREATE TABLE shop.orders (
order_id TEXT,
customer_id TEXT,
order_date TEXT,
order_status TEXT,
total_amount TEXT
);CREATE TABLE schema.table (col_name DATA_TYPE, ...);
The unwritten engineering rules: collision-free naming conventions and automated migrations that never crash pipelines.
In multi-table enterprise databases, consistency avoids ambiguity, syntax collisions, and painful quote-escaping:
| Rule | Standard (Do This) | Anti-Pattern (Avoid) | Why It Matters |
|---|---|---|---|
| Casing | snake_case |
CamelCase, UPPER |
Postgres folds unquoted to lower |
| Table Names | students, orders (Plural) |
student, order (Singular) |
Tables represent row collections |
| Column Names | email, city (Singular) |
emails, cities (Plural) |
Each cell holds a single attribute |
| Primary Keys | student_id, order_id |
id |
Unambiguous in complex multi-JOINs |
| Keywords | order_date, booked_at |
order, date, user |
Reserved words cause parser syntax errors |
"StudentRecords", you are condemned to type ugly double quotes in every query forever!
In production deployments, migration scripts run automatically. Re-running a setup script must never crash your pipeline:
-- ❌ Without Guard (Fragile Pipeline):
CREATE TABLE college.students ( student_id TEXT );
-- Run 1: SUCCESS
-- Run 2: ERROR: relation "college.students" already exists! (Pipeline Crashes)
-- ✅ With Guard (Production Idempotent Standard):
CREATE TABLE IF NOT EXISTS college.students (
student_id TEXT, first_name TEXT
);
-- Run 1: SUCCESS
-- Run 2: NOTICE: relation "college.students" already exists, skipping.
IF NOT EXISTS in DDL scripts.
CREATE SCHEMA IF NOT EXISTS •
2. CREATE TABLE IF NOT EXISTS •
3. DROP TABLE IF EXISTS •
4. snake_case everywhere
Modifying living table schemas: adding attributes, pruning obsolete columns, and refactoring names.
ALTER TABLE college.students
ADD COLUMN phone_number TEXT;NULL.ALTER TABLE college.students
DROP COLUMN IF EXISTS phone_number;ALTER TABLE college.students
RENAME COLUMN date_of_birth TO dob;ALTER TABLE college.students
RENAME TO college_students;DROP COLUMN IF EXISTS in migration scripts to maintain idempotency.
The irreversible destroyer: understanding blast radii, the CASCADE nuclear option, and zero-recovery operations.
Destroys 1 table relation. Other tables in the schema are completely unaffected.
-- Safe drop:
DROP TABLE IF EXISTS
shop.products;
The Nuclear Option. Wipes the folder AND all tables inside it simultaneously!
-- Safety lock blocks drop:
DROP SCHEMA shop; -- ❌ ERROR!
-- Nuclear bypass:
DROP SCHEMA IF EXISTS
shop CASCADE;
Purges the entire database container from server cluster disk. Gone forever.
-- Cannot drop connected db:
\c postgres
DROP DATABASE IF EXISTS
old_test_db;
IF EXISTS and verify your connection string before running DROP.
The 3-way confusion that trips up database engineers and fails technical interviews: DROP vs TRUNCATE vs DELETE.
Vaporizes the structure AND all data. Unlinks the table relation completely from the database catalog.
relation does not existWipes all rows simultaneously by deallocating storage extents on disk. The table structure remains pristine.
Surgically removes specific rows evaluated by a condition. Generates transaction log records for each removed row.
Act as the Database Architect! Build the Prestige College database schema from scratch, design student tables, mutate columns with ALTER, and master DROP vs TRUNCATE.
college.students, college.courses, shop.products, and shop.orders.SELECT count(*) → 0. Not a single human being is enrolled. No lectures scheduled. ₹0 tuition collected.
INSERT breathing vitality into the schema.
-- 1. DDL: Pristine empty classroom SELECT count(*) FROM college.students; -- Result: 0 (the table is empty) -- 2. DML: Admissions open (Life enters) INSERT INTO college.students (student_id, first_name, last_name, email, date_of_birth, is_active) VALUES ('1', 'Aarav', 'Sharma', '[email protected]', '2004-03-15', 'true');
The fundamental DML command: single rows, high-performance batching, and PostgreSQL's RETURNING superpower.
INSERT INTO college.students
(student_id, first_name, last_name,
email, date_of_birth, is_active)
VALUES
('1', 'Aarav', 'Sharma',
'[email protected]', '2004-03-15', 'true');INSERT INTO college.students
(student_id, first_name, last_name,
email, date_of_birth, is_active)
VALUES
('2', 'Diya', 'Patel',
'[email protected]', '2003-11-02', 'true'),
('3', 'Kabir', 'Singh',
'[email protected]', '2004-07-20', 'true');INSERT INTO college.students
(student_id, first_name, last_name,
email, date_of_birth, is_active)
VALUES
('4', 'Neha', 'Verma',
'[email protected]', '2004-01-10', 'true')
RETURNING *;SELECT student_id, first_name, email, is_active
FROM college.students
LIMIT 10;INSERT INTO table (col1, col2) VALUES (val1, val2); — Prevents silent column-shift bugs!
Modifying stored records, updating multiple attributes, and avoiding the catastrophic missing-WHERE disaster.
SET specifies new values; WHERE selects matching rows. Multiple columns update in a single transaction:
-- 1. Targeted single-column update:
UPDATE college.students
SET email = '[email protected]'
WHERE student_id = '1';
-- 2. Modify multiple columns simultaneously:
UPDATE college.students
SET is_active = 'false',
last_name = 'Sharma-Verma'
WHERE student_id = '3';
-- 3. RETURNING confirms change instantly:
UPDATE college.students
SET is_active = 'true'
WHERE student_id = '3'
RETURNING student_id, first_name, is_active;
student_id = '1') to guarantee pinpoint row targeting.
Without WHERE, PostgreSQL applies the new value to every single row in the table:
UPDATE college.students
SET email = '[email protected]';
-- UPDATE 4 (every student now has Aarav's email!)
UPDATE college.students
SET email = '[email protected]'
WHERE student_id = '1'
RETURNING student_id, email;
-- UPDATE 1 (Only Aarav modified; confirmed instantly)
SELECT * FROM table WHERE cond; first → Then swap to UPDATE!
Two distinct methods to remove rows: surgical row-by-row deletion with WHERE vs instant filesystem extent deallocation.
Removes individual rows evaluated by a WHERE condition. Always follow the mandatory 2-step protocol:
-- Step 1: Preview rows first with SELECT
SELECT student_id, first_name
FROM college.students
WHERE is_active = 'false';
-- Step 2: Swap to DELETE with RETURNING
DELETE FROM college.students
WHERE is_active = 'false'
RETURNING student_id, first_name;
DELETE directly. Write SELECT, verify the row count, then swap keywords to execute.
Deallocates filesystem storage extents directly, almost instantly however many rows. Table structure stays 100% intact:
-- Fast bulk empty (0 rows in 5ms):
SELECT count(*) FROM college.students; -- 5,00,000
TRUNCATE TABLE college.students;
SELECT count(*) FROM college.students; -- 0
DELETE FROM table; scans all 1 crore rows • takes minutesTRUNCATE TABLE; unlinks storage pages • 5 milliseconds
TRUNCATE does not allow a WHERE clause. It is always an all-or-nothing storage reset.
TRUNCATE is transaction-safe inside BEGIN ... ROLLBACK;!
In an all-TEXT table, you cannot do simple multiplication. SQL refuses to multiply a string:
-- ❌ FAILS with operator does not exist: text * numeric
UPDATE fees SET amount = amount * 1.1;
-- ⚠️ Clumsy double-cast workaround:
UPDATE fees
SET amount = (amount::NUMERIC * 1.1)::TEXT;
String comparison checks ASCII character codes left-to-right. '1' comes before '9':
-- 🚨 Silent bug: returns only '950', drops '1000'!
SELECT * FROM fees WHERE amount > '900';
-- In text comparison: '1000' < '900' evaluates TRUE!
-- Text sorts like a dictionary: "apple" < "banana"
ALTER COLUMN amount TYPE NUMERIC; • Math & sorting instantly work!
Breathe life into empty schemas! Practice single & multi-row INSERTs, safe targeted UPDATEs, constrained DELETEs, and RETURNING outputs.
"SQL commands fall into 5 distinct architectural categories: DDL (Data Definition Language) defines structure (CREATE, ALTER, DROP, TRUNCATE); DML (Data Manipulation Language) modifies row data (INSERT, UPDATE, DELETE); DQL (Data Query Language) reads data without altering anything (SELECT); DCL (Data Control Language) manages permissions and security (GRANT, REVOKE); and TCL (Transaction Control Language) manages atomic transactions (COMMIT, ROLLBACK)."
To check whether you view SQL as random scripts or understand database architecture and role separation.
"Is TRUNCATE DDL or DML?" Remember: TRUNCATE is DDL because it acts on the relation structure, not row-by-row.
Close your notes. Answer from memory. Click any card to reveal the verified answer.
The essential principles that govern query execution, structure creation, and data manipulation.
SELECT does not require a table. It evaluates math, system functions, string manipulation, and dates. It is your primary sandbox for testing before running production queries.
Never write bare CREATE TABLE in migration scripts. Always add IF NOT EXISTS to ensure your operations are idempotent and CI/CD safe.
Always lowercase snake_case. Tables are plural (students), columns are singular (email), and ID keys follow table_id.
UPDATE and DELETE without WHERE wipe the entire table. Always run SELECT first to preview the exact rows before executing mutations.
DROP: Demolish the room. TRUNCATE: Empty the room instantly (DDL). DELETE: Remove specific furniture (DML).
We used TEXT to master structure without casting friction. Up next, we fix the '1000' < '900' trap by giving columns proper types and constraints!
Every core command ready to copy directly into your PostgreSQL shell.
Complete these 6 hands-on exercises in your local database before moving forward to Data Types.
Create a schema named gym with IF NOT EXISTS.
Create gym.members with member_id, full_name, plan_type, join_date, is_active (all TEXT).
Insert 3 members in a single multi-row statement using RETURNING *.
Upgrade member '1' to 'Annual Premium' using UPDATE ... WHERE.
Run SELECT first, then DELETE member '3' who cancelled.
Run \dt gym.* and query the final member roster with SELECT * FROM gym.members;.