Skip to content
Web and Mobile Development

SQL interview questions and answers

20 questions, 1 with code. About 1 h 24 min to read them all.

By TestLab · Updated

Plan for an interview
20 questions

Track what you know and what to revise.

Sign in to track progress

You can open a few full answers without signing in. Sign in to read all

1

How are SQL statements commonly classified, and what is the purpose of each category?

SQL commands are commonly grouped into DDL, DML, DQL, DCL, and TCL based on their purpose. The categories are a practical classification convention rather than a universal formal division, so database vendors may present them differently.

Read full answer

Applies toSQL:2023 (ISO/IEC 9075:2023); command availability varies by database

SQL Command Categories

Category Purpose Common commands
DDL — Data Definition Language Defines and changes database objects and their structure CREATE, ALTER, DROP, TRUNCATE
DML — Data Manipulation Language Changes stored data INSERT, UPDATE, DELETE, MERGE
DQL — Data Query Language Retrieves data SELECT
DCL — Data Control Language Manages permissions and access rights GRANT, REVOKE
TCL — Transaction Control Language Controls transactions and their outcome COMMIT, ROLLBACK, SAVEPOINT

Data Definition Language

DDL establishes the structure in which data is stored.

  • CREATE TABLE defines a table, its columns, constraints, and related objects.

  • ALTER TABLE modifies an existing table, such as adding a column or changing a constraint.

  • DROP TABLE permanently removes a table and its structure.

  • TRUNCATE TABLE removes many rows efficiently while normally preserving the table structure.

  • CREATE INDEX, CREATE VIEW, and CREATE SCHEMA define other kinds of database objects.

DDL operations usually cause significant metadata changes and may require elevated privileges. Exact behavior, especially for DROP and transactional DDL, depends on the database.

Data Manipulation and Query Languages

DML modifies existing rows:

  • INSERT adds new rows.

  • UPDATE changes values in existing rows.

  • DELETE removes rows.

  • MERGE conditionally inserts, updates, or deletes rows, commonly from a source relation.

DQL retrieves information through SELECT. It is often taught separately from DML even though some classifications include querying within DML.

A query can filter rows with WHERE, group results with GROUP BY, filter groups with HAVING, order output with ORDER BY, and limit results with FETCH FIRST or OFFSET. Window functions such as ROW_NUMBER() and SUM() OVER perform calculations across related rows without collapsing them into groups.

SELECT without a WHERE clause does not necessarily return every row, and aggregate functions such as SUM() and COUNT() ignore NULL values unless the query explicitly handles them.

Access Control and Transaction Control

DCL controls who can access database objects or perform operations:

  • GRANT gives a privilege to a user or role.

  • REVOKE removes a previously granted privilege.

  • CREATE ROLE and DROP ROLE manage roles in databases that support them.

TCL controls transaction boundaries and outcomes:

  • COMMIT makes the transaction’s changes permanent.

  • ROLLBACK undoes the transaction’s uncommitted changes.

  • SAVEPOINT creates a named point to which a transaction can be rolled back partially.

Isolation is configured separately using settings such as READ COMMITTED, REPEATABLE READ, or SERIALIZABLE. PostgreSQL supports all four standard SQL isolation levels, while MySQL and SQL Server have vendor-specific restrictions or differences, particularly for READ UNCOMMITTED and SERIALIZABLE.

Vendor-Specific and Utility Commands

Databases also provide commands outside these five core categories:

  • Data utilities: ANALYZE, VACUUM, OPTIMIZE, and REINDEX maintain or optimize stored data and indexes.

  • Execution and diagnostics: EXPLAIN shows a query plan in PostgreSQL, MySQL, and SQL Server, though each has different options and output.

  • Procedural and session control: Commands for variables, cursors, procedures, sessions, and connection settings vary substantially between products.

For example, PostgreSQL uses VACUUM for routine table maintenance, MySQL commonly uses OPTIMIZE TABLE, and SQL Server uses ALTER INDEX ... REBUILD. These are not interchangeable even when they address a similar maintenance goal.

2

What does a JOIN do in SQL, and which JOIN types are available?

A JOIN combines rows from two or more tables or views according to a specified relationship. The resulting rows are determined by a join condition, usually written with ON or USING, and a row may appear multiple times when it matches multiple rows in the other relation.

Read full answer

Applies toSQL:2023; PostgreSQL 18, MySQL 8.4 LTS, SQL Server 2022, Oracle Database 23ai, and SQLite 3.50.1

How a JOIN Works

A join generally operates on two input relations:

  • The left or first relation supplies the left-hand rows.

  • The right or second relation supplies the right-hand rows.

  • The join condition evaluates the relationship between the two sides.

  • An inner join excludes rows without matches, while an outer join retains them and fills unmatched columns with NULL.

For example, joining orders to customers on customer_id connects each order with its customer. If one customer has several orders, that customer’s information appears once for each matching order.

Standard JOIN Types

Type Rows returned Common use
INNER JOIN Only rows that match on both sides Retrieve records that have a corresponding record in both tables
LEFT OUTER JOIN Every left row, plus matching right rows Preserve all customers even when they have no orders
RIGHT OUTER JOIN Every right row, plus matching left rows Preserve all records in the right table
FULL OUTER JOIN All rows from both tables, retaining unmatched rows Combine datasets while preserving unmatched records from either side
CROSS JOIN Every combination of left and right rows Generate a Cartesian product, often for reporting or test data

In standard SQL, JOIN without an explicit type means INNER JOIN.

Join Variants and Related Patterns

  • JOIN ... USING: Compares columns having the same name. The shared column appears only once in the result.

  • NATURAL JOIN: Joins automatically on every identically named column. Explicit ON conditions are generally safer because NATURAL JOIN can combine unintended columns.

  • Self-join: Joins a relation to itself, commonly to compare employees with managers or represent parent-child relationships.

  • Semi-join: Returns rows from one relation for which a matching row exists in another. It is commonly expressed with EXISTS, not dedicated standard JOIN syntax.

  • Anti-join: Returns rows for which no matching row exists. It is commonly expressed with NOT EXISTS.

  • Recursive query: Uses a recursive common table expression to traverse hierarchical relationships; it is a query pattern rather than a separate JOIN type.

NULL Semantics

NULL represents a missing or unknown value, not an ordinary empty string or numeric zero. Consequently:

  • customer_id = NULL does not produce a true match because the result is unknown.

  • Conditions testing missing values should use IS NULL or IS NOT NULL.

  • In a LEFT JOIN, unmatched right-side columns are returned as NULL.

  • In a RIGHT JOIN, unmatched left-side columns are returned as NULL.

  • In a FULL OUTER JOIN, unmatched columns are returned as NULL on the side lacking a match.

Rows whose join values are both NULL do not match through ordinary equality because NULL = NULL evaluates to unknown.

Database Differences and Best Practices

  • PostgreSQL 18, Oracle Database 23ai, and SQLite 3.50.1 support RIGHT JOIN and FULL OUTER JOIN.

  • MySQL 8.4 LTS and SQL Server 2022 support RIGHT JOIN but not FULL OUTER JOIN.

  • PostgreSQL, MySQL, Oracle Database, and SQLite support JOIN ... USING and NATURAL JOIN. SQL Server requires an explicit ON condition.

  • SQLite added RIGHT JOIN and FULL OUTER JOIN in version 3.39.0.

  • Prefer INNER JOIN when only matching records are required; it is often simpler to reason about and more efficient.

  • Use an outer join when unmatched rows must be preserved, and decide deliberately whether unmatched values should be filtered or replaced with COALESCE.

  • Write explicit ON conditions instead of relying on NATURAL JOIN.

  • Use EXISTS for semi-join and anti-join patterns because it tests existence without multiplying result rows.

  • Index foreign-key and frequently joined columns, then inspect the execution plan and actual row estimates.

  • Select named columns rather than SELECT *, especially with outer joins where additional nullable columns may appear.

3

What is a foreign key, and how is it used to maintain relationships between database tables?

A foreign key is a column or set of columns that references a candidate key—usually the primary key—in the same or another table. It enforces referential integrity, preventing child rows from referencing a parent row that does not exist.

Read full answer

Applies toall versions of standard SQL

How It Works

Consider two tables:

  • departments stores the parent records.

  • employees stores child records and contains department_id.

  • employees.department_id is a foreign key referencing departments.department_id.

When a child row is inserted or updated, the database verifies that its foreign-key value exists in the referenced table. The referenced column must be protected by a PRIMARY KEY or suitable UNIQUE constraint.

Foreign keys can also reference columns in the same table, creating a self-referencing relationship such as an employee who manages another employee.

Referential Actions

A FOREIGN KEY constraint can define what happens when a referenced parent row is deleted or its key is updated:

  • ON DELETE RESTRICT: Rejects the operation while related child rows exist.

  • ON DELETE NO ACTION: Requests that the operation be rejected, although checking timing may differ by database.

  • ON DELETE CASCADE: Deletes dependent child rows automatically.

  • ON DELETE SET NULL: Sets the foreign-key columns in dependent rows to NULL.

  • ON UPDATE CASCADE: Updates matching foreign-key values when the parent key changes.

CASCADE can remove or modify a large number of rows unexpectedly, so it should be used deliberately. RESTRICT and NO ACTION are not always interchangeable across databases because their enforcement timing can differ.

NULL Semantics

A foreign key does not require every child row to have a parent reference.

  • If the foreign-key column is nullable and stores NULL, the row normally satisfies the foreign-key constraint.

  • Under the standard MATCH SIMPLE behavior, a composite foreign key is not checked if any of its columns is NULL.

  • Under MATCH FULL, a composite foreign key must either have all columns populated or all columns set to NULL.

The referenced parent row must still exist when a non-NULL foreign-key value is supplied.

Constraints, Transactions and Performance

Foreign-key checks are enforced as part of the relevant SQL operation. If validation fails, the statement is rejected. In an explicit transaction, the application can then roll back the transaction or handle the error as appropriate.

A foreign key is a constraint, not automatically an index:

  • The referenced parent key is normally indexed because it is a primary or unique key.

  • The child foreign-key columns should also have an index when joins, deletions or updates of parent rows are common.

  • Query planners commonly use the parent key index to validate a foreign key and the child index to find dependent rows efficiently.

Exact locking, deferrability and constraint-checking behavior can vary by database engine.

Best Practices and Common Pitfalls

  • Choose the correct action: Prefer RESTRICT or NO ACTION when accidental cascading deletion would be harmful.

  • Allow NULL intentionally: Make the foreign-key column nullable only when a child record may validly exist without a parent.

  • Index the referencing columns: This helps parent-row updates and deletes locate dependent records efficiently.

  • Avoid circular dependencies unnecessarily: They complicate inserts, deletes and transactional ordering.

  • Use parameterized application queries: Pass user-provided values as typed parameters rather than concatenating them into SQL strings.

4

How would you define database normalization and illustrate how the different normal forms address data redundancy and dependency problems?

Database normalization is the process of organizing relational data so that each fact is stored in the appropriate place, duplication is minimized, and dependencies are expressed explicitly. It primarily prevents update anomalies rather than serving as a direct query-performance optimization.

Read full answer

Applies toall versions of mainstream SQL database systems; PostgreSQL-specific guidance uses PostgreSQL 18

Core Idea

Normalization decomposes tables according to how their columns depend on keys. Each normal form adds a stricter requirement:

  • Functional dependencies describe relationships such as order_id → customer_id.

  • Multivalued dependencies describe independent repeated facts.

  • Join dependencies describe relationships that cannot be represented by functional dependencies alone.

Normalization does not automatically improve every query. Its goal is accurate constraints, predictable updates and maintainable data.

Normal Forms

Normal form Requirement Problem addressed
1NF Each attribute has a single value from its domain, and repeating groups are eliminated. A comma-separated list such as multiple phone numbers in one field.
2NF The relation is in 1NF, and every non-prime attribute is fully functionally dependent on every candidate key. A partial dependency, such as StudentName depending only on StudentID when (StudentID, CourseID) is the composite key.
3NF For every nontrivial functional dependency X → A, either X is a superkey or A is a prime attribute. An improper transitive dependency, such as EmployeeID → DepartmentID → DepartmentName.
BCNF For every nontrivial functional dependency X → A, X is a superkey. Dependencies determined by a non-key attribute, even in some relations that satisfy 3NF.
4NF For every nontrivial multivalued dependency X ↠ Y, X is a superkey. Independent multivalued facts stored together, causing duplication or update anomalies.
5NF/PJNF Every nontrivial join dependency is implied by the candidate keys. Irreducible many-way relationships that cannot be represented safely by simpler decomposition.

A superkey may contain extra attributes; a candidate key is a minimal superkey. This distinction matters when comparing BCNF with 3NF.

Illustrative Decomposition

A denormalized sales relation might contain order_id, customer_id, customer_name, product_id, product_name, quantity, and unit_price. This repeats customer and product facts across order lines.

A normalized design separates the facts:

  • Customer: customer_id, name, and a unique email.

  • Product: product_id, name, and current_price.

  • Order: order_id, customer_id, and order_date.

  • OrderItem: (order_id, product_id), quantity, and the transaction-time unit_price.

Foreign keys connect these relations, and the join columns are declared NOT NULL. A customer or product is now described once, while each order item preserves the price actually charged. Updating a product name no longer requires changing every historical order line, and deleting an order does not delete the customer or product.

Advantages and Limitations

  • Advantages:

  • Reduces update, insertion and deletion anomalies.

  • Prevents duplicated facts from becoming inconsistent.

  • Makes primary-key, foreign-key, UNIQUE and CHECK constraints more precise.

  • Improves maintainability and separates reusable facts from transaction-specific facts.

  • Trade-offs:

  • Normalized schemas often require additional joins.

  • Read-heavy analytical systems may benefit from deliberate denormalization, materialized views or summary tables.

  • Normalization alone does not guarantee good performance; indexes, statistics, query shape and execution plans still matter.

Best Practices and Common Pitfalls

  • Treat 3NF as a practical baseline: Higher forms are valuable when the modeled dependencies require them, but decomposition should follow real business rules rather than arbitrary splitting.

  • Choose meaningful keys: Use stable primary keys and enforce alternate identifiers with UNIQUE constraints where appropriate.

  • Model facts precisely: Keep transaction-specific values, such as the price charged at purchase time, separate from current reference data.

  • Handle NULL correctly: SQL NULL means unknown or inapplicable, not zero or an empty string. NULL = NULL evaluates to unknown, so null tests use IS NULL and IS NOT NULL.

  • Index query paths: PostgreSQL 18 creates indexes for primary-key and unique constraints, but it does not automatically index foreign-key columns. Examine representative workloads with EXPLAIN (ANALYZE, BUFFERS) before adding indexes.

  • Denormalize deliberately: Measure real workloads and document the constraints or maintenance procedures needed to keep duplicated facts consistent.

5

When is it appropriate to intentionally introduce redundant data into a database schema to improve read performance or simplify queries?

Denormalization is the deliberate duplication or precomputation of data to reduce the number of joins, aggregations, or other expensive operations required by frequent reads. It can improve latency and scalability, but the application must manage the consistency of duplicated values.

Read full answer

Applies toSQL:2023 and compatible relational database systems

How It Works

In a normalized schema, each fact is stored once and related through keys. For example, an orders table might reference a customers table, and an order_items table might reference products.

A denormalized design might store some of those values directly in the order or order-item rows:

  • order_items.product_name duplicates the product name.

  • orders.customer_name duplicates the customer name.

  • A summary row stores the daily order total instead of recalculating it from individual orders.

The duplicated values are intentionally retained so a common query can read a single table without repeatedly joining related data.

Benefits

  • Faster read queries: Fewer joins and less repeated aggregation can reduce latency.

  • Simpler query logic: Frequently accessed data can be retrieved without complex joins.

  • Improved scalability: Read-heavy systems may scale more effectively when expensive relational operations are removed.

  • Support for specialized access patterns: A denormalized table or materialized view can be designed for dashboards, catalogs, search documents, or reporting workloads.

  • Reduced runtime computation: Precomputed totals, counts, rankings, or search attributes avoid recalculating the same results.

For example, a sales dashboard that always shows totals by day can use a daily summary table rather than grouping the complete transactional order history on every request.

Consistency and NULL Semantics

Duplicated data can become stale when its source changes. If a product is renamed, every denormalized row containing the old name may need updating.

  • Write amplification: One logical update may require updating several rows or tables.

  • Atomicity requirements: Related updates should occur within one transaction.

  • Isolation considerations: Isolation levels determine whether concurrent transactions can observe partially updated denormalized data. A suitable level, such as READ COMMITTED or stronger, may be needed depending on the consistency requirement.

  • NULL handling: Duplicated nullable columns can have different NULL states from their source. Application logic must distinguish a genuinely unknown value from a missing denormalized value.

A common strategy is to update both the source and denormalized data in the same transaction. For high-volume systems, changes can instead be propagated asynchronously through an event stream or change-data-capture mechanism, accepting temporary inconsistency.

When to Use It

Denormalization is appropriate when:

  • A read operation is frequent, expensive, or on a critical latency path.

  • Measurements show that joins, aggregation, or locking—not hardware capacity—is the main bottleneck.

  • The duplicated data is stable or changes infrequently.

  • The system prioritizes read throughput over minimal storage usage.

  • The query pattern is stable enough to justify maintaining a specialized representation.

  • The organization can define how stale the duplicated data may become.

It is usually unnecessary when:

  • Queries are infrequent or inexpensive.

  • The schema is still changing rapidly.

  • Data values change often and updates must be immediately consistent.

  • Storage or write costs are more important than read latency.

  • A simple index or query-plan improvement can resolve the performance issue.

Alternatives and Best Practices

  • Indexes: Often the first optimization to consider. Inspect the query plan before changing the schema.

  • Materialized views: A database-managed way to store the results of a query for repeated access. They still need refreshes and may not be supported identically by every database.

  • Read replicas: Useful for scaling read-heavy workloads without duplicating data in application tables.

  • CQRS: Separates update-oriented models from purpose-built read models, often using asynchronous synchronization.

  • Caching: Appropriate for temporary or tolerable levels of staleness, such as frequently requested reference data.

Prefer measured bottlenecks, explicit consistency requirements, and reversible designs. Document where each duplicated value originates, how it is updated, and how stale it may be. Also monitor query plans, refresh or repair jobs, storage growth, and the operational cost of propagating changes.

6

What is the role of the `GROUP BY` clause in a SQL query?

GROUP BY groups rows according to one or more expressions so that aggregate functions such as COUNT, SUM, AVG, MIN, and MAX can return one result per group. It is primarily used to turn individual rows into summarized results.

Read full answer

Applies toISO/IEC 9075:2023 (SQL:2023)

How It Works

GROUP BY evaluates its expressions for every input row and places rows with equivalent grouping values into the same group. The result contains one row for each group.

In a standards-compliant grouped query:

  • The select list can contain grouping columns or expressions.

  • Aggregate functions operate over all rows in each group.

  • A selected non-aggregate expression must be based on the grouping expressions, unless the database supports a relevant extension such as functional-dependency detection.

  • Rows removed by WHERE do not participate in grouping.

For example, if a sales table contains customer_id and total_amount, selecting customer_id and the sum of total_amount, grouped by customer_id, produces one row for each customer.

Multiple Grouping Columns

Multiple expressions can be listed in GROUP BY. Each distinct combination of their values forms a separate group.

For example, grouping sales by both region and product_category produces one row per region-and-category combination. The order of expressions in GROUP BY does not guarantee the order of the result rows; use ORDER BY when presentation order matters.

WHERE Versus HAVING

Clause Filtering stage Typical use
WHERE Before grouping Filter individual source rows
HAVING After grouping and aggregation Filter complete groups or aggregate results

For example, a query can restrict source rows to orders placed during 2025 with WHERE, group those rows by customer, and then use HAVING to retain only customers whose aggregate total exceeds a threshold.

Filtering with WHERE is generally preferable when it can reduce the number of rows before grouping. Aggregate expressions in HAVING are part of standard SQL; their use in WHERE is a database-specific extension.

NULL Semantics

For a grouped column, rows containing NULL are normally placed together in one NULL group. This does not mean that NULL is treated as an ordinary equal value in a WHERE comparison: NULL = NULL evaluates to unknown rather than true.

Aggregate functions also have different NULL handling. COUNT(*) counts rows, while COUNT(column) counts only rows where that column is non-NULL. SUM and AVG ignore NULL values when calculating their results.

GROUP BY, DISTINCT, and Window Functions

  • GROUP BY: collapses matching rows into groups and commonly pairs them with aggregates.

  • DISTINCT: removes duplicate rows from the result without aggregating them.

  • Window functions: calculate aggregates over partitions while retaining row-level detail; they do not collapse rows in the same way as GROUP BY.

Practical Guidance and Common Pitfalls

  • Do not select arbitrary non-grouped columns: Standards-compliant engines reject these expressions unless grouping or functional dependencies justify them. Some database extensions permit them and may return arbitrary values.

  • Do not assume output order: Add ORDER BY when the application requires deterministic ordering.

  • Use GROUP BY only when aggregation is intended: Use DISTINCT merely to eliminate duplicate rows.

  • Check aggregate NULL behavior: COUNT(*) and COUNT(column) answer different questions.

  • Inspect the execution plan: Databases may use sorting, hashing, or streaming aggregation. An appropriate index can help filtering and sometimes grouping, but the optimal design depends on the database, data volume, and chosen plan.

7

How is a subquery defined, and in what situations is it preferable to use one?

A subquery is a query nested inside another SQL statement and evaluated as part of that statement. It is often preferable when it expresses a filtered set, an existence test, or a per-row calculation more clearly than duplicating logic or restructuring the query.

Read full answer

Applies toSQL:2023

Types of Subqueries

  • Scalar subquery: Returns exactly one column and no more than one row. In a scalar context, no rows produces NULL; multiple rows cause an error.

  • IN subquery: Tests whether a value matches any value returned by the inner query.

  • EXISTS subquery: Tests whether the inner query returns at least one row. It does not transfer the matching row’s values to the outer query.

  • Correlated subquery: Refers to a column from an enclosing query block, allowing the inner query to depend on an outer row.

  • Derived table: Treats a nested SELECT as a table in the FROM clause. Give it an explicit alias for portability; many database engines require one.

Form Primary purpose Important consideration
Scalar Produce one value Exactly one column and at most one row
IN Match a set of values NOT IN has special NULL semantics
EXISTS Test whether matching rows exist The optimizer may rewrite it as a semijoin or stop early
Correlated Evaluate a condition in the context of each outer row An optimizer may decorrelate it
Derived table Present a result set as a table Use an explicit alias

NULL Semantics

SQL uses three-valued logic for comparisons involving NULL: TRUE, FALSE, or UNKNOWN.

Although value = NULL is syntactically valid, it evaluates to UNKNOWN and therefore does not test whether value is null. The appropriate test is value IS NULL.

For x IN (...), a matching non-null value produces TRUE; if there is no match but the set contains NULL, the result is UNKNOWN; otherwise, it is FALSE. Similarly, x NOT IN (...) can evaluate to UNKNOWN rather than TRUE when the set contains NULL and no non-null value matches. Prefer NOT EXISTS with an explicit equality condition when excluding rows based on related data.

When to Use a Subquery

Use a subquery when it naturally expresses one of these requirements:

  • Compare a column against a filtered set of values.

  • Find rows for which related records exist or do not exist.

  • Calculate an aggregate based on each outer row.

  • Consume a derived result as a table for further filtering or aggregation.

  • Express business rules directly in SQL without hard-coding application values.

For example, EXISTS is often appropriate when only the presence of a related record matters. A correlated scalar subquery can obtain a count or maximum associated with each outer row, although a join may be more efficient when many columns from the related table are needed.

Subqueries Versus Joins and CTEs

Neither approach is universally faster. Choose based on clarity, data volume, selectivity, and execution plans.

  • JOIN: Often appropriate when combining columns from multiple tables, particularly when the join condition has a useful index.

  • Subquery: Useful for existence checks, filtered value sets, and calculations dependent on an outer row.

  • Common table expression (CTE): Often clearer for multi-stage transformations, recursive processing, or reusing a complex intermediate result. A database may materialize or inline a CTE, depending on its optimizer and the chosen syntax.

Use the database’s execution-plan tool to compare alternatives on representative data. Ensure that columns used in join predicates, correlation conditions, and filters have appropriate indexes.

Best Practices and Common Pitfalls

  • Avoid SELECT * in outer and inner queries; name only the required columns.

  • Use parameterized queries in application code instead of concatenating user input into SQL.

  • Keep correlated subqueries selective, while remembering that the optimizer may rewrite them.

  • Do not assume NOT IN behaves like NOT EXISTS when NULL values may be present.

  • Confirm that a scalar subquery cannot return multiple rows.

  • Add explicit column aliases to derived tables.

  • Avoid unnecessary sorting or duplicate subqueries when a join or CTE expresses the same requirement more clearly.

  • Test the chosen query plan under realistic data volumes because optimizer behavior differs across database systems.

8

How do the four standard join types—INNER JOIN, LEFT JOIN, RIGHT JOIN, and FULL JOIN—differ in which rows they retain and how they handle non-matching records?

JOIN type determines which non-matching rows are preserved from the joined tables. INNER JOIN returns only matching records, while the three outer joins retain unmatched rows from one or both sides and represent missing values with NULL.

Read full answer

Applies toSQL:2023; PostgreSQL 17 and later, MySQL 8.4 LTS, SQL Server 2022, and SQLite 3.39.0 or later

Comparison of Join Types

Join type Matching rows Unmatched left rows Unmatched right rows
INNER JOIN Included Excluded Excluded
LEFT JOIN Included Included Excluded
RIGHT JOIN Included Excluded Included
FULL JOIN Included Included Included
  • INNER JOIN: Returns a row only when the join condition finds a match in both tables. It is commonly written simply as JOIN.

  • LEFT JOIN: Returns all rows from the left table, including rows with no match in the right table. Columns from the right table are NULL for those unmatched rows.

  • RIGHT JOIN: Returns all rows from the right table, including rows with no match in the left table. Columns from the left table are NULL for those unmatched rows.

  • FULL JOIN: Returns all rows from both tables. Unmatched rows from either table appear with NULL in columns belonging to the other table.

For example, joining customers to orders with LEFT JOIN retains customers who have never placed an order. A RIGHT JOIN instead retains orders whose customer record is missing. A FULL JOIN preserves both groups.

NULL and Matching Semantics

NULL means that a value is unknown or absent; it is not equivalent to an empty string or numeric zero.

  • An equality condition such as customer_id = order.customer_id does not match when either side is NULL.

  • Use IS NULL and IS NOT NULL to test for missing values.

  • Outer joins do not manufacture a matching row when no match exists. Instead, they extend the result with NULL values.

  • A FULL JOIN is often used to compare two datasets or combine records whose relationships may be incomplete.

The result can contain more rows than either input table because one row may match several rows in the other table. This many-to-many behavior is valid SQL and should be considered when aggregating or interpreting results.

Filter Placement and Row Preservation

The ON and WHERE clauses have different effects with outer joins:

  • ON: Evaluates the join condition before unmatched rows are added. Put join-specific conditions here when unmatched left rows should remain.

  • WHERE: Filters the final joined result. A condition that rejects NULL can therefore eliminate unmatched outer-join rows, making the result behave like an inner join.

For example, placing o.order_status = 'open' in the ON clause preserves customers even when they have no qualifying orders. Moving that predicate to WHERE would remove customers with no open order because the unmatched row has NULL in o.order_status.

Practical Guidance

  • Use INNER JOIN when only relationships present in both datasets matter.

  • Use LEFT JOIN when the left table defines the complete population, such as customers, employees, or products.

  • Use RIGHT JOIN when the right table must be preserved. Many teams prefer reversing the table order and using LEFT JOIN, which can be easier to read.

  • Use FULL JOIN for reconciliation, auditing, or combining data when neither side is authoritative.

  • Avoid SELECT * in production queries. Select only required columns, because outer-join rows contain NULL for unavailable values.

  • For large tables, use indexed join and filter columns and inspect the database’s execution plan. Join order and index usage vary by database engine.

9

What is the standard SQL procedure for adding a new record to a table?

Use an INSERT statement to add one or more rows to a relational table. The standard form specifies the target columns and supplies corresponding values through a VALUES clause.

Read full answer

Applies toISO/IEC 9075:2023 (SQL:2023); PostgreSQL 18, MySQL 8.4, SQL Server 2022, and Oracle Database 23ai

How INSERT Works

The basic form is INSERT INTO table_name (column1, column2) VALUES (value1, value2);. The column list identifies the destinations, and values are matched to those columns by position.

Use an explicit column list rather than relying on the table’s schema order. This avoids accidentally storing values in the wrong columns and avoids having to supply values for unrelated columns. It does not bypass validation: an omitted required column must still have a default or a value supplied elsewhere in the statement.

For rows derived from a query, SQL also defines the INSERT ... SELECT form.

Values, Defaults, and NULL

  • Supplied value: The column receives the corresponding value from the VALUES clause.

  • Omitted column: The column receives its declared DEFAULT. If it has no default, it receives NULL when nullable; otherwise, the insert fails.

  • Explicit DEFAULT: Requests the column’s configured default value, which may also generate an identity value.

  • Explicit NULL: Stores SQL NULL in a nullable, non-generated column even when that column has a default. It fails for a NOT NULL column and generally cannot be used to populate a generated column directly.

  • Multiple rows: SQL:2023 and the listed databases support multiple value groups in one statement, subject to engine limits such as SQL Server’s 1,000-row limit for a table value constructor.

Parameterized Inserts

Application code should bind untrusted values through the database driver rather than concatenating them into SQL. Parameterization prevents SQL injection for values and lets the driver represent them using the appropriate data types.

Database Common parameter syntax
PostgreSQL 18 $1, $2, $3
MySQL 8.4 ?
SQL Server 2022 @name
Oracle Database 23ai :name

The driver APIs differ, but the SQL text and typed parameter values are submitted separately. Table and column identifiers cannot ordinarily be parameterized, so they must come from a fixed allowlist rather than user-controlled text.

Constraints and Transactions

With PostgreSQL, MySQL using InnoDB, SQL Server, and Oracle, an INSERT is atomic at the statement level. If a constraint failure occurs, the statement’s row changes are rolled back; earlier statements in the same transaction may remain pending until the transaction is committed or rolled back.

Common constraints include:

  • Primary key: Requires a unique, non-NULL identifier for each row.

  • Unique constraint: Prevents duplicate non-NULL values in the specified key columns; treatment of multiple NULL values varies by database.

  • Foreign key: Requires a matching parent row for each non-NULL foreign-key value. A nullable foreign key containing NULL does not require a parent row.

  • Check constraint: Enforces a condition such as quantity > 0.

Use an explicit transaction when multiple inserts must succeed or fail together. COMMIT makes the transaction’s changes permanent, while ROLLBACK undoes them. Isolation levels govern how concurrent transactions can observe inserted data.

Generated identifiers are retrieved differently across databases. PostgreSQL 18 can append RETURNING employee_id, while SQL Server 2022 supports the OUTPUT INSERTED.employee_id clause. These extensions are not part of standard SQL.

Best Practices and Common Pitfalls

  • Always list the columns being inserted.

  • Use driver-supported parameter binding for all untrusted values.

  • Define appropriate NOT NULL, DEFAULT, UNIQUE, foreign-key, and check constraints.

  • Avoid inserting into every column merely because the table currently contains those columns.

  • Check the affected-row count when exactly one row is expected.

  • Use one transaction for related inserts that must be atomic.

  • Handle constraint and connection errors explicitly rather than assuming the insert succeeded.

10

Which SQL data type categories and concrete types can be used to store values, and how do they differ between database systems?

Data types determine the kind, precision, and range of values that a column or expression can hold. SQL standardizes major type families, while database vendors add or redefine types for JSON, UUIDs, geospatial data, arrays, and other specialized uses.

Read full answer

Applies toSQL:2023 (ISO/IEC 9075:2023); PostgreSQL 18, MySQL 8.4 LTS, SQL Server 2022, Oracle Database 23ai, and SQLite 3.50

Standard SQL Type Categories

Type family Concrete types Purpose and behavior
Exact numeric SMALLINT, INTEGER, BIGINT, NUMERIC, DECIMAL Store whole or fixed-point decimal numbers exactly. Standard ranges are implementation-defined. INTEGER is a common choice for counters and keys, but identity generation is vendor-specific.
Approximate numeric REAL, FLOAT(p), DOUBLE PRECISION Store binary floating-point values. Some decimal values cannot be represented exactly.
Character strings CHAR, VARCHAR, NCHAR, NVARCHAR Store character data. CHAR has fixed length; VARCHAR has a declared maximum. National-character support depends on the implementation’s character repertoire and encoding.
Bit and binary strings BIT, BIT VARYING, BINARY, VARBINARY Store sequences of bits or bytes. BINARY is fixed-length and VARBINARY is variable-length.
Boolean BOOLEAN Represents TRUE, FALSE, or NULL when the database supports it natively.
Date and time DATE, TIME, TIMESTAMP, and time-zone variants Store calendar dates, times, and date-time values. TIMESTAMP WITH TIME ZONE represents an instant with a time-zone offset.
Interval INTERVAL Represents a temporal duration or difference, potentially containing fields such as days, hours, and minutes.
Large objects CLOB, BLOB Represent large character and binary content. Implementations may also store large values outside ordinary rows.
Structured and user-defined ROW, DOMAIN, user-defined types Represent structured values, reusable constraints, or application-defined types.

Vendor-Specific Types

Database Representative types Important behavior
PostgreSQL 18 UUID, JSON, JSONB, BYTEA, INET, CIDR, XML, arrays, ranges, composite types JSON stores an exact copy of input JSON text. JSONB uses an internal binary format, supports indexing, and has comparison rules that differ from JSON.
MySQL 8.4 LTS JSON, ENUM, SET, YEAR JSON uses an internal binary format. BOOLEAN is an alias for TINYINT(1), not a distinct Boolean storage type.
SQL Server 2022 UNIQUEIDENTIFIER, BIT, DATETIME2, DATETIMEOFFSET, ROWVERSION UNIQUEIDENTIFIER stores a 16-byte GUID. BIT represents Boolean-like values. DATETIMEOFFSET stores a UTC value and offset.
Oracle Database 23ai NUMBER, VARCHAR2, CLOB, BLOB, INTERVAL DAY TO SECOND NUMBER supports several numeric precisions and scales. Unlike the standard DATE, Oracle DATE includes a time component.
SQLite 3.50 INTEGER, REAL, TEXT, BLOB SQLite has the storage classes NULL, INTEGER, REAL, TEXT, and BLOB. Declared types determine column affinity and influence conversion behavior.

NULL Semantics

NULL represents an unknown, missing, or inapplicable value; it is not a normal numeric or character value.

  • NULL = NULL evaluates to UNKNOWN, not true.

  • Tests for missing values use IS NULL or IS NOT NULL.

  • Arithmetic involving NULL normally produces NULL.

  • Nullability is controlled separately from a column’s data type through NULL or NOT NULL.

Choosing Types

  • Use INTEGER for ordinary whole numbers and confirm the target database’s range before choosing BIGINT. Standard SQL does not guarantee a signed 64-bit BIGINT range.

  • Use NUMERIC(p,s) or DECIMAL(p,s) for exact monetary and accounting calculations; avoid approximate numeric types for those values.

  • Prefer VARCHAR for most text. CHAR can introduce padding and vendor-specific comparison behavior.

  • For large text, use PostgreSQL or MySQL TEXT, SQL Server VARCHAR(MAX) or NVARCHAR(MAX), and Oracle CLOB.

  • Use SQL-standard DATE for calendar dates and TIMESTAMP WITHOUT TIME ZONE for local date-times. SQL Server uses DATETIME2 for this concept because its TIMESTAMP means ROWVERSION.

  • Use native Boolean types where available. PostgreSQL TIMESTAMPTZ represents an instant internally in UTC and displays it in the session time zone without retaining the original zone text.

  • Use JSON types only after comparing vendor semantics. PostgreSQL JSONB, MySQL JSON, and SQL Server JSON do not have identical validation, indexing, or comparison behavior.

Portability and Common Pitfalls

  • Type names can have different meanings across databases, so verify casts, truncation rules, limits, collations, nullability, and indexing on every target platform.

  • SQL Server’s legacy TEXT, NTEXT, and IMAGE types are deprecated. Use VARCHAR(MAX), NVARCHAR(MAX), and VARBINARY(MAX) instead.

  • Avoid unqualified FLOAT in portable schemas because vendors map it differently. FLOAT(p) requests binary precision, but the relationship between p and storage size is implementation-defined.

  • Do not assume CHAR, temporal, interval, Boolean, or string-comparison behavior is identical across vendors.

11

How do CHAR, VARCHAR, and TEXT differ in storage, length constraints, and database-specific behavior?

CHAR, VARCHAR, and TEXT represent character data with different length models: CHAR is fixed-length, VARCHAR is variable-length with a declared maximum, and TEXT is intended for large or unbounded text with engine-specific limits.

Read full answer

Applies toall versions of standard SQL; exact limits and edge cases depend on the database engine

Core Differences

Type Length model Padding Typical use
CHAR(n) Fixed logical length of n Blank-padded under standard SQL semantics Codes or values with a fixed width
VARCHAR(n) Variable length, up to n Not padded to n Names, descriptions, and bounded text
TEXT Usually has no declared length Not padded to a declared width Articles, JSON-like documents, and other large text

TEXT is not a portable ANSI SQL type name. Standard SQL represents large character objects with the CLOB type, although popular engines use TEXT or MAX variants.

Database-Specific Behavior

Database Important behavior
PostgreSQL Supports CHAR(n), VARCHAR(n), and text. text is preferred for unrestricted strings and has a practical field-size limit of about 1 GB.
MySQL Provides TEXT, TINYTEXT, MEDIUMTEXT, and LONGTEXT. Their limits depend on bytes and the column or table character set; TEXT supports up to 65,535 bytes.
SQL Server The legacy text type is deprecated; use varchar(max) instead. Use nvarchar(max) for Unicode text. VARCHAR(n) and NVARCHAR(n) express maximum byte or byte-pair counts.
SQLite Uses dynamic typing. CHAR(n) and VARCHAR(n) retain their declared types, but SQLite does not enforce their length limits.
Oracle Does not provide TEXT; use CLOB for large text and usually VARCHAR2 for variable-length strings.

Trailing Spaces and Comparisons

Trailing-space behavior is a major practical difference:

  • In PostgreSQL, CHAR(n) is physically padded, and trailing spaces are ignored when comparing char values. PostgreSQL varchar and text preserve trailing spaces and distinguish them.

  • SQL Server generally ignores trailing spaces when comparing strings for equality while still storing them.

  • MySQL’s CHAR retrieval and comparison behavior can be affected by its padding settings.

These rules are not identical across engines, so comparisons involving CHAR should be tested against the target database.

Storage and Indexing

  • Fixed-length storage: CHAR can consume the declared width even when the input contains fewer characters. This can waste space in narrow rows.

  • Variable-length storage: VARCHAR stores only the supplied content plus any required length information.

  • Large-value storage: TEXT may be stored inline or moved out of the row, depending on the engine, value size, and row format.

  • Index constraints: Indexing VARCHAR or TEXT is limited by the engine’s maximum index-key size. Long values may require a prefix, hash, or full-text index rather than an ordinary B-tree index.

  • Performance caution: A TEXT index is not automatically a full-text search index. Substring searching and full-text relevance usually require engine-specific search features.

Choosing the Right Type

  • Use CHAR(n) only when every valid value must have the same logical width.

  • Use VARCHAR(n) for most identifiers, names, addresses, and other bounded strings.

  • Use TEXT, CLOB, or the database’s MAX type for large content, but verify the engine’s actual limit.

  • Apply NOT NULL when a value is mandatory. In every type, SQL NULL means unknown or missing; it is different from an empty string.

  • Avoid comparing nullable values with = NULL; use IS NULL and IS NOT NULL.

  • Perform explicit length validation in application code when portability matters, especially with SQLite.

12

How does the `BETWEEN` operator work when filtering SQL expressions, and what behavior should developers expect around boundary values and `NULL`?

BETWEEN tests whether a value falls within an inclusive range: the lower and upper bounds both qualify. It follows SQL’s three-valued logic, so a comparison involving NULL produces UNKNOWN rather than TRUE or FALSE.

Read full answer

Applies toall versions of standard SQL

How It Works

The general form is value BETWEEN lower_bound AND upper_bound. For non-NULL values and bounds, it is equivalent to value >= lower_bound AND value <= upper_bound.

  • Inclusive boundaries: Values equal to either endpoint match.

  • Filtering context: The predicate commonly appears in WHERE or HAVING clauses.

  • Bound order: The lower bound should not exceed the upper bound for a nonempty intended range. If it does, a non-NULL value does not match because the two inclusive comparisons cannot both be satisfied. Bounds are never automatically swapped.

  • Type compatibility: The tested expression and bounds must be comparable under the database’s type rules. Some databases coerce compatible types, while others reject incompatible ones.

Boundaries and Data Types

  • Dates and times: A date-valued upper bound is commonly interpreted as midnight at the start of that date when compared with a timestamp column. For example, a range ending on January 31 can include January 31 at 00:00:00 but exclude later times that day.

  • Character values: Comparisons follow the database’s collation, ordering, and character-comparison rules.

  • Numeric values: Exact numeric types are preferable for money and other values where rounding could alter a boundary comparison.

  • Floating-point values: Values with approximate representations may not compare exactly with the decimal literals a developer expects, so equality at a boundary should be tested carefully.

NULL Semantics

If the tested value or either bound is NULL, BETWEEN evaluates to UNKNOWN. A row passes a WHERE or HAVING predicate only when it evaluates to TRUE; UNKNOWN is discarded in the same way as FALSE.

For example, NULL BETWEEN 10 AND 20 evaluates to UNKNOWN. It does not match merely because NULL is absent from the numeric range.

If missing values need separate treatment, test them explicitly with IS NULL or IS NOT NULL rather than expecting BETWEEN to include them.

NOT BETWEEN

For non-NULL values and bounds, NOT BETWEEN selects values outside the inclusive range. It is logically equivalent to value < lower_bound OR value > upper_bound.

If the tested value or either bound is NULL, however, the negated comparison also evaluates to UNKNOWN. Consequently, NOT BETWEEN does not select rows containing NULL in the tested expression or bounds.

Predicate Non-NULL result Result when a value or bound is NULL
value BETWEEN lower_bound AND upper_bound TRUE when both inclusive comparisons hold UNKNOWN
value NOT BETWEEN lower_bound AND upper_bound TRUE when the value is outside the range UNKNOWN

Performance and Best Practices

  • Use typed parameters: In application code, bind lower and upper bounds through the database driver’s typed parameter API. Do not concatenate user input into SQL.

  • Choose suitable indexes: A B-tree index on a numeric, temporal, or appropriately collated character column can often support the range predicate.

  • Inspect execution plans: Confirm that the database can use the index for the range and evaluate whether the range is sufficiently selective.

  • Keep predicates sargable: Avoid wrapping an indexed column in a function or otherwise transforming it before the range comparison.

  • Test exact boundaries: Include values equal to both endpoints, values immediately outside them, reversed bounds, and rows containing NULL.

  • Prefer half-open timestamp ranges: Use timestamp_column >= start_time AND timestamp_column < end_time to represent a time interval without depending on the precision of an inclusive final timestamp.

13

How would you use the SQL `IN` operator to determine whether an expression matches any value in a specified list or subquery result?

The IN operator is a predicate that tests whether an expression equals at least one member of a value list or any row returned by a subquery. It is useful for filtering records against multiple permitted values without writing a chain of OR conditions.

Read full answer

Applies toISO/IEC 9075:2023 (SQL:2023)

Syntax and Behavior

The general forms are:

expression IN (value_1, value_2, ...)
expression IN (subquery)
expression NOT IN (value_1, value_2, ...)
expression NOT IN (subquery)
  • List form: Compares the expression with every value in the parentheses.

  • Subquery form: Compares the expression with every value produced by the subquery, which must return exactly one column.

  • Positive match: IN evaluates to TRUE if at least one comparison is TRUE.

  • No match: It evaluates to FALSE if every comparison is definitively FALSE.

  • Duplicates: Duplicate list values or duplicate subquery results do not affect the result.

  • Type compatibility: The expression and compared values should have compatible SQL types. Implicit conversions differ between database systems, so avoid relying on them.

For example, these predicates are equivalent:

WHERE order_status = 'PENDING'
   OR order_status = 'PAID'
   OR order_status = 'PROCESSING'
WHERE order_status IN ('PENDING', 'PAID', 'PROCESSING')

The second form is usually easier to read and maintain.

NULL Semantics

SQL comparisons can return TRUE, FALSE, or UNKNOWN. UNKNOWN occurs when either operand is NULL, because SQL does not consider NULL equal to any value, including another NULL.

Consequently:

NULL IN (1, 2, 3)

evaluates to UNKNOWN, not FALSE.

NOT IN negates the complete comparison:

order_status NOT IN ('PAID', NULL)

For a non-NULL order_status, this expression cannot evaluate to TRUE. If order_status is 'PAID', the result is FALSE; otherwise, the NULL comparison produces UNKNOWN.

The same risk applies to:

column_name NOT IN (SELECT nullable_column FROM another_table)

If the subquery returns any NULL, the predicate may reject every non-matching row. Filtering the subquery with WHERE nullable_column IS NOT NULL, or preferably using a corresponding NOT EXISTS query, avoids this problem.

Choosing the Right Operator

Requirement Recommended construct Reason
Match several known values IN (list) Concise and readable
Match values from another table IN (subquery) Convenient set-membership test
Exclude several known values NOT IN (list) Safe when the list contains no NULL
Exclude values from a nullable subquery NOT EXISTS Avoids NOT IN and NULL pitfalls
Test one value = Simpler than IN with one item

IN and EXISTS are not interchangeable. IN asks whether the left-hand value occurs in the right-hand set, while EXISTS asks whether the subquery produces at least one qualifying row. Choose between them based on the required NULL semantics, correlation needs, and measured execution plan rather than assuming one is always faster.

Performance and Execution Plans

An IN predicate can use an index when the database’s optimizer chooses a suitable plan. Performance depends on the database, data distribution, list size and available statistics, so inspect the execution plan rather than assuming an index will be used.

  • Small IN lists are generally efficient.

  • Large lists may be expanded into many comparisons or handled through a join-like plan.

  • A subquery used with IN may be optimized as a semi-join.

  • NOT IN often has fewer optimization opportunities, especially when NULL handling is required.

  • Execution-plan commands are product-specific. Use the database’s native planning tools and, where available, its actual-execution facility; do not assume that EXPLAIN ANALYZE is portable.

Code Example

The following example filters orders by a known list of statuses and by customers returned from a subquery:

SELECT
    order_id,
    customer_id,
    order_status
FROM orders
-- Match one of the permitted statuses.
WHERE order_status IN ('PENDING', 'PAID', 'PROCESSING')
  -- Match a customer returned by the subquery.
  AND customer_id IN (
      SELECT customer_id
      FROM vip_customers
  )
ORDER BY order_id;

The positive IN predicate does not require the IS NOT NULL guard discussed for NOT IN. If the second predicate were changed to NOT IN, the subquery would need to exclude NULL values.

Common Pitfalls and Best Practices

  • Do not use SELECT *: The subquery should select only the column needed for membership testing.

  • Consider index selectivity: Inspect the query plan before adding an index to a column compared through IN; an index on a low-cardinality column may provide little benefit.

  • Avoid nullable NOT IN subqueries: Prefer NOT EXISTS when excluding rows based on another table.

  • Do not compare unrelated types: Use explicit, compatible column types instead of relying on database-specific coercion.

  • Parameterize application input: When an IN list comes from user input, bind every value as a typed parameter. Do not construct SQL by concatenating request data.

  • Keep lists focused: Very large IN lists can increase planning and parsing overhead; consider a temporary table or join table when the list is persistent or large.

14

How are wildcard characters used to match patterns in SQL queries?

Wildcard characters are special pattern markers used by SQL pattern-matching predicates. The standard LIKE operator uses % to match any sequence of characters and _ to match exactly one character.

Read full answer

Applies toSQL:2023; PostgreSQL 18 for PostgreSQL-specific behavior and performance guidance

Common Wildcards

Wildcard Meaning Examples
% Zero, one, or many characters LIKE 'SQL%' matches values beginning with SQL; LIKE '%SQL' matches values ending with SQL; LIKE '%SQL%' matches values containing SQL.
_ Exactly one character LIKE 'A_C' matches ABC, ADC, and similar three-character values, but not AC.

The pattern applies to the entire value; wildcard placement determines which portions are constrained. These characters have special meaning in LIKE, not in ordinary comparisons such as = or <>.

Escaping Literal Wildcards and NULL

To match a literal % or _, escape it with an explicitly declared escape character. The ESCAPE clause makes the behavior unambiguous and portable.

For example, LIKE '%50!%' ESCAPE '!' matches values containing the literal text 50%: !% represents a percent sign rather than a wildcard. The same principle applies to a literal underscore, written as !_. When ! is the escape character, !! matches a literal !.

If the value is NULL, LIKE evaluates to UNKNOWN, like other SQL comparisons involving NULL. A WHERE clause returns a row only when its predicate evaluates to TRUE, so NULL values are not matched by LIKE.

Case Sensitivity

Case behavior depends on the database, collation, and operator:

  • PostgreSQL 18: LIKE is case-sensitive, while ILIKE performs case-insensitive matching.

  • SQL Server: Behavior generally follows the column or expression collation.

  • MySQL: Behavior generally follows the selected collation; case-insensitive and case-sensitive collations are both available.

  • Oracle: LIKE is case-sensitive; NLS_COMP and NLS_SORT do not make it case-insensitive.

Use a case-insensitive operator or a normalized expression only when the business requirement clearly calls for it.

Performance and Best Practices

  • Parameterize patterns: Bind patterns as query parameters rather than concatenating user input into SQL text. In PostgreSQL, parameters such as %50!% and %INV!_% preserve wildcard semantics while avoiding SQL injection.

  • Escape literal characters explicitly: Use ESCAPE when %, _, or the escape character must be matched literally rather than interpreted as wildcards.

  • Consider leading wildcards: A predicate such as LIKE '%product' cannot ordinarily use a conventional B-tree index efficiently because the beginning of the value is unconstrained. PostgreSQL may require a sequential scan or a suitable trigram index.

  • Prefer fixed prefixes when possible: LIKE 'product%' is generally more index-friendly because the prefix is fixed.

  • Choose the appropriate mechanism: Exact equality, range predicates, full-text search, or regular-expression operators may be more appropriate when the required semantics differ.

  • Inspect the plan: Use EXPLAIN to verify the chosen access path rather than assuming an index is being used.

15

How do UNION and UNION ALL differ in their behavior when combining result sets?

UNION combines multiple SELECT queries into a single result set by removing duplicate rows, while UNION ALL returns all rows including any duplicates without any deduplication. The primary distinction lies in how they handle duplicate records, which directly impacts performance and use case suitability.

Read full answer

Applies toStandard SQL (behavior is the same for `UNION` and `UNION ALL` across PostgreSQL, MySQL, SQL Server, Oracle, and SQLite).

How They Work

Both operations merge two or more SELECT statements, but their internal processing differs significantly:

  • UNION: Applies implicit duplicate elimination to the combined result set. Most engines achieve this by sorting or hashing to identify and remove duplicate rows before returning results.

  • UNION ALL: Simply concatenates all rows from each query with no deduplication step, making it typically faster and less resource-intensive.

Key Differences

Aspect UNION UNION ALL
Duplicate handling Removes duplicates Keeps all duplicates
Performance Slower due to duplicate elimination Faster, direct concatenation
Memory usage Usually higher during elimination Usually lower, minimal processing
Use case When unique records are required When all records matter, even duplicates
Internal operation Uses implicit duplicate elimination No transformation applied

When to Use Each

  • Choose UNION when you need a list of unique values and duplicates would be incorrect or misleading, such as creating a master list of distinct entries.

  • Choose UNION ALL when you need complete data including duplicates, such as combining logs or transaction totals, or when you are certain duplicates cannot occur and want to avoid unnecessary overhead.

Important Considerations

  1. Column requirements: Both operations require the same number of columns, compatible data types in corresponding positions, and matching column order across all SELECT statements.

  2. Performance impact: For large datasets, prefer UNION ALL unless deduplication is strictly required. When performance matters, review the query plan for your database.

  3. NULL semantics: In duplicate elimination, SQL treats two NULL values as not distinct from one another, so UNION will collapse multiple NULL rows into a single NULL row. UNION ALL preserves every NULL exactly as returned.

  4. ORDER BY placement: ORDER BY should generally appear only at the end of the final unioned query. Applying ORDER BY to an individual SELECT that is part of a UNION or UNION ALL is only meaningful in specific dialect cases (e.g., parenthesized queries with LIMIT/FETCH), so rely on the final ORDER BY unless your specific database explicitly supports otherwise.

  5. Column aliases: Column aliases are defined per SELECT, but the final result set’s ordering and references for the combined query should be based on the final query’s projection, not assumed from individual SELECT aliases.

16

What methods can be used to identify rows that appear more than once within a single table?

To detect duplicate records in a table, you typically group rows by the columns that define uniqueness and filter groups where the count exceeds one. This process helps duplicate detection identify data quality issues and prepare tables for enforcing constraints.

Read full answer

Applies toPostgreSQL, MySQL, SQL Server, Oracle, SQLite (standard SQL)

How It Works

  • Use GROUP BY to aggregate rows sharing the same values in specified columns

  • Apply HAVING COUNT(*) > 1 to isolate groups appearing multiple times

  • The combination returns only the columns that define the duplicate condition

When identifying duplicates, consider whether NULL values should be treated as equal (they are not in standard SQL comparisons but are grouped together in GROUP BY).

Common Approaches

Here are the main patterns for detecting duplicates:

  • GROUP BY with HAVING: Returns one row per duplicate group

  • Self-join: Compares each row against others with identical keys

  • Window functions: Uses COUNT() OVER() to label each row with its group count

  • WITH Clause (CTE): Organizes complex duplicate detection logic

Approach Returns Best For
GROUP BY/HAVING Aggregate results Finding which combinations are duplicated
Window Functions Individual rows marked Identifying every duplicate row
Self-Join Paired duplicate rows Comparing exact row differences

Performance Considerations

  • Create indexes on the columns used for duplicate checking

  • Avoid SELECT *; specify only necessary columns

  • Large tables may require partitioning or temporary storage

  • Query plans can be examined with EXPLAIN (or database-specific equivalents) to optimize execution

Handling NULL Values

In GROUP BY, rows where the key columns contain NULL are grouped together (unlike equality checks). This means two NULL values in the same column will be considered part of the same group when using GROUP BY.

Best Practices and Common Pitfalls

  • Always test queries against development data before production runs

  • Use database-specific row-limiting clauses (e.g., LIMIT in PostgreSQL/MySQL/SQLite, TOP in SQL Server, FETCH FIRST in Oracle/PostgreSQL) when exploring large datasets during analysis

  • Remember that primary keys or unique constraints prevent duplicates at the database level

  • Document which columns define uniqueness for your business rules

  • Consider adding unique constraints after duplicate cleanup: ALTER TABLE table_name ADD CONSTRAINT unique_constraint UNIQUE (column_list)

  • Backup data before running DELETE operations on identified duplicates

  • Use parameterized queries in application code if the duplicate values are user-provided (never concatenate strings)

17

Define the Entity-Relationship Model used in database design, including its core components and purpose

The Entity-Relationship Model (ER Model) is a conceptual framework for designing databases by representing real-world objects as entities and their relationships. It serves as a blueprint before actual table creation in any relational database management system.

Read full answer

Applies toall relational database systems including PostgreSQL, MySQL, SQL Server, Oracle, and SQLite

Core Components

  • Entity: Represents a real-world object or concept that can have distinct values (e.g., Customer, Order, Product). Entities are represented as rectangles in ER diagrams.

  • Attribute: Properties that describe an entity (e.g., customer_id, customer_name, email). Attributes connect to entities with ovals in diagrams.

  • Primary keys uniquely identify each entity instance

  • Foreign keys establish relationships between entities

  • Composite attributes consist of multiple sub-attributes

  • Derived attributes are calculated from other attributes

  • Relationship: Associations between two or more entities (e.g., a Customer places an Order). Relationships appear as diamonds connecting entities.

Relationship Cardinalities

Cardinality Notation Description Example
One-to-One 1:1 One record in table A relates to exactly one record in table B Employee ↔ Company Car
One-to-Many 1:N One record in table A relates to many records in table B Department ↔ Employees
Many-to-Many M:N Many records in table A relate to many records in table B Students ↔ Courses (requires junction table)

Implementation Guidelines

When translating an ER diagram into actual database tables, follow these principles:

  1. Each entity becomes a table with primary key constraints

  2. One-to-one relationships often merge into a single table or use foreign keys with UNIQUE constraints

  3. One-to-many relationships add a foreign key column in the “many” side table

  4. Many-to-many relationships require a separate junction table with composite foreign keys

Advantages and Limitations

Advantages: - Provides visual clarity for database planning before implementation - Communicates complex data structures to non-technical stakeholders - Identifies redundancy and normalization opportunities early - Scales well for both small applications and enterprise systems

Limitations: - Doesn’t address performance considerations like indexing strategies - Multiple valid ER designs exist for the same requirements - Requires translation to actual DDL, which may involve adjustments - Less effective for NoSQL or hierarchical data models

Best Practices

  • Identify entities based on nouns from business requirements

  • Use singular names for entities and tables for consistency

  • Establish clear naming conventions for columns and constraints

  • Normalize to at least Third Normal Form (3NF) before denormalization for performance

  • Document relationship cardinalities and whether they’re mandatory or optional

  • Review the model with domain experts before coding begins

Common Pitfalls

  • Forgetting to define nullable versus NOT NULL constraints properly

  • Overlooking many-to-many relationships when creating junction tables

  • Creating circular dependencies through bidirectional foreign keys

  • Including derived attributes that should be computed in queries instead

  • Ignoring soft delete patterns that preserve referential integrity

18

How would you explain the set of properties that ensure reliable transaction processing in a database?

ACID refers to four foundational properties—Atomicity, Consistency, Isolation, and Durability—that work together to guarantee that database transactions are processed reliably, even in the face of errors, crashes, or concurrent operations.

Read full answer

Applies toall versions

The Four Core Properties

  • Atomicity: A transaction is treated as a single, indivisible unit of work. Either every operation within it succeeds and is applied, or none are applied at all. If any part fails, the entire transaction is rolled back to its previous state.

  • Consistency: A transaction must move the database from one valid state to another, always adhering to defined rules such as constraints, triggers, data types, and referential integrity. This does not mean “correct” by business logic alone, but that all enforced database rules remain satisfied after the transaction.

  • Isolation: When multiple transactions run concurrently, each must appear as if it were executing alone. The degree of separation is controlled by isolation levels, and without proper isolation, issues such as dirty reads, non-repeatable reads, or phantom reads can occur.

  • Durability: Once a transaction has been committed, its changes are permanent and will survive system failures, such as power loss or crashes. This is typically achieved through mechanisms like write-ahead logging or transaction logs.

How These Properties Interact

These properties are not enforced in isolation. For example, atomicity relies heavily on rollback mechanisms, durability relies on persistent logging, and isolation is enforced by locking, multi-versioning, or a combination of both depending on the database engine. Together, they prevent partial updates, data corruption, and interference between concurrent users.

RDBMS Implementation Differences

  • PostgreSQL: Primarily uses Multi-Version Concurrency Control (MVCC), which allows readers and writers to operate with minimal locking. Durability is enforced through its Write-Ahead Log (WAL).

  • MySQL (InnoDB): Also uses MVCC alongside locking. Behavior can differ depending on storage engine, as MyISAM does not support transactions at all.

  • SQL Server: Uses a combination of locking and row versioning. Transaction logs are critical to both rollback and durability.

  • Oracle: Relies heavily on undo segments and MVCC, with strict enforcement of its transaction model.

  • SQLite: Supports ACID transactions, but its locking model is more coarse-grained (often database-wide locks), which can limit concurrency compared to server-based RDBMSs.

Practical Guidance

  • Use explicit transactions: For any operation that must be all-or-nothing, wrap related statements in a transaction boundary and commit only when fully valid.

  • Choose isolation with intent: Stronger isolation prevents more anomalies but can increase blocking. Weaker isolation improves concurrency but requires awareness of potential read anomalies.

  • Respect constraints: Consistency is only as strong as the rules you enforce. Rely on database constraints alongside validation, rather than assuming application checks alone are sufficient.

  • Plan for recovery: Durability is an engine guarantee, but testing recovery scenarios is still important in production environments.

  • Consider NULL semantics: Remember that NULL is unknown, not equal to anything. Comparisons with NULL behave differently and can affect consistency checks if not handled carefully.

19

In what manner do database indexes operate, and which categories of indexes are commonly used across major relational databases?

Database indexes are auxiliary data structures maintained separately from a table’s heap or clustered storage that allow an engine to locate qualifying rows without performing a full table scan. By narrowing the set of pages to read, they can reduce I/O for read-heavy lookups, but they require additional storage and impose overhead on data modification operations.

Read full answer

Applies toPostgreSQL 16, MySQL 8.4, SQL Server 2022, Oracle 23ai, SQLite 3.46 (core concepts apply to all; specific index types vary by dialect)

How It Works

  • Entry mapping: An index stores a copy of the indexed key values alongside a pointer (row identifier, tuple ID, or clustered key) that leads back to the full row.

  • Traversal over structure: Most general-purpose indexes use a balanced tree, allowing the engine to traverse down to matching entries instead of reading every row in the table.

  • Cardinality matters: The benefit is heavily tied to selectivity. A highly selective predicate (matching few rows) is far more likely to benefit from an index than a predicate that matches most of the table.

  • Cost trade-off: Every INSERT, UPDATE, or DELETE can require changes to affected indexes. For this reason, indexing everything is rarely optimal.

Common Index Types

Index Type Where Commonly Used Purpose Notes
B-tree PostgreSQL, MySQL, SQL Server, Oracle, SQLite Equality, range, > <, BETWEEN, and ordered scans. The most widely supported general-purpose type. Behavior with NULL differs by dialect and nulls placement settings.
Hash PostgreSQL (as an index access method for specific cases), MySQL (in some storage engines historically), others vary Exact equality lookups only. Not suitable for range predicates or ordering. Support and optimizer use are not universal.
Clustered SQL Server, Oracle (index-organized tables are a related concept), others differ Determines the physical order of table data itself. Only one per table in the strict clustered sense. Treating this as the same as a non-clustered index is incorrect across dialects.
Non-clustered SQL Server, MySQL (secondary indexes), others differ A separate structure pointing to the clustered key or row locator. May require a lookup back to the base row depending on whether all needed columns are present in the index.
GIN PostgreSQL Indexing composite values, such as arrays or full-text elements. Optimized for containment/search over multiple elements, not for simple range ordering in most use cases.
GiST PostgreSQL Geometric, range, and nearest-neighbor style searches. Extensible access method; applicable when the data has non-trivial ordering or overlap rules.
BRIN PostgreSQL Very large, naturally ordered tables (e.g. time-series). Stores summary ranges per block, so it is compact but far less precise than a B-tree.
Bitmap Oracle (with restrictions), not a mainstream default in PostgreSQL/MySQL/SQLite/modern SQL Server in the same form Combining many low-cardinality predicates. Often used in data warehousing scenarios. Usage is highly dependent on concurrency and workload.

NULL Semantics, Indexes, and Query Plans

  • NULL placement: NULL is not equal to NULL. Whether NULL keys are stored at the front, back, or excluded can differ by index type and database. Some indexes can be configured to treat NULLS FIRST or NULLS LAST, but support is not identical across all of the named databases.

  • Index usage is not guaranteed: The optimizer chooses access paths based on statistics. Even if an index exists, it may be ignored for small tables, stale statistics, functions applied to the indexed column, implicit type conversions, or predicates that don’t match the index key order.

  • Covering matters for lookups: If an index contains every column needed by the query, the engine may avoid reading the base table entirely. This depends on the dialect and the index definition, and should not be assumed for SELECT * (which is discouraged by subject rules).

  • Plans should be read, not guessed: To verify usage, inspect the actual query plan for your dialect. The plan will show scans, seeks, index lookups, and estimated costs, which are more reliable than assuming an index is used.

Advantages and Limitations

  • Advantages: Faster filtering, faster joins on indexed keys in suitable cases, support for enforced uniqueness in many dialects, and the ability to enforce ordering without a full sort when the plan can use the index.

  • Limitations: Increased disk usage, slower writes, potential index bloat over time, risk of redundant indexes, and possible query regression if statistics are outdated. A single wrong index can add maintenance with little benefit.

Best Practices

  • Index for predicates and joins: Prioritize columns used in WHERE, join conditions, and ORDER BY that are selective for your workload, rather than indexing every column.

  • Mind key order: For composite indexes, put the most selective or most frequently filtered leading columns first, as many access methods cannot fully use a composite index if the leading key isn’t used.

  • Account for NULLs explicitly: Know how your chosen dialect indexes NULL. If you need to find or exclude NULL efficiently, verify plan behavior for that dialect before relying on it.

  • Avoid broad selects: Do not rely on SELECT * to “just use an index”. Only project needed columns, and consider covering only when it is justified by measured plans.

  • Maintain statistics and review: Re-evaluate indexes as data and queries change. Unused or duplicate indexes are a common source of avoidable write overhead.

  • Respect dialect constraints: Do not assume hash, bitmap, GIN, GiST, or BRIN exist or behave identically. If a feature is unavailable in your dialect/version, use a supported B-tree-based approach or redesign the predicate instead.

20

Can you explain what materialized views are and how they contrast with regular views?

A materialized view is a database object that stores the result of a query physically on disk, unlike a standard view which is only a saved query definition executed each time it is referenced. Materialized views trade storage space and refresh overhead for faster read performance on complex aggregations or joins.

Read full answer

Applies toStandard SQL concept; implementations vary across PostgreSQL 16, Oracle 23c, SQL Server 2022 (indexed views), MySQL 8.0 (via tables), and SQLite (not natively supported).

How They Work

  • Standard view: A virtual table defined by a SELECT statement. The database rewrites queries against the view into queries against the base tables at execution time. No data is stored; the definition is stored in the catalog.

  • Materialized view: The defining query is executed once (or on a schedule) and its result set is persisted as a physical table. Subsequent queries read this precomputed data directly. The data becomes stale until the view is refreshed.

Refresh Strategies

Strategy Description Typical Use Case
Full refresh Re-executes the defining query and replaces all rows. Small datasets, infrequent changes, or when incremental logic is complex.
Incremental (fast) refresh Applies only the changes since the last refresh using materialized view logs or change tracking. Large datasets with frequent, small updates; requires base-table change capture.
On commit Refreshes automatically when a transaction modifying base tables commits. Near-real-time requirements; adds commit latency.
On demand / scheduled Manual REFRESH MATERIALIZED VIEW or cron/agent job. Batch reporting, data warehouses, acceptable staleness windows.

Key Differences

  • Storage: Standard views use negligible space (metadata only). Materialized views consume space proportional to their result set, plus indexes.

  • Read performance: Materialized views eliminate repeated computation of expensive joins, aggregations, or window functions. Standard views re-execute the full logic every time.

  • Write impact: Base-table modifications do not affect standard views. Materialized views require refresh operations that lock or block depending on the database and method (concurrent refresh in PostgreSQL avoids exclusive locks).

  • Data freshness: Standard views always reflect committed data. Materialized views show a snapshot as of the last refresh.

  • Indexing: Most systems allow indexes on materialized views (PostgreSQL, Oracle, SQL Server indexed views), enabling further query acceleration. Standard views cannot be indexed directly.

Database-Specific Notes

  • PostgreSQL: CREATE MATERIALIZED VIEW name AS SELECT ... with REFRESH MATERIALIZED VIEW [CONCURRENTLY] name. Supports unique indexes for concurrent refresh.

  • Oracle: CREATE MATERIALIZED VIEW ... BUILD IMMEDIATE REFRESH FAST ON COMMIT with materialized view logs on base tables for incremental refresh.

  • SQL Server: Implemented as indexed views — a standard view with a unique clustered index (CREATE UNIQUE CLUSTERED INDEX ... ON view_name). The view becomes materialized automatically; refresh is synchronous with base-table writes.

  • MySQL: No native materialized view. Common pattern: a summary table populated by triggers, events, or application logic.

  • SQLite: Not supported; use a regular table maintained by application code or triggers.

When to Use Which

  • Standard view: Simple projections, security abstraction, or when data must always be current and the query is cheap.

  • Materialized view: Expensive aggregations over large fact tables, pre-joined star-schema snapshots, dashboard queries with sub-second SLA, or cross-database reporting where base tables are remote.

Best Practices

  • Define refresh cadence aligned with business tolerance for staleness; document it in the data dictionary.

  • Index materialized views on the columns used in dashboard WHERE and JOIN clauses.

  • Monitor refresh duration and failures; long refreshes can cause lock contention or miss scheduling windows.

  • Consider partitioning the materialized view (where supported) to enable partition-wise refresh and faster truncate/reload cycles.

  • Avoid nesting materialized views on top of each other unless refresh order is strictly managed; dependency chains complicate scheduling and increase staleness.

Sign in to keep reading

You've opened 2 full answers. Sign in to read all 20 SQL answers, mark what you know and pick up where you left off.

All Web and Mobile Development subjects