[{"data":1,"prerenderedAt":263},["ShallowReactive",2],{"topic-sql-functions":3},{"framework":4,"topic":16,"subtopics":25},{"id":5,"description":6,"extension":7,"icon":8,"meta":9,"name":10,"order":11,"slug":12,"stem":13,"tier":14,"__hash__":15},"frameworks\u002Fframeworks\u002Fsql.yml","SQL interview questions on queries, joins and aggregation — essential for every backend, data and analytics interview.","yml","database",{},"SQL",4,"sql","frameworks\u002Fsql",1,"lpzsOj2p9p9W0Tctwc61nP-ulZAA80R5gJiyaZS6ZeI",{"id":17,"description":18,"extension":7,"frameworkSlug":12,"meta":19,"name":20,"order":21,"slug":22,"stem":23,"__hash__":24},"topics\u002Ftopics\u002Fsql-functions.yml","String, numeric, date and conditional functions — the everyday built-ins for transforming values inside a query.",{},"Built-in Functions",8,"functions","topics\u002Fsql-functions","URYP9hB2ywnRDyd-PGG8-IF2ZiWvdYdWVL7D8bC3VZU",[26,111,187],{"id":27,"title":28,"body":29,"description":33,"difficulty":36,"extension":37,"framework":10,"frameworkSlug":12,"meta":38,"navigation":39,"order":14,"path":40,"questions":41,"questionsCount":103,"related":104,"seo":105,"seoDescription":106,"stem":107,"subtopic":108,"topic":20,"topicSlug":22,"updated":109,"__hash__":110},"qa\u002Fsql\u002Ffunctions\u002Fstring-numeric-functions.md","String Numeric Functions",{"type":30,"value":31,"toc":32},"minimark",[],{"title":33,"searchDepth":34,"depth":34,"links":35},"",2,[],"easy","md",{},true,"\u002Fsql\u002Ffunctions\u002Fstring-numeric-functions",[42,46,50,54,58,62,66,71,75,79,83,87,91,95,99],{"id":43,"difficulty":36,"q":44,"a":45},"concat","How do you concatenate strings in SQL?","SQL offers both a standard operator and functions for string concatenation.\nThe `||` operator is ANSI standard; `CONCAT()` is supported everywhere and\nhandles NULLs differently.\n\n```sql\n-- ANSI standard: || operator (Postgres, SQL Server 2012+, SQLite)\nSELECT first_name || ' ' || last_name AS full_name FROM users;\n\n-- NULL propagation: NULL || anything = NULL\nSELECT 'Hello' || NULL;  -- → NULL\n\n-- CONCAT() function: NULLs treated as empty strings\nSELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;\n-- If first_name IS NULL → ' Smith' (NULL coerced to '')\n\n-- MySQL also has CONCAT_WS (with separator — skips NULLs)\nSELECT CONCAT_WS(' ', first_name, middle_name, last_name) AS full_name;\n-- skips NULL middle_name without leaving a double space\n```\n\n**Rule of thumb:** use `CONCAT_WS` when joining fields that may be NULL\nand you do not want extra separators. Use `||` in Postgres for simple\nconcatenation; use `CONCAT()` in MySQL-compatible code.\n",{"id":47,"difficulty":36,"q":48,"a":49},"substring","How do you extract a part of a string?","`SUBSTRING` (or `SUBSTR`) extracts a portion of a string by position and\noptional length.\n\n```sql\n-- Standard SQL: SUBSTRING(string FROM start FOR length)\nSELECT SUBSTRING('Hello World' FROM 7 FOR 5);   -- → 'World'\n\n-- Shorthand (all databases):\nSELECT SUBSTRING('Hello World', 7, 5);           -- → 'World'\nSELECT SUBSTR('Hello World', 7, 5);              -- → 'World' (MySQL, Postgres)\n\n-- Extract from end: use negative position in MySQL\nSELECT SUBSTRING('Hello World', -5);             -- → 'World' (MySQL only)\n\n-- Postgres: RIGHT \u002F LEFT shortcuts\nSELECT LEFT('Hello World', 5);   -- → 'Hello'\nSELECT RIGHT('Hello World', 5);  -- → 'World'\n\n-- Regex-based extraction (Postgres)\nSELECT SUBSTRING('Order #12345' FROM '[0-9]+');  -- → '12345'\n```\n\n**Rule of thumb:** use `LEFT(str, n)` and `RIGHT(str, n)` for simple\nprefix\u002Fsuffix extraction — they are clearer than `SUBSTRING`. Use the\nregex form of `SUBSTRING` in Postgres for pattern-based extraction.\n",{"id":51,"difficulty":36,"q":52,"a":53},"upper-lower","How do you change string case in SQL?","`UPPER()` and `LOWER()` convert all characters in a string to upper or lower\ncase. `INITCAP()` (Postgres, Oracle) title-cases each word.\n\n```sql\nSELECT UPPER('hello world');    -- → 'HELLO WORLD'\nSELECT LOWER('HELLO WORLD');    -- → 'hello world'\nSELECT INITCAP('hello world');  -- → 'Hello World'  (Postgres\u002FOracle)\n\n-- Common use: case-insensitive comparison\nSELECT * FROM users WHERE LOWER(email) = 'alice@example.com';\n-- Better: create a functional index so this is fast:\n-- CREATE INDEX idx_users_email_lower ON users (LOWER(email));\n```\n\n**Rule of thumb:** for case-insensitive searches, use `LOWER()` with a\nmatching functional index rather than `ILIKE` (Postgres) on large tables,\nbecause `LOWER(col) = value` can be indexed but `col ILIKE value` typically\ncannot.\n",{"id":55,"difficulty":36,"q":56,"a":57},"trim","How do you remove whitespace or specific characters from a string?","`TRIM`, `LTRIM`, and `RTRIM` remove characters (default: spaces) from the\nstart, end, or both ends of a string.\n\n```sql\nSELECT TRIM('  hello  ');           -- → 'hello'\nSELECT LTRIM('  hello  ');          -- → 'hello  '\nSELECT RTRIM('  hello  ');          -- → '  hello'\n\n-- Remove specific characters (Postgres \u002F SQL Server)\nSELECT TRIM(BOTH '.' FROM '...hello...');   -- → 'hello'\nSELECT TRIM(LEADING '0' FROM '00042');       -- → '42'\n\n-- MySQL TRIM with specific characters\nSELECT TRIM(LEADING '0' FROM '00042');       -- → '42'\n```\n\n**Rule of thumb:** always `TRIM()` user-supplied input before storing it\nin the database. Trailing spaces cause subtle equality failures and bloat\n`CHAR(n)` columns.\n",{"id":59,"difficulty":36,"q":60,"a":61},"replace","How do you replace occurrences of a substring?","`REPLACE(source, from, to)` substitutes all occurrences of `from` with `to`\nin `source`. The replacement is case-sensitive in most databases.\n\n```sql\nSELECT REPLACE('Hello World', 'World', 'SQL');   -- → 'Hello SQL'\nSELECT REPLACE('aababab', 'ab', 'X');            -- → 'aXXX'\n\n-- Common use: sanitise data\nUPDATE products SET sku = REPLACE(sku, '-', '');  -- remove dashes from SKUs\n\n-- Postgres: regexp_replace for pattern-based replacement\nSELECT regexp_replace('Order #12345', '[0-9]+', 'XXXXXX');  -- → 'Order #XXXXXX'\nSELECT regexp_replace('a1b2c3', '[0-9]', '#', 'g');         -- → 'a#b#c#'\n-- 'g' flag = replace all occurrences (global)\n```\n\n**Rule of thumb:** use `REPLACE` for literal string swaps; use\n`regexp_replace` (Postgres) or `REGEXP_REPLACE` (MySQL 8+) when the\npattern to replace requires a regular expression.\n",{"id":63,"difficulty":36,"q":64,"a":65},"length","How do you get the length of a string in SQL?","`LENGTH` and `CHAR_LENGTH` return the number of characters in a string.\n`OCTET_LENGTH` returns the byte count (differs for multi-byte UTF-8).\n\n```sql\nSELECT LENGTH('Hello');          -- → 5\nSELECT CHAR_LENGTH('Hello');     -- → 5 (standard SQL; MySQL synonym)\nSELECT LENGTH('こんにちは');      -- Postgres: 5 chars; MySQL: 15 bytes!\n\n-- Postgres: char vs byte length\nSELECT char_length('こんにちは');  -- → 5 (characters)\nSELECT octet_length('こんにちは'); -- → 15 (bytes in UTF-8)\n\n-- Practical use: enforce a max length\nSELECT * FROM users WHERE LENGTH(username) > 50;\n```\n\n**Rule of thumb:** in MySQL, `LENGTH()` returns byte count for multibyte\nstrings — use `CHAR_LENGTH()` to count characters. In Postgres, `LENGTH()`\nreturns character count.\n",{"id":67,"difficulty":68,"q":69,"a":70},"string-search","medium","How do you search for a substring within a string?","`POSITION`, `CHARINDEX`, `INSTR`, and `STRPOS` all find the starting\nposition of a substring (returning 0 or NULL if not found, depending on\nthe database).\n\n```sql\n-- ANSI standard\nSELECT POSITION('World' IN 'Hello World');    -- → 7\n\n-- Postgres\nSELECT STRPOS('Hello World', 'World');         -- → 7\n\n-- MySQL\nSELECT INSTR('Hello World', 'World');          -- → 7\nSELECT LOCATE('World', 'Hello World');         -- → 7\nSELECT LOCATE('World', 'Hello World', 8);      -- → 0 (start search at pos 8)\n\n-- SQL Server\nSELECT CHARINDEX('World', 'Hello World');      -- → 7\n\n-- Check existence: returns 0 if not found\nSELECT * FROM products WHERE POSITION('PRO' IN UPPER(sku)) > 0;\n-- Often better expressed as:\nSELECT * FROM products WHERE sku ILIKE '%PRO%';  -- Postgres\n```\n\n**Rule of thumb:** use `LIKE` or `ILIKE` for simple contains-checks — they\nare cleaner and the optimizer understands them. Use position functions when\nyou need the actual offset or want to split on a delimiter.\n",{"id":72,"difficulty":36,"q":73,"a":74},"round-ceil-floor","How do you round numeric values in SQL?","`ROUND`, `CEIL`\u002F`CEILING`, and `FLOOR` control numeric rounding.\n\n```sql\nSELECT ROUND(3.14159, 2);    -- → 3.14\nSELECT ROUND(3.145, 2);      -- → 3.15 (or 3.14 — depends on type\u002Frounding mode)\nSELECT ROUND(3.5);           -- → 4\nSELECT ROUND(-3.5);          -- → -4 (Postgres); -3 (some databases)\n\nSELECT CEIL(3.2);            -- → 4  (round up to nearest integer)\nSELECT CEILING(3.2);         -- → 4  (SQL Server alias)\nSELECT FLOOR(3.8);           -- → 3  (round down to nearest integer)\n\n-- Truncate without rounding (Postgres)\nSELECT TRUNC(3.999, 1);      -- → 3.9\nSELECT TRUNC(-3.999, 1);     -- → -3.9 (towards zero, not floor)\n\n-- Practical: round to 2 decimal places for currency display\nSELECT ROUND(SUM(total), 2) AS revenue FROM orders;\n```\n\n**Rule of thumb:** use `ROUND(x, 2)` for display formatting of monetary\nvalues. Use `TRUNC` (not `FLOOR`) when you need to drop fractional parts\nwithout rounding, especially for negative numbers.\n",{"id":76,"difficulty":36,"q":77,"a":78},"mod-abs-power","What are MOD, ABS, and POWER and how do you use them?","```sql\n-- MOD: remainder after integer division\nSELECT MOD(10, 3);      -- → 1\nSELECT 10 % 3;          -- → 1 (Postgres, SQL Server, MySQL)\n-- Common use: every Nth row, parity checks\nSELECT id FROM orders WHERE MOD(id, 2) = 0;  -- even IDs\n\n-- ABS: absolute value (removes sign)\nSELECT ABS(-42);        -- → 42\nSELECT ABS(-3.14);      -- → 3.14\n-- Use: distance calculations, deviation from target\nSELECT product_id, ABS(actual_stock - expected_stock) AS discrepancy\nFROM inventory_check;\n\n-- POWER: exponentiation\nSELECT POWER(2, 10);    -- → 1024\nSELECT 2 ^ 10;          -- → 1024 (Postgres)\n-- Use: compound interest, exponential growth models\nSELECT principal * POWER(1 + rate, years) AS future_value\nFROM loans;\n```\n\n**Rule of thumb:** prefer the operator form (`%`, `^`) in Postgres for\nbrevity; use `MOD()` and `POWER()` for cross-database portability.\n",{"id":80,"difficulty":68,"q":81,"a":82},"cast-convert","How do you convert a value from one data type to another?","Type conversion is done with `CAST` (standard), `::` (Postgres shorthand),\nor `CONVERT` (MySQL\u002FSQL Server).\n\n```sql\n-- Standard SQL CAST\nSELECT CAST('42' AS INTEGER);              -- string → integer\nSELECT CAST(3.14 AS TEXT);                 -- number → string\nSELECT CAST('2026-06-20' AS DATE);         -- string → date\n\n-- Postgres shorthand\nSELECT '42'::INTEGER;\nSELECT 3.14::TEXT;\nSELECT '2026-06-20'::DATE;\n\n-- MySQL CONVERT\nSELECT CONVERT('42', UNSIGNED);\nSELECT CONVERT(3.14, CHAR);\n\n-- SQL Server CONVERT (also supports format style codes)\nSELECT CONVERT(INT, '42');\nSELECT CONVERT(VARCHAR, GETDATE(), 103);   -- → '20\u002F06\u002F2026' (UK format)\n\n-- Safe cast (returns NULL instead of error on failure — Postgres 14+)\nSELECT pg_catalog.pg_safe_cast('abc', 'integer');  -- → NULL, no error\n-- MySQL: CAST('abc' AS UNSIGNED) → 0 (silent coercion, not NULL)\n```\n\n**Rule of thumb:** use `CAST(x AS type)` for portability. In Postgres,\n`::type` is idiomatic. Always validate input before casting to avoid\nruntime errors; use `TRY_CAST` (SQL Server) or `TRY_CONVERT` to return\nNULL on failure instead of raising an exception.\n",{"id":84,"difficulty":36,"q":85,"a":86},"string-padding","How do you pad a string to a fixed length?","`LPAD` and `RPAD` pad a string to a target length with a specified fill\ncharacter (defaulting to spaces).\n\n```sql\nSELECT LPAD('42', 5, '0');     -- → '00042'  (zero-pad a number)\nSELECT RPAD('hello', 8, '.');  -- → 'hello...'\nSELECT LPAD('hello', 3, ' ');  -- → 'hel'  (truncates if longer!)\n\n-- Common use: format fixed-width codes\nSELECT LPAD(CAST(id AS TEXT), 8, '0') AS padded_id FROM orders;\n-- id=42 → '00000042'\n\n-- Postgres alternative for numeric zero-padding\nSELECT TO_CHAR(42, 'FM00000000');  -- → '00000042'\n```\n\n**Rule of thumb:** use `LPAD(value::text, width, '0')` for zero-padding\nintegers when formatting export files or codes. Note that `LPAD` truncates\nfrom the left if the string is already longer than the target — always\nconfirm the max width before using it.\n",{"id":88,"difficulty":68,"q":89,"a":90},"format-to-char","How do you format numbers and dates as strings?","`TO_CHAR` (Postgres, Oracle) and `FORMAT` (MySQL, SQL Server) convert\nnumbers and dates to formatted strings.\n\n```sql\n-- Postgres TO_CHAR for numbers\nSELECT TO_CHAR(1234567.89, 'FM$999,999,990.00');  -- → '$1,234,567.89'\nSELECT TO_CHAR(0.153, 'FM90.0%');                  -- → '15.3%'\n\n-- Postgres TO_CHAR for dates\nSELECT TO_CHAR(now(), 'YYYY-MM-DD HH24:MI:SS');   -- → '2026-06-20 14:30:00'\nSELECT TO_CHAR(now(), 'Day, DD Month YYYY');       -- → 'Saturday, 20 June 2026'\n\n-- MySQL FORMAT for numbers\nSELECT FORMAT(1234567.89, 2);   -- → '1,234,567.89' (locale-aware)\n\n-- SQL Server FORMAT\nSELECT FORMAT(1234567.89, 'N2');            -- → '1,234,567.89'\nSELECT FORMAT(GETDATE(), 'yyyy-MM-dd');     -- → '2026-06-20'\n```\n\n**Rule of thumb:** format values for display in the application layer when\npossible — SQL formatting functions are less testable and locale-handling\nvaries. Use `TO_CHAR` in SQL when formatting is needed in the query itself\n(e.g., CSV exports, stored reports).\n",{"id":92,"difficulty":68,"q":93,"a":94},"string-split","How do you split a delimited string in SQL?","Splitting a delimited string (e.g., `'a,b,c'`) into rows requires\ndatabase-specific functions.\n\n```sql\n-- Postgres: STRING_TO_ARRAY + UNNEST\nSELECT UNNEST(STRING_TO_ARRAY('a,b,c', ',')) AS item;\n-- → rows: 'a', 'b', 'c'\n\n-- Postgres: regexp_split_to_table\nSELECT regexp_split_to_table('one two three', '\\s+') AS word;\n\n-- MySQL 8+: JSON_TABLE workaround (no native split)\nSELECT jt.item\nFROM JSON_TABLE(\n  CONCAT('[\"', REPLACE('a,b,c', ',', '\",\"'), '\"]'),\n  '$[*]' COLUMNS (item VARCHAR(100) PATH '$')\n) AS jt;\n\n-- SQL Server: STRING_SPLIT (SQL Server 2016+)\nSELECT value AS item FROM STRING_SPLIT('a,b,c', ',');\n```\n\n**Rule of thumb:** storing comma-separated values in a single column is a\n1NF violation — use a child table instead. When you must parse a legacy\nstring, `UNNEST(STRING_TO_ARRAY(...))` in Postgres is the cleanest approach.\n",{"id":96,"difficulty":68,"q":97,"a":98},"aggregate-string","How do you aggregate multiple rows into a single string?","`STRING_AGG` (Postgres 9.0+, SQL Server 2017+) and `GROUP_CONCAT` (MySQL)\nconcatenate values from multiple rows into one string, with a separator.\n\n```sql\n-- Postgres \u002F SQL Server\nSELECT customer_id,\n       STRING_AGG(product_name, ', ' ORDER BY product_name) AS products\nFROM   order_items\nGROUP  BY customer_id;\n-- → customer_id=1: 'Pen, Pencil, Ruler'\n\n-- MySQL\nSELECT customer_id,\n       GROUP_CONCAT(product_name ORDER BY product_name SEPARATOR ', ') AS products\nFROM   order_items\nGROUP  BY customer_id;\n\n-- With DISTINCT to deduplicate\nSELECT STRING_AGG(DISTINCT tag, ', ') FROM article_tags WHERE article_id = 1;\n```\n\n**Rule of thumb:** use `STRING_AGG` to build comma-separated lists for\nreports or JSON responses without a second round-trip. Be mindful of the\nresult length — `GROUP_CONCAT` in MySQL defaults to a 1 024-byte limit\n(configurable via `group_concat_max_len`).\n",{"id":100,"difficulty":68,"q":101,"a":102},"regex-functions","How do you use regular expressions in SQL?","Most databases support regex-based filtering and extraction, though the\nsyntax differs.\n\n```sql\n-- Postgres: ~ (match), !~ (no match), ~* (case-insensitive)\nSELECT * FROM users WHERE email ~ '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$';\n-- Extract a substring matching a pattern\nSELECT REGEXP_MATCH(description, '\\d+') AS first_number FROM products;\n-- Replace with regex\nSELECT REGEXP_REPLACE(phone, '[^0-9]', '', 'g') AS digits_only FROM users;\n\n-- MySQL: REGEXP \u002F RLIKE (filter), REGEXP_REPLACE, REGEXP_SUBSTR (8.0+)\nSELECT * FROM users WHERE email REGEXP '^[a-z0-9._%+-]+@';\nSELECT REGEXP_REPLACE(phone, '[^0-9]', '') FROM users;\n\n-- SQL Server: no built-in regex; use LIKE for simple patterns\n-- or CLR functions \u002F JSON PATH for complex cases\nSELECT * FROM users WHERE email LIKE '%@%.%';\n```\n\n**Rule of thumb:** use `LIKE` for simple prefix\u002Fsuffix\u002Fcontains patterns —\nit is portable and index-friendly (leading wildcards aside). Use regex\nfunctions only when the pattern is too complex for `LIKE`, and only in\nPostgres or MySQL where support is solid.\n",15,null,{"description":33},"SQL string and numeric function interview questions — CONCAT, SUBSTRING, TRIM, REPLACE, UPPER\u002FLOWER, LENGTH, ROUND, CEIL, FLOOR, MOD, CAST, and dialect differences across Postgres, MySQL, and SQL Server.","sql\u002Ffunctions\u002Fstring-numeric-functions","String & Numeric Functions","2026-06-20","jLD6OehmtGFFohDiH_eWEI5-sQoDyYQ_QrEhbvk3apg",{"id":112,"title":113,"body":114,"description":33,"difficulty":68,"extension":37,"framework":10,"frameworkSlug":12,"meta":118,"navigation":39,"order":34,"path":119,"questions":120,"questionsCount":103,"related":104,"seo":182,"seoDescription":183,"stem":184,"subtopic":185,"topic":20,"topicSlug":22,"updated":109,"__hash__":186},"qa\u002Fsql\u002Ffunctions\u002Fdate-functions.md","Date Functions",{"type":30,"value":115,"toc":116},[],{"title":33,"searchDepth":34,"depth":34,"links":117},[],{},"\u002Fsql\u002Ffunctions\u002Fdate-functions",[121,125,129,133,137,141,145,149,154,158,162,166,170,174,178],{"id":122,"difficulty":36,"q":123,"a":124},"current-date-time","How do you get the current date and time in SQL?","Each database provides standard and proprietary functions for the current\ntimestamp.\n\n```sql\n-- ANSI standard (all databases)\nSELECT CURRENT_DATE;        -- date only: '2026-06-20'\nSELECT CURRENT_TIME;        -- time only: '14:30:00.000000+00'\nSELECT CURRENT_TIMESTAMP;   -- date + time: '2026-06-20 14:30:00.000000+00'\n\n-- Postgres: preferred shorthands\nSELECT now();               -- same as CURRENT_TIMESTAMP (tz-aware)\nSELECT CURRENT_DATE;\nSELECT CURRENT_TIME;\n\n-- MySQL\nSELECT NOW();               -- '2026-06-20 14:30:00'\nSELECT CURDATE();           -- '2026-06-20'\nSELECT CURTIME();           -- '14:30:00'\nSELECT UTC_TIMESTAMP();     -- always UTC\n\n-- SQL Server\nSELECT GETDATE();           -- local server time\nSELECT GETUTCDATE();        -- UTC\nSELECT SYSDATETIMEOFFSET(); -- with timezone offset\n```\n\n**Rule of thumb:** always store and compare timestamps in UTC. In Postgres,\nuse `now()` (returns `TIMESTAMPTZ` in UTC) rather than `LOCALTIMESTAMP`\n(returns naive timestamp in the server timezone).\n",{"id":126,"difficulty":36,"q":127,"a":128},"date-arithmetic","How do you add or subtract time from a date?","Date arithmetic is done with intervals or database-specific functions.\n\n```sql\n-- Postgres: interval arithmetic\nSELECT now() + INTERVAL '7 days';             -- 7 days from now\nSELECT now() - INTERVAL '1 month';            -- 1 month ago\nSELECT '2026-06-20'::DATE + INTERVAL '1 year 3 months';\nSELECT '2026-06-20'::DATE - '2026-01-01'::DATE;  -- → 170 (days as integer)\n\n-- MySQL: DATE_ADD \u002F DATE_SUB\nSELECT DATE_ADD(NOW(), INTERVAL 7 DAY);\nSELECT DATE_SUB(NOW(), INTERVAL 1 MONTH);\nSELECT DATEDIFF('2026-06-20', '2026-01-01');  -- → 170\n\n-- SQL Server: DATEADD \u002F DATEDIFF\nSELECT DATEADD(day, 7, GETDATE());\nSELECT DATEADD(month, -1, GETDATE());\nSELECT DATEDIFF(day, '2026-01-01', '2026-06-20');  -- → 170\n```\n\n**Rule of thumb:** in Postgres, prefer the `+`\u002F`-` operator with `INTERVAL`\nliterals — it is readable and handles month-end edge cases correctly\n(`'2026-01-31'::DATE + INTERVAL '1 month'` → `'2026-02-28'`).\n",{"id":130,"difficulty":68,"q":131,"a":132},"date-trunc","What does DATE_TRUNC do and how is it used for grouping?","`DATE_TRUNC(unit, timestamp)` truncates a timestamp to the specified\nprecision — zeroing out all smaller units. This is the standard way to\ngroup time-series data by day, week, month, etc.\n\n```sql\n-- Postgres DATE_TRUNC\nSELECT DATE_TRUNC('month', now());           -- → '2026-06-01 00:00:00+00'\nSELECT DATE_TRUNC('week',  now());           -- → '2026-06-16 00:00:00+00' (Monday)\nSELECT DATE_TRUNC('hour',  now());           -- → '2026-06-20 14:00:00+00'\n\n-- Group orders by month\nSELECT DATE_TRUNC('month', created_at) AS month,\n       SUM(total)                      AS revenue,\n       COUNT(*)                        AS orders\nFROM   orders\nGROUP  BY 1\nORDER  BY 1;\n\n-- MySQL equivalent: DATE_FORMAT\nSELECT DATE_FORMAT(created_at, '%Y-%m-01') AS month,\n       SUM(total) AS revenue\nFROM   orders\nGROUP  BY 1;\n\n-- SQL Server: DATETRUNC (SQL Server 2022+) or DATEFROMPARTS workaround\nSELECT DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1) AS month,\n       SUM(total) AS revenue\nFROM   orders\nGROUP  BY DATEFROMPARTS(YEAR(created_at), MONTH(created_at), 1);\n```\n\n**Rule of thumb:** `DATE_TRUNC` is the idiomatic Postgres way to bucket\ntime-series data. Pair it with an index on the timestamp column for\nefficient range scans per bucket.\n",{"id":134,"difficulty":36,"q":135,"a":136},"extract","How do you extract a specific part of a date (year, month, day)?","`EXTRACT` (standard SQL) and database-specific functions pull individual\ncomponents out of a date or timestamp.\n\n```sql\n-- ANSI EXTRACT (all databases)\nSELECT EXTRACT(YEAR  FROM now());   -- → 2026\nSELECT EXTRACT(MONTH FROM now());   -- → 6\nSELECT EXTRACT(DAY   FROM now());   -- → 20\nSELECT EXTRACT(DOW   FROM now());   -- → 6 (0=Sunday … 6=Saturday, Postgres)\nSELECT EXTRACT(WEEK  FROM now());   -- → ISO week number\n\n-- Postgres shorthand\nSELECT DATE_PART('year', now());    -- equivalent to EXTRACT\n\n-- MySQL\nSELECT YEAR(now());    -- → 2026\nSELECT MONTH(now());   -- → 6\nSELECT DAY(now());     -- → 20\nSELECT DAYOFWEEK(now()); -- 1=Sunday … 7=Saturday\n\n-- SQL Server\nSELECT YEAR(GETDATE());\nSELECT DATEPART(WEEKDAY, GETDATE());\n```\n\n**Rule of thumb:** use `EXTRACT` for portable code. In Postgres, `DATE_PART`\nis equivalent but returns `DOUBLE PRECISION` — use `EXTRACT` when you need\nan integer result for arithmetic.\n",{"id":138,"difficulty":68,"q":139,"a":140},"age-datediff","How do you calculate the difference between two dates?","```sql\n-- Postgres: AGE() returns a human-readable interval\nSELECT AGE('2026-06-20', '1993-03-15');\n-- → '33 years 3 mons 5 days'\n\nSELECT AGE(now(), birth_date) FROM users;\n-- → age as interval; use EXTRACT to get the years component\nSELECT EXTRACT(YEAR FROM AGE(now(), birth_date)) AS age_years FROM users;\n\n-- Postgres: simple subtraction → interval (days)\nSELECT '2026-06-20'::DATE - '2026-01-01'::DATE;  -- → 170 (integer days)\n\n-- MySQL\nSELECT DATEDIFF('2026-06-20', '2026-01-01');      -- → 170 (days)\nSELECT TIMESTAMPDIFF(YEAR, birth_date, NOW())  AS age FROM users;\nSELECT TIMESTAMPDIFF(MONTH, '2026-01-01', '2026-06-20');  -- → 5 months\n\n-- SQL Server\nSELECT DATEDIFF(day,  '2026-01-01', '2026-06-20');  -- → 170\nSELECT DATEDIFF(year, birth_date, GETDATE()) AS age FROM users;\n```\n\n**Rule of thumb:** use `TIMESTAMPDIFF` (MySQL) or `DATEDIFF` (SQL Server)\nfor simple numeric differences. In Postgres, subtract dates directly for\ninteger days; use `AGE()` when you need a human-readable breakdown.\n",{"id":142,"difficulty":68,"q":143,"a":144},"timezone-handling","How do you handle timezones when storing and querying timestamps?","The fundamental rule: **store in UTC, display in the user's timezone**.\nUse timezone-aware column types so conversions are unambiguous.\n\n```sql\n-- Postgres: TIMESTAMPTZ stores in UTC; AT TIME ZONE converts for display\nCREATE TABLE events (\n  id         BIGSERIAL PRIMARY KEY,\n  occurred   TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n\n-- Convert to a specific timezone for display\nSELECT occurred AT TIME ZONE 'America\u002FNew_York' AS local_time FROM events;\nSELECT occurred AT TIME ZONE 'Asia\u002FKolkata'     AS ist_time   FROM events;\n\n-- Set the session timezone (affects display, not storage)\nSET TIME ZONE 'America\u002FNew_York';\nSELECT now();  -- displayed in ET, still stored as UTC internally\n\n-- MySQL: CONVERT_TZ\nSELECT CONVERT_TZ(occurred, 'UTC', 'America\u002FNew_York') FROM events;\n\n-- SQL Server: AT TIME ZONE (SQL Server 2016+)\nSELECT occurred AT TIME ZONE 'UTC' AT TIME ZONE 'Eastern Standard Time' FROM events;\n```\n\n**Rule of thumb:** always use `TIMESTAMPTZ` (Postgres) or an equivalent\nUTC-storing type. Never store local times without an explicit timezone\noffset — daylight-saving transitions will corrupt historical data.\n",{"id":146,"difficulty":36,"q":147,"a":148},"date-format","How do you format a date as a string?","```sql\n-- Postgres: TO_CHAR\nSELECT TO_CHAR(now(), 'YYYY-MM-DD');            -- → '2026-06-20'\nSELECT TO_CHAR(now(), 'DD\u002FMM\u002FYYYY');            -- → '20\u002F06\u002F2026'\nSELECT TO_CHAR(now(), 'Month DD, YYYY');        -- → 'June     20, 2026'\nSELECT TO_CHAR(now(), 'FMMonth DD, YYYY');      -- → 'June 20, 2026' (FM removes padding)\nSELECT TO_CHAR(now(), 'YYYY-MM-DD HH24:MI:SS'); -- → '2026-06-20 14:30:00'\n\n-- MySQL: DATE_FORMAT\nSELECT DATE_FORMAT(NOW(), '%Y-%m-%d');          -- → '2026-06-20'\nSELECT DATE_FORMAT(NOW(), '%d\u002F%m\u002F%Y %H:%i:%s'); -- → '20\u002F06\u002F2026 14:30:00'\n\n-- SQL Server: FORMAT \u002F CONVERT\nSELECT FORMAT(GETDATE(), 'yyyy-MM-dd');         -- → '2026-06-20'\nSELECT CONVERT(VARCHAR, GETDATE(), 23);         -- → '2026-06-20' (style 23)\n```\n\n**Rule of thumb:** format dates as strings in the application layer when\npossible, since formatting functions are not portable and locale handling\nvaries. Use `TO_CHAR` in SQL only for exports or reports generated entirely\nin the database.\n",{"id":150,"difficulty":151,"q":152,"a":153},"generate-series","hard","How do you generate a date series to fill gaps in time-series data?","`GENERATE_SERIES` (Postgres) produces a set of dates or timestamps, making\nit easy to build a complete date spine and `LEFT JOIN` to fill in zero\nvalues for missing days.\n\n```sql\n-- Postgres: daily date spine for June 2026\nSELECT generate_series(\n  '2026-06-01'::DATE,\n  '2026-06-30'::DATE,\n  INTERVAL '1 day'\n) AS day;\n\n-- Fill gaps in daily revenue (days with no orders show 0)\nSELECT day::DATE,\n       COALESCE(SUM(o.total), 0) AS revenue\nFROM generate_series('2026-06-01'::DATE, '2026-06-30'::DATE, '1 day') AS day\nLEFT JOIN orders o ON o.created_at::DATE = day::DATE\nGROUP BY 1\nORDER BY 1;\n```\n\nMySQL and SQL Server do not have `GENERATE_SERIES` natively; the common\nworkaround is a **numbers\u002Fcalendar table** or a recursive CTE.\n\n**Rule of thumb:** always use a date spine (`GENERATE_SERIES` or a calendar\ntable) when charting time-series data — direct `GROUP BY` on timestamps\nsilently omits days with no records, creating misleading gaps in charts.\n",{"id":155,"difficulty":68,"q":156,"a":157},"interval-type","What is the INTERVAL type and how do you use it?","An **interval** represents a duration (not a point in time). Postgres has\na rich `INTERVAL` type; MySQL and SQL Server use numeric + unit keywords\ninstead.\n\n```sql\n-- Postgres INTERVAL literals\nSELECT INTERVAL '1 year 2 months 3 days';\nSELECT INTERVAL '90 minutes';\nSELECT INTERVAL '2 hours 30 minutes';\n\n-- Arithmetic with intervals\nSELECT now() - INTERVAL '1 week';\nSELECT now() + INTERVAL '90 days';\n\n-- Storing intervals (Postgres)\nCREATE TABLE subscriptions (\n  id         SERIAL PRIMARY KEY,\n  started_at TIMESTAMPTZ NOT NULL,\n  duration   INTERVAL NOT NULL DEFAULT '1 month'\n);\nSELECT started_at + duration AS expires_at FROM subscriptions;\n\n-- Extract numeric parts from an interval\nSELECT EXTRACT(DAYS FROM INTERVAL '1 year 3 days');    -- → 3 (only the days part)\nSELECT EXTRACT(EPOCH FROM INTERVAL '2 hours');         -- → 7200 (total seconds)\n```\n\n**Rule of thumb:** use `EXTRACT(EPOCH FROM interval_col)` to convert an\ninterval to a total number of seconds for arithmetic or comparison — it is\nthe most reliable way to compare durations of mixed units.\n",{"id":159,"difficulty":151,"q":160,"a":161},"now-vs-clock-timestamp","What is the difference between now() and clock_timestamp() in Postgres?","In Postgres, `now()` (and `CURRENT_TIMESTAMP`) returns the timestamp at\nthe **start of the current transaction** — it does not change within the\nsame transaction. `clock_timestamp()` returns the **actual wall-clock time**\nat the moment it is called.\n\n```sql\nBEGIN;\n  SELECT now();             -- → '2026-06-20 14:30:00.000'\n  PERFORM pg_sleep(2);\n  SELECT now();             -- → '2026-06-20 14:30:00.000'  (same! start of txn)\n  SELECT clock_timestamp(); -- → '2026-06-20 14:30:02.005'  (actual time now)\nCOMMIT;\n```\n\nThis matters for:\n- **Audit columns**: `DEFAULT now()` is fine — all rows in the same\n  transaction get the same \"created_at\", which is expected.\n- **Benchmarking \u002F profiling**: use `clock_timestamp()` to measure elapsed\n  time within a transaction.\n- **Rate limiting**: if checking \"last request time\" within a long transaction,\n  use `clock_timestamp()` to get the real current time.\n\n**Rule of thumb:** use `now()` for audit timestamps (consistent within a\ntransaction is a feature, not a bug). Use `clock_timestamp()` when you need\nthe actual wall time, such as for elapsed-time calculations or timeouts\nchecked inside a transaction.\n",{"id":163,"difficulty":151,"q":164,"a":165},"date-trunc-index","How do you efficiently query by time period without slowing down writes?","Range queries on timestamp columns are common (`WHERE created_at >= X AND\ncreated_at \u003C Y`). The key to making them fast is a **B-tree index on the\nraw timestamp**, combined with using range predicates rather than wrapping\nthe column in a function.\n\n```sql\n-- GOOD: range predicate — index on created_at is used\nCREATE INDEX idx_orders_created ON orders (created_at);\n\nSELECT * FROM orders\nWHERE created_at >= '2026-06-01'\n  AND created_at \u003C  '2026-07-01';\n\n-- BAD: function applied to the column — index NOT used\nSELECT * FROM orders WHERE DATE_TRUNC('month', created_at) = '2026-06-01';\n\n-- Fix: use a range instead of DATE_TRUNC in the WHERE clause\nSELECT * FROM orders\nWHERE created_at >= DATE_TRUNC('month', now())\n  AND created_at \u003C  DATE_TRUNC('month', now()) + INTERVAL '1 month';\n-- OR: functional index on DATE_TRUNC\nCREATE INDEX idx_orders_month ON orders (DATE_TRUNC('month', created_at));\n```\n\n**Rule of thumb:** never wrap a timestamp column in a function in a\n`WHERE` clause if an index on the raw column exists. Rewrite the predicate\nas a range (`col >= start AND col \u003C end`) so the index is used.\n",{"id":167,"difficulty":151,"q":168,"a":169},"lag-lead-dates","How do you calculate time between consecutive events per user?","Use the `LAG` window function to access the previous row's timestamp within\na partition, then subtract to get the gap.\n\n```sql\n-- Time between consecutive logins per user\nSELECT user_id,\n       logged_in_at,\n       LAG(logged_in_at) OVER (\n         PARTITION BY user_id\n         ORDER BY logged_in_at\n       ) AS prev_login,\n       logged_in_at - LAG(logged_in_at) OVER (\n         PARTITION BY user_id\n         ORDER BY logged_in_at\n       ) AS gap\nFROM   user_logins\nORDER  BY user_id, logged_in_at;\n\n-- Average gap between sessions per user\nSELECT user_id,\n       AVG(gap) AS avg_session_gap\nFROM (\n  SELECT user_id,\n         logged_in_at - LAG(logged_in_at) OVER (\n           PARTITION BY user_id ORDER BY logged_in_at\n         ) AS gap\n  FROM user_logins\n) sub\nWHERE gap IS NOT NULL  -- first row per user has no previous\nGROUP BY user_id;\n```\n\n**Rule of thumb:** `LAG` with a date subtraction is the standard SQL\npattern for inter-event time calculations. The first event per partition\nreturns NULL for `LAG` — always filter or handle that case explicitly.\n",{"id":171,"difficulty":36,"q":172,"a":173},"make-date","How do you construct a date from year, month, and day components?","```sql\n-- Postgres: MAKE_DATE \u002F MAKE_TIMESTAMP\nSELECT MAKE_DATE(2026, 6, 20);                        -- → '2026-06-20'\nSELECT MAKE_TIMESTAMP(2026, 6, 20, 14, 30, 0);        -- → '2026-06-20 14:30:00'\nSELECT MAKE_TIMESTAMPTZ(2026, 6, 20, 14, 30, 0, 'UTC'); -- tz-aware\n\n-- MySQL: MAKEDATE \u002F MAKETIME \u002F STR_TO_DATE\nSELECT MAKEDATE(2026, 171);            -- → '2026-06-20' (day 171 of 2026)\nSELECT STR_TO_DATE('20\u002F06\u002F2026', '%d\u002F%m\u002F%Y');  -- → '2026-06-20'\n\n-- SQL Server: DATEFROMPARTS \u002F DATETIMEFROMPARTS\nSELECT DATEFROMPARTS(2026, 6, 20);    -- → '2026-06-20'\nSELECT DATETIMEFROMPARTS(2026, 6, 20, 14, 30, 0, 0);\n\n-- Practical: reconstruct a date from separate year\u002Fmonth columns\nSELECT MAKE_DATE(order_year, order_month, 1) AS period_start\nFROM   monthly_summaries;\n```\n\n**Rule of thumb:** use `MAKE_DATE` (Postgres) or `DATEFROMPARTS` (SQL\nServer) rather than string concatenation + casting — they are cleaner and\ncorrectly reject invalid dates (e.g., February 30) instead of silently\ncoercing them.\n",{"id":175,"difficulty":36,"q":176,"a":177},"age-function","How do you calculate a person's age or the time elapsed between two dates?","```sql\n-- Postgres: AGE() returns an interval\nSELECT AGE(birthdate)                   AS age       FROM persons;  -- age from today\nSELECT AGE(NOW(), birthdate)            AS age       FROM persons;  -- explicit end date\nSELECT EXTRACT(YEAR FROM AGE(birthdate)) AS age_years FROM persons;\n\n-- MySQL: TIMESTAMPDIFF\nSELECT TIMESTAMPDIFF(YEAR, birthdate, CURDATE()) AS age_years FROM persons;\nSELECT TIMESTAMPDIFF(MONTH, start_date, end_date) AS months_elapsed FROM projects;\n\n-- SQL Server: DATEDIFF\nSELECT DATEDIFF(YEAR,  birthdate, GETDATE()) AS age_years   FROM persons;\nSELECT DATEDIFF(DAY,   start_dt,  end_dt)    AS days_open   FROM tickets;\nSELECT DATEDIFF(MONTH, start_dt,  end_dt)    AS months_open FROM projects;\n```\n\nNote: `DATEDIFF(YEAR, …)` in SQL Server counts year boundaries crossed,\nnot full years lived — a birthday on December 31 would add 1 year on\nJanuary 1 of the next year. Use `TIMESTAMPDIFF` (MySQL) or `AGE` (Postgres)\nfor more accurate age calculation.\n\n**Rule of thumb:** for precise age-in-years, use `TIMESTAMPDIFF(YEAR, …)`\n(MySQL) or `EXTRACT(YEAR FROM AGE(…))` (Postgres). Never subtract year\nnumbers directly — they don't account for the month\u002Fday portion.\n",{"id":179,"difficulty":151,"q":180,"a":181},"date-range-overlap","How do you find rows where two date ranges overlap?","Two date ranges `[A_start, A_end]` and `[B_start, B_end]` overlap when\n`A_start \u003C B_end AND A_end > B_start`. This is the standard overlap\npredicate — all seven overlap cases satisfy it.\n\n```sql\n-- Find all bookings that overlap with a requested range\nSELECT *\nFROM   bookings\nWHERE  start_date \u003C '2026-07-01'   -- booking starts before requested end\n  AND  end_date   > '2026-06-15';  -- booking ends after requested start\n\n-- Find overlapping pairs within the same table (self-join)\nSELECT a.id AS booking_a, b.id AS booking_b\nFROM   bookings a\nJOIN   bookings b ON a.id \u003C b.id          -- avoid duplicate pairs\n       AND a.start_date \u003C b.end_date\n       AND a.end_date   > b.start_date;\n\n-- Postgres: use the built-in OVERLAPS operator (inclusive bounds)\nSELECT *\nFROM   bookings\nWHERE  (start_date, end_date) OVERLAPS ('2026-06-15'::DATE, '2026-07-01'::DATE);\n```\n\n**Rule of thumb:** the overlap condition is `A.start \u003C B.end AND A.end >\nB.start`. Make sure to use `\u003C` \u002F `>` (exclusive) vs `\u003C=` \u002F `>=` (inclusive)\nconsistently with your data model — half-open intervals `[start, end)` are\ngenerally easiest to reason about.\n",{"description":33},"SQL date and time function interview questions — NOW, CURRENT_DATE, DATE_TRUNC, EXTRACT, date arithmetic, interval types, timezone handling, and formatting across Postgres, MySQL, and SQL Server.","sql\u002Ffunctions\u002Fdate-functions","Date & Time Functions","W1E2_wYmKlkz2SVIgqmzxIqqc-UnOTEwm6BLBI9gDlU",{"id":188,"title":189,"body":190,"description":33,"difficulty":68,"extension":37,"framework":10,"frameworkSlug":12,"meta":194,"navigation":39,"order":195,"path":196,"questions":197,"questionsCount":103,"related":104,"seo":258,"seoDescription":259,"stem":260,"subtopic":261,"topic":20,"topicSlug":22,"updated":109,"__hash__":262},"qa\u002Fsql\u002Ffunctions\u002Fconditional-null-functions.md","Conditional Null Functions",{"type":30,"value":191,"toc":192},[],{"title":33,"searchDepth":34,"depth":34,"links":193},[],{},3,"\u002Fsql\u002Ffunctions\u002Fconditional-null-functions",[198,202,206,210,214,218,222,226,230,234,238,242,246,250,254],{"id":199,"difficulty":36,"q":200,"a":201},"case-expression","What is the CASE expression and what are its two forms?","`CASE` is SQL's conditional expression — an inline if\u002Felse that returns a\nvalue. It has two forms:\n\n**Simple CASE** — compares one expression to several values:\n\n```sql\nSELECT order_id,\n       CASE status\n         WHEN 'pending'   THEN 'Awaiting payment'\n         WHEN 'shipped'   THEN 'On its way'\n         WHEN 'delivered' THEN 'Complete'\n         ELSE 'Unknown'\n       END AS status_label\nFROM orders;\n```\n\n**Searched CASE** — evaluates independent boolean conditions:\n\n```sql\nSELECT order_id, total,\n       CASE\n         WHEN total >= 500 THEN 'Large'\n         WHEN total >= 100 THEN 'Medium'\n         ELSE 'Small'\n       END AS size_category\nFROM orders;\n```\n\n`CASE` can appear anywhere an expression is valid: `SELECT`, `WHERE`,\n`ORDER BY`, `GROUP BY`, inside aggregate functions.\n\n**Rule of thumb:** use the simple form for equality checks on a single\ncolumn; use the searched form when conditions involve different columns,\ncomparisons, or `IS NULL` checks.\n",{"id":203,"difficulty":36,"q":204,"a":205},"coalesce","What does COALESCE do?","`COALESCE(expr1, expr2, …)` returns the **first non-NULL value** from its\nargument list. It is the standard way to substitute a default for a NULL.\n\n```sql\n-- Use a fallback when a column might be NULL\nSELECT COALESCE(phone, 'N\u002FA')          AS phone    FROM users;\nSELECT COALESCE(discount, 0)           AS discount FROM orders;\nSELECT COALESCE(nickname, first_name)  AS display  FROM users;\n\n-- Chain multiple fallbacks\nSELECT COALESCE(preferred_email, work_email, personal_email, 'no-email@unknown.com')\nFROM contacts;\n\n-- Avoid NULL in arithmetic (NULL + anything = NULL)\nSELECT price * COALESCE(quantity, 0) AS line_total FROM cart_items;\n```\n\n`COALESCE` short-circuits: it stops evaluating arguments as soon as it\nfinds a non-NULL value. All arguments must be type-compatible.\n\n**Rule of thumb:** use `COALESCE` to provide defaults for nullable columns.\nIt is cleaner and more portable than `CASE WHEN col IS NULL THEN default\nELSE col END` — they are exactly equivalent.\n",{"id":207,"difficulty":68,"q":208,"a":209},"nullif","What does NULLIF do and when is it useful?","`NULLIF(expr1, expr2)` returns `NULL` if the two expressions are equal,\notherwise returns `expr1`. It is the inverse of `COALESCE` — converting a\nspecific value to `NULL`.\n\n```sql\n-- Prevent division-by-zero: replace 0 with NULL before dividing\nSELECT numerator \u002F NULLIF(denominator, 0) AS ratio FROM metrics;\n-- If denominator = 0 → NULL instead of ERROR\n\n-- Convert a sentinel value to NULL\nSELECT NULLIF(phone, 'N\u002FA') AS phone FROM contacts;\n-- 'N\u002FA' → NULL; other values pass through unchanged\n\n-- Combine with COALESCE for clean defaults\nSELECT COALESCE(NULLIF(TRIM(notes), ''), 'No notes') AS notes FROM tickets;\n-- Empty-string or whitespace-only → 'No notes'\n```\n\n**Rule of thumb:** reach for `NULLIF(col, 0)` any time you are dividing\nby a column that may be zero. Pair with `COALESCE` when you also want to\nreplace the resulting NULL with a default.\n",{"id":211,"difficulty":36,"q":212,"a":213},"ifnull-nvl-isnull","What are IFNULL, NVL, and ISNULL — and how do they compare to COALESCE?","These are two-argument shortcuts for replacing NULL with a default — they\nare equivalent to `COALESCE(expr, default)` but are database-specific:\n\n| Function | Database |\n|---|---|\n| `IFNULL(expr, default)` | MySQL |\n| `NVL(expr, default)` | Oracle |\n| `ISNULL(expr, default)` | SQL Server \u002F Sybase |\n| `COALESCE(expr, default)` | All (ANSI SQL) |\n\n```sql\n-- MySQL\nSELECT IFNULL(discount, 0) FROM orders;\n\n-- SQL Server\nSELECT ISNULL(discount, 0) FROM orders;\n\n-- COALESCE — works everywhere and accepts more than two arguments\nSELECT COALESCE(discount, 0) FROM orders;\n```\n\n**Rule of thumb:** use `COALESCE` in any code that needs to be portable.\nUse `IFNULL`\u002F`ISNULL` only in database-specific stored procedures where\nportability is not a concern. Avoid `NVL` outside Oracle.\n",{"id":215,"difficulty":36,"q":216,"a":217},"iif","What is IIF and when can you use it?","`IIF(condition, true_value, false_value)` is a compact inline if\u002Felse\navailable in **SQL Server** and **MySQL** (as `IF`). It is syntactic sugar\nfor a two-branch `CASE`.\n\n```sql\n-- SQL Server IIF\nSELECT order_id,\n       IIF(total > 100, 'Large', 'Small') AS size\nFROM orders;\n\n-- MySQL IF (same concept, different name)\nSELECT order_id,\n       IF(total > 100, 'Large', 'Small') AS size\nFROM orders;\n\n-- Equivalent CASE (works everywhere)\nSELECT order_id,\n       CASE WHEN total > 100 THEN 'Large' ELSE 'Small' END AS size\nFROM orders;\n```\n\n**Rule of thumb:** use `CASE` for portability. Use `IIF` (SQL Server) or\n`IF` (MySQL) in database-specific scripts where you prefer the terser\nsyntax, but be aware that porting the query later requires rewriting it.\n",{"id":219,"difficulty":68,"q":220,"a":221},"null-in-comparisons","Why do comparisons with NULL often return unexpected results?","`NULL` represents an unknown value. Any comparison involving `NULL` evaluates\nto **UNKNOWN** (not TRUE or FALSE) under SQL's three-valued logic. Since\n`WHERE` clauses only keep rows where the condition is TRUE, rows with NULL\nin a comparison are silently excluded.\n\n```sql\n-- All of these evaluate to UNKNOWN, not TRUE:\nSELECT NULL = NULL;      -- UNKNOWN\nSELECT NULL \u003C> 1;        -- UNKNOWN\nSELECT NULL > 0;         -- UNKNOWN\nSELECT 1 = NULL;         -- UNKNOWN\n\n-- Correct tests for NULL\nSELECT NULL IS NULL;     -- TRUE\nSELECT NULL IS NOT NULL; -- FALSE\n\n-- Common mistake: missing IS NULL rows\nSELECT * FROM orders WHERE discount \u003C> 0;\n-- Excludes rows where discount IS NULL — those rows have unknown discount\n\n-- Fix: explicitly include the NULL case\nSELECT * FROM orders WHERE discount \u003C> 0 OR discount IS NULL;\n```\n\n**Rule of thumb:** whenever a column may be NULL, check whether your\n`WHERE` clause correctly handles it. Any condition using `=`, `\u003C>`, `>`,\n`\u003C`, `LIKE`, or `IN` silently drops NULL rows.\n",{"id":223,"difficulty":68,"q":224,"a":225},"null-in-aggregates","How does NULL affect aggregate functions?","All aggregate functions (except `COUNT(*)`) **ignore NULL values** in their\ninput. This is usually what you want, but it can produce surprising results.\n\n```sql\n-- Setup: discount column has some NULLs\n-- | id | total | discount |\n-- | 1  | 100   | 10       |\n-- | 2  | 200   | NULL     |\n-- | 3  | 300   | 20       |\n\nSELECT COUNT(*)        AS total_rows,       -- → 3 (counts all rows)\n       COUNT(discount) AS non_null_discount, -- → 2 (skips NULLs)\n       SUM(discount)   AS total_discount,    -- → 30 (NULLs ignored)\n       AVG(discount)   AS avg_discount       -- → 15 (30 \u002F 2, not 30 \u002F 3!)\nFROM orders;\n-- AVG denominator is the COUNT of non-NULL rows, not total rows.\n\n-- Fix: use COALESCE to treat NULL as 0 in AVG\nSELECT AVG(COALESCE(discount, 0)) AS avg_discount FROM orders; -- → 10\n```\n\n**Rule of thumb:** `AVG(col)` divides by the count of non-NULL values —\nthis can silently exclude missing data from the average. When NULLs should\nbe treated as 0 in averages, use `AVG(COALESCE(col, 0))`.\n",{"id":227,"difficulty":68,"q":228,"a":229},"conditional-aggregation","What is conditional aggregation and how do you use it?","**Conditional aggregation** uses a `CASE` expression inside an aggregate\nfunction to count, sum, or average only rows matching a condition — pivoting\nrow data into columns without a JOIN.\n\n```sql\n-- Count orders by status in a single pass\nSELECT COUNT(*)                                         AS total,\n       COUNT(CASE WHEN status = 'pending'   THEN 1 END) AS pending,\n       COUNT(CASE WHEN status = 'shipped'   THEN 1 END) AS shipped,\n       COUNT(CASE WHEN status = 'delivered' THEN 1 END) AS delivered,\n       SUM(CASE WHEN status = 'pending' THEN total ELSE 0 END) AS pending_revenue\nFROM orders;\n\n-- Postgres \u002F SQL Server shorthand: FILTER clause\nSELECT COUNT(*) FILTER (WHERE status = 'pending')   AS pending,\n       COUNT(*) FILTER (WHERE status = 'shipped')   AS shipped,\n       SUM(total) FILTER (WHERE status = 'pending') AS pending_revenue\nFROM orders;\n```\n\n**Rule of thumb:** use conditional aggregation to pivot data in a single\nquery instead of multiple subqueries or UNION. The `FILTER` clause\n(Postgres\u002FSQL Server) is cleaner than `CASE WHEN … THEN 1 END` — prefer\nit when available.\n",{"id":231,"difficulty":36,"q":232,"a":233},"greatest-least","What are GREATEST and LEAST?","`GREATEST(val1, val2, …)` returns the largest value from its arguments.\n`LEAST(val1, val2, …)` returns the smallest. They work across any\ncomparable types and return NULL if any argument is NULL (Postgres\u002FMySQL);\nSQL Server does not have these functions natively.\n\n```sql\nSELECT GREATEST(10, 20, 5);          -- → 20\nSELECT LEAST(10, 20, 5);             -- → 5\nSELECT GREATEST(NULL, 10, 20);       -- → NULL (any NULL propagates)\n\n-- Practical: clamp a value within a min\u002Fmax range\nSELECT LEAST(GREATEST(user_rating, 1), 5) AS clamped_rating FROM reviews;\n-- Ensures rating is always between 1 and 5\n\n-- Use COALESCE to handle NULLs when comparing columns\nSELECT GREATEST(COALESCE(a, 0), COALESCE(b, 0)) AS max_ab FROM t;\n```\n\nSQL Server equivalent using CASE:\n```sql\nSELECT CASE WHEN a > b THEN a ELSE b END AS greatest_ab FROM t;\n```\n\n**Rule of thumb:** use `GREATEST`\u002F`LEAST` for clamping values to a range\nor comparing columns from the same row — they are cleaner than nested\n`CASE` expressions for this pattern.\n",{"id":235,"difficulty":68,"q":236,"a":237},"null-safe-equals","How do you compare two values that may both be NULL?","Standard `=` returns UNKNOWN when either side is NULL. To check whether two\nnullable columns are \"equal\" (including both being NULL), use database-\nspecific NULL-safe equality.\n\n```sql\n-- Standard SQL: verbose but portable\nSELECT *\nFROM   t1\nJOIN   t2 ON (t1.col = t2.col) OR (t1.col IS NULL AND t2.col IS NULL);\n\n-- Postgres: IS NOT DISTINCT FROM (NULL-safe equality)\nSELECT * FROM t1 JOIN t2 ON t1.col IS NOT DISTINCT FROM t2.col;\n-- NULL IS NOT DISTINCT FROM NULL → TRUE\n-- 1   IS NOT DISTINCT FROM 1    → TRUE\n-- 1   IS NOT DISTINCT FROM NULL → FALSE\n\n-- MySQL: \u003C=> (spaceship operator)\nSELECT * FROM t1 JOIN t2 ON t1.col \u003C=> t2.col;\n-- NULL \u003C=> NULL → 1 (TRUE)\n\n-- SQL Server: no shorthand — use the verbose ANSI form\n```\n\n**Rule of thumb:** use `IS NOT DISTINCT FROM` (Postgres) or `\u003C=>` (MySQL)\nwhen joining or comparing nullable columns where two NULLs should be\nconsidered equal. This is common in upsert logic and change-detection\nqueries.\n",{"id":239,"difficulty":151,"q":240,"a":241},"boolean-logic-nulls","How does three-valued logic affect AND and OR with NULLs?","SQL uses three truth values: TRUE, FALSE, and UNKNOWN (NULL). `AND` and\n`OR` follow specific rules when UNKNOWN is involved:\n\n```\nTRUE  AND UNKNOWN = UNKNOWN\nFALSE AND UNKNOWN = FALSE     ← FALSE wins in AND\nTRUE  OR  UNKNOWN = TRUE      ← TRUE wins in OR\nFALSE OR  UNKNOWN = UNKNOWN\nNOT UNKNOWN       = UNKNOWN\n```\n\n```sql\n-- Pitfall: NOT IN with a NULL in the subquery\nSELECT * FROM products\nWHERE  id NOT IN (SELECT product_id FROM discontinued WHERE product_id IS NULL);\n-- If the subquery returns even one NULL, NOT IN always returns UNKNOWN\n-- → zero rows returned! (UNKNOWN is treated as FALSE in WHERE)\n\n-- Fix: use NOT EXISTS instead\nSELECT * FROM products p\nWHERE NOT EXISTS (\n  SELECT 1 FROM discontinued d WHERE d.product_id = p.id\n);\n-- NOT EXISTS correctly handles NULLs — returns TRUE if no match found\n```\n\n**Rule of thumb:** never use `NOT IN (subquery)` when the subquery can\nreturn NULLs — it silently returns zero rows. Always use `NOT EXISTS` as\nthe safe alternative.\n",{"id":243,"difficulty":36,"q":244,"a":245},"decode-decode","How do you handle multiple conditional mappings cleanly?","For mapping one value to another across many cases, `CASE` is the standard\napproach. Some databases also offer compact alternatives.\n\n```sql\n-- Standard CASE — readable and portable\nSELECT status,\n       CASE status\n         WHEN 1 THEN 'Active'\n         WHEN 2 THEN 'Inactive'\n         WHEN 3 THEN 'Banned'\n         ELSE        'Unknown'\n       END AS status_label\nFROM users;\n\n-- Oracle DECODE (not available in other databases)\nSELECT DECODE(status, 1, 'Active', 2, 'Inactive', 3, 'Banned', 'Unknown')\nFROM users;\n\n-- Postgres: use a lookup table or CASE — no DECODE\n-- Alternative: join to a status lookup table (preferred for long lists)\nSELECT u.id, s.label\nFROM users u\nJOIN status_codes s ON s.code = u.status;\n```\n\n**Rule of thumb:** for short, stable mappings (\u003C 6 values), use `CASE`.\nFor longer or changeable mappings, maintain a lookup\u002Freference table and\njoin to it — the mapping is then data, not code, and can be updated without\na schema change.\n",{"id":247,"difficulty":68,"q":248,"a":249},"try-convert","How do you safely cast a string to a number or date without raising an error?","If a string column contains non-numeric values and you try to `CAST` it to\na number, the query fails. SQL Server and MySQL offer safe-cast functions\nthat return NULL on failure. Postgres requires a different approach.\n\n```sql\n-- SQL Server: TRY_CAST \u002F TRY_CONVERT (return NULL on failure)\nSELECT TRY_CAST('123'   AS INT);        -- → 123\nSELECT TRY_CAST('abc'   AS INT);        -- → NULL (no error)\nSELECT TRY_CONVERT(DATE, '2026-06-20'); -- → '2026-06-20'\nSELECT TRY_CONVERT(DATE, 'not-a-date'); -- → NULL\n\n-- MySQL: CAST silently coerces and produces 0 or NULL\nSELECT CAST('abc' AS UNSIGNED);  -- → 0 (silent coercion, with a warning)\n\n-- Postgres: no TRY_CAST built-in; use regexp check before casting\nSELECT CASE\n         WHEN value ~ '^\\d+$' THEN value::INT\n         ELSE NULL\n       END AS safe_int\nFROM input_data;\n-- Or wrap in a PL\u002FpgSQL function that catches exceptions\n```\n\n**Rule of thumb:** validate and sanitise data at the application boundary\nbefore it enters the database. When cleaning dirty data inside SQL, use\n`TRY_CAST` (SQL Server) or a regex guard (Postgres) to avoid query-aborting\ncast errors.\n",{"id":251,"difficulty":68,"q":252,"a":253},"case-in-order-by","How can you use CASE in ORDER BY to create custom sort orders?","`CASE` inside `ORDER BY` lets you define a custom sort priority that is not\npossible with a simple column sort — for example, putting a specific status\nfirst, sorting NULL values to the bottom, or combining multiple conditions.\n\n```sql\n-- Sort 'pending' orders first, then 'shipped', then all others alphabetically\nSELECT order_id, status\nFROM   orders\nORDER  BY CASE status\n            WHEN 'pending' THEN 1\n            WHEN 'shipped' THEN 2\n            ELSE 3\n          END,\n         status ASC;  -- secondary sort within the ELSE group\n\n-- Sort NULLs to the bottom (Postgres also has NULLS LAST natively)\nSELECT id, priority\nFROM   tasks\nORDER  BY CASE WHEN priority IS NULL THEN 1 ELSE 0 END,\n         priority ASC;\n\n-- Postgres native null ordering (cleaner):\nSELECT id, priority FROM tasks ORDER BY priority ASC NULLS LAST;\n```\n\n**Rule of thumb:** use `CASE` in `ORDER BY` when the sort requirement\ncannot be expressed as `ASC`\u002F`DESC` on existing columns. For `NULL` ordering\nspecifically, prefer `NULLS FIRST` \u002F `NULLS LAST` in databases that support\nit (Postgres, Oracle, SQL Server 2022+) — it is more readable.\n",{"id":255,"difficulty":151,"q":256,"a":257},"pivot-with-case","How do you pivot rows into columns using CASE?","**Pivoting** transforms row values into column headers. SQL lacks a native\n`PIVOT` syntax in all databases, but conditional aggregation with `CASE`\nachieves the same result and is portable.\n\n```sql\n-- Data: (year, quarter, revenue)\n-- Goal: one row per year with columns q1, q2, q3, q4\n\nSELECT year,\n       SUM(CASE WHEN quarter = 1 THEN revenue END) AS q1,\n       SUM(CASE WHEN quarter = 2 THEN revenue END) AS q2,\n       SUM(CASE WHEN quarter = 3 THEN revenue END) AS q3,\n       SUM(CASE WHEN quarter = 4 THEN revenue END) AS q4\nFROM   quarterly_revenue\nGROUP  BY year\nORDER  BY year;\n\n-- SQL Server native PIVOT syntax (not portable):\nSELECT year, [1] AS q1, [2] AS q2, [3] AS q3, [4] AS q4\nFROM   quarterly_revenue\nPIVOT (SUM(revenue) FOR quarter IN ([1],[2],[3],[4])) AS p;\n```\n\n**Rule of thumb:** use `CASE`-based conditional aggregation for portability\nand readability. Use SQL Server's `PIVOT` syntax only in SQL Server-specific\ncode where dynamic pivot (unknown column count) is needed — even then, it\nrequires dynamic SQL to handle a variable number of pivot columns.\n",{"description":33},"SQL conditional and NULL function interview questions — CASE, COALESCE, NULLIF, IIF, GREATEST, LEAST, NVL, NULL handling pitfalls, and conditional aggregation across Postgres, MySQL, and SQL Server.","sql\u002Ffunctions\u002Fconditional-null-functions","Conditional & NULL Functions","dBvP1iGJ3yvGCVGzunS29UEwxdb3VGDQxovXGTlNlQQ",1782244099092]