JOIN vs Subquery
Answer
Imagine you are compiling a comprehensive corporate directory. You can either lay out a master spreadsheet by matching two files side-by-side using an employee ID, or write a script that pauses at every individual row to look up information from a completely nested file.
A JOIN explicitly combines rows from two or more tables based on a related column between them. It is generally preferred by relational engine optimizers because it evaluates data sets simultaneously, leading to faster data retrieval speeds.
A Subquery is an entire query nested tightly inside another outer query. It can sit comfortably in the WHERE, FROM, or SELECT clauses, acting as a stepping stone that generates standalone single values or temporary intermediate tables.
Example:
A classic JOIN looks like this:
SELECT e.name, d.department_name
FROM employees e
JOIN departments d
ON e.department_id = d.department_id;
Interview Tip: Reach for a JOIN as your standard choice when combining matching tables for speed, and turn to Subqueries when nested isolation or intermediate calculations make the code cleaner to read.
EXISTS vs JOIN
Answer
Imagine a manager checking a vendor list. The manager can either read the entire contract spreadsheet line-by-line to match all details, or simply skim down the list and stop the very second they confirm a vendor exists, ignoring all remaining redundant entries.
EXISTS is a high-speed logic check that evaluates whether matching rows exist in a subquery. The moment it locates a single matching record, it immediately halts its search, returns a simple TRUE or FALSE, and moves on to the next row.
A JOIN is a thorough combination tool that physically fuses matching rows from different tables together. It maps out duplicate matching records and returns complete columns from both datasets.
Example:
Using EXISTS to pull active departments looks like this:
SELECT *
FROM departments d
WHERE EXISTS (
SELECT 1
FROM employees e
WHERE e.department_id = d.department_id
);
Interview Tip: Deploy EXISTS when you only need to run a quick checklist pass for presence, and stick to a JOIN when you actually need to select and print out data columns from both tables.
LEFT JOIN vs NOT EXISTS (Anti Join)
Answer
Example: Think about generating an urgent report of customers who have never placed a single order. You can either pull every single customer record and filter out the ones where order details turn up completely blank, or run a check that drops a customer row the moment an order history is spotted.
A LEFT JOIN returns all rows from the primary left table along with matching rows from the right table. If there is no match, the right side simply populates with blank NULL values, forcing you to filter them out manually in a WHERE clause.
NOT EXISTS acts as a true anti-join. It scans the secondary table and drops rows from your main query immediately if a match is found, returning only the isolated records that have no related rows at all.
Example:
An optimal anti-join using NOT EXISTS looks like this:
SELECT *
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
);
Interview Tip: Prefer NOT EXISTS for finding missing relationships because modern database engines optimize it as a fast anti-join, outperforming a standard LEFT JOIN with a NULL check.
CROSS APPLY vs OUTER APPLY (SQL Server)
Answer
Imagine you are matching a master list of departments with a specialized function that calculates the top three highest-paid employees for each group. You must decide whether to completely hide departments that have zero employees.
CROSS APPLY functions like a strict inner join within SQL Server. It evaluates a table-valued function or correlated subquery for every row of the outer table, completely hiding the outer row if the applied function returns an empty set.
OUTER APPLY behaves exactly like a left outer join. It retains every single row from the outer table, and if the applied function returns an empty set, the resulting columns simply present as blank NULL values.
Interview Tip: Use the APPLY operators in SQL Server when you need to join a standard table to a dynamic table-valued function or a complex correlated subquery that must calculate data row-by-row.
JOIN vs UNION
Answer
Imagine building a data presentation. You can either snap two different sheets together side-by-side to expand your columns, or stack a second set of rows directly underneath your first sheet to lengthen your total list.
A JOIN expands your data table horizontally by connecting related columns from different tables based on a shared relationship or common key.
A UNION appends your data table vertically by stacking the result rows of multiple SELECT statements together. This requires both queries to share the exact same number of columns and matching data types.
Interview Tip: Remember the layout geometry: a JOIN adds columns horizontally to show relationships, while a UNION adds rows vertically to aggregate similar lists.
Subquery vs Correlated Subquery
Answer
Imagine searching through employee folders to find individuals who earn above average. You can either calculate the overall company average once and use that static number as a benchmark, or open every single folder, look at the department name, and run a brand new average calculation for that specific department group over and over again.
A standard Subquery is completely independent of the outer query. It runs exactly once, gathers its static data, and hands that fixed result set directly over to the main query to use.
A Correlated Subquery is deeply intertwined with the outer query. It forces the database engine to execute the inner query repeatedly—once for every single row evaluated by the outer query—because it references a column belonging to that outer row.
Example:
Finding employees earning more than their department average looks like this:
SELECT *
FROM employees e
WHERE salary >
(
SELECT AVG(salary)
FROM employees
WHERE department_id = e.department_id
);
Interview Tip: Correlated subqueries are incredibly powerful for row-by-row context logic, but they can be slow. Consider refactoring them into joins or window functions to boost query speeds.
CTE vs Subquery
Answer
Example: Think about organizing a massive, multi-step query. You can either cleanly define a named temporary result set right at the top of your script to reference later, or bundle nested queries inside clauses like a set of Russian nesting dolls.
A CTE or Common Table Expression is a named, temporary result set defined right before your main query blocks. It dramatically improves code readability, acts like a virtual temporary table, and can even reference itself or be pulled into your query multiple times.
A Subquery is a query tucked inside another query statement. Because it is embedded inline, it is generally written to be used once, and nesting multiple subqueries deep can quickly make code unreadable.
Interview Tip: Reach for CTEs as your default choice to break down complex queries, keep code maintainable, or build recursive hierarchies.
Recursive CTE vs Normal CTE
Answer
Imagine analyzing a company's data architecture. You can either build a single view to gather a quick sum of today's sales transactions, or construct a loop that starts at the CEO and automatically drills down through every lower management tier until it maps the entire corporate tree.
A Normal CTE is a linear tool that executes exactly once to clean up and simplify a query layout.
A Recursive CTE is an advanced programming structure that references its own definition in a loop. It combines a base anchor query with a recursive query to continuously dig through deeply nested, hierarchical data relationships.
Interview Tip: Turn to normal CTEs to organize standard query logic, and pull out recursive CTEs when traversing organizational charts, deep category folders, or complex bill-of-materials structures.
View vs CTE
Answer
Imagine storing a customized report layout. You can either register it permanently in the database catalog so any application can call it tomorrow, or write a quick scratchpad expression that vanishes into thin air the exact millisecond your script finishes running.
A View is a permanent database object saved directly into the system schema. It stores a query definition that acts exactly like a reusable virtual table, making it accessible to multiple users and applications across the entire system.
A CTE lives strictly in the moment. It is a temporary inline structure that exists solely during the lifecycle of that single query execution block and cannot be called or reused anywhere else once the query completes.
Interview Tip: Save your query logic as a View if it needs to be shared across multiple scripts and applications, and write a quick CTE if it is a temporary helper layout for a single query.
View vs Materialized View
Answer
Example: Think about checking a live scoreboard. You can either rerun a live camera feed to calculate the current scores fresh every time you look, or take a physical snapshot of the board and look at that paper copy, updating it only when changes occur.
A standard View is a simple shortcut definition. It stores zero actual data on disk. Every single time you run a query against a view, it fires up the underlying SQL code fresh, ensuring you see the absolute latest live table data.
A Materialized View physicalizes its data. It runs the underlying query code in advance and physically saves those rows onto disk storage. This results in blistering performance for heavy analytical reports, but it requires a periodic refresh schedule to keep the data from growing stale.
Interview Tip: Deploy standard Views for normal operational workflows where data must be live, and implement Materialized Views to speed up heavy, slow data warehousing reports.
RANK() vs DENSE_RANK() vs ROW_NUMBER()
Answer
Imagine ranking runners crossing a finish line where two athletes tie for second place. You must decide how to award the ranks and what number the next runner receives.
ROW_NUMBER assigns a completely unique, sequential integer to every single row regardless of duplicate values, meaning it never skips a number and never repeats.
RANK recognizes ties and awards them the exact same position number, but it leaves a gap in the sequence afterward to account for the duplicate places.
DENSE_RANK also recognizes ties and awards them the same position number, but it keeps the sequence tightly packed together, leaving absolutely zero numerical gaps for the next runner in line.
Example:
For a set of test scores like 100, 90, 90, and 80:
- ROW_NUMBER outputs: 1, 2, 3, 4
- RANK outputs: 1, 2, 2, 4
- DENSE_RANK outputs: 1, 2, 2, 3
Interview Tip: Use ROW_NUMBER for pagination or breaking ties arbitrarily, select RANK for standard competition logic, and choose DENSE_RANK when you want clean, consecutive rank groups.
COUNT(*) vs COUNT(column)
Answer
Example: You have a customer list containing exactly one hundred rows, but ten customers chose to leave their phone number field completely blank.
COUNT(*) is a broad row counter. It targets the physical row structure itself, counting every single record that matches your query criteria regardless of whether individual columns contain empty or NULL values.
COUNT(column) is a specific value counter. It looks exclusively inside the specified column and counts only the rows where an actual, valid value exists, completely ignoring any rows where that column is marked as NULL.
Example:
In a table with 100 total rows and 10 NULL values:
- COUNT(*) returns 100
- COUNT(salary) returns 90
Interview Tip: Use COUNT(*) to get the true total size of a dataset, and use COUNT(column) when you specifically need to know how many rows actually populated a particular piece of data.
COUNT(*) vs COUNT(1)
Answer
Imagine standing at a turnstile counting people entering a park. You can either count the entire physical human body as it passes through, or drop a token into a bucket for every single person who walks by and count the tokens instead.
COUNT(*) is the official ANSI SQL standard method for counting database rows. It instructs the database engine to return a count of all rows in the results.
COUNT(1) passes a constant literal value of 1 for every single row encountered, aggregating those placeholder values to produce the exact same final row count.
Interview Tip: In legacy database systems, developers used COUNT(1) thinking it bypassed full table scans to run faster. In modern database optimizers, both commands are translated into the exact same execution plan. Stick to COUNT(*) because it is the standard and reads cleanly.
COALESCE() vs ISNULL()
Answer
Example: A user profile allows entries for a home phone, a cell phone, and a work phone. You need a fallback script that checks these inputs in order and outputs a generic text warning if all three fields were left completely blank.
COALESCE is an ANSI SQL standard function that accepts an unlimited list of arguments. It evaluates them sequentially from left to right and immediately returns the very first non-NULL value it encounters. Because it is an industry standard, it runs seamlessly across almost all database platforms.
ISNULL is a proprietary function built specifically for Microsoft SQL Server. It is rigid, accepting exactly two arguments, and simply replaces the first value with the second value if the first happens to turn up NULL.
Example:
An elegant multi-column fallback using COALESCE looks like this:
COALESCE(phone, mobile, 'Not Available')
Interview Tip: Lean on COALESCE for portable, platform-agnostic code and when you need to evaluate multiple fallback variables in a single expression.
COALESCE() vs NVL()
Answer
Example: Think about building a billing system where you look up a customer's secondary contact phone number, and if that is missing, you look for a primary phone, and if that is also missing, you fall back to printing a default corporate office number.
COALESCE is an open ANSI SQL standard function designed to accept a long chain of multiple arguments, scanning down the list to return the first value that is not blank. It is universally supported by almost all major relational databases.
NVL is a proprietary, dedicated function built strictly for Oracle databases. It is a simple two-argument switcher that swaps out a single column value with a default fallback if a NULL value is detected.
Example:
- COALESCE(phone, mobile, office_phone, 'NA') handles multiple layers of fallbacks.
- NVL(phone, 'NA') handles a basic single column fallback in Oracle.
Interview Tip: Stick to COALESCE to ensure your SQL scripts remain highly portable across PostgreSQL, MySQL, and SQL Server, and deploy NVL when writing code specifically optimized for Oracle ecosystems.
Premium Content
Unlock Comparison Scenarios - Part 2 and all premium lessons with a subscription.
All premium lessons
Ad-free experience
Priority support
From ₹199.99/year — See plans