[{"data":1,"prerenderedAt":123},["ShallowReactive",2],{"qa-\u002Fsql\u002Fbasics\u002Fselect-where":3},{"page":4,"siblings":102,"blog":120},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":12,"path":20,"questions":21,"questionsCount":92,"related":93,"seo":94,"seoDescription":95,"stem":96,"subtopic":97,"topic":98,"topicSlug":99,"updated":100,"__hash__":101},"qa\u002Fsql\u002Fbasics\u002Fselect-where.md","Select Where",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"easy","md","SQL","sql",{},true,"\u002Fsql\u002Fbasics\u002Fselect-where",[22,26,30,34,39,43,47,51,55,59,63,68,72,76,80,84,88],{"id":23,"difficulty":14,"q":24,"a":25},"what-is-select","What does a SELECT statement do?","`SELECT` **reads rows** from one or more tables and returns a result set. It has\ntwo core jobs: **projection** (which columns to return, listed after `SELECT`)\nand **source** (which table, named after `FROM`). Everything else — filtering,\ngrouping, ordering — refines that result.\n\n```sql\n-- project two columns from every row of users\nSELECT id, name\nFROM users;\n```\n\n`SELECT` is **read-only**: it never changes the data. `SELECT *` returns every\ncolumn, convenient for exploring but discouraged in production code because the\nresult silently changes when the table's columns do.\n\nRule of thumb: list the columns you actually need instead of `SELECT *`.\n",{"id":27,"difficulty":14,"q":28,"a":29},"where-clause","What does the WHERE clause do?","`WHERE` **filters rows** before they're returned, keeping only those for which the\ncondition evaluates to **`true`**. Rows that evaluate to `false` *or* `unknown`\n(NULL comparisons) are dropped.\n\n```sql\nSELECT name, age\nFROM users\nWHERE age >= 18;        -- only adults\n```\n\n`WHERE` runs **after `FROM`** (the rows exist) but **before `GROUP BY`,\n`SELECT`, and `ORDER BY`. Because it runs before `SELECT`, you generally **can't\nreference a column alias** defined in the `SELECT` list inside `WHERE`.\n\nRule of thumb: `WHERE` filters individual rows; use `HAVING` to filter groups.\n",{"id":31,"difficulty":14,"q":32,"a":33},"comparison-operators","What comparison operators can you use in WHERE?","The standard set: `=`, `\u003C>` (or `!=`) for not-equal, `\u003C`, `>`, `\u003C=`, `>=`. They\nwork on numbers, strings (lexicographic order), and dates. Combine them with the\nlogical operators `AND`, `OR`, and `NOT`.\n\n```sql\nSELECT *\nFROM orders\nWHERE total > 100 AND status \u003C> 'cancelled';\n```\n\nComparisons against `NULL` are special — `total > 100` is `unknown` when `total`\nis `NULL`, so that row is excluded. Use `IS NULL` \u002F `IS NOT NULL` for NULL tests.\n\nRule of thumb: `\u003C>` is the SQL-standard not-equal; `!=` works in most dialects but\nisn't standard.\n",{"id":35,"difficulty":36,"q":37,"a":38},"and-or-precedence","medium","How do AND and OR precedence interact?","`AND` binds **tighter** than `OR` (like `*` vs `+` in arithmetic), so `OR` groups\nare evaluated last. Mixing them without parentheses is a classic bug that quietly\nreturns the wrong rows.\n\n```sql\n-- WRONG: parsed as  status='active' OR (status='trial' AND age > 18)\nSELECT * FROM users WHERE status = 'active' OR status = 'trial' AND age > 18;\n\n-- RIGHT: parenthesize the OR group\nSELECT * FROM users WHERE (status = 'active' OR status = 'trial') AND age > 18;\n```\n\nRule of thumb: when a `WHERE` clause mixes `AND` and `OR`, always add explicit\nparentheses — don't rely on precedence.\n",{"id":40,"difficulty":14,"q":41,"a":42},"distinct","What does SELECT DISTINCT do?","`DISTINCT` **removes duplicate rows** from the result, based on **all selected\ncolumns** together — not just the first one. Two rows are duplicates only if every\nprojected column matches.\n\n```sql\n-- unique cities\nSELECT DISTINCT city FROM users;\n\n-- unique (city, country) PAIRS, not unique cities\nSELECT DISTINCT city, country FROM users;\n```\n\n`DISTINCT` requires sorting or hashing the result to find duplicates, so it has a\ncost on large sets. If you're really testing for existence, `EXISTS` is often\ncheaper than `SELECT DISTINCT`.\n\nRule of thumb: `DISTINCT` applies to the whole row, not to one column.\n",{"id":44,"difficulty":14,"q":45,"a":46},"like-operator","How does the LIKE operator work?","`LIKE` does **pattern matching** on strings with two wildcards: `%` matches **any\nsequence** of characters (including none), and `_` matches **exactly one**\ncharacter.\n\n```sql\nSELECT * FROM users\nWHERE email LIKE '%@gmail.com'   -- ends with @gmail.com\n  AND name  LIKE 'A%'            -- starts with A\n  AND code  LIKE 'A_C';          -- A, any one char, C\n```\n\nMatching is case-sensitive in some databases (Postgres) and insensitive in others\n(MySQL default); Postgres offers `ILIKE` for case-insensitive matching. A leading\n`%` (`'%term'`) usually **can't use an index**, making it slow on big tables.\n\nRule of thumb: avoid a leading wildcard if you need the query to use an index.\n",{"id":48,"difficulty":14,"q":49,"a":50},"in-operator","What does the IN operator do?","`IN` tests whether a value **matches any item** in a list (or subquery result). It's\nshorthand for a chain of `OR` equality checks.\n\n```sql\n-- these are equivalent\nSELECT * FROM orders WHERE status IN ('shipped', 'delivered');\nSELECT * FROM orders WHERE status = 'shipped' OR status = 'delivered';\n```\n\n`IN` also accepts a subquery: `WHERE user_id IN (SELECT id FROM vips)`. Beware the\n**`NOT IN` NULL trap** — if the list\u002Fsubquery contains a `NULL`, `NOT IN` returns\nno rows at all. Prefer `NOT EXISTS` when NULLs are possible.\n\nRule of thumb: `IN` for a known list; `NOT EXISTS` instead of `NOT IN` when NULLs\nmight appear.\n",{"id":52,"difficulty":14,"q":53,"a":54},"between-operator","How does BETWEEN work, and is it inclusive?","`BETWEEN a AND b` tests whether a value falls in a range, and it is **inclusive of\nboth endpoints** — equivalent to `>= a AND \u003C= b`.\n\n```sql\nSELECT * FROM orders\nWHERE total BETWEEN 100 AND 200;   -- includes exactly 100 and 200\n```\n\nThe inclusive upper bound is a trap with **dates\u002Ftimestamps**: `BETWEEN '2026-01-01'\nAND '2026-01-31'` misses times on Jan 31 after midnight. For timestamps, prefer a\nhalf-open range: `>= '2026-01-01' AND \u003C '2026-02-01'`.\n\nRule of thumb: use `BETWEEN` for inclusive integer ranges; use `>= ... \u003C ...` for\ndate\u002Ftime ranges.\n",{"id":56,"difficulty":36,"q":57,"a":58},"is-null","How do you test for NULL values?","Use **`IS NULL`** and **`IS NOT NULL`** — never `= NULL`. In SQL, `NULL` means\n\"unknown,\" and any comparison *with* `NULL` (including `NULL = NULL`) evaluates to\n`unknown`, which `WHERE` treats as not-true.\n\n```sql\nSELECT * FROM users WHERE deleted_at IS NULL;       -- active users\nSELECT * FROM users WHERE phone = NULL;             -- BUG: returns nothing\n```\n\nThis three-valued logic (`true` \u002F `false` \u002F `unknown`) is why `NULL` rows slip\nthrough filters unexpectedly. Functions like `COALESCE(col, default)` let you treat\nNULLs as a concrete value when comparing.\n\nRule of thumb: NULL tests always use `IS NULL` \u002F `IS NOT NULL`.\n",{"id":60,"difficulty":14,"q":61,"a":62},"column-aliases","What are column and table aliases?","An **alias** renames a column or table for the duration of the query, using `AS`\n(optional for columns, conventional to omit for tables). Column aliases clean up\noutput and name computed expressions; table aliases shorten qualified references.\n\n```sql\nSELECT u.name AS customer,\n       o.total * 1.1 AS total_with_tax\nFROM users u\nJOIN orders o ON o.user_id = u.id;\n```\n\nBecause aliases are assigned in the `SELECT` step (which runs after `WHERE`), you\nusually **can't use a column alias in `WHERE`** — but you **can** in `ORDER BY`,\nwhich runs last.\n\nRule of thumb: alias every computed column so the result has meaningful names.\n",{"id":64,"difficulty":65,"q":66,"a":67},"order-of-execution","hard","In what logical order are the clauses of a SELECT evaluated?","Though you *write* `SELECT` first, the database evaluates clauses in this **logical\norder**: `FROM`\u002F`JOIN` → `WHERE` → `GROUP BY` → `HAVING` → `SELECT` → `DISTINCT` →\n`ORDER BY` → `LIMIT`.\n\n```sql\nSELECT user_id, COUNT(*) AS n   -- 5: projection + alias\nFROM orders                     -- 1: source rows\nWHERE total > 0                 -- 2: filter rows\nGROUP BY user_id                -- 3: group\nHAVING COUNT(*) > 5             -- 4: filter groups\nORDER BY n DESC                 -- 6: alias visible here\nLIMIT 10;                       -- 7\n```\n\nThis order explains the rules: `WHERE` can't see `SELECT` aliases (it runs first),\nbut `ORDER BY` can (it runs last), and `HAVING` can reference aggregates.\n\nRule of thumb: remember `FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT`.\n",{"id":69,"difficulty":14,"q":70,"a":71},"arithmetic-expressions","Can you compute expressions in a SELECT list?","Yes — the `SELECT` list can contain **arithmetic, function calls, and concatenation**,\nnot just bare columns. The expression is computed per row.\n\n```sql\nSELECT name,\n       price * quantity        AS line_total,\n       ROUND(price * 0.2, 2)   AS tax\nFROM order_items;\n```\n\nWatch **integer division** in some databases: `5 \u002F 2` returns `2`, not `2.5`,\nunless an operand is a decimal\u002Ffloat (`5.0 \u002F 2`). Cast when you need fractional\nresults.\n\nRule of thumb: cast to a decimal type before dividing integers if you want a\nfractional answer.\n",{"id":73,"difficulty":36,"q":74,"a":75},"not-operator","How does the NOT operator behave with NULLs?","`NOT` negates a condition, but under three-valued logic `NOT unknown` is still\n**`unknown`** — so negating a comparison that involves `NULL` doesn't \"flip in\" the\nNULL rows you might expect.\n\n```sql\n-- rows where status IS NULL are excluded by BOTH of these\nSELECT * FROM tasks WHERE status = 'done';\nSELECT * FROM tasks WHERE NOT (status = 'done');\n```\n\nTo include the NULLs, handle them explicitly: `WHERE status \u003C> 'done' OR status IS\nNULL`. This surprises people expecting a query and its `NOT` to partition the table.\n\nRule of thumb: when negating a condition, decide explicitly what should happen to\nNULL rows.\n",{"id":77,"difficulty":36,"q":78,"a":79},"case-in-select","How do you return a conditional value in SELECT?","Use a **`CASE` expression** — SQL's if\u002Felse inside a query. It evaluates `WHEN`\nconditions top to bottom and returns the first match's value, or the `ELSE` (or\n`NULL` if no `ELSE`).\n\n```sql\nSELECT name,\n       CASE\n         WHEN age \u003C 13 THEN 'child'\n         WHEN age \u003C 20 THEN 'teen'\n         ELSE 'adult'\n       END AS age_group\nFROM users;\n```\n\n`CASE` works anywhere an expression is allowed — `SELECT`, `WHERE`, `ORDER BY`, even\ninside aggregates like `SUM(CASE WHEN ... THEN 1 ELSE 0 END)` for conditional counts.\n\nRule of thumb: reach for `CASE` whenever you need branching logic inside a query.\n",{"id":81,"difficulty":36,"q":82,"a":83},"filtering-strings-case","How do you do case-insensitive string filtering?","Either normalize both sides with `LOWER()`\u002F`UPPER()`, or use a dialect feature.\nForcing the case on both sides works everywhere but can defeat a plain index.\n\n```sql\n-- portable: compare in a single case\nSELECT * FROM users WHERE LOWER(email) = LOWER('Alice@Example.com');\n\n-- Postgres shortcut for patterns\nSELECT * FROM users WHERE email ILIKE 'alice@%';\n```\n\nFor performance on large tables, store a normalized column or build a **functional\nindex** on `LOWER(email)` so the comparison stays index-friendly.\n\nRule of thumb: normalize with `LOWER()` for portability; add a functional index if\nit's a hot path.\n",{"id":85,"difficulty":14,"q":86,"a":87},"limit-preview","How do you return just a few rows to preview a table?","Limit the row count: `LIMIT n` in Postgres\u002FMySQL\u002FSQLite, `FETCH FIRST n ROWS ONLY`\nin the SQL standard \u002F Oracle, and `SELECT TOP n` in SQL Server.\n\n```sql\nSELECT * FROM users LIMIT 10;                 -- Postgres\u002FMySQL\nSELECT * FROM users FETCH FIRST 10 ROWS ONLY; -- standard\nSELECT TOP 10 * FROM users;                   -- SQL Server\n```\n\nWithout an `ORDER BY`, the rows returned are **arbitrary** — the database may return\nany 10. Add `ORDER BY` for a deterministic preview.\n\nRule of thumb: `LIMIT` without `ORDER BY` gives unpredictable rows.\n",{"id":89,"difficulty":36,"q":90,"a":91},"where-vs-having","What is the difference between WHERE and HAVING?","`WHERE` filters **individual rows before grouping**; `HAVING` filters **groups after\naggregation**. Because `WHERE` runs first, it **can't** reference aggregate functions\nlike `COUNT()`; `HAVING` can.\n\n```sql\nSELECT user_id, COUNT(*) AS orders\nFROM orders\nWHERE total > 0          -- drop junk rows first (per-row)\nGROUP BY user_id\nHAVING COUNT(*) > 5;     -- keep only busy users (per-group)\n```\n\nFiltering early in `WHERE` is also cheaper — fewer rows reach the grouping step.\n\nRule of thumb: filter raw rows in `WHERE`, filter aggregates in `HAVING`.\n",17,null,{"description":11},"SQL SELECT and WHERE interview questions — projecting columns, filtering rows, DISTINCT, LIKE, IN, BETWEEN, NULL handling and operator precedence.","sql\u002Fbasics\u002Fselect-where","SELECT & WHERE","Query Basics","basics","2026-06-20","jXdsYzvmdzmQPaNl6AcPW6mbKYqG-S5HAYiy7OjV_IY",[103,107,108,112,116],{"subtopic":104,"path":105,"order":106},"Joins","\u002Fsql\u002Fbasics\u002Fjoins",1,{"subtopic":97,"path":20,"order":12},{"subtopic":109,"path":110,"order":111},"Sorting & Limiting","\u002Fsql\u002Fbasics\u002Fsorting-limiting",3,{"subtopic":113,"path":114,"order":115},"Aggregation & GROUP BY","\u002Fsql\u002Fbasics\u002Faggregation",4,{"subtopic":117,"path":118,"order":119},"Set Operations","\u002Fsql\u002Fbasics\u002Fset-operations",5,{"path":121,"title":122},"\u002Fblog\u002Fsql-select-where-filtering","SQL SELECT & WHERE — Filtering Rows the Right Way",1782244106800]