SQL CASE, Functions and Calculated Columns

Learn to compute new columns in SQL queries. Calculate with arithmetic and clear column aliases, convert values between data types, clean and cut text such as names and email addresses, and work out dates, months, ages and deadlines. Handle missing values with NULL logic, COALESCE and NULLIF, and sort rows into categories with CASE expressions. Examples run on SQLite and PostgreSQL, and the differences you meet in MySQL, SQL Server and Oracle are pointed out along the way.

BeginnerLesson 3 of 6 in SQL Basics Course

What you will learn

  1. Compute new values for every row with arithmetic and functions, give the results clear names, and keep data types from changing the answer.
    • Compute new columns and name them with aliases
      Write expressions in the SELECT list, label the results with AS, and know where in a query those labels can be used.
    • Calculate with arithmetic operators and number functions
      Use +, -, *, / and % in the right order, avoid integer-division surprises, and round results with ROUND and related functions.
    • Convert values between data types
      Recognize the type of each value, convert it explicitly with CAST, and spot the automatic conversions that change sorting, comparisons and results.
  2. Predict what NULL does to calculations, comparisons and filters, and replace or create missing values on purpose.
    • Predict how NULL behaves in calculations and conditions
      Explain what NULL means, how it spreads through expressions, and why a condition that meets NULL is neither true nor false.
    • Replace and create missing values with COALESCE and NULLIF
      Fill gaps with COALESCE, turn zeros and placeholders into NULL with NULLIF, and choose replacement values that keep the meaning of the data.
  3. Join, tidy, measure and cut text values so that names, email addresses and codes come out consistent and usable.
    • Join and clean text values
      Concatenate text, change letter case, trim spaces and replace characters, and nest these functions to clean messy input.
    • Measure and extract parts of text
      Count characters, find where a character appears, and cut out pieces of text using positions that start at 1.
  4. Write and read date values correctly, take them apart and format them, and calculate with days, months and time spans.
    • Write, store and read date and time values
      Know the date and time types, write dates in the unambiguous ISO format, convert text to dates and get the current date and time.
    • Extract and format parts of dates
      Pull out the year, month or weekday, group dates by period, and turn dates into readable text.
    • Calculate with dates and filter date ranges
      Add days and months, measure the time between two dates, and write date filters that include every row in a period.
  5. Turn conditions into labels, categories and custom sort orders with CASE, and combine it with the other functions in complete report queries.
    • Sort rows into categories with searched CASE
      Write CASE WHEN … THEN … ELSE … END, order the conditions so each row lands in the right group, and handle boundary values and NULLs.
    • Use simple CASE and put CASE to work across a query
      Translate codes with simple CASE, build flags and custom sort orders, and recognize the shorter forms that other databases offer.
    • Combine functions into a complete report query
      Build a report with several calculated columns step by step, nest functions correctly, and check each column against rows worked out by hand.

SQL CASE and Functions study guide

5 chapters

1

Build calculated columns with expressions and aliases

Calculated columns turn the values already in a row into useful answers: a line total, a discount, or a percentage change. Clear aliases make those results readable, while parentheses and data types determine what SQL actually calculates. You will build expressions from small steps, see why integer division can erase a fraction, and choose explicit conversions when the stored form does not suit the operation.

1.1 Compute new columns and name them with aliases

A result column can show a value that is not stored anywhere in the table. SQL calculates it from the current row. An expression combines columns, fixed values, operators, and function calls.

Calculate a value for each row
SELECT order_id,
       quantity * unit_price AS line_total
FROM orders;

For quantity 3 and unit price 12.50, the new column shows 37.5. Each row supplies its own quantity and price. The query leaves the stored columns unchanged. A single-row function, such as ROUND(unit_price, 0), also returns one value per input row. Adding it to the SELECT list does not combine rows. An aggregate function such as SUM or AVG instead calculates a value for a group.

The name after AS is a column alias: a heading for the result. Without it, SQLite uses the expression text as the heading; PostgreSQL uses ?column? for this arithmetic expression. AS is optional, but it makes a name easier to distinguish from a missing comma.

Choose names and quotes

Use a simple name such as line_total: letters, digits, and underscores, starting with a letter. A name containing a space or a reserved word needs double quotes: AS "Line Total" or AS "order". Single quotes mark text values, such as 'Boston'. MySQL also accepts backticks for names, and SQL Server accepts square brackets.

PostgreSQL folds unquoted names to lowercase: AS LineTotal produces linetotal. Double quotes keep the spelling in AS "LineTotal". SQLite keeps an alias as typed. Lowercase names with underscores work neatly in both.

Use an alias at the right stage

The logical order is FROM, WHERE, SELECT, then ORDER BY. The table is chosen and rows are filtered before output expressions receive their aliases. Sorting happens afterward.

SELECT order_id,
       quantity * unit_price AS line_total
FROM orders
WHERE quantity * unit_price > 100
ORDER BY line_total DESC;

ORDER BY can use line_total. Standard SQL, PostgreSQL, and MySQL cannot use that new alias in WHERE; repeat the expression there. SQLite accepts an alias in WHERE as an extension, but that form will fail when moved to PostgreSQL.

A new alias is also unavailable to another expression in the same SELECT list. After unit_price * 1.08 AS with_tax, writing with_tax - unit_price AS tax fails if the table has no with_tax column. Write unit_price * 1.08 - unit_price AS tax instead.

Diagnose a surprising result

SELECT first_name last_name FROM customers; is valid SQL. It displays one column of first names with the alias last_name. To retrieve both columns, write SELECT first_name, last_name FROM customers;. A missing comma can therefore give a misleading result without an error.

For a quick expression check, a table is unnecessary: SELECT 19.99 * 3 AS total; returns 59.97 in SQLite and PostgreSQL. Oracle before Release 23 requires FROM DUAL for this query; Release 23 and later also allow SELECT without FROM.

Key Takeaways

  • A calculated column computes a value for each row without changing stored data.
  • A single-row function preserves rows; an aggregate combines values across a group.
  • Use double quotes for names that need quoting and single quotes for text values.
  • ORDER BY can use an output alias; repeat the expression in a portable WHERE clause.
  • A new alias is unavailable elsewhere in the same SELECT list.
  • A missing comma can turn the next column name into an alias.

1.2 Calculate with arithmetic operators and number functions

Arithmetic expressions turn raw quantities into amounts, rates, and differences. The operators are +, -, *, /, and %; modulo gives the remainder after division.

Control the calculation order

Multiplication, division, and modulo come before addition and subtraction. Equal-rank operators run left to right. Thus 10 + 3 * 2 gives 16, while (10 + 3) * 2 gives 26.

For quantity 3, unit price 12.50, and discount percentage 10:

quantity * unit_price * (1 - discount_pct / 100.0)

First 10 / 100.0 gives 0.1. The parentheses give the fraction paid, 0.9. The gross amount is 37.5, so the net amount is 33.75. Without parentheses, quantity * unit_price * 1 - discount_pct / 100.0 subtracts 0.1 from 37.5 and gives 37.4.

Keep fractions during division

In SQLite, PostgreSQL, and SQL Server, integer divided by integer drops the fraction toward zero: 7 / 2 gives 3, and -7 / 2 gives -3. MySQL and Oracle give a decimal answer for /. In SQLite and PostgreSQL, make an operand decimal before dividing: 7 / 2.0, 7 * 1.0 / 2, or CAST(7 AS REAL) / 2 gives 3.5. CAST(7 / 2 AS REAL) is too late and gives 3.0.

For a rise from 80 to 92, (92 - 80) * 100.0 / 80 gives 15.0 percent. Dividing the integer difference by 80 first loses the fraction. Modulo keeps the leftover part: 135 / 60 is 2 hours and 135 % 60 is 15 minutes. In SQLite and PostgreSQL, -7 % 3 gives -1; the remainder follows the left operand's sign. Oracle writes MOD(17, 5) instead of %.

A zero divisor raises an error in PostgreSQL and SQL Server; SQLite and MySQL return NULL. amount / NULLIF(divisor, 0) makes the zero case return NULL across these systems.

Round or choose a whole number

ROUND(x, n) rounds to n decimal places. A lamp priced at 24.00 with cost 15.50 has profit 8.5. ROUND((price - cost) / price * 100, 1) gives a margin of 35.4 percent. PostgreSQL supports negative digits: ROUND(1234.5678, -2) gives 1200; SQLite treats the digits as zero and gives 1235.0.

FunctionMeaningExample
ABSDistance from zeroABS(-4.2) gives 4.2
CEILSmallest integer at least as largeCEIL(4.1) gives 5
FLOORLargest integer at most as largeFLOOR(-4.1) gives -5
TRUNCRemove the fraction toward zeroTRUNC(42.8) gives 42

PostgreSQL provides these functions. SQLite provides ABS; CEIL, FLOOR, and TRUNC require a build with math functions, available from 3.35.

Match precision to the data

SQLite ROUND and PostgreSQL's numeric ROUND round exact halves away from zero: ROUND(-2.5) gives -3. PostgreSQL floating-point rounding usually takes ties to the nearest even integer; its two-argument ROUND requires numeric, so use ROUND(CAST(x AS numeric), 2) for a double-precision column.

Floating-point numbers approximate many decimal fractions. SQLite can make 0.1 + 0.2 = 0.3 false and ROUND(2.675, 2) return 2.67. PostgreSQL's decimal literals use exact numeric. For exact money, use PostgreSQL NUMERIC/DECIMAL or integer cents in SQLite. Declaring SQLite DECIMAL does not create exact decimal storage. Keep precision through calculations and round for display.

Key Takeaways

  • Parentheses control which amounts are combined before multiplication or division.
  • Convert an operand before integer division; converting the result cannot restore lost digits.
  • Modulo returns a remainder, such as minutes left after full hours.
  • ROUND, FLOOR, CEIL, and TRUNC make different choices about a fractional value.
  • Floating-point rounding and available number functions depend on type and database.
  • Use exact numeric storage in PostgreSQL or integer cents in SQLite for exact amounts.
  • NULLIF can make a zero divisor produce NULL instead of an error.

1.3 Convert values between data types

A value's data type affects arithmetic, comparisons, and sorting. The number 42 is different from the text '42', even when a result grid displays them alike.

Convert a value explicitly

Common types include INTEGER for whole numbers, NUMERIC or DECIMAL for exact decimals in PostgreSQL, REAL or double precision for approximate numbers, TEXT or VARCHAR for text, and DATE, TIME, TIMESTAMP, and BOOLEAN where supported.

SELECT CAST('42' AS INTEGER) AS number_value,
       CAST(42 AS TEXT) AS text_value;

CAST(expression AS type) is standard SQL. The first result is a number; the second is text. PostgreSQL also accepts '42'::integer. Casting NULL still gives NULL.

For decimal division in SQLite, write CAST(quantity AS REAL) / 4; in PostgreSQL, write CAST(quantity AS NUMERIC) / 4. Quantity 7 gives 1.75. SQLite's CAST(7 AS NUMERIC) / 2 still gives 3: NUMERIC leaves that whole value stored as an integer. REAL is the suitable SQLite cast for this purpose.

Know what an integer cast does
Input and castSQLitePostgreSQL
CAST(3.9 AS INTEGER)34
CAST(-3.9 AS INTEGER)-3-4
CAST('abc' AS INTEGER)0Error

SQLite removes a fraction toward zero. PostgreSQL rounds a numeric value to the nearest integer. Use ROUND, TRUNC, FLOOR, or CEIL first when the intended rule matters. PostgreSQL rejects text that is not a valid number. SQLite reads a leading numeric part: '12abc' becomes 12 and '12.5' becomes 12 when cast to INTEGER. Zero after a SQLite cast therefore does not prove that the original text was a valid zero. SQL Server's TRY_CAST returns NULL on a failed conversion.

Separate quantities from identifiers

Text sorts character by character. The strings '9', '10', and '100' sort as '10', '100', '9'. To order genuine numeric quantities kept as text, use ORDER BY CAST(code AS INTEGER) after ensuring they are valid numbers.

A ZIP code such as '02134' is an identifier. Casting it gives 2134 and loses the first zero. Phone numbers also belong as text: their digits label something, rather than describe an amount to add or average.

Recognize automatic conversions

Both SQLite and PostgreSQL convert the number to text in 'Order #' || 42, producing 'Order #42'. Both also evaluate the quoted literal in '5' + 3 as a number. In PostgreSQL, that literal initially has an open type; a TEXT column does not have the same freedom. Adding 3 to a TEXT column fails, so cast the column explicitly.

SQLite gives columns a type preference, called type affinity, and can keep different value types in one ordinary column. typeof(42), typeof(4.2), typeof('42'), and typeof(NULL) report 'integer', 'real', 'text', and 'null'. SQLite has no dedicated DATE type: CAST('2026-03-14' AS DATE) gives 2026. Use date('2026-03-14') for its date functions.

Oracle provides TO_NUMBER, TO_CHAR, and TO_DATE. PostgreSQL's TO_CHAR and TO_DATE take format patterns such as 'MM/DD/YYYY'. SQL Server's CONVERT uses a numeric style code: CONVERT(date, '03/14/2026', 101) reads the US date layout.

Key Takeaways

  • CAST explicitly changes a value to a requested type; NULL remains NULL.
  • SQLite truncates numeric-to-integer casts, while PostgreSQL rounds them.
  • SQLite can turn invalid numeric text into a partial number or zero.
  • Keep ZIP codes and phone numbers as text to preserve their identity.
  • Text sorting and numeric sorting can give different orders.
2

Handle missing values with NULL logic and COALESCE

A missing value can change a calculation and also change which rows a filter keeps. Understanding NULL means following both the data and the condition that uses it. You will distinguish missingness from zero and empty text, trace TRUE, FALSE, and UNKNOWN through a query, and choose fallbacks that preserve the meaning of a report. COALESCE and NULLIF then let you fill gaps or create them deliberately.

2.1 Predict how NULL behaves in calculations and conditions

NULL marks a missing or unknown value. A blank result cell might display NULL, but NULL is not an ordinary value that you compare with =.

Follow a missing input through an expression

Zero is a known number. Empty text, '', is a known string with no characters. The text 'N/A' is a recorded label. NULL says that no value is available. Oracle treats empty text as NULL; SQLite and PostgreSQL distinguish them.

Most arithmetic and functions pass a missing input on as NULL. With salary 50000 and bonus NULL, salary + bonus gives NULL. The total is unknown because the bonus is unknown. UPPER(NULL), ROUND(NULL, 2), and 'Ana ' || NULL also return NULL in SQLite and PostgreSQL.

Test for NULL directly

bonus = NULL, bonus <> NULL, and even NULL = NULL produce UNKNOWN. Two missing values are not known to be equal. Use bonus IS NULL or bonus IS NOT NULL. These tests always return TRUE or FALSE.

SQL conditions have three possible results: TRUE, FALSE, and UNKNOWN. SQLite displays TRUE as 1, FALSE as 0, and UNKNOWN as NULL.

ConditionResultReason
FALSE AND UNKNOWNFALSEOne false requirement rules out AND
TRUE AND UNKNOWNUNKNOWNThe second requirement is unresolved
TRUE OR UNKNOWNTRUEOne true alternative establishes OR
FALSE OR UNKNOWNUNKNOWNThe remaining alternative is unresolved
NOT UNKNOWNUNKNOWNReversing an unknown does not resolve it
Understand which rows WHERE keeps

WHERE keeps only TRUE. FALSE and UNKNOWN both leave the row out. For bonuses 1000, 0, NULL, 2500, and NULL, WHERE bonus < 1000 keeps only the zero. WHERE bonus >= 1000 keeps 1000 and 2500. These filters cover three rows, not five. WHERE NOT (bonus < 1000) also keeps only the two known bonuses at least 1000.

The same issue affects <>. With cities Boston, Denver, NULL, Boston, and Austin, WHERE city <> 'Boston' keeps Denver and Austin. Include the unknown city explicitly:

WHERE city <> 'Boston' OR city IS NULL

This keeps three rows. NOT does not automatically bring missing values back.

Watch lists and comparisons

3 IN (1, 2, NULL) is UNKNOWN: the missing member could be 3. For x NOT IN (1, 2, NULL), a matching value fails NOT IN and any unmatched value gives UNKNOWN. In a WHERE clause this fixed list keeps no rows. Remove an unintended NULL from an exclusion list before using it.

For equality that treats two NULLs as equal, use a IS NOT DISTINCT FROM b. Its opposite, a IS DISTINCT FROM b, is TRUE when one value is NULL and the other is not; two NULLs are not distinct. PostgreSQL and SQLite 3.39 onward support these forms. SQLite also has IS and IS NOT; MySQL writes NULL-safe equality as <=>.

NULL sorting defaults differ: SQLite puts NULL first in ascending order; PostgreSQL puts it last. State the placement when it matters: ORDER BY bonus DESC NULLS LAST puts known bonuses in descending order and missing bonuses at the end.

Key Takeaways

  • NULL is missing or unknown, not zero or empty text.
  • Comparisons with NULL produce UNKNOWN; test missingness with IS NULL.
  • WHERE keeps only TRUE, so NOT and <> do not restore unknown rows.
  • AND and OR can resolve an unknown only when the other condition decides the result.
  • A NULL in a NOT IN list prevents that condition from being TRUE.
  • IS NOT DISTINCT FROM treats two NULLs as equal.
  • NULLS FIRST or NULLS LAST makes sorting explicit.

2.2 Replace and create missing values with COALESCE and NULLIF

Missing values sometimes need a fallback, and sometimes should stay missing. The choice is part of what a report means. COALESCE fills a gap; NULLIF turns a matching placeholder into a gap.

Choose the first available value

COALESCE(a, b, ...) checks left to right and returns the first non-NULL argument. It returns NULL if every argument is NULL.

SELECT COALESCE(mobile_phone, home_phone, 'no phone') AS contact
FROM customers;

With no mobile number and home phone '555-0100', the contact is '555-0100'. If neither number is recorded, it is 'no phone'. A known zero or empty string counts as available: COALESCE skips NULL, not values that look blank or false.

For pay, salary + COALESCE(bonus, 0) gives 50000 when salary is 50000 and bonus is NULL. This is appropriate when a missing bonus means none was paid. A missing product rating does not mean a rating of zero; replacing it would invent a poor rating. Leave that number missing or create a text display label.

Keep fallback types compatible

PostgreSQL cannot combine integer rating with text 'not rated' in one numeric result. Convert the known ratings to text first:

COALESCE(CAST(rating AS TEXT), 'not rated')

A rating of 4 displays as text '4'; a NULL displays as 'not rated'. SQLite accepts mixed numeric and text arguments, but then the output can contain different types. Make the intended result type clear when moving a query between databases.

COALESCE is standard SQL and accepts two or more arguments. The common two-argument alternatives are IFNULL in SQLite and MySQL, ISNULL in SQL Server, and NVL in Oracle.

Turn a placeholder into NULL

NULLIF(a, b) returns NULL if the arguments are equal; otherwise it returns the first argument. NULLIF(0, 0) is NULL, while NULLIF(5, 0) is 5.

A useful rate expression is:

orders * 100.0 / NULLIF(visits, 0) AS conversion_pct

With 12 orders and 300 visits, the divisor stays 300 and the rate is 4.0. With zero visits, the divisor becomes NULL and the rate becomes NULL. PostgreSQL avoids a division-by-zero error, and the result correctly marks an undefined rate. Replacing that rate with zero would make a different claim.

Clean gaps before supplying a label
COALESCE(NULLIF(phone, ''), 'unknown')

The inner NULLIF turns an empty string into NULL. The outer COALESCE then labels either an original NULL or an empty string as 'unknown'. A recorded phone number passes through unchanged.

For an optional middle name, include its trailing space inside the fallback:

first_name || ' ' || COALESCE(middle_name || ' ', '') || last_name

A NULL middle name contributes nothing, producing 'Ana Lopez'. A middle name of 'Maria' contributes 'Maria ', producing 'Ana Maria Lopez'.

These expressions change the query result only. They leave the original values stored in the table. A filter such as WHERE COALESCE(bonus, 0) = 0 groups known zero bonuses and missing bonuses together, so use it when those are the two groups you want.

Key Takeaways

  • COALESCE returns the first non-NULL argument, including a known zero or empty text.
  • A fallback must express the meaning of the missing data and have a compatible type.
  • NULLIF returns NULL on equality and otherwise returns its first argument.
  • Use NULLIF around a divisor to make a zero denominator return NULL.
  • Clean a placeholder with NULLIF before applying a display fallback with COALESCE.
3

Clean, combine and take apart text with string functions

Text from a table often needs a usable form: a full name, a consistent email address, or a piece of a code. SQL can join and clean strings, count their characters, and extract the part before or after a separator. You will follow the character positions through each step and check how spaces, letter case, and NULL affect the result. SQLite and PostgreSQL share many functions but differ on several names and behaviors.

3.1 Join and clean text values

SQL treats a text value as a string of characters. Text functions can build a full name or tidy an email address for comparison. Their results appear in the query; stored text stays unchanged.

Write and join text

Text literals use single quotes. To include an apostrophe, double it: 'O''Brien' represents "O'Brien". A pair of single quotes, '', is empty text.

SELECT first_name || ' ' || last_name AS full_name
FROM customers;

The concatenation operator || joins the pieces exactly as written. With 'Ana' and 'Lopez', the result is 'Ana Lopez'. The middle literal supplies the space; concatenation does not insert one automatically.

SQLite, PostgreSQL, and Oracle use ||. MySQL's default || means OR, so use CONCAT there. SQL Server uses + and also supports || from 2025. To join a numeric order ID, SQLite and PostgreSQL convert it to text in 'Order #' || order_id. With SQL Server's +, cast the number first: 'Order #' + CAST(order_id AS VARCHAR(10)).

Choose how to join a missing part

In SQLite and PostgreSQL, a NULL operand makes a || result NULL. Oracle treats that missing part as empty text. CONCAT also differs by database:

DatabaseCONCAT('Ana', NULL, 'Lopez')
PostgreSQL, SQL Server, SQLite 3.44 onward'AnaLopez'
MySQLNULL

Oracle's CONCAT takes exactly two arguments. CONCAT_WS(' ', first_name, middle_name, last_name) places spaces between non-NULL parts, so a missing middle name gives 'Ana Lopez'. SQLite added CONCAT and CONCAT_WS in 3.44. Skipping NULL still does not remove an empty string.

Change case and trim edges

UPPER('boston') gives 'BOSTON'; LOWER produces lowercase. PostgreSQL's INITCAP('ana LOPEZ') gives 'Ana Lopez'. SQLite has no INITCAP, and its built-in case conversion changes A–Z only: UPPER('é') stays 'é' without the ICU extension. PostgreSQL case conversion follows its locale.

TRIM removes spaces at both ends; LTRIM removes them only at the start and RTRIM only at the end. TRIM(' New York ') gives 'New York', keeping the two spaces inside. To remove x from both ends, SQLite writes TRIM('xxBostonxx', 'x'); PostgreSQL writes TRIM(BOTH 'x' FROM 'xxBostonxx').

Replace characters and nest cleaning steps

REPLACE('555-0100', '-', '') gives '5550100'. It replaces every exact occurrence. Letter case matters: replacing 'st' in 'Main St' changes nothing because 'St' has a capital S.

SELECT LOWER(TRIM(email)) AS email_clean
FROM customers;

For ' Ana@Example.COM ', TRIM first produces 'Ana@Example.COM', then LOWER produces 'ana@example.com'. Nesting means the innermost function supplies the input of the next.

Ordinary text equality is case-sensitive by default in SQLite and PostgreSQL. WHERE LOWER(TRIM(email)) = 'ana@example.com' can match differently capitalized input with edge spaces. MySQL's default collation ignores case, and SQLite LIKE ignores A–Z case even though its default = comparison does not.

Key Takeaways

  • Use single quotes for text and double an apostrophe inside a literal.
  • Concatenation joins exact pieces; include separators explicitly.
  • NULL behavior differs between ||, CONCAT, and database dialects.
  • TRIM removes edge spaces, while REPLACE changes matching characters throughout.
  • Nested functions run from the inside out.
  • Case conversion and case-sensitive matching depend on the database and locale.

3.2 Measure and extract parts of text

Text functions locate and cut pieces by character position. SQL starts positions at 1, so the first character is position 1. A found position and a number of characters are different inputs.

Measure and cut a string

LENGTH('Boston') gives 6. Spaces count: LENGTH(' Bo ') gives 4. Empty text has length 0; LENGTH(NULL) is NULL. MySQL's LENGTH counts bytes, so use CHAR_LENGTH for characters there. SQL Server uses LEN, which ignores trailing spaces.

SELECT SUBSTR('Boston', 1, 3) AS beginning,
       SUBSTR('Boston', 4) AS ending;

The results are 'Bos' and 'ton'. The first call takes three characters starting at position 1. The second omits the count and takes everything from position 4 onward. PostgreSQL also writes SUBSTRING('Boston' FROM 4 FOR 3). SQLite accepts SUBSTRING as a name from 3.34. SQL Server uses SUBSTRING(text, start, count); the count is required through 2022 and optional from 2025.

SQLite can count from the right: SUBSTR('Boston', -3) gives 'ton'. PostgreSQL negative starts do not count backward from the end. Use RIGHT('Boston', 3) there; LEFT takes characters from the beginning. SQLite has neither LEFT nor RIGHT.

Find the split point

SQLite's INSTR(text, search) returns the first matching position. PostgreSQL uses STRPOS or POSITION(search IN text). These functions return 0 when no match is found. The search is case-sensitive: INSTR('Ana', 'a') is 3.

For 'ana@example.com', the @ is at position 4. The user name ends just before it; the domain starts just after it:

SUBSTR(email, 1, INSTR(email, '@') - 1) AS user_name,
SUBSTR(email, INSTR(email, '@') + 1) AS domain

The first expression becomes SUBSTR(email, 1, 3) and yields 'ana'. The second starts at 5 and yields 'example.com'. Omitting - 1 includes @ in the user name. Omitting + 1 includes it at the start of the domain. In PostgreSQL, substitute STRPOS for INSTR, or use SPLIT_PART(email, '@', 2) for the domain.

Handle a missing separator

For 'anaexample.com', INSTR returns 0. The domain formula starts at 1 and misleadingly returns the whole string. SQLite's user-name formula gives empty text; PostgreSQL rejects its negative substring length. Guard the extraction:

CASE
  WHEN INSTR(email, '@') > 0
  THEN SUBSTR(email, INSTR(email, '@') + 1)
END

This returns NULL when the separator is absent. The guard checks for a separator, not every rule of a valid email address.

Build short labels and fixed-width codes

SUBSTR(first_name, 1, 1) || '. ' || last_name gives 'A. Lopez' for Ana Lopez. WHERE LENGTH(zip_code) <> 5 finds recorded ZIP strings of the wrong length; NULL lengths produce UNKNOWN and are left out.

PostgreSQL's LPAD('42', 5, '0') gives '00042'; RPAD pads on the right. SQLite can build that code with SUBSTR('00000' || 42, -5). Keeping only five characters also cuts off a longer value, so this fixed-width expression needs a code that fits.

Key Takeaways

  • SQL string positions start at 1; LENGTH includes spaces.
  • SUBSTR takes a start position and an optional character count.
  • INSTR, STRPOS, and POSITION return 0 when a match is absent.
  • Use position minus 1 before a separator and position plus 1 after it.
  • Check for a missing separator before extracting a piece.
  • End extraction and padding have different names in SQLite and PostgreSQL.
4

Work with dates and times

Date work starts with a clear distinction between a calendar day, a time, and a timestamp. From there, SQL can create month labels, calculate deadlines, measure elapsed time, and filter a complete period. You will keep date values separate from display text, account for month ends and birthdays, and compare the SQLite and PostgreSQL expressions that carry out the same task. Boundary cases make these calculations reliable.

4.1 Write, store and read date and time values

A date is easier to calculate with when its storage and spelling are clear. The text '03/04/2026' could mean March 4 or April 3. An ISO date, '2026-03-14', puts year, month, and day in an unambiguous order.

Match the value to its type

PostgreSQL has DATE for a calendar day, TIME for a time of day, and TIMESTAMP for a date and time. Its TIMESTAMP WITH TIME ZONE, also called timestamptz, stores a moment in UTC and displays it in the session's time zone. A plain TIMESTAMP does not attach that zone meaning.

SQLite stores dates and times as ISO text or as numbers, not dedicated date/time types. Numeric forms include Julian day numbers and Unix time. The date functions interpret these values and return a date, time, or numeric measure.

Use fixed-width ISO text such as '2026-03-14', including leading zeros. Add a time using the 24-hour clock: '2026-03-14 09:30:00'. PostgreSQL's typed literal DATE '2026-03-14' explicitly creates a date.

Preserve chronological order

Fixed-width ISO text compares in date order because it starts with the largest part. '2026-03-14' comes before '2026-11-02'. US display text does not have this property: as strings, '11/02/2025' sorts after '03/14/2026' even though its date is earlier.

SQLite's parser needs the supported layout. date('2026-3-14') returns NULL because the month is not two digits. It can also normalize an impossible date: date('2026-02-30') returns '2026-03-02'. A non-NULL result alone is not proof that the input was a valid calendar day. PostgreSQL rejects DATE '2026-02-30'.

Read text in another layout

PostgreSQL and Oracle use TO_DATE('03/14/2026', 'MM/DD/YYYY') to read a US layout. PostgreSQL also reads ISO text with CAST('2026-03-14' AS DATE) or '2026-03-14'::date.

SQLite has no TO_DATE parser. For fixed MM/DD/YYYY text in d, rebuild ISO text:

SUBSTR(d, 7, 4) || '-' ||
SUBSTR(d, 1, 2) || '-' || SUBSTR(d, 4, 2)

The pieces are the year '2026', month '03', and day '14'. The result is '2026-03-14', which SQLite's date functions can read. The positions rely on the input actually using the fixed layout.

Get the current date and time

CURRENT_DATE and CURRENT_TIMESTAMP work in SQLite and PostgreSQL. SQLite returns UTC text; date('now', 'localtime') gives the local date. Near midnight, UTC and the local calendar date can differ.

PostgreSQL uses the session's time zone. CURRENT_TIMESTAMP and NOW() reflect the start of the current transaction, so waiting inside that transaction does not advance their value. MySQL offers NOW() and CURDATE(); SQL Server has GETDATE() and SYSDATETIME(); Oracle has SYSDATE, which includes a time, and CURRENT_DATE.

To check a due-date calculation, first use a fixed anchor such as DATE '2026-10-21' in PostgreSQL or '2026-10-21' in SQLite. You can then compare the result with a calendar without the answer changing the next day.

Key Takeaways

  • DATE is a calendar day; TIMESTAMP adds a time of day.
  • Use fixed-width YYYY-MM-DD for portable, unambiguous date text.
  • ISO text sorts chronologically when its layout and time basis are consistent.
  • A SQLite date result can normalize an impossible input instead of rejecting it.
  • Current-date functions use different time-zone and transaction rules.

4.2 Extract and format parts of dates

Extract a date part when you need a year or weekday to calculate with. Format a date when you need text for a reader. These operations can produce different types even when the displayed characters look alike.

Extract a year, month, or weekday

PostgreSQL uses standard EXTRACT(part FROM value). For DATE '2026-03-14', extracting YEAR gives 2026 and MONTH gives 3, both as numbers. MySQL and Oracle also support EXTRACT.

SQLite uses strftime(format, value), which returns text:

SQLite patternResult for '2026-03-14'
'%Y''2026'
'%m''03'
'%d''14'
'%w''6'

SQLite's %w and PostgreSQL's DOW number Sunday as 0 through Saturday as 6. March 14, 2026, is Saturday. PostgreSQL ISODOW instead numbers Monday as 1 through Sunday as 7. A SQLite weekend filter is WHERE strftime('%w', order_date) IN ('0', '6').

Compare the right types

strftime('%m', order_date) = 3 is false for a March date in SQLite: the function returns text '03', not number 3. Compare it with = '03', or convert first:

CAST(strftime('%m', order_date) AS INTEGER) = 3

This is also useful in the quarter expression (CAST(strftime('%m', d) AS INTEGER) + 2) / 3. Integer division gives quarter 1 for March and quarter 4 for November. PostgreSQL has EXTRACT(QUARTER FROM d) directly.

Identify a month as a whole period

strftime('%Y-%m', order_date) creates a SQLite label such as '2026-03'. date(order_date, 'start of month') creates '2026-03-01'. PostgreSQL can use TO_CHAR(order_date, 'YYYY-MM') for a label or DATE_TRUNC('month', order_date) for the first moment of that month.

Keep the year in a period label. A bare month value of 3 would combine March from different years. The first-of-month value can remain the calculation and sorting value while a separate expression supplies the display.

Format at the output stage

In PostgreSQL, TO_CHAR(DATE '2026-03-14', 'MM/DD/YYYY') gives '03/14/2026', and the pattern 'Mon DD, YYYY' gives 'Mar 14, 2026'. Oracle uses these patterns too. SQLite's US layout is strftime('%m/%d/%Y', '2026-03-14').

Pattern letters belong to their database. PostgreSQL MM means month and MI means minute. SQLite %m means month and %M means minute; changing that capital changes the information requested.

SELECT TO_CHAR(order_date, 'MM/DD/YYYY') AS order_day
FROM orders
ORDER BY order_date;

Here PostgreSQL displays readable text but sorts the actual dates. Sorting order_day would sort the display strings by month first and could put dates from different years out of order.

Key Takeaways

  • PostgreSQL EXTRACT returns numeric parts; SQLite strftime returns text.
  • Compare a zero-padded SQLite month with text or cast it to a number.
  • Weekday numbering depends on the requested convention.
  • Keep the year in month labels and format only for display.

4.3 Calculate with dates and filter date ranges

Calendar arithmetic must account for month lengths, time components, and the kind of answer needed. A number of days, a calendar month, and a whole year of age are different measures.

Add days and calendar months

PostgreSQL writes DATE '2026-03-14' + 30; SQLite writes date('2026-03-14', '+30 days'). Both reach April 13: 17 days finish March and 13 more reach April 13. SQLite's direct expression '2026-03-14' + 30 instead gives 2056, because arithmetic reads the leading year as a number.

Month ends need an explicit policy. PostgreSQL's DATE '2026-01-31' + INTERVAL '1 month' gives February 28 at midnight, using the last available day. SQLite's date('2026-01-31', '+1 month') rolls excess days into March and gives '2026-03-03'. From SQLite 3.46, add 'floor' after the shift to choose '2026-02-28'. Oracle ADD_MONTHS also moves to the shorter month's last day.

Measure elapsed time

PostgreSQL DATE subtraction returns whole days: DATE '2026-04-13' - DATE '2026-03-14' is 30. SQLite subtracts numeric day values: julianday('2026-04-13') - julianday('2026-03-14') is 30.0. SQL Server uses DATEDIFF(day, start, end); MySQL uses DATEDIFF(end, start).

For timestamps from March 14 at 09:30 to 18:00, PostgreSQL subtraction gives an interval of 08:30:00; EXTRACT(EPOCH FROM interval) / 3600 expresses it as 8.5 hours. SQLite 3.38 onward can write:

(unixepoch('2026-03-14 18:00') -
 unixepoch('2026-03-14 09:30')) / 3600.0

This gives 8.5. A Julian-day difference multiplied by 24 can give 8.49999999627471 because of floating-point approximation; round the display. A fractional timestamp duration should not be cast to INTEGER before the desired unit is calculated.

Count completed birthdays

A person born November 2, 1990, is 35 on October 21, 2026. The year numbers differ by 36, but the birthday has not arrived. PostgreSQL uses EXTRACT(YEAR FROM AGE(DATE '2026-10-21', DATE '1990-11-02')), giving 35.

SQLite subtracts one if this year's birthday is still ahead:

strftime('%Y', '2026-10-21') - strftime('%Y', birth_date)
- (strftime('%m-%d', '2026-10-21') < strftime('%m-%d', birth_date))

The comparison yields 1 when the birthday is ahead and 0 otherwise. Dividing elapsed days by 365 is unreliable: on November 1, 2026, this person has lived 13148 days, which divided by 365 gives 36.02 even though the 36th birthday is tomorrow. SQL Server DATEDIFF counts boundaries: crossing December 31 to January 1 counts as a year boundary, not a completed year of age.

Include every timestamp in a period

BETWEEN includes both endpoints. For DATE columns, March 1 through March 31 covers March. For timestamps, an upper bound of '2026-03-31' means midnight at the start of the last day and misses the rest of it.

WHERE ordered_at >= '2026-03-01'
  AND ordered_at < '2026-04-01'

This half-open range includes the start and excludes the next period's start, covering every March time without choosing a final fraction of a second.

Overdue dates satisfy due_date < CURRENT_DATE. PostgreSQL's last-30-days lower bound is CURRENT_DATE - 30; SQLite's is date('now', '-30 days'). Days until a deadline are due_date - CURRENT_DATE for PostgreSQL DATE values and julianday(due_date) - julianday(date('now')) in SQLite.

Key Takeaways

  • Use date arithmetic functions or typed dates, not arithmetic on SQLite date text.
  • Decide how an added month should handle a day missing from the next month.
  • DATE differences count days; timestamp differences can include fractions.
  • Age counts completed birthdays, not elapsed days divided by 365.
  • A half-open timestamp range includes the period start and excludes the next start.
  • Use a date-only current value for whole-day deadline calculations.
5

Classify rows with CASE expressions

A report often needs a rule that turns data into a label or a fee: a small order, a pending shipment, or a priority rank. CASE expresses that choice inside a query. You will trace which condition wins, place exact boundaries and missing values in the right category, and translate codes with simple CASE. The final report combines classification with arithmetic, text, dates, and fallbacks, checking each output against known rows.

5.1 Sort rows into categories with searched CASE

A CASE expression chooses one result for each row. You can use it to label a price band, calculate a fee, or mark a record for attention.

Read the searched form
CASE
  WHEN total < 50 THEN 'small'
  WHEN total < 200 THEN 'medium'
  ELSE 'large'
END AS size_band

Each WHEN supplies a condition, and THEN supplies the value to return if that condition is TRUE. The database checks from top to bottom and takes the first true condition. END closes the expression; an alias can follow it. If no condition is TRUE and ELSE is absent, the result is NULL.

A total of 35 meets the first condition and becomes 'small'. A total of 50 fails the first but meets the second, becoming 'medium'. The same is true for 199.99. A total of 200 reaches ELSE and becomes 'large'.

Make order and boundaries match the rule

The second branch needs no total >= 50: a value below 50 has already returned from the first branch. Ascending < thresholds work because earlier branches remove the smaller values. Descending >= thresholds can work for the same reason.

Putting WHEN total < 200 first makes a later < 50 branch unreachable. A total of 35 already satisfies < 200, so it stops there. CASE does not choose the most specific condition or check which label sounds best.

Compare the business rule with each endpoint. < 50 places exactly 50 in the medium band; <= 50 places it in the small band. Check 49.99, 50, 199.99, and 200 to see both sides and the boundaries. If conditions are written as explicit intervals, make their endpoints fit without a gap or unintended overlap.

Handle NULL before ordinary ranges

A NULL total makes both comparisons UNKNOWN. In the first expression it therefore reaches ELSE and becomes 'large', which is misleading. Catch missingness first:

CASE
  WHEN total IS NULL THEN 'unknown'
  WHEN total < 50 THEN 'small'
  WHEN total < 200 THEN 'medium'
  ELSE 'large'
END

ELSE means "no condition was true," so it can catch missing inputs as well as known values outside the tested ranges.

Return a consistent kind of value

PostgreSQL needs compatible THEN and ELSE types. CASE WHEN quantity > 0 THEN unit_price ELSE 'n/a' END fails if unit_price is numeric, because 'n/a' cannot become a number. Use NULL for an unavailable numeric result, or cast the price to text if the whole output is a display label. SQLite permits mixed result types, which can complicate later calculations.

A condition can combine columns:

CASE
  WHEN country = 'US' AND subtotal >= 100 THEN 0
  ELSE 7.99
END AS shipping_fee

The returned fee can appear inside subtotal + CASE ... END AS amount_due. A CASE is a value expression, not a command that edits several columns; each result column needs its own expression.

CASE can also guard a row-level division: CASE WHEN visits = 0 THEN NULL ELSE orders * 100.0 / visits END. A zero row selects the NULL branch. orders * 100.0 / NULLIF(visits, 0) expresses the same missing-rate rule more briefly.

Key Takeaways

  • CASE returns the value from the first condition that is TRUE.
  • Order overlapping range conditions so a broad test does not hide a narrower one.
  • Choose < or <= to put an exact boundary in the intended band.
  • Test NULL explicitly before a range when ELSE should not label it.
  • Keep result types compatible and use CASE anywhere a value expression fits.

5.2 Use simple CASE and put CASE to work across a query

The simple CASE form translates one value into another. Searched CASE tests conditions; simple CASE compares the same expression with a series of values.

Translate codes with simple CASE
CASE status
  WHEN 'S' THEN 'Shipped'
  WHEN 'P' THEN 'Pending'
  WHEN 'C' THEN 'Canceled'
  ELSE 'Other'
END AS status_label

The implied tests are status = 'S', then status = 'P', then status = 'C'. A status outside these codes gets 'Other'. The searched form can write the same equality tests explicitly. Choose searched CASE for ranges, LIKE patterns, NULL tests, or conditions across several columns.

CASE region WHEN NULL THEN 'missing' ELSE region END cannot catch a NULL region. Its implied region = NULL is UNKNOWN, so the row reaches ELSE and remains NULL. Write CASE WHEN region IS NULL THEN 'missing' ELSE region END.

Assign a custom sort rank

Alphabetical priority order is high, low, medium. To sort by urgency, translate the labels to numbers:

SELECT ticket_id, priority
FROM tickets
ORDER BY CASE priority
           WHEN 'high' THEN 1
           WHEN 'medium' THEN 2
           WHEN 'low' THEN 3
           ELSE 4
         END;

Ascending order now places high before medium before low, with other or missing values last. The CASE is a sort expression; it need not be a displayed column. Using DESC would reverse the numeric ranking.

Create a numeric flag
CASE WHEN discount_pct > 0 THEN 1 ELSE 0 END AS is_discounted

A positive percentage gets 1; a known zero gets 0. A NULL percentage also gets 0 because > 0 is UNKNOWN and ELSE catches it. This flag therefore means "a positive discount is recorded." It does not preserve the distinction between no discount and an unknown discount. If that distinction is needed, add an IS NULL branch with its own result.

Flags can be selected, sorted, filtered, or included in arithmetic. In a portable WHERE clause, repeat the expression or use its original condition instead of referring to a newly defined output alias.

Recognize related conditional functions

COALESCE(bonus, 0) is a short form of CASE WHEN bonus IS NOT NULL THEN bonus ELSE 0 END. NULLIF(a, b) corresponds to CASE WHEN a = b THEN NULL ELSE a END. Their names communicate these common purposes directly.

SQLite from 3.32 and SQL Server have IIF(condition, true_value, false_value); MySQL has IF. Oracle DECODE translates equality matches, for example DECODE(status, 'S', 'Shipped', 'P', 'Pending', 'Other'). DECODE treats two NULLs as equal, unlike the equality comparisons in simple CASE. Standard CASE works across these systems.

A CASE can contain another CASE, but combined conditions often make a flat expression clearer. A shipping rule can directly say WHEN country = 'US' AND subtotal >= 100 THEN 0, keeping the country and subtotal tests together.

Key Takeaways

  • Simple CASE compares one expression with equality values.
  • Use searched CASE for NULL tests, ranges, and combined conditions.
  • A numeric CASE in ORDER BY creates a custom ranking.
  • An ELSE 0 flag also assigns zero when its condition is UNKNOWN.
  • COALESCE and NULLIF express common special cases of CASE.
  • Oracle DECODE treats two NULLs as equal; simple CASE does not.

5.3 Combine functions into a complete report query

A report can clean names, calculate net totals, identify months, and label shipping speed in one SELECT. Build each expression from its inputs and check the result before combining it with the others.

Work from known rows

An orders table contains these records. Dates are ISO text for the SQLite query below; quantity is an integer and unit_price is numeric.

OrderCustomerQuantityPriceDiscount %OrderedShipped
1001Ana Lopez312.50102026-03-142026-03-17
1002Ben Okafor1240.0002026-03-30NULL
1003Chen Wu124.25NULL2026-04-022026-04-03

Here a missing discount means no discount. Start by retrieving the raw fields. Add one calculated column at a time and use LIMIT 5 while checking a small sample.

Trace a nested calculation
ROUND(quantity * unit_price *
      (1 - COALESCE(discount_pct, 0) / 100.0), 2)

COALESCE provides a known discount first. Dividing by 100.0 preserves its fraction. Subtracting from 1 gives the fraction paid, multiplication gives the net amount, and ROUND sets the final precision. Ana's amount is 33.75, Ben's is 240.0, and Chen's is 51.0. Chen's NULL discount becomes zero; a missing shipping date, by contrast, should stay missing in the day count.

Assemble the SQLite report
SELECT order_id,
       first_name || ' ' || last_name AS customer,
       ROUND(quantity * unit_price *
         (1 - COALESCE(discount_pct, 0) / 100.0), 2) AS net_total,
       strftime('%Y-%m', order_date) AS order_month,
       CAST(julianday(shipped_date) - julianday(order_date)
         AS INTEGER) AS days_to_ship,
       CASE
         WHEN shipped_date IS NULL THEN 'not shipped'
         -- fast = shipped within 2 days
         WHEN julianday(shipped_date) - julianday(order_date) <= 2
           THEN 'fast'
         ELSE 'standard'
       END AS shipping
FROM orders
ORDER BY order_id;

Each expression has a clear output alias. The numeric day difference is cast to INTEGER because these are date-only values. The IS NULL branch comes before the speed comparison, so an unshipped order receives a useful label.

The output is:

OrderCustomerNet totalMonthDaysShipping
1001Ana Lopez33.752026-033standard
1002Ben Okafor240.02026-03NULLnot shipped
1003Chen Wu51.02026-041fast
Adapt types and query clauses

For PostgreSQL DATE columns, use TO_CHAR(order_date, 'YYYY-MM') for the month and shipped_date - order_date for the day count and CASE comparison. Keep unit_price as exact numeric for two-argument ROUND. The concatenation, discount fallback, and classification rule keep the same meaning.

To filter for rounded totals above 50 in both databases, repeat the full net expression in WHERE. Orders 1002 and 1003 remain. SQLite also permits WHERE net_total > 50, but PostgreSQL cannot see that new alias at the filtering stage. ORDER BY net_total DESC works in both.

When moving a report, check the types and function names in each expression. A text function must receive text, a date calculation must receive a supported date value, and a numeric fallback must express the intended rule. Keep each output on its own line; a short comment can define a business threshold without obscuring the SQL.

Key Takeaways

  • Build and check one calculated column at a time on a small sample.
  • Nested functions pass their results outward; keep each input type suitable.
  • A missing discount and a missing shipping date can need different handling.
  • Use dialect-appropriate date expressions while keeping the report rules consistent.
  • Filter with the full expression and sort with its output alias when appropriate.

SQL CASE and Functions examples

5 Walkthroughs

1

Build and check a discounted line total

16 steps
Calculate an amount, name it, preserve its fraction, and check the types behind the result.
  1. 1
    IntroductionTurn one row into a useful amount

    An order line records eight items at 7.50 each with a 20 percent discount. The report needs a net amount, but the table stores only the inputs. You will build the expression in stages, name its output, and check what integer division does to the discount.

    The examples use SQLite and PostgreSQL. Where their type rules differ, the database name identifies the result.

  2. 2
    ExplanationStart with an expression

    An expression combines values to produce another value. Here quantity * unit_price uses two columns from the current row.

    SELECT quantity * unit_price AS gross_total
    FROM order_lines;
    

    gross_total is the output alias. For the sample row, the new value is 60.0 in SQLite. Each other row supplies its own inputs; the query does not write these totals back into the table.

  3. 3
    ConnectionOne value still belongs to each row

    A single-row function returns one value for each input row. ROUND(unit_price, 0) changes the price displayed for a row, while UPPER(item_name) changes its displayed text.

    These functions do not combine the order lines. An aggregate such as SUM does combine values across a group, so it answers a different kind of question. A calculation in SELECT can keep the detail of individual orders.

  4. 4
    TipName the result clearly

    AS net_total is a simple, readable alias. Use double quotes when a name has a space, as in AS "Net Total". Single quotes identify text values instead.

    Keep a comma between output expressions. SELECT quantity unit_price returns one column of quantities headed unit_price: without a comma, the second name becomes an alias. That query can succeed while showing fewer columns than intended.

  5. 5
    ChallengePlace the alias in the query

    The table has only quantity and unit_price. A query defines quantity * unit_price AS gross_total. Can a PostgreSQL WHERE filter use gross_total > 50, and can ORDER BY use gross_total DESC?

    ---reveal---

    WHERE cannot see that new alias because it logically precedes SELECT. Repeat quantity * unit_price > 50. ORDER BY follows SELECT and can use gross_total DESC. SQLite allows the WHERE alias as an extension, but PostgreSQL does not.

  6. 6
    ExampleTurn a discount into the fraction paid

    The gross amount is 8 * 7.50, or 60. A 20 percent discount leaves 80 percent to pay. Express that as 1 - discount_pct / 100.0.

    quantity * unit_price * (1 - discount_pct / 100.0)
    

    The division gives 0.2; the parentheses give 0.8; multiplying gives a net amount of 48.0. Parentheses keep the subtraction together as a multiplier.

  7. 7
    Key InsightA later decimal cannot repair earlier division

    Suppose discount_pct is an integer and the divisor is written as 100. SQLite and PostgreSQL compute 20 / 100 as integer division and get 0. The multiplier becomes 1, so the net wrongly stays 60.

    The decimal price elsewhere does not rescue that inner division. Use 100.0 in the division itself. Casting the completed quotient afterward changes its type but cannot restore the missing fraction.

  8. 8
    ChallengeFind the calculation that lost its fraction

    A SQLite row has integer completed = 9 and integer assigned = 12. What does completed / assigned * 100.0 return, and how should the expression calculate the percentage instead?

    ---reveal---

    It returns 0.0: integer 9 / 12 becomes 0 first. Write completed * 100.0 / assigned, which gives 75.0. The conversion must occur before division, not just before the final display.

  9. 9
    ExplanationChoose the whole-number rule

    ROUND chooses a nearby rounded value. FLOOR goes down to a whole number, CEIL goes up, and TRUNC cuts the fraction toward zero.

    For -6.4, FLOOR gives -7, while CEIL and TRUNC give -6. The direction matters for negative inputs. PostgreSQL provides these functions; SQLite CEIL, FLOOR, and TRUNC require a build with math functions. ABS, which gives distance from zero, is built into SQLite.

  10. 10
    TipCheck the number type before rounding

    In PostgreSQL, two-argument ROUND takes numeric, not double precision. For a floating-point column, write ROUND(CAST(amount AS numeric), 2). SQLite accepts its own numeric ROUND form.

    Approximate floating-point values can produce surprising ties. For exact amounts, PostgreSQL NUMERIC/DECIMAL stores exact decimals; SQLite can keep whole cents as integers. Round the displayed result after the calculation.

  11. 11
    ExplanationMake a type change explicit

    CAST(expression AS type) requests a conversion. The text '28' can become integer 28 with CAST('28' AS INTEGER). The number can become text with CAST(28 AS TEXT).

    For fractional division, SQLite needs REAL for a reliable noninteger operand. CAST(7 AS NUMERIC) can still be integer 7 there. PostgreSQL NUMERIC gives an exact decimal type. A CAST of NULL remains NULL.

  12. 12
    ExampleKeep identifiers separate from quantities

    Suppose a label stores the ZIP code '00421'. Converting it to an integer gives 421, losing the leading zeros. It is an identifier, so arithmetic is not the task.

    A text column that genuinely holds quantities may need conversion for numeric sorting. Text '12', '3', and '80' sorts in that order; numeric sorting gives 3, 12, 80. Choose the conversion from the meaning of the column.

  13. 13
    ChallengeInterpret a cast without trusting it as validation

    SQLite runs CAST('31units' AS INTEGER) successfully. What value does it return, and does success show that the original text was a valid integer?

    ---reveal---

    It returns 31 by reading the leading numeric portion. Success does not validate the full string. PostgreSQL rejects the same text as an integer. SQLite can also turn text with no numeric prefix into zero, masking a bad input.

  14. 14
    Key InsightAn integer cast is not the same operation everywhere

    For numeric 4.7, SQLite CAST(... AS INTEGER) truncates to 4, while PostgreSQL rounds to 5. If a report means "whole units not exceeding this value," express that with FLOOR before the cast.

    An implicit conversion is also database-specific. PostgreSQL can infer a number from a quoted literal in arithmetic, but a TEXT column needs an explicit cast. The visible characters alone do not establish the operand type.

  15. 15
    ChallengeCheck the complete net expression

    For eight items at 7.50 with integer discount_pct = 20, a report writes ROUND(quantity * unit_price * (1 - discount_pct / 100.0), 2) AS net_total. What is the result, and can that alias sort the output?

    ---reveal---

    The gross is 60, the fraction paid is 0.8, and the net is 48.0 in SQLite. ORDER BY net_total is valid in both SQLite and PostgreSQL because sorting can see the SELECT alias.

  16. 16
    RecapRead the inputs, calculation, and output name

    A calculated amount has a path: the row supplies values, arithmetic and functions produce the result, and an alias names it. Check the operation order and the operand types before accepting a displayed number.

    When a result is unexpected, inspect an inner division or cast first. To filter a computed value across databases, repeat its expression in WHERE; to sort it, use the output alias in ORDER BY.

2

Follow missing data through a query

12 steps
Trace UNKNOWN conditions, then choose and combine COALESCE and NULLIF.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionA missing amount changes more than a cell
  2. 2
    ExplanationNULL is an unknown input
  3. 3
    ExampleUse a test that answers missingness
  4. 4
    Key InsightWHERE accepts only TRUE
  5. 5
    ChallengeCombine a known condition with an unknown one
  6. 6
    TipDo not leave NULL in an exclusion list
  7. 7
    ExplanationChoose a fallback from the meaning
  8. 8
    ExampleKeep the display type consistent
  9. 9
    ExampleTurn a zero denominator into a gap
  10. 10
    ChallengeClean empty text before the label
  11. 11
    ChallengeSeparate a display choice from a filter choice
  12. 12
    RecapKeep the meaning of a gap visible
3

Clean a name and extract an email domain

14 steps
Join text, nest cleaning functions, and trace character positions around a separator.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionMake text usable without losing its pieces
  2. 2
    ExampleSupply the space between names
  3. 3
    ExplanationDecide what a missing name part does
  4. 4
    ExampleClean the email from inside outward
  5. 5
    ChallengeChoose between trimming and replacing
  6. 6
    TipCheck character and dialect rules
  7. 7
    ExplanationCount from one
  8. 8
    ExampleLocate the separator
  9. 9
    ExampleTake the characters before and after @
  10. 10
    ChallengeFind the off-by-one result
  11. 11
    Key InsightA zero search result needs its own path
  12. 12
    TipTake endings and pad codes in the right dialect
  13. 13
    ChallengeCombine cleaning with extraction
  14. 14
    RecapTrace the exact characters
4

Build date labels and reliable period filters

18 steps
Read date values, format them for display, and calculate deadlines, durations, and complete periods.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionSeparate the calendar value from its label
  2. 2
    ExplanationChoose a clear date representation
  3. 3
    ExampleRead a fixed local layout
  4. 4
    ChallengeExplain which text sorts by date
  5. 5
    TipCheck parsing and the current time basis
  6. 6
    ExampleExtract the month with the right return type
  7. 7
    ExplanationKeep the year in the month label
  8. 8
    ExampleCreate a display without changing the sort
  9. 9
    ChallengeFind the correct quarter
  10. 10
    ConnectionChoose the weekday numbering convention
  11. 11
    ExampleAdd ten days to the booking
  12. 12
    Key InsightOne month is not a fixed number of days
  13. 13
    ExampleMeasure days and hours in suitable units
  14. 14
    ChallengeInclude the last afternoon of the month
  15. 15
    ExplanationCount birthdays that have actually arrived
  16. 16
    TipRead relative dates as moving boundaries
  17. 17
    ChallengeCombine a deadline and a month selection
  18. 18
    RecapCheck type, calendar rule, and endpoints
5

Label orders and build the final report

17 steps
Choose CASE branches, translate priorities, and combine several calculated outputs.

Save this lesson to follow every Walkthrough step by step.

  1. 1
    IntroductionGive each order a useful label
  2. 2
    ExplanationChoose the first true condition
  3. 3
    ExampleTrace the exact cutoff
  4. 4
    Key InsightELSE can also catch an unknown amount
  5. 5
    ChallengeRepair a hidden small band
  6. 6
    TipReturn compatible results
  7. 7
    ExampleTranslate one status value
  8. 8
    ChallengeWhy does WHEN NULL miss the row?
  9. 9
    ExampleSort priority by rank
  10. 10
    Key InsightA flag can merge known and unknown cases
  11. 11
    ExampleStart the report with known inputs
  12. 12
    ExampleCalculate the net amount first
  13. 13
    ExampleAdd dates with a separate missing rule
  14. 14
    ChallengeCheck a second row across both rules
  15. 15
    ConnectionTranslate the date expressions to PostgreSQL
  16. 16
    ChallengeFilter the amount that the report actually shows
  17. 17
    RecapCheck the report as a set of explicit choices

SQL CASE and Functions practice questions

40 Practice Questions in 4 sets

Set 1: Clean text and work with calendar values10 Practice Questions

Question 1 of 10

What does LOWER(TRIM(' RAE@Mail.COM ')) return in SQLite?
  1. ' rae@mail.com '
  2. 'RAE@Mail.COM' (case kept)
  3. 'rae@mail.com'
  4. 'rae@mail.com '
Show answerHide answer
Answer: C
TRIM first removes the spaces at both ends, then LOWER changes the ASCII letters to lowercase. The result is the cleaned text 'rae@mail.com'. Keeping the trailing space would mean that only the left edge had been trimmed; ordinary TRIM handles both edges and does not need a separate RTRIM.

Question 2 of 10

A SQLite 3.44+ row has first_name = 'Mira', middle_name = NULL, and last_name = 'Shah'. What do first_name || ' ' || middle_name || ' ' || last_name and CONCAT_WS(' ', first_name, middle_name, last_name) return, in that order?
  1. NULL; 'Mira Shah'
  2. 'Mira Shah'; 'Mira Shah'
  3. NULL; NULL
  4. 'Mira Shah'; 'Mira Shah'
Show answerHide answer
Answer: A
The || expression propagates its NULL middle-name input, making the whole name NULL. CONCAT_WS skips the missing part and puts one separator between Mira and Shah. Two spaces would result from retaining an empty middle-name position, but the input here is NULL and CONCAT_WS skips it.

Question 3 of 10

In SQLite, what does SUBSTR('Seattle', 3, 3) return?
  1. 'attl'
  2. 'tle'
  3. 'eat'
  4. 'att'
Show answerHide answer
Answer: D
SQL positions begin at 1, so position 3 is the first t in Seattle. Taking three characters from there gives 'att'. The fragment 'eat' starts at position 2 and would follow from treating the supplied starting position as one character earlier than it actually is.

Question 4 of 10

In PostgreSQL, a TEXT column email may contain NULL or text with no @. For text containing @, return everything after its first @; otherwise return NULL. What expression fits?
  1. CASE WHEN STRPOS(email, '@') > 0 THEN SUBSTR(email, STRPOS(email, '@')) END
  2. CASE WHEN STRPOS(email, '@') > 0 THEN SUBSTR(email, STRPOS(email, '@') + 1) END
  3. CASE WHEN STRPOS(email, '@') >= 0 THEN SUBSTR(email, STRPOS(email, '@') + 1) END
  4. CASE WHEN email IS NOT NULL THEN SUBSTR(email, STRPOS(email, '@') + 1) END
Show answerHide answer
Answer: B
A positive STRPOS result establishes that a separator was found; adding 1 starts after it. With no match, the WHEN is false, and with NULL it is UNKNOWN, so both return the implicit NULL. Testing >= 0 also accepts a missing separator, whose position is zero, and would return the whole malformed string.

Question 5 of 10

What ISO date text represents November 6, 2027?
  1. '2027-11-06'
  2. '11/06/2027'
  3. '2027-06-11'
  4. '2027-11-6'
Show answerHide answer
Answer: A
ISO date text uses year, month, and day, with a two-digit month and day. November is month 11 and the sixth day is 06, so the result is '2027-11-06'. The reversed ending 06-11 represents June 11, while a one-digit final day does not follow the fixed-width layout.

Question 6 of 10

What does current SQLite return for date('2027-04-31')?
  1. '2027-04-30'
  2. An invalid-date error
  3. '2027-05-01'
  4. NULL
Show answerHide answer
Answer: C
SQLite normalizes the extra April day into May, giving '2027-05-01'. A non-NULL date result therefore does not guarantee that the original day existed in that month. PostgreSQL rejects the equivalent typed DATE literal, but that strict input behavior should not be assumed for SQLite's date function.

Question 7 of 10

In SQLite, d holds '2027-08-19'. What are the results of strftime('%m', d) = 8 and CAST(strftime('%m', d) AS INTEGER) = 8, in that order?
  1. TRUE; TRUE
  2. FALSE; TRUE
  3. FALSE; FALSE
  4. TRUE; FALSE
Show answerHide answer
Answer: B
strftime returns the text '08', which does not equal the number 8 in the first expression. Casting that text to INTEGER produces 8 and makes the second comparison true. The leading zero is part of the returned text; displaying a month as digits does not make the function's result numeric.

Question 8 of 10

A PostgreSQL DATE column event_date spans several years. A report displays dates as MM/DD/YYYY and must keep chronological order. How should it select and sort the column?
  1. SELECT TO_CHAR(event_date, 'MM/DD/YYYY') AS day_label FROM events ORDER BY day_label;
  2. SELECT TO_CHAR(event_date, 'MM/DD/YYYY') AS day_label FROM events ORDER BY EXTRACT(MONTH FROM event_date), EXTRACT(DAY FROM event_date);
  3. SELECT TO_CHAR(event_date, 'MM/DD/YYYY') AS day_label FROM events ORDER BY CAST(TO_CHAR(event_date, 'MM/DD/YYYY') AS TEXT);
  4. SELECT TO_CHAR(event_date, 'MM/DD/YYYY') AS day_label FROM events ORDER BY event_date;
Show answerHide answer
Answer: D
TO_CHAR supplies the requested display text, while sorting the original DATE keeps year, month, and day in chronological order. Sorting the display alias starts with the month characters and can interleave years. Sorting only extracted month and day has the same missing-year problem even though those parts are numeric.

Question 9 of 10

A SQLite date d is '2027-04-24'. What does date(d, '+10 days') return?
  1. '2027-05-03'
  2. '2027-04-34'
  3. '2027-05-04'
  4. '2027-05-05'
Show answerHide answer
Answer: C
Six days after April 24 reaches April 30, and four more reaches May 4, so the result is '2027-05-04'. May 3 counts the starting date as one of the ten added days. A date shift measures days after the starting point and carries the remaining days into the next month.

Question 10 of 10

A PostgreSQL TIMESTAMP column has no time zone and records times throughout June 2027. What filter includes every June value and no July value?
  1. logged_at >= '2027-06-01' AND logged_at < '2027-07-01'
  2. logged_at BETWEEN '2027-06-01' AND '2027-06-30'
  3. logged_at >= '2027-06-01' AND logged_at <= '2027-07-01'
  4. logged_at > '2027-06-01' AND logged_at < '2027-07-01'
Show answerHide answer
Answer: A
The inclusive June 1 lower bound keeps the first instant, and the exclusive July 1 upper bound includes every time before the next month. BETWEEN ending on June 30 reaches only midnight at the start of that day. Including July 1 would admit an unwanted boundary value, while excluding June 1 would lose its midnight record.
Set 2: Calculate amounts and handle missing data10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A PostgreSQL table has only units and unit_cost. What SELECT list returns their product with the heading extended_cost?
  1. units + unit_cost AS extended_cost
  2. units * unit_cost AS extended_cost
  3. units * unit_cost, extended_cost
  4. units * 'unit_cost' AS extended_cost
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

The table lines contains only units and price. A query must run in SQLite and PostgreSQL. How should it keep rows with a product above 90 and sort the largest product first?
  1. SELECT units * price AS amount FROM lines WHERE amount > 90 ORDER BY amount DESC;
  2. SELECT units * price AS amount FROM lines WHERE units * price > 90 ORDER BY amount ASC;
  3. SELECT units * price AS amount FROM lines ORDER BY amount DESC WHERE units * price > 90;
  4. SELECT units * price AS amount FROM lines WHERE units * price > 90 ORDER BY amount DESC;
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

What does SELECT 14 + 5 * 3; return in SQLite?
  1. 29
  2. 57
  3. 33
  4. 75
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

In SQLite, old_count and new_count are integers holding 60 and 69. What is the result of (new_count - old_count) / old_count * 100?
  1. 15
  2. 0.15
  3. 0
  4. 9
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A SQLite invoice row has integer units = 5 and discount_pct = 12, with price = 18.0. What result does ROUND(units * price * (1 - discount_pct / 100), 2) produce?
  1. 79.2
  2. 90.0
  3. 89.88
  4. 78.0
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

What does SQLite return for CAST('27boxes' AS INTEGER)?
  1. 27
  2. 0
  3. An invalid-integer error
  4. NULL
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

What condition finds rows with no recorded delivery_date?
  1. delivery_date = NULL
  2. delivery_date <> NULL
  3. delivery_date = ''
  4. delivery_date IS NULL
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A table contains exactly four region values: 'East', 'West', NULL, and 'North'. How many rows satisfy WHERE NOT (region = 'East')?
  1. 3
  2. 2
  3. 1
  4. 4
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

An empty phone string and NULL should both display as 'unlisted' in SQLite. What expression does this while preserving recorded numbers?
  1. NULLIF(COALESCE(phone, ''), 'unlisted')
  2. COALESCE(phone, '', 'unlisted')
  3. COALESCE(NULLIF(phone, ''), 'unlisted')
  4. COALESCE(NULLIF(phone, 'unlisted'), '')
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A PostgreSQL report has integer sales and visits. A rate should keep fractional percentages and stay NULL when visits is zero. It must not invent a zero rate. What expression fits?
  1. COALESCE(sales * 100.0 / NULLIF(visits, 0), 0)
  2. sales / NULLIF(visits, 0) * 100.0
  3. sales * 100.0 / COALESCE(visits, 1)
  4. sales * 100.0 / NULLIF(visits, 0)
Show answerHide answer

Save this lesson to see this answer.

Set 3: Classify rows and diagnose report logic10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

What label does this CASE produce for score = 75: CASE WHEN score < 60 THEN 'review' WHEN score < 80 THEN 'ready' ELSE 'strong' END?
  1. 'review'
  2. 'strong'
  3. NULL
  4. 'ready'
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

Why does CASE WHEN amount < 500 THEN 'regular' WHEN amount < 100 THEN 'small' ELSE 'large' END never return 'small' for non-NULL amounts?
  1. CASE checks the last matching range first.
  2. Every amount below 100 already matches the earlier below-500 test.
  3. The ELSE branch overrides a later matching range.
  4. The second condition is tested only for amounts equal to 500.
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

A numeric balance may be NULL. Required labels are 'unknown' for missing, 'credit' below 0, 'settled' at 0, and 'due' above 0. What CASE meets every rule?
  1. CASE WHEN balance < 0 THEN 'credit' WHEN balance = 0 THEN 'settled' ELSE 'due' END
  2. CASE WHEN balance IS NULL THEN 'unknown' WHEN balance <= 0 THEN 'credit' WHEN balance = 0 THEN 'settled' ELSE 'due' END
  3. CASE WHEN balance IS NULL THEN 'unknown' WHEN balance < 0 THEN 'credit' WHEN balance = 0 THEN 'settled' ELSE 'due' END
  4. CASE balance WHEN NULL THEN 'unknown' WHEN 0 THEN 'settled' ELSE 'due' END
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

A state value 'H' means held and 'R' means released. What does CASE state WHEN 'H' THEN 'held' WHEN 'R' THEN 'released' ELSE 'other' END return for 'R'?
  1. 'released'
  2. 'held'
  3. 'other'
  4. NULL
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

A report must sort 'urgent', 'normal', and 'deferred' in that order, with all other or NULL priorities last. What ORDER BY expression fits?
  1. CASE priority WHEN 'urgent' THEN 1 WHEN 'normal' THEN 2 ELSE 3 END DESC
  2. CASE priority WHEN 'urgent' THEN 1 WHEN 'normal' THEN 2 WHEN 'deferred' THEN 3 ELSE 4 END DESC
  3. CASE priority WHEN 'urgent' THEN 1 WHEN 'normal' THEN 3 WHEN 'deferred' THEN 2 ELSE 4 END ASC
  4. CASE priority WHEN 'urgent' THEN 1 WHEN 'normal' THEN 2 WHEN 'deferred' THEN 3 ELSE 4 END ASC
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A SQLite row has qty = 4, price = 15.0, discount_pct = NULL, and shipped_date = NULL. Missing discount means none. What pair comes from ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) and CASE WHEN shipped_date IS NULL THEN 'open' ELSE 'sent' END?
  1. NULL; 'open'
  2. 0.0; 'open'
  3. 60.0; 'open'
  4. 60.0; 'sent'
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

A SQLite report shows LOWER(TRIM(customer)) AS name and date(due_date, '+7 days') AS reminder_date. Raw customer is ' IVAN ' and due_date is '2027-09-26'. What output pair should a sample check find?
  1. 'ivan'; '2027-10-03'
  2. ' ivan '; '2027-10-03'
  3. 'ivan'; '2027-10-02'
  4. 'IVAN' (case kept); '2027-10-03'
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A PostgreSQL table contains x values 2, 7, NULL, and 11. What are the row counts for WHERE x NOT IN (2, NULL) and WHERE x <> 2 OR x IS NULL, respectively?
  1. 2 and 3
  2. 0 and 3
  3. 0 and 2
  4. 3 and 3
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A person was born on '1998-12-18'. As of '2027-12-17', what is the completed age, and why can subtracting the two year numbers be wrong?
  1. 28; the birthday has not occurred yet in 2027.
  2. 29; a new calendar year completes a year of age.
  3. 28; December births always count one year less.
  4. 29; elapsed days divided by 365 give the age.
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A PostgreSQL INTEGER column rating may be NULL. A display column must show the rating as text or 'unrated'. What expression fits?
  1. CAST(COALESCE(rating, 'unrated') AS TEXT)
  2. NULLIF(CAST(rating AS TEXT), 'unrated')
  3. COALESCE(CAST(rating AS TEXT), 'unrated')
  4. CAST(NULLIF(rating, 0) AS TEXT)
Show answerHide answer

Save this lesson to see this answer.

Set 4: Check complete reports and tricky boundaries10 Practice Questions

Save this lesson to see every answer and practice every set.

Question 1 of 10

A PostgreSQL table has only integer qty, NUMERIC price, and integer discount_pct. Missing discount means none. A report must keep rounded net totals above 75 and sort them highest first. What complete query fits?
  1. SELECT ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) AS net FROM lines WHERE net > 75 ORDER BY net DESC;
  2. SELECT ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) AS net FROM lines WHERE ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) > 75 ORDER BY net DESC;
  3. SELECT ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100), 2) AS net FROM lines WHERE ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100), 2) > 75 ORDER BY net DESC;
  4. SELECT ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) AS net FROM lines WHERE ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) > 75 ORDER BY net ASC;
Show answerHide answer

Save this lesson to see this answer.

Question 2 of 10

A PostgreSQL column reading is DOUBLE PRECISION. How can a report round it to two decimal places without a function-signature error?
  1. ROUND(reading, 2)
  2. CAST(ROUND(reading) AS NUMERIC)
  3. ROUND(CAST(reading AS NUMERIC), 2)
  4. ROUND(CAST(reading AS INTEGER), 2)
Show answerHide answer

Save this lesson to see this answer.

Question 3 of 10

What does COALESCE(NULL, '', 'backup') return in SQLite?
  1. ''
  2. 'backup'
  3. NULL
  4. 'NULL'
Show answerHide answer

Save this lesson to see this answer.

Question 4 of 10

In SQLite, a row has email = 'nora@research.org'. What result does SUBSTR(email, INSTR(email, '@')) produce?
  1. 'research.org'
  2. 'nora'
  3. 'nora@'
  4. '@research.org'
Show answerHide answer

Save this lesson to see this answer.

Question 5 of 10

For '2027-01-31', compare SQLite 3.46+ date(d, '+1 month'), SQLite date(d, '+1 month', 'floor'), and PostgreSQL DATE '2027-01-31' + INTERVAL '1 month'. What calendar dates result, in that order?
  1. February 28; February 28; February 28
  2. March 3; March 3; February 28
  3. March 3; February 28; February 28
  4. February 28; March 3; March 3
Show answerHide answer

Save this lesson to see this answer.

Question 6 of 10

A PostgreSQL INTEGER stock may be NULL. Required numeric flag: NULL for unknown stock, 1 for stock below 5, and 0 otherwise. What expression fits?
  1. CASE WHEN stock < 5 THEN 1 ELSE 0 END
  2. CASE WHEN stock IS NULL THEN NULL WHEN stock < 5 THEN 1 ELSE 0 END
  3. CASE WHEN stock IS NULL THEN NULL WHEN stock <= 5 THEN 1 ELSE 0 END
  4. CASE stock WHEN NULL THEN NULL WHEN 5 THEN 0 ELSE 1 END
Show answerHide answer

Save this lesson to see this answer.

Question 7 of 10

SQLite 3.38+ records a session from '2027-05-08 10:15:00' to '2027-05-08 13:45:00'. What does (unixepoch(end_time) - unixepoch(start_time)) / 3600.0 return?
  1. 3.0
  2. 210.0
  3. 12600.0
  4. 3.5
Show answerHide answer

Save this lesson to see this answer.

Question 8 of 10

A SQLite order has integer qty = 6, price = 8.0, discount_pct = 25, ordered '2027-07-10' and shipped '2027-07-12'. Net uses ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2). A CASE labels NULL shipping as 'open', elapsed days <= 2 as 'fast', otherwise 'standard'. What net and label result?
  1. 36.0; 'fast'
  2. 48.0; 'fast'
  3. 36.0; 'standard'
  4. 12.0; 'fast'
Show answerHide answer

Save this lesson to see this answer.

Question 9 of 10

A SQLite report needs quarter numbers 1 through 4 for any valid date d. How can it calculate the quarter for every month?
  1. CAST(strftime('%m', d) AS INTEGER) / 3 + 1
  2. (CAST(strftime('%m', d) AS INTEGER) + 1) / 3
  3. ROUND(CAST(strftime('%m', d) AS INTEGER) / 3.0)
  4. (CAST(strftime('%m', d) AS INTEGER) + 2) / 3
Show answerHide answer

Save this lesson to see this answer.

Question 10 of 10

A SQLite table has rows A (qty 3, price 22.0, discount_pct NULL), B (qty 4, price 20.0, discount_pct 25), and C (qty 2, price 40.0, discount_pct 10). Missing discount means none. It computes ROUND(qty * price * (1 - COALESCE(discount_pct, 0) / 100.0), 2) AS net, keeps net > 60 using the full expression in WHERE, and orders by net DESC. What is the row order?
  1. B, C, A
  2. C, A
  3. C, A, B
  4. A, C
Show answerHide answer

Save this lesson to see this answer.

Stuck on your own question? Homework Help

Key Terms

61 Key Terms, each with its definition

B

Boundary value
A boundary value sits at a cutoff between categories, so the choice of < or <= determines which category contains it.
At the cutoff 50, total < 50 is false; changing the test to total <= 50 makes it true.

C

Calculated column
A calculated column in a query is an output column whose value is computed for each row, without changing the stored data.
The result can show quantity * unit_price AS line_total even when the table has no line_total column.
Case conversion
Case conversion changes letters to uppercase or lowercase with functions such as UPPER and LOWER, subject to database character rules.
LOWER(TRIM(email)) makes a cleaned ASCII email address consistent for a case-normalized comparison.
CEIL
CEIL is a number function that returns the smallest integer greater than or equal to its input; CEILING is another name.
The calculation CEIL(4.1) gives 5 because four is below the input and five is the next whole number.
Character position
A character position identifies where a character occurs in a string; SQL text positions start at 1, including spaces.
The @ in 'ana@example.com' is at position 4, so the domain begins at position 5.
COALESCE
COALESCE is a standard SQL function that returns the first non-NULL argument, or NULL when all its arguments are NULL.
COALESCE(mobile_phone, home_phone, 'no phone') uses the home number if the mobile number is missing.
Collation
A collation supplies rules for comparing and ordering text, including whether differently capitalized letters compare as equal.
MySQL's default case-insensitive collation can make 'Boston' = 'boston' true when default SQLite does not.
Column alias
A column alias is a name assigned to a query output column, often with AS, so the result has a clear heading.
The query labels its computed amount with AS line_total and can sort it with ORDER BY line_total.
Completed age
A completed age counts whole birthdays reached by a reference date, so a year-number difference may need one year subtracted.
Someone born on November 2, 1990, is still 35 on October 21, 2026, before that year's birthday.
Concatenation
Concatenation joins text pieces end to end, preserving their characters and requiring any desired separator to be supplied.
first_name || ' ' || last_name inserts a space between the two recorded name parts.
Custom sort rank
A custom sort rank assigns numeric positions to values so ORDER BY follows an intended sequence instead of alphabetical order.
A priority CASE maps high to 1, medium to 2, and low to 3, then sorts the result ascending.

D

Data type
A data type describes the kind of value and helps determine which operations, conversions, comparisons, and sorting rules apply.
The text literal '42' and the integer 42 can look alike in a grid while behaving differently in SQL.
DATE
A DATE is a data type for a calendar day in PostgreSQL and standard SQL; it has no time-of-day component.
DATE '2026-03-14' explicitly marks March 14, 2026, as a PostgreSQL date value.

E

ELSE fallback
An ELSE fallback is the CASE result used when no WHEN condition is TRUE, including when conditions are UNKNOWN.
A NULL total can reach ELSE 'large' unless an earlier branch explicitly tests total IS NULL.
Explicit conversion
An explicit conversion is a type change requested by the query, usually with CAST, so the intended operation is clear.
The expression CAST(quantity AS REAL) / 4 changes the SQLite operand before division can lose a fraction.
Expression
An expression combines values, column names, operators, or function calls to produce a value that SQL can use.
In an order query, quantity * unit_price is an expression that produces the line amount for each row.
EXTRACT
EXTRACT is a standard SQL function that takes a named part, such as year or month, from a date/time value as a number.
PostgreSQL EXTRACT(MONTH FROM DATE '2026-03-14') returns the numeric month value 3.

F

Fallback value
A fallback value is the replacement chosen when preferred data is missing; it must fit both the meaning and type of the output.
A report can use zero for an unpaid bonus while leaving an unrecorded rating missing instead of inventing a score.
Floating-point number
A floating-point number is an approximate numeric representation that can introduce small errors when storing decimal fractions.
SQLite can make 0.1 + 0.2 = 0.3 false because the stored approximations do not sum to the same value.
FLOOR
FLOOR is a number function that returns the greatest integer less than or equal to its input, moving toward smaller values.
For a negative input, FLOOR(-4.1) is -5, so it differs from simply cutting off the fraction.
Format pattern
A format pattern specifies which date/time parts to display and how to arrange them, using symbols defined by the database.
SQLite uses '%m/%d/%Y' for a US date label; PostgreSQL TO_CHAR uses 'MM/DD/YYYY'.
Function nesting
Function nesting places one function call inside another, so the inner result becomes an argument of the enclosing call.
In ROUND(COALESCE(amount, 0), 2), the fallback is resolved before the rounding function receives its input.

H

Half-open range
A half-open range includes its starting boundary and excludes its ending boundary, making adjacent timestamp periods fit together.
ordered_at >= '2026-03-01' AND ordered_at < '2026-04-01' includes all March timestamps.

I

Implicit conversion
An implicit conversion is a type change made automatically by the database while it resolves an operation or expression.
SQLite and PostgreSQL convert 42 to text in 'Order #' || 42, producing the label 'Order #42'.
INSTR
INSTR is a SQLite function that returns the first position of a search string within text, or zero when there is no match.
INSTR('ana@example.com', '@') gives 4, while a string with no @ produces zero.
Integer division
Integer division divides whole-number operands and drops the fractional part toward zero in SQLite and PostgreSQL.
A query using 7 / 2 produces 3; making the second operand 2.0 keeps the fraction and produces 3.5.
Interval
An interval is a PostgreSQL value representing a time span, such as one calendar month or the difference between timestamps.
Adding INTERVAL '1 month' to a PostgreSQL DATE returns a timestamp and follows its month-end rule.
IS NULL
IS NULL is a missing-value test that returns TRUE when its operand is NULL and FALSE when a value is present.
WHERE phone IS NULL retrieves customers with no recorded phone without comparing a value to NULL using equality.
ISO date format
The ISO date format YYYY-MM-DD places fixed-width year, month, and day in descending size order and avoids local layout ambiguity.
The text '2026-03-14' has leading zeros and sorts before '2026-11-02' in date order.

J

Julian day number
A Julian day number represents a date and time as a numeric day count; subtracting two values measures elapsed days.
SQLite uses julianday(end_date) - julianday(start_date) instead of subtracting ISO date strings directly.

L

LENGTH
LENGTH counts characters, including spaces, for text in SQLite and PostgreSQL; empty text has length zero and NULL returns NULL.
LENGTH(' Bo ') gives 4 because both edge spaces count as well as the two letters.
Literal
A literal is a value written directly in SQL, such as a number or quoted text, instead of read from a column.
The space in first_name || ' ' || last_name is a text literal supplied by the query.
Logical query order
The logical query order describes how clauses build a result: FROM chooses rows, WHERE filters, SELECT computes, and ORDER BY sorts.
An output alias is available to ORDER BY, but a portable WHERE condition repeats the calculated expression behind that alias.

M

Modulo
Modulo returns the remainder from division and is written with % in SQLite and PostgreSQL or MOD in several dialects.
For 135 minutes, 135 % 60 gives the 15 minutes remaining after two complete hours.
Month label
A month label is a text representation of a year and month that identifies one reporting period without merging years.
The label '2026-03' keeps March 2026 separate from March 2025 in a report display.

N

NULL
NULL is a marker for a missing or unknown value, distinct from zero and, in SQLite and PostgreSQL, an empty string.
A missing bonus makes salary + bonus unknown even though the employee's salary is recorded.
NULL-safe comparison
A NULL-safe comparison gives a definite comparison result even for missing inputs and can treat two NULL values as equal.
NULL IS NOT DISTINCT FROM NULL is TRUE in PostgreSQL and in SQLite from version 3.39.
NULLIF
NULLIF is a standard SQL function that returns NULL when its two arguments compare equal and otherwise returns the first argument.
NULLIF(visits, 0) keeps positive visit counts but turns zero into a missing divisor for a rate.
Numeric flag
A numeric flag is an output such as 1 or 0 that records whether a specified condition was true for a row.
CASE WHEN discount_pct > 0 THEN 1 ELSE 0 END assigns zero to missing as well as nonpositive discounts.
Numeric text
Numeric text is a string containing number characters; its text form affects sorting until it is converted for numeric use.
The strings '10', '100', and '9' appear in that ascending text order, which differs from numeric order.

O

Operator precedence
Operator precedence is the ranking that determines which operations happen first when an expression has no overriding parentheses.
SQL multiplies before adding in 10 + 3 * 2, so the result is 16 rather than 26.

P

Padding
Padding adds fill characters to reach a chosen string width; a fixed-width padding operation can also cut an overlong value.
PostgreSQL LPAD('42', 5, '0') returns '00042', giving a short code five characters.
Placeholder
A placeholder is a recorded value used to stand for missing information, such as empty text, which may need conversion to NULL.
NULLIF(phone, '') turns an empty phone string into NULL before a display label is supplied.

R

REPLACE
REPLACE is a string function that substitutes every exact occurrence of one text fragment with another in the returned value.
REPLACE('555-0100', '-', '') removes the hyphen and displays '5550100' without editing the table.
Report query
A report query selects and presents data for a purpose, often combining calculated amounts, cleaned labels, dates, and classifications.
An order report can show a net total beside an order month and a CASE label for shipping speed.
ROUND
ROUND is a number function that chooses the nearest value at a requested decimal precision, with tie rules that depend on type.
The report uses ROUND((price - cost) / price * 100, 1) to display a margin to one decimal place.

S

Sample check
A sample check compares calculated outputs with manually worked results for a few known rows before trusting the full report.
Adding LIMIT 5 while building the report makes a missing space or integer-division error easier to locate.
Searched CASE
A searched CASE tests conditions in written order and returns the result of the first TRUE condition for the current row.
CASE WHEN total < 50 THEN 'small' ELSE 'large' END chooses a label from the row's amount.
Simple CASE
A simple CASE compares one expression with a sequence of equality values and returns the result for the first matching value.
CASE status WHEN 'S' THEN 'Shipped' ELSE 'Other' END translates a stored status code.
Single-row function
A single-row function returns one value for each input row; it is also called a scalar function and does not group rows.
Using UPPER(city) in SELECT changes the displayed city text for each customer returned by the query.
strftime
strftime is a SQLite date/time function that returns text selected by a format pattern, including zero-padded date parts.
strftime('%m', '2026-03-14') returns '03', which should be compared with text or cast before numeric use.
String
A string is a text value made of characters, including letters, digits, punctuation, and spaces, written as a single quoted literal in SQL.
The string ' New York ' includes edge spaces and two internal spaces that affect its length.
Substring
A substring is a consecutive piece of a string, extracted using a starting position and, when needed, a character count.
SUBSTR('Boston', 1, 3) returns 'Bos', taking three characters beginning at the first position.

T

Three-valued logic
Three-valued logic combines TRUE, FALSE, and UNKNOWN in SQL conditions, so negation does not always produce a known result.
FALSE AND UNKNOWN is FALSE, but TRUE AND UNKNOWN stays UNKNOWN because the second requirement is unresolved.
TIMESTAMP
A TIMESTAMP holds a date and time of day, allowing a query to distinguish moments within the same calendar day.
A record at '2026-03-31 14:05:00' is later than midnight at the start of March 31.
TRIM
TRIM is a string function that removes spaces at both ends by default while leaving spaces within the text unchanged.
After TRIM(' New York '), the two spaces between the words remain in the returned text.
Type affinity
Type affinity is SQLite's preference for how a column stores values; an ordinary column can still contain values of different types.
A SQLite NUMERIC declaration can keep 7 as an integer, so it does not guarantee decimal division.

U

Unix time
Unix time measures time in seconds from a fixed UTC origin; differences between Unix times can be converted into elapsed hours.
SQLite subtracts two unixepoch results and divides by 3600.0 to keep fractional hours in a duration.
UNKNOWN
UNKNOWN is the third result of a SQL condition, produced when missing information prevents a definite true or false comparison.
The condition bonus = NULL is UNKNOWN, so it cannot identify a missing bonus in a WHERE filter.
UTC
UTC is the common time reference SQLite uses for CURRENT_DATE and CURRENT_TIMESTAMP, which can differ from a local calendar date.
A report near local midnight can show a different day when it uses SQLite CURRENT_DATE instead of localtime.

W

WHEN branch
A WHEN branch is one condition-result choice in CASE, where THEN supplies the value selected if that condition is the first true one.
Putting a broad < 200 branch first can prevent a later < 50 branch from ever being selected.

About this lesson

By StudeliReport a mistake

Make your own Study Materials

Turn your notes, slides and documents into Study Guides, Practice Questions, Flashcards and 6 more tools.

Get Started