SQL Interview Questions with Answers
Most Asked SQL Interview Questions for Database Engineers
Introduction
SQL is the backbone of data management – a powerful language for querying, updating, and administering relational databases. This page brings together the most frequently asked SQL interview questions, from basic SELECT statements and joins to advanced window functions, CTEs, performance tuning, and JSON handling – essential for any backend, data, or full‑stack developer.
Why SQL?
- Universal standard for relational databases
- Essential for back‑end and full‑stack development
- Data manipulation, definition, and control in one language
- Powerful analytical capabilities with window functions and aggregations
- Critical for data engineering and business intelligence
- High demand in every technology company
Most Asked SQL Interview Questions
SQL (Structured Query Language) is a domain-specific language used for managing and manipulating relational databases.
- Data definition: CREATE, ALTER, DROP
- Data manipulation: SELECT, INSERT, UPDATE, DELETE
- Data control: GRANT, REVOKE
- Transaction control: COMMIT, ROLLBACK, SAVEPOINT
- Relational: Tables, relationships, constraints
-- Hello World in SQL
SELECT 'Hello, World!' AS greeting;SQL variables can be user-defined, system, or local variables within stored procedures.
- User variables:
@variable_name - System variables:
@@variable_name - Local variables:
DECLARE var_name - Assignment:
SET @var = value - SELECT INTO:
SELECT column INTO @var
-- Variables in SQL
-- User-defined variables
SET @mutableVar = 'Hello';
SET @immutableVar = 'World';
-- System variables
SELECT @@version;
SELECT @@autocommit;
-- Local variables in stored procedures
DELIMITER //
CREATE PROCEDURE demoVariables()
BEGIN
DECLARE localVar VARCHAR(20) DEFAULT 'Local';
SELECT localVar;
END//
DELIMITER ;
-- Display
SELECT @mutableVar;
SELECT @immutableVar;SQL provides various data types including numeric, string, date/time, JSON, and spatial types.
- Numeric: INT, TINYINT, DECIMAL, FLOAT, DOUBLE
- String: CHAR, VARCHAR, TEXT, BLOB
- Date/Time: DATE, DATETIME, TIMESTAMP, TIME, YEAR
- JSON: JSON data type (5.7+)
- Spatial: POINT, LINESTRING, POLYGON
- Enum/Set: ENUM, SET
-- Data Types in SQL
-- Numeric types
CREATE TABLE data_types (
int_col INT,
tinyint_col TINYINT,
smallint_col SMALLINT,
mediumint_col MEDIUMINT,
bigint_col BIGINT,
decimal_col DECIMAL(10,2),
float_col FLOAT,
double_col DOUBLE,
-- String types
char_col CHAR(10),
varchar_col VARCHAR(100),
text_col TEXT,
blob_col BLOB,
-- Date and time
date_col DATE,
datetime_col DATETIME,
timestamp_col TIMESTAMP,
time_col TIME,
year_col YEAR,
-- Boolean (TINYINT)
is_active BOOLEAN,
-- JSON
json_col JSON,
-- Enum
status ENUM('active', 'inactive', 'pending')
);
-- Type checking
SELECT
COLUMN_NAME,
DATA_TYPE,
CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'data_types';SQL functions are stored routines that return a single value and can be used in SQL statements.
- CREATE FUNCTION:
CREATE FUNCTION name(params) RETURNS type - DETERMINISTIC: Returns same result for same inputs
- READS SQL DATA: Indicates data reading
- Return value: Must return a value
- Usage:
SELECT function_name()
-- Functions in SQL
-- Basic function
DELIMITER //
CREATE FUNCTION add_numbers(a INT, b INT)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN a + b;
END//
DELIMITER ;
-- Function with default parameters
DELIMITER //
CREATE FUNCTION greet(name VARCHAR(100))
RETURNS VARCHAR(200)
DETERMINISTIC
BEGIN
IF name IS NULL THEN
SET name = 'Guest';
END IF;
RETURN CONCAT('Hello, ', name, '!');
END//
DELIMITER ;
-- Function with multiple statements
DELIMITER //
CREATE FUNCTION divide_numbers(a INT, b INT)
RETURNS VARCHAR(100)
DETERMINISTIC
BEGIN
DECLARE result VARCHAR(100);
IF b = 0 THEN
SET result = 'Division by zero';
ELSE
SET result = CONCAT('Quotient: ', a DIV b, ', Remainder: ', a MOD b);
END IF;
RETURN result;
END//
DELIMITER ;
-- Usage
SELECT add_numbers(5, 3);
SELECT greet('Alice');
SELECT divide_numbers(10, 3);SQL doesn't have built-in arrays. Alternatives include JSON arrays, temporary tables, or multiple columns.
- JSON arrays:
JSON_ARRAY(),JSON_EXTRACT() - Temporary tables: Session-specific tables
- Separate tables: Normalized relationships
- JSON functions:
JSON_LENGTH(),JSON_CONTAINS() - Full-text search: For text arrays
-- Arrays in SQL
-- SQL doesn't have arrays, but we can use JSON or temporary tables
-- Using JSON arrays
CREATE TABLE json_demo (
id INT PRIMARY KEY,
numbers JSON
);
INSERT INTO json_demo VALUES (1, '[1, 2, 3, 4, 5]');
INSERT INTO json_demo VALUES (2, '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]');
-- Access array elements
SELECT
id,
JSON_EXTRACT(numbers, '$[2]') AS third_element,
JSON_LENGTH(numbers) AS array_length
FROM json_demo;
-- Using temporary table as array
CREATE TEMPORARY TABLE temp_array (
id INT AUTO_INCREMENT PRIMARY KEY,
value INT
);
INSERT INTO temp_array (value) VALUES (1), (2), (3), (4), (5);
-- Iterate through array
SELECT value FROM temp_array ORDER BY id;
-- JSON array functions
SELECT
JSON_ARRAY(1, 2, 3, 4, 5) AS numbers,
JSON_ARRAY_APPEND('[1,2,3]', '$', 4) AS appended,
JSON_ARRAY_INSERT('[1,2,3]', '$[1]', 99) AS inserted,
JSON_REMOVE('[1,2,3,4]', '$[2]') AS removed;SQL uses tables as collections. Rows are documents/records, and columns are fields.
- Tables: Collection of rows
- Rows: Individual records
- Columns: Fields/attributes
- JSON collections: JSON data type for flexible schemas
- Views: Virtual collections
-- Collections in SQL
-- Tables as collections
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
age INT,
city VARCHAR(100)
);
-- Insert documents (rows)
INSERT INTO users (name, age, city) VALUES
('Alice', 25, 'NYC'),
('Bob', 30, 'LA'),
('Charlie', 35, 'Chicago');
-- Query all
SELECT * FROM users;
-- Query with filter
SELECT * FROM users WHERE age > 25;
-- Update
UPDATE users SET age = 26 WHERE name = 'Alice';
-- Delete
DELETE FROM users WHERE name = 'Bob';
-- Count
SELECT COUNT(*) FROM users WHERE age > 25;
-- JSON collections
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT,
items JSON,
total DECIMAL(10,2)
);
INSERT INTO orders (customer_id, items, total) VALUES
(1, '[{"product": "A", "qty": 2}, {"product": "B", "qty": 1}]', 30.00);
SELECT
id,
JSON_EXTRACT(items, '$[0].product') AS first_product
FROM orders;Tables serve as data classes in SQL, defining the structure of records with columns and constraints.
- Table definition:
CREATE TABLE - Constraints: PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK
- Default values:
DEFAULTclause - Auto-increment:
AUTO_INCREMENT - Views: Virtual tables
-- Data Classes (Tables as Objects)
-- Creating a table as a data class
CREATE TABLE person (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
age INT CHECK (age >= 0),
city VARCHAR(100) DEFAULT 'Unknown',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert data
INSERT INTO person (name, age, city) VALUES
('Alice', 25, 'NYC'),
('Bob', 30, 'LA');
-- Query
SELECT * FROM person WHERE id = 1;
-- Update
UPDATE person SET age = 26 WHERE id = 1;
-- Delete
DELETE FROM person WHERE id = 2;
-- Copy
CREATE TABLE person_backup AS SELECT * FROM person;
-- Struct-like views
CREATE VIEW person_view AS
SELECT
id,
name,
age,
city,
CONCAT(name, ' (', age, ')') AS display_name
FROM person;Schema validation enforces data integrity through constraints and data types. SQL 8.0+ supports JSON schema validation.
- Constraints: NOT NULL, UNIQUE, CHECK, FOREIGN KEY
- Data types: Define allowed data formats
- JSON validation:
JSON_SCHEMA_VALID() - Triggers: Custom validation logic
- Stored procedures: Complex validation
-- Schema Validation in SQL
-- MySQL 8.0+ supports JSON schema validation
-- Create table with JSON validation
CREATE TABLE validated_users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
age INT CHECK (age >= 0 AND age <= 150),
status ENUM('active', 'inactive', 'pending') DEFAULT 'pending',
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Unique constraint
CONSTRAINT unique_email UNIQUE (email)
);
-- Check constraints
ALTER TABLE validated_users
ADD CONSTRAINT check_age_positive
CHECK (age >= 0);
-- JSON schema validation (MySQL 8.0+)
ALTER TABLE validated_users
ADD CONSTRAINT valid_metadata
CHECK (JSON_SCHEMA_VALID(
'{
"type": "object",
"properties": {
"department": {"type": "string"},
"role": {"type": "string"}
},
"required": ["department"]
}',
metadata
));
-- Test validation
INSERT INTO validated_users (name, email, age, metadata)
VALUES ('Alice', 'alice@example.com', 25, '{"department": "IT"}');
-- This will fail validation
INSERT INTO validated_users (name, email, age, metadata)
VALUES ('Bob', 'bob@example.com', 30, '{"role": "admin"}');SQL uses NULL to represent missing data. Various functions and operators handle NULL values safely.
- IS NULL: Check for NULL values
- COALESCE: First non-NULL value
- IFNULL: SQL-specific NULL handling
- NULLIF: Returns NULL if values equal
- NOT NULL: Constraint for non-null columns
-- Null Safety in SQL
-- Handling NULL values
-- NULL vs NOT NULL
CREATE TABLE null_demo (
id INT PRIMARY KEY,
nullable_field VARCHAR(100) NULL,
non_nullable_field VARCHAR(100) NOT NULL
);
-- Insert NULL
INSERT INTO null_demo (id, nullable_field, non_nullable_field)
VALUES (1, NULL, 'value');
-- Query NULL
SELECT * FROM null_demo WHERE nullable_field IS NULL;
SELECT * FROM null_demo WHERE nullable_field IS NOT NULL;
-- COALESCE - first non-NULL
SELECT
COALESCE(nullable_field, 'default') AS with_default
FROM null_demo;
-- IFNULL - MySQL specific
SELECT IFNULL(nullable_field, 'default') AS with_default FROM null_demo;
-- NULLIF - returns NULL if equal
SELECT NULLIF(1, 1); -- Returns NULL
SELECT NULLIF(1, 2); -- Returns 1
-- ISNULL function
SELECT ISNULL(nullable_field) FROM null_demo;
-- Handling NULL in aggregates
SELECT
COUNT(*), -- Counts all rows
COUNT(column), -- Counts non-NULL values
AVG(column), -- Ignores NULL values
SUM(column) -- Ignores NULL values
FROM null_demo;
-- Using DEFAULT
INSERT INTO null_demo (id, nullable_field)
VALUES (2, DEFAULT(non_nullable_field));SQL provides IF, CASE, WHILE, REPEAT, and LOOP statements for control flow in stored procedures.
- IF: Conditional execution
- CASE: Switch-like conditional
- WHILE: Loop with condition
- REPEAT: Loop with until condition
- LOOP: Infinite loop with LEAVE
-- Control Flow in SQL
-- IF statement in stored procedures
DELIMITER //
CREATE PROCEDURE check_age(IN age INT)
BEGIN
IF age < 18 THEN
SELECT 'Minor' AS status;
ELSE
SELECT 'Adult' AS status;
END IF;
END//
DELIMITER ;
-- IF function (in SELECT)
SELECT
name,
age,
IF(age < 18, 'Minor', 'Adult') AS status
FROM users;
-- CASE expression
SELECT
name,
age,
CASE
WHEN age < 18 THEN 'Minor'
WHEN age < 65 THEN 'Adult'
ELSE 'Senior'
END AS category
FROM users;
-- CASE with simple values
SELECT
name,
status,
CASE status
WHEN 'active' THEN 'Active'
WHEN 'inactive' THEN 'Inactive'
ELSE 'Unknown'
END AS status_description
FROM users;
-- WHILE loop
DELIMITER //
CREATE PROCEDURE while_demo()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 5 DO
SELECT i;
SET i = i + 1;
END WHILE;
END//
DELIMITER ;
-- REPEAT loop
DELIMITER //
CREATE PROCEDURE repeat_demo()
BEGIN
DECLARE i INT DEFAULT 1;
REPEAT
SELECT i;
SET i = i + 1;
UNTIL i > 5
END REPEAT;
END//
DELIMITER ;
-- LOOP with LEAVE
DELIMITER //
CREATE PROCEDURE loop_demo()
BEGIN
DECLARE i INT DEFAULT 1;
loop_label: LOOP
SELECT i;
SET i = i + 1;
IF i > 5 THEN
LEAVE loop_label;
END IF;
END LOOP;
END//
DELIMITER ;SQL doesn't support inheritance directly. Patterns like Single Table Inheritance, Class Table Inheritance, and Polymorphic Associations are used.
- Single Table Inheritance: One table with type column
- Class Table Inheritance: Separate tables with foreign keys
- Concrete Table Inheritance: Separate tables for each type
- Polymorphic Associations: Type and ID columns
- Views: Combine inherited data
-- Inheritance in SQL
-- SQL doesn't support inheritance, but patterns exist
-- Single Table Inheritance
CREATE TABLE animals (
id INT PRIMARY KEY AUTO_INCREMENT,
type VARCHAR(50),
name VARCHAR(100),
sound VARCHAR(100),
breed VARCHAR(100), -- For dogs
color VARCHAR(50) -- For cats
);
INSERT INTO animals (type, name, sound, breed) VALUES
('Dog', 'Rex', 'Woof!', 'German Shepherd');
INSERT INTO animals (type, name, sound, color) VALUES
('Cat', 'Whiskers', 'Meow!', 'Black');
-- Class Table Inheritance
CREATE TABLE persons (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(255)
);
CREATE TABLE employees (
person_id INT PRIMARY KEY,
employee_id VARCHAR(50),
department VARCHAR(100),
FOREIGN KEY (person_id) REFERENCES persons(id)
);
CREATE TABLE customers (
person_id INT PRIMARY KEY,
customer_id VARCHAR(50),
loyalty_points INT,
FOREIGN KEY (person_id) REFERENCES persons(id)
);
-- Concrete Table Inheritance
CREATE TABLE employees_detail (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
employee_id VARCHAR(50),
department VARCHAR(100)
);
CREATE TABLE customers_detail (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
customer_id VARCHAR(50),
loyalty_points INT
);
-- Polymorphic associations
CREATE TABLE comments (
id INT PRIMARY KEY AUTO_INCREMENT,
content TEXT,
commentable_type VARCHAR(50),
commentable_id INT
);Columns define the properties of records in a table. They have data types, constraints, and attributes.
- Data types: Define what data can be stored
- Constraints: PRIMARY KEY, NOT NULL, UNIQUE, CHECK
- Default values:
DEFAULTclause - Generated columns: Computed from other columns
- Indexes: Improve query performance
-- Properties (Columns) in SQL
-- Table with various column properties
CREATE TABLE products (
-- Primary key
id INT PRIMARY KEY AUTO_INCREMENT,
-- NOT NULL constraint
name VARCHAR(200) NOT NULL,
-- UNIQUE constraint
sku VARCHAR(50) UNIQUE NOT NULL,
-- Default value
price DECIMAL(10,2) DEFAULT 0.00,
-- Check constraint
quantity INT CHECK (quantity >= 0),
-- Foreign key
category_id INT,
-- Timestamps with automatic updates
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Index
INDEX idx_name (name),
-- Composite index
INDEX idx_category_price (category_id, price),
-- Full-text index
FULLTEXT INDEX ft_description (description),
-- Foreign key constraint
FOREIGN KEY (category_id) REFERENCES categories(id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
-- Computed columns (generated columns)
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(10,2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);
-- Virtual generated column
CREATE TABLE sales (
id INT PRIMARY KEY AUTO_INCREMENT,
amount DECIMAL(10,2) NOT NULL,
tax_rate DECIMAL(5,2) NOT NULL,
tax_amount DECIMAL(10,2) GENERATED ALWAYS AS (amount * tax_rate / 100) VIRTUAL
);Stored procedures are saved SQL code that can be called multiple times. They can have parameters and return result sets.
- CREATE PROCEDURE: Define procedure
- Parameters: IN, OUT, INOUT
- Transaction support: COMMIT, ROLLBACK
- Error handling: DECLARE ... HANDLER
- CALL: Execute procedure
-- Stored Procedures in SQL
-- Basic stored procedure
DELIMITER //
CREATE PROCEDURE get_users(IN city_param VARCHAR(100))
BEGIN
SELECT * FROM users WHERE city = city_param;
END//
DELIMITER ;
-- Stored procedure with multiple parameters
DELIMITER //
CREATE PROCEDURE get_users_by_age(
IN min_age INT,
IN max_age INT
)
BEGIN
SELECT * FROM users WHERE age BETWEEN min_age AND max_age;
END//
DELIMITER ;
-- Stored procedure with OUT parameters
DELIMITER //
CREATE PROCEDURE get_user_count(
IN city_param VARCHAR(100),
OUT user_count INT
)
BEGIN
SELECT COUNT(*) INTO user_count
FROM users
WHERE city = city_param;
END//
DELIMITER ;
-- Stored procedure with INOUT parameters
DELIMITER //
CREATE PROCEDURE increment_age(
INOUT age_param INT,
IN increment INT
)
BEGIN
SET age_param = age_param + increment;
END//
DELIMITER ;
-- Stored procedure with transaction
DELIMITER //
CREATE PROCEDURE transfer_funds(
IN from_account INT,
IN to_account INT,
IN amount DECIMAL(10,2)
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'Transaction failed' AS message;
END;
START TRANSACTION;
UPDATE accounts SET balance = balance - amount WHERE id = from_account;
UPDATE accounts SET balance = balance + amount WHERE id = to_account;
COMMIT;
SELECT 'Transaction successful' AS message;
END//
DELIMITER ;
-- Call procedures
CALL get_users('NYC');
CALL get_users_by_age(18, 30);
CALL get_user_count('NYC', @count);
SELECT @count;SQL handles exceptions using DECLARE ... HANDLER statements, SIGNAL for custom errors, and GET DIAGNOSTICS for error details.
- DECLARE HANDLER: Handle specific errors
- SIGNAL: Raise custom errors
- GET DIAGNOSTICS: Get error details
- EXIT HANDLER: Exit on error
- CONTINUE HANDLER: Continue after error
-- Exception Handling in SQL
-- MySQL 5.5+: DECLARE ... HANDLER
-- Basic exception handling
DELIMITER //
CREATE PROCEDURE safe_divide(
IN numerator INT,
IN denominator INT
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SELECT 'Error occurred' AS result;
END;
SELECT numerator / denominator AS result;
END//
DELIMITER ;
-- Specific error handling
DELIMITER //
CREATE PROCEDURE safe_insert(
IN name VARCHAR(100),
IN email VARCHAR(255)
)
BEGIN
DECLARE EXIT HANDLER FOR 1062 -- Duplicate entry error
BEGIN
SELECT 'Email already exists' AS error;
END;
INSERT INTO users (name, email) VALUES (name, email);
SELECT 'User inserted successfully' AS result;
END//
DELIMITER ;
-- Custom error messages
DELIMITER //
CREATE PROCEDURE validate_age(
IN age INT
)
BEGIN
IF age < 0 OR age > 150 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid age: Age must be between 0 and 150';
END IF;
SELECT 'Age is valid' AS result;
END//
DELIMITER ;
-- Using GET DIAGNOSTICS (MySQL 5.6+)
DELIMITER //
CREATE PROCEDURE get_error_info()
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
@sqlstate = RETURNED_SQLSTATE,
@errno = MYSQL_ERRNO,
@text = MESSAGE_TEXT;
SELECT @sqlstate, @errno, @text;
END;
-- This will cause an error
INSERT INTO nonexistent_table VALUES (1);
END//
DELIMITER ;SQL functions are stored routines that return a single value. They can be used in SELECT, WHERE, and other SQL clauses.
- CREATE FUNCTION: Define function
- RETURNS: Specify return type
- DETERMINISTIC: Same inputs, same output
- READS SQL DATA: Indicates data reading
- Usage:
SELECT function_name()
-- Functions in SQL (UDF)
-- User-defined functions
-- Simple function
DELIMITER //
CREATE FUNCTION square(x INT)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN x * x;
END//
DELIMITER ;
-- Function with multiple parameters
DELIMITER //
CREATE FUNCTION multiply(a INT, b INT)
RETURNS INT
DETERMINISTIC
BEGIN
RETURN a * b;
END//
DELIMITER ;
-- Function with default value
DELIMITER //
CREATE FUNCTION greet_user(name VARCHAR(100))
RETURNS VARCHAR(200)
DETERMINISTIC
BEGIN
IF name IS NULL THEN
RETURN 'Hello, Guest!';
END IF;
RETURN CONCAT('Hello, ', name, '!');
END//
DELIMITER ;
-- Function with validation
DELIMITER //
CREATE FUNCTION calculate_bonus(
salary DECIMAL(10,2),
performance_rating INT
)
RETURNS DECIMAL(10,2)
DETERMINISTIC
BEGIN
DECLARE bonus DECIMAL(10,2);
CASE performance_rating
WHEN 5 THEN SET bonus = salary * 0.20;
WHEN 4 THEN SET bonus = salary * 0.15;
WHEN 3 THEN SET bonus = salary * 0.10;
WHEN 2 THEN SET bonus = salary * 0.05;
ELSE SET bonus = 0;
END CASE;
RETURN bonus;
END//
DELIMITER ;
-- Usage
SELECT
square(5),
multiply(3, 4),
greet_user('Alice'),
calculate_bonus(50000, 4);Window functions perform calculations across a set of rows related to the current row. Available in SQL 8.0+.
- ROW_NUMBER: Sequential row number
- RANK: Rank with gaps
- DENSE_RANK: Rank without gaps
- LAG/LEAD: Previous/next row values
- SUM/AVG: Aggregate with window
-- Window Functions in SQL (8.0+)
-- Window functions for advanced analytics
-- ROW_NUMBER
SELECT
name,
age,
city,
ROW_NUMBER() OVER (PARTITION BY city ORDER BY age) AS row_num
FROM users;
-- RANK and DENSE_RANK
SELECT
name,
salary,
RANK() OVER (ORDER BY salary DESC) AS rank_position,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank_position
FROM employees;
-- LAG and LEAD
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS previous_amount,
LEAD(amount, 1) OVER (ORDER BY date) AS next_amount
FROM sales;
-- NTILE
SELECT
name,
salary,
NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM employees;
-- SUM with window
SELECT
name,
salary,
SUM(salary) OVER (ORDER BY salary) AS cumulative_salary
FROM employees;
-- Moving average
SELECT
date,
amount,
AVG(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3
FROM sales;
-- FIRST_VALUE and LAST_VALUE
SELECT
name,
department,
salary,
FIRST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC) AS highest_paid,
LAST_VALUE(name) OVER (PARTITION BY department ORDER BY salary DESC
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS lowest_paid
FROM employees;Common Table Expressions (CTEs) are temporary result sets that can be referenced within a query. Available in SQL 8.0+.
- WITH: Define CTE
- Recursive CTE: Self-referencing CTE
- Multiple CTEs: Define multiple in one query
- Reference: Use CTE name in query
- Hierarchical queries: Recursive for tree structures
-- Common Table Expressions (CTE) in SQL
-- CTE for complex queries
-- Simple CTE
WITH user_stats AS (
SELECT
city,
COUNT(*) AS user_count,
AVG(age) AS avg_age
FROM users
GROUP BY city
)
SELECT * FROM user_stats WHERE user_count > 5;
-- Recursive CTE (MySQL 8.0+)
WITH RECURSIVE numbers (n) AS (
SELECT 1 -- Anchor
UNION ALL
SELECT n + 1 -- Recursive
FROM numbers
WHERE n < 10
)
SELECT * FROM numbers;
-- Recursive CTE for hierarchical data
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
manager_id INT
);
INSERT INTO employees VALUES
(1, 'CEO', NULL),
(2, 'VP', 1),
(3, 'Manager', 2),
(4, 'Developer', 3),
(5, 'Developer', 3);
WITH RECURSIVE org_chart AS (
SELECT
id,
name,
manager_id,
0 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
SELECT
e.id,
e.name,
e.manager_id,
oc.level + 1
FROM employees e
INNER JOIN org_chart oc ON e.manager_id = oc.id
)
SELECT
CONCAT(REPEAT(' ', level), name) AS org_chart
FROM org_chart
ORDER BY level, id;
-- Multiple CTEs
WITH
stats AS (
SELECT
city,
COUNT(*) AS count,
AVG(age) AS avg_age
FROM users
GROUP BY city
),
ranked AS (
SELECT
city,
count,
avg_age,
RANK() OVER (ORDER BY count DESC) AS rank_position
FROM stats
)
SELECT * FROM ranked WHERE rank_position <= 5;Views are virtual tables based on a SELECT query. They provide a way to simplify complex queries and enforce security.
- CREATE VIEW: Define view
- Updatable views: Views that allow DML operations
- WITH CHECK OPTION: Enforce view conditions
- Algorithm: MERGE, TEMPTABLE, UNDEFINED
- Drop view:
DROP VIEW
-- Views in SQL
-- Views are virtual tables
-- Create simple view
CREATE VIEW active_users AS
SELECT id, name, email, age
FROM users
WHERE status = 'active';
-- Create view with JOIN
CREATE VIEW user_orders AS
SELECT
u.id AS user_id,
u.name AS user_name,
o.id AS order_id,
o.total AS order_total,
o.created_at AS order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id;
-- Create view with aggregation
CREATE VIEW city_stats AS
SELECT
city,
COUNT(*) AS user_count,
AVG(age) AS avg_age,
MIN(age) AS min_age,
MAX(age) AS max_age
FROM users
GROUP BY city;
-- Create view with algorithm
CREATE ALGORITHM = MERGE VIEW user_names AS
SELECT id, CONCAT(first_name, ' ', last_name) AS full_name
FROM users;
-- Create view with check option
CREATE VIEW ny_users AS
SELECT * FROM users WHERE city = 'NYC'
WITH CHECK OPTION;
-- Update data through view
UPDATE active_users SET age = 26 WHERE id = 1;
-- Drop view
DROP VIEW IF EXISTS active_users;
-- Show create view
SHOW CREATE VIEW user_orders;
-- Information about views
SELECT * FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'database_name';Indexes improve query performance by allowing faster data retrieval. They are created on columns used in WHERE, JOIN, and ORDER BY clauses.
- CREATE INDEX: Create index
- UNIQUE INDEX: Enforce uniqueness
- Composite index: Multiple columns
- FULLTEXT index: Full-text search
- SPATIAL index: Geospatial data
-- Indexes in SQL
-- Index types and usage
-- CREATE INDEX
CREATE INDEX idx_users_name ON users(name);
-- CREATE UNIQUE INDEX
CREATE UNIQUE INDEX idx_users_email ON users(email);
-- CREATE COMPOSITE INDEX
CREATE INDEX idx_users_city_age ON users(city, age);
-- CREATE FULLTEXT INDEX
CREATE FULLTEXT INDEX idx_posts_content ON posts(content);
-- CREATE SPATIAL INDEX
CREATE SPATIAL INDEX idx_locations_coords ON locations(coordinates);
-- CREATE INDEX WITH DESC
CREATE INDEX idx_users_name_desc ON users(name DESC);
-- Index on prefix
CREATE INDEX idx_users_name_prefix ON users(name(10));
-- DROP INDEX
DROP INDEX idx_users_name ON users;
-- Show indexes
SHOW INDEX FROM users;
-- Analyze table for index usage
ANALYZE TABLE users;
-- Optimize table
OPTIMIZE TABLE users;
-- Index usage with EXPLAIN
EXPLAIN SELECT * FROM users WHERE name = 'Alice';
-- Invisible index (MySQL 8.0+)
CREATE INDEX idx_invisible ON users(name) INVISIBLE;
-- Visible/invisible toggle
ALTER TABLE users ALTER INDEX idx_invisible VISIBLE;
ALTER TABLE users ALTER INDEX idx_invisible INVISIBLE;Triggers are stored programs that automatically execute in response to DML events (INSERT, UPDATE, DELETE) on a table.
- BEFORE/AFTER: Timing of execution
- INSERT/UPDATE/DELETE: Triggering event
- NEW/OLD: Access new and old row values
- Validation: Enforce business rules
- Audit logging: Track changes
-- Triggers in SQL
-- Triggers for automatic actions
-- BEFORE INSERT trigger
DELIMITER //
CREATE TRIGGER before_user_insert
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
SET NEW.created_at = NOW();
SET NEW.updated_at = NOW();
END//
DELIMITER ;
-- BEFORE UPDATE trigger
DELIMITER //
CREATE TRIGGER before_user_update
BEFORE UPDATE ON users
FOR EACH ROW
BEGIN
SET NEW.updated_at = NOW();
END//
DELIMITER ;
-- AFTER INSERT trigger for logging
DELIMITER //
CREATE TRIGGER after_user_insert
AFTER INSERT ON users
FOR EACH ROW
BEGIN
INSERT INTO audit_log (table_name, action, record_id, user_name, timestamp)
VALUES ('users', 'INSERT', NEW.id, NEW.name, NOW());
END//
DELIMITER ;
-- BEFORE DELETE trigger
DELIMITER //
CREATE TRIGGER before_user_delete
BEFORE DELETE ON users
FOR EACH ROW
BEGIN
INSERT INTO deleted_users (id, name, email, deleted_at)
VALUES (OLD.id, OLD.name, OLD.email, NOW());
END//
DELIMITER ;
-- Trigger with validation
DELIMITER //
CREATE TRIGGER validate_user_age
BEFORE INSERT ON users
FOR EACH ROW
BEGIN
IF NEW.age < 0 OR NEW.age > 150 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Invalid age';
END IF;
END//
DELIMITER ;
-- Show triggers
SHOW TRIGGERS;
SHOW TRIGGERS LIKE 'users%';
-- Drop trigger
DROP TRIGGER IF EXISTS after_user_insert;Stored procedures and functions are both stored routines, but they have different purposes and characteristics.
- Procedures: Can modify data, multiple parameters
- Functions: Return single value, called in SELECT
- Procedures: Can have OUT/INOUT parameters
- Functions: Must be deterministic for replication
- Procedures: Called with CALL, not in expressions
-- Stored Procedures vs Functions
-- Comparison and examples
-- Stored Procedure (can modify data)
DELIMITER //
CREATE PROCEDURE update_user_age(
IN user_id INT,
IN new_age INT
)
BEGIN
UPDATE users SET age = new_age WHERE id = user_id;
SELECT ROW_COUNT() AS rows_affected;
END//
DELIMITER ;
-- Function (must be deterministic, cannot modify data)
DELIMITER //
CREATE FUNCTION get_user_age(user_id INT)
RETURNS INT
DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE user_age INT;
SELECT age INTO user_age FROM users WHERE id = user_id;
RETURN user_age;
END//
DELIMITER ;
-- Procedure with multiple outputs
DELIMITER //
CREATE PROCEDURE get_user_stats(
IN user_id INT,
OUT user_name VARCHAR(100),
OUT user_age INT,
OUT user_city VARCHAR(100)
)
BEGIN
SELECT name, age, city
INTO user_name, user_age, user_city
FROM users
WHERE id = user_id;
END//
DELIMITER ;
-- Calling procedures
CALL update_user_age(1, 26);
CALL get_user_stats(1, @name, @age, @city);
SELECT @name, @age, @city;
-- Using functions
SELECT get_user_age(1) AS age;Transactions ensure atomicity of database operations. They group multiple statements into a single unit of work.
- START TRANSACTION: Begin transaction
- COMMIT: Save changes permanently
- ROLLBACK: Undo changes
- SAVEPOINT: Partial rollback
- Isolation levels: Control concurrency
-- Transactions in SQL
-- ACID properties and transaction control
-- Basic transaction
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Transaction with rollback
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Check balance
SELECT balance FROM accounts WHERE id = 1;
IF balance < 0 THEN
ROLLBACK;
SELECT 'Insufficient balance' AS result;
ELSE
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
SELECT 'Transfer successful' AS result;
END IF;
-- SAVEPOINT
START TRANSACTION;
INSERT INTO orders (user_id, total) VALUES (1, 100);
SAVEPOINT order_inserted;
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (LAST_INSERT_ID(), 1, 2);
-- If order item fails, rollback to savepoint
ROLLBACK TO order_inserted;
COMMIT;
-- Transaction isolation levels
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- Locking reads
START TRANSACTION;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 1;
COMMIT;
-- Shared lock
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;User-defined variables are session-specific variables that can store values for use in statements and queries.
- @variable: Session scope
- SET: Assign value
- SELECT INTO: Assign from query
- SQL statements: Use in any statement
- Limitations: Session only, no persistence
-- User-Defined Variables in SQL
-- Session-level variables
-- Set variables
SET @var1 = 10;
SET @var2 := 20; -- Alternative syntax
SELECT @var3 := 30; -- Set from SELECT
-- Use variables in queries
SET @min_age = 18;
SET @max_age = 30;
SELECT * FROM users WHERE age BETWEEN @min_age AND @max_age;
-- Variables from SELECT
SELECT COUNT(*) INTO @user_count FROM users;
SELECT @user_count;
-- Multiple variables
SELECT name, age INTO @name, @age FROM users WHERE id = 1;
SELECT @name, @age;
-- Variables in LIMIT
SET @offset = 0;
SET @limit = 10;
PREPARE stmt FROM 'SELECT * FROM users LIMIT ?, ?';
EXECUTE stmt USING @offset, @limit;
-- Session vs global variables
SET SESSION sort_buffer_size = 1024;
SET GLOBAL max_connections = 1000;
-- System variables
SELECT @@global.max_connections;
SELECT @@session.autocommit;
-- Show all variables
SHOW VARIABLES;
SHOW SESSION VARIABLES;
SHOW GLOBAL VARIABLES;Prepared statements allow parameterized queries for better performance and SQL injection prevention.
- PREPARE: Prepare statement
- EXECUTE: Execute with parameters
- DEALLOCATE: Free resources
- Dynamic SQL: Build queries at runtime
- Performance: Reuse execution plan
-- Prepared Statements in SQL
-- SQL injection prevention and performance
-- Prepare statement
PREPARE stmt1 FROM 'SELECT * FROM users WHERE id = ?';
SET @user_id = 1;
EXECUTE stmt1 USING @user_id;
DEALLOCATE PREPARE stmt1;
-- Multiple parameters
PREPARE stmt2 FROM 'SELECT * FROM users WHERE age BETWEEN ? AND ?';
SET @min_age = 18;
SET @max_age = 30;
EXECUTE stmt2 USING @min_age, @max_age;
DEALLOCATE PREPARE stmt2;
-- Dynamic table name (not directly possible, use CONCAT)
SET @table_name = 'users';
SET @query = CONCAT('SELECT * FROM ', @table_name, ' WHERE id = ?');
PREPARE stmt3 FROM @query;
SET @user_id = 1;
EXECUTE stmt3 USING @user_id;
DEALLOCATE PREPARE stmt3;
-- Dynamic ORDER BY
SET @order_by = 'name';
SET @direction = 'DESC';
SET @query = CONCAT('SELECT * FROM users ORDER BY ', @order_by, ' ', @direction);
PREPARE stmt4 FROM @query;
EXECUTE stmt4;
DEALLOCATE PREPARE stmt4;
-- Using with stored procedures
DELIMITER //
CREATE PROCEDURE dynamic_query(
IN table_name VARCHAR(100),
IN column_name VARCHAR(100)
)
BEGIN
SET @query = CONCAT('SELECT ', column_name, ' FROM ', table_name);
PREPARE stmt FROM @query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END//
DELIMITER ;
CALL dynamic_query('users', 'name');Partitioning divides a table into smaller pieces for better performance and management. SQL supports RANGE, LIST, HASH, and KEY partitioning.
- RANGE: Partition by value ranges
- LIST: Partition by list of values
- HASH: Partition by hash function
- KEY: Partition by SQL hash function
- Subpartitioning: Nested partitions
-- Partitioning in SQL
-- Table partitioning for performance
-- Range partitioning
CREATE TABLE orders_partitioned (
id INT,
order_date DATE,
customer_id INT,
amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022),
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- List partitioning
CREATE TABLE users_by_region (
id INT,
name VARCHAR(100),
region VARCHAR(50)
)
PARTITION BY LIST (region) (
PARTITION p_north VALUES IN ('North', 'Northeast'),
PARTITION p_south VALUES IN ('South', 'Southeast'),
PARTITION p_west VALUES IN ('West', 'Southwest'),
PARTITION p_east VALUES IN ('East', 'Midwest')
);
-- Hash partitioning
CREATE TABLE logs (
id INT,
log_data TEXT,
created_at DATETIME
)
PARTITION BY HASH (id)
PARTITIONS 4;
-- Key partitioning (similar to hash but uses MySQL's hash function)
CREATE TABLE sessions (
id INT,
session_id VARCHAR(255),
data TEXT
)
PARTITION BY KEY (id)
PARTITIONS 4;
-- Subpartitioning
CREATE TABLE sales (
id INT,
sale_date DATE,
amount DECIMAL(10,2),
region VARCHAR(50)
)
PARTITION BY RANGE (YEAR(sale_date))
SUBPARTITION BY HASH (id)
SUBPARTITIONS 2 (
PARTITION p2020 VALUES LESS THAN (2021),
PARTITION p2021 VALUES LESS THAN (2022)
);
-- Query partition information
SELECT * FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_NAME = 'orders_partitioned';
-- Partition pruning
EXPLAIN SELECT * FROM orders_partitioned
WHERE order_date BETWEEN '2021-01-01' AND '2021-12-31';
-- Add partition
ALTER TABLE orders_partitioned
ADD PARTITION (PARTITION p2024 VALUES LESS THAN (2025));
-- Drop partition
ALTER TABLE orders_partitioned DROP PARTITION p_future;
-- Reorganize partitions
ALTER TABLE orders_partitioned
REORGANIZE PARTITION p_future INTO (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p_future VALUES LESS THAN MAXVALUE
);Replication copies data from one SQL server to another. It provides high availability, read scaling, and backup capabilities.
- Master-slave: One master, multiple slaves
- Master-master: Bidirectional replication
- Binary log: Records changes
- GTID: Global transaction identifiers
- Semi-sync: Acknowledgment from slaves
-- Replication in SQL
-- Master-slave replication concepts
-- On Master server
-- Enable binary log
-- my.cnf:
-- server-id = 1
-- log-bin = mysql-bin
-- binlog-do-db = mydatabase
-- Create replication user
CREATE USER 'replication'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%';
-- Get master status
SHOW MASTER STATUS;
-- On Slave server
-- my.cnf:
-- server-id = 2
-- relay-log = mysql-relay-bin
-- Configure slave
CHANGE MASTER TO
MASTER_HOST = 'master_host',
MASTER_USER = 'replication',
MASTER_PASSWORD = 'password',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 123;
-- Start slave
START SLAVE;
-- Check slave status
SHOW SLAVE STATUSG
-- Stop slave
STOP SLAVE;
-- Reset slave
RESET SLAVE ALL;
-- Multi-source replication (MySQL 5.7+)
CHANGE MASTER TO
MASTER_HOST = 'master1_host',
MASTER_USER = 'replication',
MASTER_PASSWORD = 'password',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 123
FOR CHANNEL 'channel1';
CHANGE MASTER TO
MASTER_HOST = 'master2_host',
MASTER_USER = 'replication',
MASTER_PASSWORD = 'password',
MASTER_LOG_FILE = 'mysql-bin.000001',
MASTER_LOG_POS = 456
FOR CHANNEL 'channel2';
START SLAVE FOR CHANNEL 'channel1';
START SLAVE FOR CHANNEL 'channel2';
-- Semi-synchronous replication
INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
SET GLOBAL rpl_semi_sync_master_enabled = 1;
SET GLOBAL rpl_semi_sync_slave_enabled = 1;Performance optimization involves query optimization, indexing, configuration tuning, and hardware considerations.
- EXPLAIN: Analyze query execution
- Indexes: Create appropriate indexes
- Query cache: Cache query results
- Slow query log: Identify slow queries
- Configuration: Tune SQL variables
-- Performance Tuning in SQL
-- Query optimization and tuning
-- Query cache (MySQL 5.7 and earlier)
SHOW VARIABLES LIKE 'query_cache%';
SET GLOBAL query_cache_size = 1000000;
-- SQL_NO_CACHE (for testing)
SELECT SQL_NO_CACHE * FROM users WHERE age > 18;
-- SQL_CACHE (force cache)
SELECT SQL_CACHE * FROM users WHERE age > 18;
-- Analyze slow query log
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 2;
-- Use EXPLAIN for query analysis
EXPLAIN SELECT * FROM users WHERE age > 18;
-- Use EXPLAIN FORMAT=JSON
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE age > 18;
-- Query profiling
SET profiling = 1;
SELECT * FROM users WHERE age > 18;
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;
-- OPTIMIZE TABLE
OPTIMIZE TABLE users;
-- ANALYZE TABLE
ANALYZE TABLE users;
-- CHECK TABLE
CHECK TABLE users;
-- REPAIR TABLE
REPAIR TABLE users;
-- Innodb status
SHOW ENGINE INNODB STATUSG
-- Processlist
SHOW FULL PROCESSLIST;
-- Kill query
KILL QUERY 123;
-- Kill connection
KILL 123;SQL offers various backup methods including mysqldump, SQL Enterprise Backup, and Percona XtraBackup.
- mysqldump: Logical backup
- mysqlbackup: Physical backup (Enterprise)
- XtraBackup: Physical backup (Percona)
- Binary logs: Point-in-time recovery
- CSV export: Data export/import
-- Backup and Restore in SQL
-- Backup methods
-- mysqldump
-- mysqldump -u username -p database_name > backup.sql
-- mysqldump specific tables
-- mysqldump -u username -p database_name users orders > backup.sql
-- mysqldump with options
-- mysqldump -u username -p --add-drop-table --create-options database_name > backup.sql
-- mysqldump for all databases
-- mysqldump -u username -p --all-databases > all_backup.sql
-- mysqldump with compression
-- mysqldump -u username -p database_name | gzip > backup.sql.gz
-- mysqldump with --single-transaction (for InnoDB)
-- mysqldump -u username -p --single-transaction database_name > backup.sql
-- mysqldump with --master-data (for replication)
-- mysqldump -u username -p --master-data=2 database_name > backup.sql
-- Restore
-- mysql -u username -p database_name < backup.sql
-- Restore with compression
-- gunzip < backup.sql.gz | mysql -u username -p database_name
-- Restore without database creation
-- mysql -u username -p < backup.sql
-- MySQL Enterprise Backup
-- mysqlbackup --defaults-file=/etc/my.cnf --backup-dir=/backup backup
-- Percona XtraBackup
-- xtrabackup --backup --target-dir=/backup
-- Binary log backup
-- mysqlbinlog mysql-bin.000001 > binlog.sql
-- Export to CSV
SELECT * FROM users INTO OUTFILE '/tmp/users.csv'
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '
';
-- Import from CSV
LOAD DATA INFILE '/tmp/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '
'
IGNORE 1 LINES;User management includes creating users, granting privileges, managing roles, and setting password policies.
- CREATE USER: Create database users
- GRANT: Assign privileges
- REVOKE: Remove privileges
- Roles: Groups of privileges (8.0+)
- Password policies: Enforce security
-- User Management in SQL
-- Creating and managing users
-- Create user
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'password';
CREATE USER 'app_user'@'%' IDENTIFIED BY 'password';
-- Drop user
DROP USER 'app_user'@'localhost';
-- Rename user
RENAME USER 'old_user'@'localhost' TO 'new_user'@'localhost';
-- Grant privileges
GRANT ALL PRIVILEGES ON database_name.* TO 'app_user'@'localhost';
GRANT SELECT, INSERT, UPDATE ON database_name.* TO 'app_user'@'localhost';
GRANT ALL PRIVILEGES ON *.* TO 'admin_user'@'localhost' WITH GRANT OPTION;
-- Grant specific privileges
GRANT SELECT ON database_name.users TO 'app_user'@'localhost';
GRANT EXECUTE ON PROCEDURE database_name.procedure_name TO 'app_user'@'localhost';
-- Revoke privileges
REVOKE ALL PRIVILEGES ON database_name.* FROM 'app_user'@'localhost';
REVOKE GRANT OPTION ON *.* FROM 'admin_user'@'localhost';
-- Show grants
SHOW GRANTS FOR 'app_user'@'localhost';
SHOW GRANTS; -- For current user
-- Create user with password expiration
CREATE USER 'temp_user'@'localhost' IDENTIFIED BY 'password'
PASSWORD EXPIRE INTERVAL 30 DAY;
-- Create user with account lock
CREATE USER 'locked_user'@'localhost' IDENTIFIED BY 'password'
ACCOUNT LOCK;
-- Unlock account
ALTER USER 'locked_user'@'localhost' ACCOUNT UNLOCK;
-- Change password
ALTER USER 'app_user'@'localhost' IDENTIFIED BY 'new_password';
SET PASSWORD FOR 'app_user'@'localhost' = PASSWORD('new_password');
-- Roles (MySQL 8.0+)
CREATE ROLE 'app_role', 'admin_role';
GRANT SELECT ON database_name.* TO 'app_role';
GRANT ALL PRIVILEGES ON *.* TO 'admin_role';
GRANT 'app_role' TO 'app_user'@'localhost';
SET DEFAULT ROLE 'app_role' TO 'app_user'@'localhost';Security best practices include authentication, authorization, encryption, and auditing.
- Authentication: Strong passwords, SSL/TLS
- Authorization: Principle of least privilege
- Encryption: Data at rest and in transit
- Audit logging: Track activities
- Updates: Keep SQL up-to-date
-- Security Best Practices in SQL
-- Security configurations
-- Remove anonymous users
DELETE FROM mysql.user WHERE User = '';
FLUSH PRIVILEGES;
-- Remove test database
DROP DATABASE IF EXISTS test;
-- Disable remote root login
RENAME USER 'root'@'%' TO 'root'@'localhost';
-- Use SSL/TLS
-- my.cnf:
-- ssl-ca=/etc/mysql/ssl/ca.pem
-- ssl-cert=/etc/mysql/ssl/server-cert.pem
-- ssl-key=/etc/mysql/ssl/server-key.pem
-- Require SSL for user
ALTER USER 'app_user'@'%' REQUIRE SSL;
-- Set password policy
SET GLOBAL validate_password.policy = STRONG;
SET GLOBAL validate_password.length = 8;
-- Enable audit log
INSTALL PLUGIN audit_log SONAME 'audit_log.so';
SET GLOBAL audit_log_policy = ALL;
-- Limit connections per user
ALTER USER 'app_user'@'%' WITH MAX_CONNECTIONS_PER_HOUR 100;
-- Limit queries per hour
ALTER USER 'app_user'@'%' WITH MAX_QUERIES_PER_HOUR 1000;
-- Limit updates per hour
ALTER USER 'app_user'@'%' WITH MAX_UPDATES_PER_HOUR 100;
-- Disable LOAD DATA LOCAL INFILE
SET GLOBAL local_infile = 0;
-- Enable SQL_MODE
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE';
-- Show security variables
SHOW VARIABLES LIKE '%ssl%';
SHOW VARIABLES LIKE '%validate%';
SHOW VARIABLES LIKE '%secure%';
-- Check user connections
SELECT user, host, connection_id FROM information_schema.processlist;Find maximum salary using MAX() function or ORDER BY LIMIT.
- MAX():
SELECT MAX(salary) FROM employees - ORDER BY:
SELECT salary FROM employees ORDER BY salary DESC LIMIT 1 - Subquery:
SELECT * FROM employees WHERE salary = (SELECT MAX(salary) FROM employees)
-- Normalization in SQL
-- Database normalization principles
-- First Normal Form (1NF) - Atomic values
-- Bad: Products table with multiple categories
CREATE TABLE products_bad (
id INT PRIMARY KEY,
name VARCHAR(100),
categories VARCHAR(255) -- Comma separated values
);
-- Good: 1NF compliant
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE product_categories (
product_id INT,
category VARCHAR(50),
PRIMARY KEY (product_id, category)
);
-- Second Normal Form (2NF) - Remove partial dependencies
-- Bad: Orders with product details
CREATE TABLE orders_bad (
order_id INT,
product_id INT,
product_name VARCHAR(100),
quantity INT,
unit_price DECIMAL(10,2),
PRIMARY KEY (order_id, product_id)
);
-- Good: 2NF compliant
CREATE TABLE orders (
order_id INT PRIMARY KEY,
order_date DATE
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
unit_price DECIMAL(10,2),
PRIMARY KEY (order_id, product_id)
);
CREATE TABLE products (
product_id INT PRIMARY KEY,
name VARCHAR(100)
);
-- Third Normal Form (3NF) - Remove transitive dependencies
-- Bad: Orders with customer details
CREATE TABLE orders_bad (
order_id INT PRIMARY KEY,
customer_id INT,
customer_name VARCHAR(100),
customer_city VARCHAR(100),
order_date DATE
);
-- Good: 3NF compliant
CREATE TABLE customers (
customer_id INT PRIMARY KEY,
name VARCHAR(100),
city VARCHAR(100)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
-- Boyce-Codd Normal Form (BCNF)
-- Handle overlapping candidate keys
-- Fourth Normal Form (4NF)
-- Handle multi-valued dependencies
-- Fifth Normal Form (5NF)
-- Handle join dependenciesFind second highest salary using LIMIT, subquery, or window functions.
- LIMIT:
SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1 - Subquery:
SELECT MAX(salary) FROM employees WHERE salary < (SELECT MAX(salary) FROM employees) - Window function:
DENSE_RANK() OVER (ORDER BY salary DESC) - Complexity: O(n log n)
-- Denormalization in SQL
-- Denormalization for performance
-- Denormalized table with redundant data
CREATE TABLE orders_denormalized (
order_id INT PRIMARY KEY,
customer_id INT,
customer_name VARCHAR(100),
customer_email VARCHAR(255),
product_id INT,
product_name VARCHAR(100),
quantity INT,
unit_price DECIMAL(10,2),
total DECIMAL(10,2),
order_date DATE
);
-- Update denormalized data with triggers
DELIMITER //
CREATE TRIGGER update_order_total
BEFORE INSERT ON orders_denormalized
FOR EACH ROW
BEGIN
SET NEW.total = NEW.quantity * NEW.unit_price;
END//
DELIMITER ;
-- Materialized views (using tables and triggers)
CREATE TABLE sales_summary (
product_id INT PRIMARY KEY,
total_sales DECIMAL(10,2),
units_sold INT,
last_updated TIMESTAMP
);
-- Refresh materialized view
UPDATE sales_summary ss
JOIN (
SELECT
product_id,
SUM(quantity * unit_price) AS total_sales,
SUM(quantity) AS units_sold
FROM order_items
GROUP BY product_id
) o ON ss.product_id = o.product_id
SET
ss.total_sales = o.total_sales,
ss.units_sold = o.units_sold,
ss.last_updated = NOW();
-- Denormalized query performance
-- Without denormalization (JOIN)
SELECT
o.order_id,
c.name AS customer_name,
p.name AS product_name,
oi.quantity,
oi.unit_price
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;
-- With denormalization (single table)
SELECT * FROM orders_denormalized;Find duplicate emails using GROUP BY and HAVING.
- GROUP BY:
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1 - Self-join:
SELECT DISTINCT a.email FROM users a JOIN users b ON a.email = b.email AND a.id != b.id - Subquery:
SELECT email FROM (SELECT email, COUNT(*) as cnt FROM users GROUP BY email) t WHERE cnt > 1 - Complexity: O(n)
-- JSON in SQL (MySQL 5.7+)
-- JSON data type and functions
-- Create table with JSON column
CREATE TABLE json_data (
id INT PRIMARY KEY AUTO_INCREMENT,
data JSON
);
-- Insert JSON
INSERT INTO json_data (data) VALUES
('{"name": "Alice", "age": 25, "city": "NYC"}'),
('{"name": "Bob", "age": 30, "city": "LA", "hobbies": ["reading", "gaming"]}');
-- Extract value
SELECT
id,
JSON_EXTRACT(data, '$.name') AS name,
JSON_EXTRACT(data, '$.age') AS age
FROM json_data;
-- Extract with -> operator
SELECT
id,
data->'$.name' AS name,
data->'$.age' AS age
FROM json_data;
-- Extract as text with ->> operator
SELECT
id,
data->>'$.name' AS name,
data->>'$.age' AS age
FROM json_data;
-- JSON functions
SELECT
JSON_OBJECT('name', name, 'age', age) AS user_json
FROM users;
SELECT
JSON_ARRAY(name, age, city) AS user_array
FROM users;
-- Update JSON
UPDATE json_data
SET data = JSON_SET(data, '$.age', 26, '$.city', 'NYC')
WHERE id = 1;
-- Add to JSON
UPDATE json_data
SET data = JSON_INSERT(data, '$.status', 'active')
WHERE id = 1;
-- Remove from JSON
UPDATE json_data
SET data = JSON_REMOVE(data, '$.status')
WHERE id = 1;
-- Search in JSON
SELECT * FROM json_data
WHERE JSON_EXTRACT(data, '$.name') = 'Alice';
-- JSON path
SELECT
JSON_SEARCH(data, 'one', 'Alice') AS path
FROM json_data;
-- JSON table (MySQL 8.0+)
SELECT *
FROM JSON_TABLE(
'[{"name": "Alice"}, {"name": "Bob"}]',
'$[*]' COLUMNS (
name VARCHAR(100) PATH '$.name'
)
) AS jt;Delete duplicate records keeping the first (lowest ID) occurrence.
- Self-join:
DELETE a FROM users a JOIN users b ON a.email = b.email AND a.id > b.id - Subquery:
DELETE FROM users WHERE id NOT IN (SELECT MIN(id) FROM users GROUP BY email) - Window function:
WITH ranked AS (SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) as rn FROM users) DELETE FROM users WHERE id IN (SELECT id FROM ranked WHERE rn > 1) - Complexity: O(n log n)
-- Full-Text Search in SQL
-- Full-text indexing and searching
-- Create table with FULLTEXT index
CREATE TABLE articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255),
content TEXT,
FULLTEXT INDEX ft_title_content (title, content)
);
-- Insert data
INSERT INTO articles (title, content) VALUES
('MySQL Full-Text Search', 'MySQL supports full-text searching and indexing'),
('Advanced SQL Queries', 'Learn about complex SQL queries and optimization'),
('Database Performance Tuning', 'Tips for improving database performance');
-- NATURAL LANGUAGE MODE
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL' IN NATURAL LANGUAGE MODE);
-- BOOLEAN MODE
-- + Required, - Excluded, * Wildcard, > Increase rank, < Decrease rank
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('+MySQL +search' IN BOOLEAN MODE);
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL -Oracle' IN BOOLEAN MODE);
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL*' IN BOOLEAN MODE);
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('>MySQL +search' IN BOOLEAN MODE);
-- WITH QUERY EXPANSION
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL' WITH QUERY EXPANSION);
-- Query with relevance score
SELECT
*,
MATCH(title, content) AGAINST('MySQL') AS relevance
FROM articles
WHERE MATCH(title, content) AGAINST('MySQL')
ORDER BY relevance DESC;
-- Full-text search on multiple tables (using UNION)
SELECT * FROM articles
WHERE MATCH(title, content) AGAINST('MySQL')
UNION
SELECT * FROM comments
WHERE MATCH(comment) AGAINST('MySQL');
-- Stopwords
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
-- Minimum word length
SHOW VARIABLES LIKE 'ft_min_word_len';
SHOW VARIABLES LIKE 'innodb_ft_min_token_size';Find employees who earn more than their managers using self-join.
- Self-join:
SELECT e.name FROM employees e JOIN employees m ON e.manager_id = m.id WHERE e.salary > m.salary - Subquery:
SELECT name FROM employees e WHERE salary > (SELECT salary FROM employees WHERE id = e.manager_id) - Complexity: O(n log n)
-- Spatial Data in SQL (MySQL 5.7+)
-- Spatial data types and functions
-- Create table with spatial column
CREATE TABLE locations (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
coordinates POINT NOT NULL,
SPATIAL INDEX idx_coords (coordinates)
);
-- Insert spatial data
INSERT INTO locations (name, coordinates) VALUES
('Central Park', ST_PointFromText('POINT(-73.9654 40.7829)')),
('Times Square', ST_PointFromText('POINT(-73.9855 40.7580)')),
('Empire State', ST_PointFromText('POINT(-73.9857 40.7484)'));
-- Create polygon
CREATE TABLE areas (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
boundary POLYGON NOT NULL,
SPATIAL INDEX idx_boundary (boundary)
);
INSERT INTO areas (name, boundary) VALUES
('Manhattan', ST_PolygonFromText('POLYGON((-74.05 40.70, -73.90 40.70, -73.90 40.80, -74.05 40.80, -74.05 40.70))'));
-- Spatial queries
-- ST_Distance
SELECT
name,
ST_Distance(coordinates, ST_PointFromText('POINT(-73.97 40.76)')) AS distance
FROM locations
ORDER BY distance;
-- ST_Within
SELECT
l.name AS location,
a.name AS area
FROM locations l, areas a
WHERE ST_Within(l.coordinates, a.boundary);
-- ST_Contains
SELECT
a.name AS area,
COUNT(*) AS location_count
FROM areas a, locations l
WHERE ST_Contains(a.boundary, l.coordinates)
GROUP BY a.id;
-- ST_Buffer
SELECT
name,
ST_AsText(ST_Buffer(coordinates, 0.01)) AS buffer
FROM locations;
-- ST_Intersects
SELECT
l1.name AS point1,
l2.name AS point2,
ST_Distance(l1.coordinates, l2.coordinates) AS distance
FROM locations l1, locations l2
WHERE l1.id < l2.id
AND ST_Distance(l1.coordinates, l2.coordinates) < 0.01;
-- ST_Area
SELECT
name,
ST_Area(boundary) AS area_sq_degrees
FROM areas;
-- ST_Centroid
SELECT
name,
ST_AsText(ST_Centroid(boundary)) AS centroid
FROM areas;Find employees in the department with the highest average salary.
- Subquery:
SELECT * FROM employees WHERE department_id = (SELECT department_id FROM employees GROUP BY department_id ORDER BY AVG(salary) DESC LIMIT 1) - CTE:
WITH dept_avg AS (SELECT department_id, AVG(salary) as avg_sal FROM employees GROUP BY department_id) SELECT e.* FROM employees e JOIN dept_avg d ON e.department_id = d.department_id WHERE d.avg_sal = (SELECT MAX(avg_sal) FROM dept_avg) - Complexity: O(n)
-- Generated Columns in SQL
-- Virtual and stored generated columns
-- Stored generated column
CREATE TABLE sales (
id INT PRIMARY KEY AUTO_INCREMENT,
quantity INT NOT NULL,
unit_price DECIMAL(10,2) NOT NULL,
total_price DECIMAL(10,2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);
-- Virtual generated column
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(100),
last_name VARCHAR(100),
full_name VARCHAR(200) GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) VIRTUAL
);
-- Generated column with complex expression
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
subtotal DECIMAL(10,2),
tax_rate DECIMAL(5,2) DEFAULT 10.00,
tax_amount DECIMAL(10,2) GENERATED ALWAYS AS (subtotal * tax_rate / 100) STORED,
total DECIMAL(10,2) GENERATED ALWAYS AS (subtotal + (subtotal * tax_rate / 100)) STORED
);
-- Generated column with conditional logic
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
price DECIMAL(10,2),
discount_percent DECIMAL(5,2) DEFAULT 0,
discounted_price DECIMAL(10,2) GENERATED ALWAYS AS (
CASE
WHEN discount_percent > 0 THEN price * (1 - discount_percent / 100)
ELSE price
END
) STORED
);
-- Index on generated column
CREATE INDEX idx_discounted_price ON products(discounted_price);
-- Generated column with JSON data
CREATE TABLE json_users (
id INT PRIMARY KEY AUTO_INCREMENT,
user_data JSON,
user_name VARCHAR(100) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(user_data, '$.name'))) STORED,
user_age INT GENERATED ALWAYS AS (JSON_EXTRACT(user_data, '$.age')) VIRTUAL
);
-- Insert into generated column
INSERT INTO sales (quantity, unit_price) VALUES (5, 10.00);
INSERT INTO employees (first_name, last_name) VALUES ('John', 'Doe');
INSERT INTO json_users (user_data) VALUES ('{"name": "Alice", "age": 25}');
-- Select generated columns
SELECT * FROM sales;
SELECT full_name FROM employees;
SELECT user_name, user_age FROM json_users;Calculate cumulative sum using window functions or self-join.
- Window function:
SELECT id, amount, SUM(amount) OVER (ORDER BY id) as cumulative_sum FROM transactions - Self-join:
SELECT a.id, a.amount, SUM(b.amount) as cumulative_sum FROM transactions a JOIN transactions b ON b.id <= a.id GROUP BY a.id, a.amount - Complexity: O(n log n)
-- Views with Check Option in SQL
-- Updatable views with check option
-- Basic view with check option
CREATE VIEW active_users AS
SELECT id, name, email, status
FROM users
WHERE status = 'active'
WITH CHECK OPTION;
-- Insert through view
INSERT INTO active_users (name, email, status)
VALUES ('Alice', 'alice@example.com', 'active'); -- Works
-- This will fail (status not active)
INSERT INTO active_users (name, email, status)
VALUES ('Bob', 'bob@example.com', 'inactive'); -- Fails
-- View with cascade check option
CREATE VIEW nyc_users AS
SELECT id, name, city, status
FROM active_users
WHERE city = 'NYC'
WITH CASCADED CHECK OPTION;
-- View with local check option
CREATE VIEW la_users AS
SELECT id, name, city, status
FROM active_users
WHERE city = 'LA'
WITH LOCAL CHECK OPTION;
-- View with no check option
CREATE VIEW all_users AS
SELECT id, name, email, status, city
FROM users
WHERE status = 'active';
-- Insert into no-check view
INSERT INTO all_users (name, email, status, city)
VALUES ('Bob', 'bob@example.com', 'inactive', 'LA');
-- Update view with check option
UPDATE active_users SET status = 'inactive' WHERE id = 1; -- Fails
UPDATE active_users SET status = 'active' WHERE id = 1; -- Works
-- View check option restrictions
-- Views with JOIN, GROUP BY, DISTINCT, UNION, subqueries cannot be updatable
-- Check if view is updatable
SELECT
TABLE_NAME,
IS_UPDATABLE
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'database_name';Calculate moving average over a window of rows.
- Window function:
SELECT date, amount, AVG(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) as moving_avg FROM sales - Self-join:
SELECT a.date, a.amount, AVG(b.amount) as moving_avg FROM sales a JOIN sales b ON b.date BETWEEN DATE_SUB(a.date, INTERVAL 2 DAY) AND a.date GROUP BY a.date, a.amount - Complexity: O(n log n)
-- Temporary Tables in SQL
-- Temporary tables for session-specific data
-- Create temporary table
CREATE TEMPORARY TABLE temp_users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
age INT
);
-- Insert data
INSERT INTO temp_users (name, age) VALUES
('Alice', 25),
('Bob', 30);
-- Query temporary table
SELECT * FROM temp_users;
-- Temporary table with SELECT
CREATE TEMPORARY TABLE temp_active_users
SELECT * FROM users WHERE status = 'active';
-- Temporary table with indexes
CREATE TEMPORARY TABLE temp_orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
total DECIMAL(10,2),
INDEX idx_user (user_id)
);
-- Temporary table with ENGINE option
CREATE TEMPORARY TABLE temp_memory (
id INT PRIMARY KEY,
data VARCHAR(100)
) ENGINE = MEMORY;
-- Temporary table with ON COMMIT DELETE ROWS
CREATE TEMPORARY TABLE temp_sessions (
session_id VARCHAR(255),
data TEXT
) ON COMMIT DELETE ROWS;
-- Drop temporary table
DROP TEMPORARY TABLE temp_users;
DROP TEMPORARY TABLE IF EXISTS temp_users;
-- Check if table is temporary
SELECT
TABLE_NAME,
TABLE_TYPE
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'database_name'
AND TABLE_TYPE = 'TEMPORARY';
-- Using temporary table for complex query
CREATE TEMPORARY TABLE temp_high_spenders
SELECT
user_id,
SUM(total) AS total_spent
FROM orders
GROUP BY user_id
HAVING total_spent > 1000;
SELECT
u.name,
u.email,
t.total_spent
FROM users u
JOIN temp_high_spenders t ON u.id = t.user_id
ORDER BY t.total_spent DESC;
-- Temporary table for pagination
CREATE TEMPORARY TABLE temp_page
SELECT * FROM users
ORDER BY name
LIMIT 10 OFFSET 20;
-- Clean up
DROP TEMPORARY TABLE temp_high_spenders;
DROP TEMPORARY TABLE temp_page;Create a pivot table using conditional aggregation.
- CASE with SUM:
SELECT product, SUM(CASE WHEN month = 'Jan' THEN sales ELSE 0 END) as Jan, SUM(CASE WHEN month = 'Feb' THEN sales ELSE 0 END) as Feb FROM sales GROUP BY product - IF with SUM:
SELECT product, SUM(IF(month = 'Jan', sales, 0)) as Jan, SUM(IF(month = 'Feb', sales, 0)) as Feb FROM sales GROUP BY product - Complexity: O(n)
-- Character Sets and Collations in SQL
-- Character set and collation management
-- Show character sets
SHOW CHARACTER SET;
SHOW CHARSET;
-- Show collations
SHOW COLLATION;
SHOW COLLATION LIKE 'utf8%';
-- Set character set for database
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Set character set for table
CREATE TABLE mytable (
id INT PRIMARY KEY,
name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Set character set for column
ALTER TABLE users MODIFY name VARCHAR(100) CHARACTER SET utf8mb4;
-- Convert table character set
ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Convert column character set
ALTER TABLE users MODIFY name VARCHAR(100) CHARACTER SET latin1;
-- Set session character set
SET NAMES 'utf8mb4';
SET CHARACTER SET utf8mb4;
-- Set global character set
SET GLOBAL character_set_server = 'utf8mb4';
SET GLOBAL collation_server = 'utf8mb4_unicode_ci';
-- Check character sets
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
-- Compare strings with different collations
SELECT 'abc' = 'ABC' COLLATE utf8mb4_unicode_ci; -- 1
SELECT 'abc' = 'ABC' COLLATE utf8mb4_bin; -- 0
-- Use specific collation in query
SELECT * FROM users
WHERE name = 'Alice' COLLATE utf8mb4_unicode_ci;
-- Unicode vs non-unicode
-- utf8mb4 supports all Unicode characters (including emoji)
CREATE TABLE messages (
id INT PRIMARY KEY,
content VARCHAR(255) CHARACTER SET utf8mb4
);
INSERT INTO messages (content) VALUES ('Hello 😊');Get top N records per group using window functions or correlated subqueries.
- Window function:
WITH ranked AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) as rn FROM employees) SELECT * FROM ranked WHERE rn <= 3 - Correlated subquery:
SELECT * FROM employees e WHERE (SELECT COUNT(*) FROM employees WHERE department_id = e.department_id AND salary > e.salary) < 3 - Complexity: O(n log n)
-- Information Schema in SQL
-- Querying metadata
-- List all databases
SELECT * FROM INFORMATION_SCHEMA.SCHEMATA;
-- List all tables in database
SELECT
TABLE_NAME,
TABLE_TYPE,
ENGINE,
ROW_FORMAT,
TABLE_ROWS,
DATA_LENGTH,
INDEX_LENGTH
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'database_name';
-- List all columns in table
SELECT
COLUMN_NAME,
DATA_TYPE,
IS_NULLABLE,
COLUMN_DEFAULT,
CHARACTER_MAXIMUM_LENGTH,
NUMERIC_PRECISION,
COLUMN_KEY,
EXTRA
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'database_name'
AND TABLE_NAME = 'users';
-- List indexes
SELECT * FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'database_name'
AND TABLE_NAME = 'users';
-- List foreign keys
SELECT
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = 'database_name'
AND REFERENCED_TABLE_NAME IS NOT NULL;
-- List views
SELECT
TABLE_NAME,
VIEW_DEFINITION,
CHECK_OPTION,
IS_UPDATABLE
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'database_name';
-- List privileges
SELECT * FROM INFORMATION_SCHEMA.USER_PRIVILEGES
WHERE GRANTEE LIKE '%app_user%';
-- Table sizes
SELECT
TABLE_NAME,
ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS size_mb
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'database_name'
ORDER BY size_mb DESC;
-- Query optimizer statistics
SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
-- InnoDB metrics
SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS;
-- Show process list
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;Find missing IDs in a sequence using recursive CTE or self-join.
- Recursive CTE:
WITH RECURSIVE numbers(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM numbers WHERE n < (SELECT MAX(id) FROM table)) SELECT n FROM numbers WHERE n NOT IN (SELECT id FROM table) - Self-join:
SELECT t1.id + 1 as missing_id FROM table t1 LEFT JOIN table t2 ON t2.id = t1.id + 1 WHERE t2.id IS NULL AND t1.id < (SELECT MAX(id) FROM table) - Complexity: O(n)
-- Find maximum salary
SELECT MAX(salary) AS max_salary FROM employees;
-- Find employee with max salary
SELECT * FROM employees
WHERE salary = (SELECT MAX(salary) FROM employees);
-- Using ORDER BY with LIMIT
SELECT * FROM employees
ORDER BY salary DESC
LIMIT 1;Find consecutive days of activity using window functions.
- ROW_NUMBER:
WITH numbered AS (SELECT date, ROW_NUMBER() OVER (ORDER BY date) as rn FROM activities) SELECT MIN(date) as start_date, MAX(date) as end_date, COUNT(*) as days FROM (SELECT date, rn - DATEDIFF(date, (SELECT MIN(date) FROM activities)) as grp FROM numbered) grouped GROUP BY grp HAVING COUNT(*) >= 3 - Complexity: O(n log n)
-- Find second highest salary
-- Using LIMIT with OFFSET
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
-- Using subquery
SELECT MAX(salary)
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
-- Using window function (MySQL 8.0+)
SELECT DISTINCT salary
FROM (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
) t
WHERE rnk = 2;Find overlapping time intervals using self-join.
- Self-join:
SELECT a.id as interval1, b.id as interval2 FROM intervals a JOIN intervals b ON a.id < b.id AND a.start < b.end AND a.end > b.start - Complexity: O(n²)
-- Find duplicate emails
SELECT email, COUNT(*) AS count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- Find duplicate emails with IDs
SELECT u1.id, u1.email
FROM users u1
JOIN users u2 ON u1.email = u2.email AND u1.id != u2.id
ORDER BY u1.email;Traverse hierarchical data using recursive CTE.
- Recursive CTE:
WITH RECURSIVE org_tree(id, name, manager_id, level) AS (SELECT id, name, manager_id, 0 FROM employees WHERE manager_id IS NULL UNION ALL SELECT e.id, e.name, e.manager_id, ot.level + 1 FROM employees e JOIN org_tree ot ON e.manager_id = ot.id) SELECT * FROM org_tree ORDER BY level, id - Complexity: O(n)
-- Delete duplicates keeping the lowest ID
DELETE u1
FROM users u1
JOIN users u2 ON u1.email = u2.email AND u1.id > u2.id;
-- Using subquery
DELETE FROM users
WHERE id NOT IN (
SELECT MIN(id)
FROM users
GROUP BY email
);
-- Using CTE (MySQL 8.0+)
WITH ranked AS (
SELECT id, ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
)
DELETE FROM users
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);Find department with highest average or total salary.
- GROUP BY:
SELECT department_id, AVG(salary) as avg_salary FROM employees GROUP BY department_id ORDER BY avg_salary DESC LIMIT 1 - Subquery:
SELECT department_id, AVG(salary) as avg_salary FROM employees GROUP BY department_id HAVING AVG(salary) = (SELECT MAX(avg_salary) FROM (SELECT AVG(salary) as avg_salary FROM employees GROUP BY department_id) t) - Complexity: O(n)
-- Find employees earning more than their managers
SELECT e.name AS employee, e.salary AS emp_salary,
m.name AS manager, m.salary AS mgr_salary
FROM employees e
JOIN employees m ON e.manager_id = m.id
WHERE e.salary > m.salary;
-- Using subquery
SELECT name, salary
FROM employees e
WHERE salary > (SELECT salary FROM employees WHERE id = e.manager_id);Calculate percentage contribution of each item to the total.
- Window function:
SELECT category, amount, (amount / SUM(amount) OVER ()) * 100 as percentage FROM sales - Subquery:
SELECT category, amount, (amount / (SELECT SUM(amount) FROM sales)) * 100 as percentage FROM sales - Complexity: O(n)
-- Find employees in department with highest average salary
SELECT e.*
FROM employees e
WHERE e.department_id = (
SELECT department_id
FROM employees
GROUP BY department_id
ORDER BY AVG(salary) DESC
LIMIT 1
);
-- Using CTE (MySQL 8.0+)
WITH dept_avg AS (
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY department_id
)
SELECT e.*
FROM employees e
JOIN dept_avg d ON e.department_id = d.department_id
WHERE d.avg_sal = (SELECT MAX(avg_sal) FROM dept_avg);Calculate running total per category using window function.
- Window function:
SELECT category, date, amount, SUM(amount) OVER (PARTITION BY category ORDER BY date) as running_total FROM sales - Complexity: O(n log n)
-- Cumulative sum using window function (MySQL 8.0+)
SELECT
id,
amount,
SUM(amount) OVER (ORDER BY id) AS cumulative_sum
FROM transactions;
-- Cumulative sum using self-join
SELECT
a.id,
a.amount,
SUM(b.amount) AS cumulative_sum
FROM transactions a
JOIN transactions b ON b.id <= a.id
GROUP BY a.id, a.amount
ORDER BY a.id;Find groups of consecutive records (islands) using window functions.
- ROW_NUMBER:
WITH numbered AS (SELECT *, ROW_NUMBER() OVER (ORDER BY date) as rn FROM activities), grouped AS (SELECT *, rn - ROW_NUMBER() OVER (ORDER BY date) as grp FROM numbered) SELECT MIN(date) as start, MAX(date) as end, COUNT(*) as days FROM grouped GROUP BY grp HAVING COUNT(*) > 1 - Complexity: O(n log n)
-- Moving average (MySQL 8.0+)
SELECT
date,
amount,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg_3
FROM sales;
-- Moving average using self-join
SELECT
a.date,
a.amount,
AVG(b.amount) AS moving_avg_3
FROM sales a
JOIN sales b ON b.date BETWEEN DATE_SUB(a.date, INTERVAL 2 DAY) AND a.date
GROUP BY a.date, a.amount
ORDER BY a.date;Find gaps in sequences using window functions.
- LAG:
WITH numbered AS (SELECT id, LAG(id) OVER (ORDER BY id) as prev_id FROM table) SELECT prev_id + 1 as gap_start, id - 1 as gap_end FROM numbered WHERE id > prev_id + 1 - Complexity: O(n log n)
-- Pivot table using CASE
SELECT
product,
SUM(CASE WHEN month = 'Jan' THEN sales ELSE 0 END) AS Jan,
SUM(CASE WHEN month = 'Feb' THEN sales ELSE 0 END) AS Feb,
SUM(CASE WHEN month = 'Mar' THEN sales ELSE 0 END) AS Mar
FROM sales_data
GROUP BY product;
-- Using IF function
SELECT
product,
SUM(IF(month = 'Jan', sales, 0)) AS Jan,
SUM(IF(month = 'Feb', sales, 0)) AS Feb,
SUM(IF(month = 'Mar', sales, 0)) AS Mar
FROM sales_data
GROUP BY product;Find the most frequent value using GROUP BY and ORDER BY.
- GROUP BY:
SELECT column, COUNT(*) as count FROM table GROUP BY column ORDER BY count DESC LIMIT 1 - Subquery:
SELECT column FROM table GROUP BY column HAVING COUNT(*) = (SELECT MAX(count) FROM (SELECT COUNT(*) as count FROM table GROUP BY column) t) - Complexity: O(n log n)
-- Top 3 employees per department (MySQL 8.0+)
WITH ranked AS (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rn
FROM employees
)
SELECT * FROM ranked WHERE rn <= 3;
-- Using correlated subquery
SELECT *
FROM employees e
WHERE (
SELECT COUNT(*)
FROM employees
WHERE department_id = e.department_id AND salary > e.salary
) < 3;Find the median value using window functions or subqueries.
- PERCENTILE_CONT:
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) OVER () as median FROM employees LIMIT 1 - NTILE:
SELECT AVG(salary) as median FROM (SELECT salary, NTILE(2) OVER (ORDER BY salary) as tile FROM employees) t WHERE tile = 1 OR tile = 2 GROUP BY tile - Complexity: O(n log n)
-- Find missing IDs using recursive CTE (MySQL 8.0+)
WITH RECURSIVE numbers(n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM numbers WHERE n < (SELECT MAX(id) FROM mytable)
)
SELECT n AS missing_id
FROM numbers
WHERE n NOT IN (SELECT id FROM mytable);
-- Using self-join
SELECT t1.id + 1 AS missing_id
FROM mytable t1
LEFT JOIN mytable t2 ON t2.id = t1.id + 1
WHERE t2.id IS NULL
AND t1.id < (SELECT MAX(id) FROM mytable);Find the N-th highest salary using LIMIT or window functions.
- LIMIT:
SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT N-1, 1 - DENSE_RANK:
WITH ranked AS (SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) as rnk FROM employees) SELECT DISTINCT salary FROM ranked WHERE rnk = N - Complexity: O(n log n)
-- Find consecutive days of activity (MySQL 8.0+)
WITH numbered AS (
SELECT
date,
ROW_NUMBER() OVER (ORDER BY date) AS rn
FROM activities
),
grouped AS (
SELECT
date,
rn - DATEDIFF(date, (SELECT MIN(date) FROM activities)) AS grp
FROM numbered
)
SELECT
MIN(date) AS start_date,
MAX(date) AS end_date,
COUNT(*) AS days
FROM grouped
GROUP BY grp
HAVING COUNT(*) >= 3
ORDER BY start_date;Calculate cumulative distribution using window functions.
- CUME_DIST:
SELECT value, CUME_DIST() OVER (ORDER BY value) as cume_dist FROM table - PERCENT_RANK:
SELECT value, PERCENT_RANK() OVER (ORDER BY value) as percent_rank FROM table - Complexity: O(n log n)
-- Find overlapping intervals
SELECT
a.id AS interval1,
b.id AS interval2,
a.start_date AS start1,
a.end_date AS end1,
b.start_date AS start2,
b.end_date AS end2
FROM intervals a
JOIN intervals b ON a.id < b.id
WHERE a.start_date < b.end_date
AND a.end_date > b.start_date;Get first and last values in a group using window functions.
- FIRST_VALUE:
SELECT category, FIRST_VALUE(value) OVER (PARTITION BY category ORDER BY date) as first_value FROM table - LAST_VALUE:
SELECT category, LAST_VALUE(value) OVER (PARTITION BY category ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) as last_value FROM table - Complexity: O(n log n)
-- Recursive hierarchy (MySQL 8.0+)
WITH RECURSIVE org_tree AS (
-- Anchor: top-level employees
SELECT
id,
name,
manager_id,
0 AS level,
CAST(name AS CHAR(200)) AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- Recursive: children
SELECT
e.id,
e.name,
e.manager_id,
ot.level + 1,
CONCAT(ot.path, ' -> ', e.name)
FROM employees e
JOIN org_tree ot ON e.manager_id = ot.id
)
SELECT
CONCAT(REPEAT(' ', level), name) AS org_chart,
level,
path
FROM org_tree
ORDER BY path;Get previous and next row values using LAG and LEAD.
- LAG:
SELECT date, amount, LAG(amount, 1) OVER (ORDER BY date) as previous_amount FROM sales - LEAD:
SELECT date, amount, LEAD(amount, 1) OVER (ORDER BY date) as next_amount FROM sales - Complexity: O(n log n)
-- Department with highest average salary
SELECT
department_id,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
ORDER BY avg_salary DESC
LIMIT 1;
-- Using subquery
SELECT
department_id,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) = (
SELECT MAX(avg_salary)
FROM (SELECT AVG(salary) AS avg_salary FROM employees GROUP BY department_id) t
);Calculate difference between current and previous row using LAG.
- LAG:
SELECT date, amount, amount - LAG(amount, 1) OVER (ORDER BY date) as difference FROM sales - Self-join:
SELECT a.date, a.amount - b.amount as difference FROM sales a JOIN sales b ON b.date = (SELECT MAX(date) FROM sales WHERE date < a.date) - Complexity: O(n log n)
-- Percentage contribution (MySQL 8.0+)
SELECT
category,
amount,
(amount / SUM(amount) OVER ()) * 100 AS percentage
FROM sales;
-- Using subquery
SELECT
category,
amount,
(amount / (SELECT SUM(amount) FROM sales)) * 100 AS percentage
FROM sales;
-- Percentage with grouping
SELECT
category,
amount,
(amount / SUM(amount) OVER (PARTITION BY category)) * 100 AS percentage_in_category
FROM sales;Calculate percentage change between current and previous row.
- LAG:
SELECT date, amount, ((amount - LAG(amount, 1) OVER (ORDER BY date)) / LAG(amount, 1) OVER (ORDER BY date)) * 100 as pct_change FROM sales - Self-join:
SELECT a.date, ((a.amount - b.amount) / b.amount) * 100 as pct_change FROM sales a JOIN sales b ON b.date = (SELECT MAX(date) FROM sales WHERE date < a.date) - Complexity: O(n log n)
-- Running total per category (MySQL 8.0+)
SELECT
category,
date,
amount,
SUM(amount) OVER (PARTITION BY category ORDER BY date) AS running_total
FROM sales
ORDER BY category, date;
-- Running total using self-join
SELECT
a.category,
a.date,
a.amount,
SUM(b.amount) AS running_total
FROM sales a
JOIN sales b ON a.category = b.category AND b.date <= a.date
GROUP BY a.category, a.date, a.amount
ORDER BY a.category, a.date;Compare values with the same period in the previous year.
- LAG with INTERVAL:
SELECT date, amount, LAG(amount, 12) OVER (ORDER BY date) as same_period_previous_year FROM monthly_sales - Self-join:
SELECT a.date, a.amount - b.amount as yoy_change FROM sales a JOIN sales b ON YEAR(a.date) = YEAR(b.date) + 1 AND MONTH(a.date) = MONTH(b.date) - Complexity: O(n log n)
-- Find islands of data (MySQL 8.0+)
WITH numbered AS (
SELECT
date,
ROW_NUMBER() OVER (ORDER BY date) AS rn
FROM activities
),
grouped AS (
SELECT
date,
rn - ROW_NUMBER() OVER (ORDER BY date) AS grp
FROM numbered
)
SELECT
MIN(date) AS start_date,
MAX(date) AS end_date,
COUNT(*) AS days
FROM grouped
GROUP BY grp
HAVING COUNT(*) > 1
ORDER BY start_date;Calculate rolling sum over a window.
- Window function:
SELECT date, amount, SUM(amount) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as rolling_7day_sum FROM daily_sales - Complexity: O(n log n)
-- Gap analysis using LAG (MySQL 8.0+)
WITH numbered AS (
SELECT
id,
LAG(id) OVER (ORDER BY id) AS prev_id
FROM mytable
)
SELECT
prev_id + 1 AS gap_start,
id - 1 AS gap_end
FROM numbered
WHERE id > prev_id + 1;
-- Gap analysis using self-join
SELECT
t1.id + 1 AS gap_start,
MIN(t2.id) - 1 AS gap_end
FROM mytable t1
JOIN mytable t2 ON t2.id > t1.id
WHERE NOT EXISTS (
SELECT 1 FROM mytable t3
WHERE t3.id > t1.id AND t3.id < t2.id
)
GROUP BY t1.id;Fill missing dates in a time series using recursive CTE.
- Recursive CTE:
WITH RECURSIVE dates(dt) AS (SELECT MIN(date) FROM sales UNION ALL SELECT dt + INTERVAL 1 DAY FROM dates WHERE dt < (SELECT MAX(date) FROM sales)) SELECT d.dt, COALESCE(s.amount, 0) as amount FROM dates d LEFT JOIN sales s ON d.dt = s.date - Complexity: O(n)
-- Mode (most frequent value)
SELECT column_name, COUNT(*) AS frequency
FROM mytable
GROUP BY column_name
ORDER BY frequency DESC
LIMIT 1;
-- Using subquery
SELECT column_name
FROM mytable
GROUP BY column_name
HAVING COUNT(*) = (
SELECT MAX(count)
FROM (SELECT COUNT(*) AS count FROM mytable GROUP BY column_name) t
);Calculate difference between dates using DATEDIFF or TIMESTAMPDIFF.
- DATEDIFF:
SELECT DATEDIFF(end_date, start_date) as days_diff FROM projects - TIMESTAMPDIFF:
SELECT TIMESTAMPDIFF(DAY, start_date, end_date) as days_diff FROM projects - Complexity: O(1)
-- Median using PERCENTILE_CONT (MySQL 8.0+)
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)
OVER () AS median
FROM employees
LIMIT 1;
-- Median using NTILE (MySQL 8.0+)
SELECT AVG(salary) AS median
FROM (
SELECT
salary,
NTILE(2) OVER (ORDER BY salary) AS tile
FROM employees
) t
WHERE tile = 1 OR tile = 2
GROUP BY tile;
-- Median using user variables (older MySQL)
SELECT AVG(salary) AS median
FROM (
SELECT salary
FROM employees
ORDER BY salary
LIMIT 2 - (SELECT COUNT(*) FROM employees) % 2
OFFSET (SELECT (COUNT(*) - 1) / 2 FROM employees)
) t;Extract year, month, day from date using YEAR, MONTH, DAY functions.
- YEAR:
SELECT YEAR(date) as year, MONTH(date) as month, DAY(date) as day FROM events - DATE_FORMAT:
SELECT DATE_FORMAT(date, '%Y') as year, DATE_FORMAT(date, '%m') as month FROM events - EXTRACT:
SELECT EXTRACT(YEAR FROM date) as year, EXTRACT(MONTH FROM date) as month FROM events - Complexity: O(1)
-- Nth highest salary using LIMIT
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT N-1, 1;
-- Nth highest using DENSE_RANK (MySQL 8.0+)
WITH ranked AS (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT DISTINCT salary
FROM ranked
WHERE rnk = N;
-- 3rd highest salary example
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 2, 1;Format date using DATE_FORMAT function.
- DATE_FORMAT:
SELECT DATE_FORMAT(date, '%Y-%m-%d') as formatted_date FROM events - Common formats:
SELECT DATE_FORMAT(date, '%M %e, %Y') as full_date FROM events - Complexity: O(1)
-- Cumulative distribution (MySQL 8.0+)
SELECT
value,
CUME_DIST() OVER (ORDER BY value) AS cume_dist,
PERCENT_RANK() OVER (ORDER BY value) AS percent_rank
FROM mytable
ORDER BY value;
-- Manual calculation
SELECT
value,
(SELECT COUNT(*) FROM mytable WHERE value <= t.value) / COUNT(*) AS cume_dist
FROM mytable t
GROUP BY value
ORDER BY value;Add or subtract intervals from dates using DATE_ADD or DATE_SUB.
- DATE_ADD:
SELECT DATE_ADD(date, INTERVAL 1 DAY) as next_day FROM events - DATE_SUB:
SELECT DATE_SUB(date, INTERVAL 1 MONTH) as previous_month FROM events - Complexity: O(1)
-- First and last value in group (MySQL 8.0+)
SELECT
category,
date,
value,
FIRST_VALUE(value) OVER (PARTITION BY category ORDER BY date) AS first_value,
LAST_VALUE(value) OVER (
PARTITION BY category
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_value
FROM mytable
ORDER BY category, date;
-- Using subquery
SELECT
category,
date,
value,
(SELECT value FROM mytable t2
WHERE t2.category = t1.category
ORDER BY t2.date ASC LIMIT 1) AS first_value,
(SELECT value FROM mytable t2
WHERE t2.category = t1.category
ORDER BY t2.date DESC LIMIT 1) AS last_value
FROM mytable t1;Get current date and time using NOW, CURDATE, CURTIME.
- NOW:
SELECT NOW() as current_datetime - CURDATE:
SELECT CURDATE() as current_date - CURTIME:
SELECT CURTIME() as current_time - Complexity: O(1)
-- LAG and LEAD (MySQL 8.0+)
SELECT
date,
amount,
LAG(amount, 1) OVER (ORDER BY date) AS previous_amount,
LAG(amount, 2) OVER (ORDER BY date) AS previous_2_amount,
LEAD(amount, 1) OVER (ORDER BY date) AS next_amount,
LEAD(amount, 2) OVER (ORDER BY date) AS next_2_amount
FROM sales
ORDER BY date;
-- LAG and LEAD with partition
SELECT
category,
date,
amount,
LAG(amount, 1) OVER (PARTITION BY category ORDER BY date) AS prev_in_category,
LEAD(amount, 1) OVER (PARTITION BY category ORDER BY date) AS next_in_category
FROM sales
ORDER BY category, date;Concatenate strings using CONCAT or CONCAT_WS.
- CONCAT:
SELECT CONCAT(first_name, ' ', last_name) as full_name FROM users - CONCAT_WS:
SELECT CONCAT_WS(' ', first_name, last_name) as full_name FROM users - Complexity: O(n)
-- Difference between current and previous (MySQL 8.0+)
SELECT
date,
amount,
amount - LAG(amount, 1) OVER (ORDER BY date) AS difference,
amount - LAG(amount, 1) OVER (ORDER BY date) AS diff_from_prev
FROM sales
ORDER BY date;
-- Using self-join
SELECT
a.date,
a.amount,
a.amount - b.amount AS difference
FROM sales a
JOIN sales b ON b.date = (
SELECT MAX(date)
FROM sales
WHERE date < a.date
)
ORDER BY a.date;Split string using SUBSTRING_INDEX or JSON functions.
- SUBSTRING_INDEX:
SELECT SUBSTRING_INDEX('a,b,c', ',', 1) as first, SUBSTRING_INDEX(SUBSTRING_INDEX('a,b,c', ',', -1), ',', 1) as second - JSON:
SELECT JSON_EXTRACT('["a","b","c"]', '$[0]') as first, JSON_EXTRACT('["a","b","c"]', '$[1]') as second - Complexity: O(n)
-- Percentage change (MySQL 8.0+)
SELECT
date,
amount,
((amount - LAG(amount, 1) OVER (ORDER BY date)) /
LAG(amount, 1) OVER (ORDER BY date)) * 100 AS pct_change
FROM sales
ORDER BY date;
-- Percentage change from previous year
SELECT
date,
amount,
((amount - LAG(amount, 12) OVER (ORDER BY date)) /
LAG(amount, 12) OVER (ORDER BY date)) * 100 AS yoy_pct_change
FROM monthly_sales
ORDER BY date;Match string patterns using LIKE or REGEXP.
- LIKE:
SELECT * FROM users WHERE email LIKE '%@example.com' - REGEXP:
SELECT * FROM users WHERE email REGEXP '^[a-z]+@[a-z]+\.[a-z]+$' - RLIKE:
SELECT * FROM users WHERE email RLIKE '^[a-zA-Z]+@[a-zA-Z]+\.[a-zA-Z]+$' - Complexity: O(n)
-- Year-over-year comparison
SELECT
YEAR(date) AS year,
MONTH(date) AS month,
amount,
LAG(amount, 12) OVER (ORDER BY date) AS same_month_last_year,
amount - LAG(amount, 12) OVER (ORDER BY date) AS yoy_change,
((amount - LAG(amount, 12) OVER (ORDER BY date)) /
LAG(amount, 12) OVER (ORDER BY date)) * 100 AS yoy_pct_change
FROM monthly_sales
ORDER BY date;Extract substring using SUBSTR or LEFT/RIGHT.
- SUBSTR:
SELECT SUBSTR('Hello World', 1, 5) as first_word - LEFT:
SELECT LEFT('Hello World', 5) as first_word - RIGHT:
SELECT RIGHT('Hello World', 5) as last_word - Complexity: O(n)
-- Rolling sum (MySQL 8.0+)
SELECT
date,
amount,
SUM(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_7day_sum,
AVG(amount) OVER (
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_7day_avg
FROM daily_sales
ORDER BY date;
-- Rolling sum with partition
SELECT
category,
date,
amount,
SUM(amount) OVER (
PARTITION BY category
ORDER BY date
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS rolling_7day_sum_category
FROM daily_sales
ORDER BY category, date;Convert case using UPPER, LOWER, or INITCAP (SQL 8.0+).
- UPPER:
SELECT UPPER('hello') as upper_string - LOWER:
SELECT LOWER('HELLO') as lower_string - INITCAP:
SELECT INITCAP('hello world') as title_case - Complexity: O(n)
-- Fill missing dates (MySQL 8.0+)
WITH RECURSIVE dates(dt) AS (
SELECT MIN(date) FROM sales
UNION ALL
SELECT dt + INTERVAL 1 DAY
FROM dates
WHERE dt < (SELECT MAX(date) FROM sales)
)
SELECT
d.dt AS date,
COALESCE(s.amount, 0) AS amount
FROM dates d
LEFT JOIN sales s ON d.dt = s.date
ORDER BY d.dt;
-- Fill missing dates with previous value
WITH RECURSIVE dates(dt) AS (
SELECT MIN(date) FROM sales
UNION ALL
SELECT dt + INTERVAL 1 DAY
FROM dates
WHERE dt < (SELECT MAX(date) FROM sales)
),
filled AS (
SELECT
d.dt AS date,
s.amount,
@prev_amount := COALESCE(s.amount, @prev_amount) AS filled_amount
FROM dates d
LEFT JOIN sales s ON d.dt = s.date
CROSS JOIN (SELECT @prev_amount := 0) init
)
SELECT date, filled_amount AS amount
FROM filled;Clean strings using TRIM, RTRIM, LTRIM.
- TRIM:
SELECT TRIM(' hello ') as trimmed - LTRIM:
SELECT LTRIM(' hello') as left_trimmed - RTRIM:
SELECT RTRIM('hello ') as right_trimmed - Complexity: O(n)
-- Difference between dates
SELECT
start_date,
end_date,
DATEDIFF(end_date, start_date) AS days_diff,
TIMESTAMPDIFF(DAY, start_date, end_date) AS days_diff_2,
TIMESTAMPDIFF(MONTH, start_date, end_date) AS months_diff,
TIMESTAMPDIFF(YEAR, start_date, end_date) AS years_diff
FROM projects;
-- Difference from current date
SELECT
start_date,
DATEDIFF(CURDATE(), start_date) AS days_since_start,
TIMESTAMPDIFF(DAY, start_date, CURDATE()) AS days_since_start_2
FROM projects;Replace substrings using REPLACE or REGEXP_REPLACE.
- REPLACE:
SELECT REPLACE('Hello World', 'World', 'SQL') as replaced - REGEXP_REPLACE:
SELECT REGEXP_REPLACE('Hello 123 World', '[0-9]+', '') as cleaned - Complexity: O(n)
-- Extract date parts
SELECT
date_col,
YEAR(date_col) AS year,
MONTH(date_col) AS month,
DAY(date_col) AS day,
HOUR(date_col) AS hour,
MINUTE(date_col) AS minute,
SECOND(date_col) AS second,
WEEK(date_col) AS week,
QUARTER(date_col) AS quarter,
DAYOFWEEK(date_col) AS day_of_week,
DAYOFYEAR(date_col) AS day_of_year,
WEEKDAY(date_col) AS weekday_index
FROM events;
-- Using DATE_FORMAT
SELECT
date_col,
DATE_FORMAT(date_col, '%Y') AS year,
DATE_FORMAT(date_col, '%m') AS month,
DATE_FORMAT(date_col, '%d') AS day,
DATE_FORMAT(date_col, '%W') AS day_name,
DATE_FORMAT(date_col, '%M') AS month_name
FROM events;Get string length using LENGTH or CHAR_LENGTH.
- LENGTH:
SELECT LENGTH('Hello') as bytes_length - CHAR_LENGTH:
SELECT CHAR_LENGTH('Hello') as char_length - Complexity: O(1)
-- Format date
SELECT
date_col,
DATE_FORMAT(date_col, '%Y-%m-%d') AS yyyy_mm_dd,
DATE_FORMAT(date_col, '%m/%d/%Y') AS mm_dd_yyyy,
DATE_FORMAT(date_col, '%M %e, %Y') AS full_date,
DATE_FORMAT(date_col, '%W, %M %e, %Y') AS full_date_with_day,
DATE_FORMAT(date_col, '%r') AS time_12h,
DATE_FORMAT(date_col, '%H:%i:%s') AS time_24h,
DATE_FORMAT(date_col, '%Y-%m-%d %H:%i:%s') AS datetime_format
FROM events;Find position of substring using LOCATE or POSITION.
- LOCATE:
SELECT LOCATE('World', 'Hello World') as position - POSITION:
SELECT POSITION('World' IN 'Hello World') as position - INSTR:
SELECT INSTR('Hello World', 'World') as position - Complexity: O(n)
-- Date arithmetic
SELECT
date_col,
DATE_ADD(date_col, INTERVAL 1 DAY) AS tomorrow,
DATE_ADD(date_col, INTERVAL 1 WEEK) AS next_week,
DATE_ADD(date_col, INTERVAL 1 MONTH) AS next_month,
DATE_ADD(date_col, INTERVAL 1 YEAR) AS next_year,
DATE_SUB(date_col, INTERVAL 1 DAY) AS yesterday,
DATE_SUB(date_col, INTERVAL 1 MONTH) AS last_month,
date_col + INTERVAL 1 DAY AS tomorrow_alt,
date_col - INTERVAL 1 DAY AS yesterday_alt
FROM events;
-- Date arithmetic with NOW
SELECT
NOW() AS now,
NOW() + INTERVAL 1 HOUR AS in_1_hour,
NOW() - INTERVAL 1 DAY AS yesterday,
DATE_ADD(NOW(), INTERVAL 1 HOUR) AS in_1_hour_2;Use JSON functions for aggregation and data manipulation.
- JSON_ARRAYAGG:
SELECT JSON_ARRAYAGG(name) as names FROM users WHERE city = 'NYC' - JSON_OBJECTAGG:
SELECT JSON_OBJECTAGG(id, name) as user_map FROM users - Complexity: O(n)
-- Current date and time
SELECT
NOW() AS current_datetime,
CURDATE() AS current_date,
CURTIME() AS current_time,
UTC_TIMESTAMP() AS utc_timestamp,
UNIX_TIMESTAMP() AS unix_timestamp,
CURRENT_TIMESTAMP() AS current_timestamp,
SYSDATE() AS sysdate;
-- Date only
SELECT
DATE(NOW()) AS date_only,
TIME(NOW()) AS time_only,
YEAR(NOW()) AS current_year,
MONTH(NOW()) AS current_month,
DAY(NOW()) AS current_day;Query JSON data using path expressions.
- JSON_EXTRACT:
SELECT JSON_EXTRACT(data, '$.name') as name FROM users - ->:
SELECT data->'$.name' as name FROM users - ->>:
SELECT data->>'$.name' as name FROM users - Complexity: O(1)
-- String concatenation
SELECT
CONCAT(first_name, ' ', last_name) AS full_name,
CONCAT_WS(' ', first_name, last_name) AS full_name_ws,
CONCAT(first_name, ' ', last_name, ' (', email, ')') AS detailed_info
FROM users;
-- Concatenation with NULL handling
SELECT
CONCAT(first_name, ' ', COALESCE(middle_name, ''), ' ', last_name) AS full_name,
CONCAT_WS(' ', first_name, middle_name, last_name) AS full_name_ws
FROM users;
-- Group concatenation
SELECT
department_id,
GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') AS employees
FROM employees
GROUP BY department_id;Manipulate JSON arrays using JSON functions.
- JSON_ARRAY_APPEND:
SELECT JSON_ARRAY_APPEND('[1,2,3]', '$', 4) as appended - JSON_ARRAY_INSERT:
SELECT JSON_ARRAY_INSERT('[1,2,3]', '$[1]', 99) as inserted - JSON_REMOVE:
SELECT JSON_REMOVE('[1,2,3,4]', '$[2]') as removed - Complexity: O(n)
-- String splitting using SUBSTRING_INDEX
SELECT
'a,b,c,d,e' AS original,
SUBSTRING_INDEX('a,b,c,d,e', ',', 1) AS first,
SUBSTRING_INDEX('a,b,c,d,e', ',', 2) AS first_two,
SUBSTRING_INDEX('a,b,c,d,e', ',', -1) AS last,
SUBSTRING_INDEX(SUBSTRING_INDEX('a,b,c,d,e', ',', 3), ',', -1) AS third;
-- Splitting into rows using JSON (MySQL 8.0+)
SELECT
JSON_EXTRACT(
JSON_ARRAY('a', 'b', 'c', 'd', 'e'),
CONCAT('$[', n, ']')
) AS element
FROM (
SELECT 0 AS n UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4
) numbers;Convert JSON to relational table using JSON_TABLE (SQL 8.0+).
- JSON_TABLE:
SELECT * FROM JSON_TABLE('[{"name":"Alice"},{"name":"Bob"}]', '$[*]' COLUMNS (name VARCHAR(100) PATH '$.name')) AS jt - Complexity: O(n)
-- String pattern matching using LIKE
SELECT * FROM users
WHERE email LIKE '%@example.com';
SELECT * FROM users
WHERE name LIKE 'A%';
SELECT * FROM users
WHERE name LIKE '%son%';
-- Using REGEXP
SELECT * FROM users
WHERE email REGEXP '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$';
SELECT * FROM users
WHERE name REGEXP '^[A-Z]';
SELECT * FROM users
WHERE phone REGEXP '^[0-9]{3}-[0-9]{3}-[0-9]{4}$';
-- Using RLIKE (synonym for REGEXP)
SELECT * FROM users
WHERE email RLIKE '^[a-z]+@[a-z]+\.[a-z]+$';Validate JSON using JSON_VALID (SQL 5.7+).
- JSON_VALID:
SELECT JSON_VALID('{"name":"Alice"}') as valid, JSON_VALID('invalid') as invalid - Schema validation:
SELECT JSON_SCHEMA_VALID('{"type":"object"}', '{"name":"Alice"}') as valid - Complexity: O(n)
-- Substring extraction
SELECT
'Hello World' AS original,
SUBSTR('Hello World', 1, 5) AS first_word,
SUBSTRING('Hello World', 7, 5) AS second_word,
SUBSTR('Hello World', 7) AS from_position_7,
LEFT('Hello World', 5) AS left_5,
RIGHT('Hello World', 5) AS right_5,
SUBSTRING_INDEX('Hello World', ' ', 1) AS first_word_2,
SUBSTRING_INDEX('Hello World', ' ', -1) AS last_word
FROM dual;
-- Extract from column
SELECT
email,
SUBSTRING_INDEX(email, '@', 1) AS username,
SUBSTRING_INDEX(email, '@', -1) AS domain
FROM users;Concatenate values from grouped rows using GROUP_CONCAT.
- GROUP_CONCAT:
SELECT department_id, GROUP_CONCAT(name ORDER BY name SEPARATOR ', ') as employees FROM employees GROUP BY department_id - Complexity: O(n log n)
-- Case conversion
SELECT
UPPER('hello world') AS upper_case,
LOWER('HELLO WORLD') AS lower_case,
'hello world' AS original,
UPPER(first_name) AS upper_name,
LOWER(email) AS lower_email
FROM users;
-- INITCAP (MySQL 8.0+)
SELECT
INITCAP('hello world') AS title_case;
-- Custom INITCAP for older MySQL
SELECT
CONCAT(
UPPER(SUBSTR('hello world', 1, 1)),
LOWER(SUBSTR('hello world', 2))
) AS custom_initcap;Generate random numbers using RAND or RANDOM.
- RAND:
SELECT RAND() as random, FLOOR(RAND() * 100) as random_int - RANDOM:
SELECT RANDOM() as random - Complexity: O(1)
-- String cleaning
SELECT
' hello ' AS original,
TRIM(' hello ') AS trimmed,
LTRIM(' hello') AS left_trimmed,
RTRIM('hello ') AS right_trimmed,
TRIM(LEADING ' ' FROM ' hello') AS lead_trimmed,
TRIM(TRAILING ' ' FROM 'hello ') AS trail_trimmed,
TRIM(BOTH ' ' FROM ' hello ') AS both_trimmed;
-- Trimming specific characters
SELECT
TRIM(LEADING 'x' FROM 'xxhelloxx') AS trim_leading_x,
TRIM(TRAILING 'x' FROM 'xxhelloxx') AS trim_trailing_x,
TRIM(BOTH 'x' FROM 'xxhelloxx') AS trim_both_x;Generate row numbers using ROW_NUMBER or user variables.
- ROW_NUMBER:
SELECT *, ROW_NUMBER() OVER (ORDER BY id) as row_num FROM users - User variable:
SELECT *, @row_num := @row_num + 1 as row_num FROM users, (SELECT @row_num := 0) r - Complexity: O(n log n)
-- Replace in string
SELECT
'Hello World' AS original,
REPLACE('Hello World', 'World', 'MySQL') AS replaced,
REPLACE('Hello World', 'o', '0') AS replace_o;
-- REGEXP_REPLACE (MySQL 8.0+)
SELECT
'Hello 123 World 456' AS original,
REGEXP_REPLACE('Hello 123 World 456', '[0-9]+', '') AS remove_numbers,
REGEXP_REPLACE('Hello 123 World 456', '[^0-9]+', ' ') AS extract_numbers,
REGEXP_REPLACE('Hello World', '([A-Z])', '_\1') AS add_underscore;
-- Remove multiple spaces
SELECT
REGEXP_REPLACE('Hello World', '\s+', ' ') AS normalize_spaces;Aggregate with conditions using CASE in aggregates.
- CASE with SUM:
SELECT department, SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_count, SUM(CASE WHEN status = 'inactive' THEN 1 ELSE 0 END) as inactive_count FROM users GROUP BY department - Complexity: O(n)
-- String length
SELECT
'Hello' AS str,
LENGTH('Hello') AS bytes_length,
CHAR_LENGTH('Hello') AS char_length,
CHARACTER_LENGTH('Hello') AS char_length_2;
-- Unicode strings
SELECT
'你好' AS unicode_str,
LENGTH('你好') AS bytes_length_unicode,
CHAR_LENGTH('你好') AS char_length_unicode;
-- Length of column
SELECT
name,
LENGTH(name) AS byte_length,
CHAR_LENGTH(name) AS char_length
FROM users;Use filtered aggregates for conditional counts.
- FILTER:
SELECT department, COUNT(*) FILTER (WHERE status = 'active') as active_count FROM users GROUP BY department - Complexity: O(n)
-- Find position of substring
SELECT
'Hello World' AS str,
LOCATE('World', 'Hello World') AS locate_position,
POSITION('World' IN 'Hello World') AS position_function,
INSTR('Hello World', 'World') AS instr_function,
LOCATE('o', 'Hello World', 5) AS locate_from_position,
LOCATE('World', 'Hello World') AS locate_position;
-- Position in column
SELECT
email,
LOCATE('@', email) AS at_position,
LOCATE('.', email) AS dot_position
FROM users;Find greatest or least value among columns using GREATEST and LEAST.
- GREATEST:
SELECT GREATEST(col1, col2, col3) as max_value FROM table - LEAST:
SELECT LEAST(col1, col2, col3) as min_value FROM table - Complexity: O(1)
-- JSON aggregation (MySQL 5.7+)
SELECT
department_id,
JSON_ARRAYAGG(name) AS employee_names,
JSON_OBJECTAGG(id, name) AS employee_map
FROM employees
GROUP BY department_id;
-- JSON_ARRAY with grouping
SELECT
city,
JSON_ARRAYAGG(name) AS users_in_city
FROM users
GROUP BY city;
-- JSON_OBJECT with grouping
SELECT
department_id,
JSON_OBJECT(
'department', department_id,
'employees', JSON_ARRAYAGG(name),
'count', COUNT(*)
) AS department_info
FROM employees
GROUP BY department_id;Use bitwise operators for flag manipulation.
- Bitwise AND:
SELECT flags & 1 as flag1, flags & 2 as flag2 FROM permissions - Bitwise OR:
UPDATE permissions SET flags = flags | 4 WHERE id = 1 - Bitwise XOR:
SELECT flags ^ 4 as toggled FROM permissions - Complexity: O(1)
-- JSON path queries
SELECT
JSON_EXTRACT(data, '$.name') AS name,
JSON_EXTRACT(data, '$.age') AS age,
data->'$.name' AS name_operator,
data->>'$.name' AS name_operator_text,
data->'$.address.city' AS city,
JSON_EXTRACT(data, '$.hobbies[0]') AS first_hobby
FROM users_json;
-- Nested JSON path
SELECT
data->>'$.name' AS name,
data->'$.address' AS address,
data->'$.address.city' AS city,
JSON_EXTRACT(data, '$.address.zip') AS zip
FROM users_json;Convert between data types using CAST or CONVERT.
- CAST:
SELECT CAST('123' AS SIGNED) as int_val, CAST('123.45' AS DECIMAL(10,2)) as dec_val - CONVERT:
SELECT CONVERT('123', SIGNED) as int_val, CONVERT('2024-01-01', DATE) as date_val - Complexity: O(1)
-- JSON array operations
SELECT
'["a","b","c"]' AS original,
JSON_ARRAY_APPEND('["a","b","c"]', '$', 'd') AS append,
JSON_ARRAY_INSERT('["a","b","c"]', '$[1]', 'x') AS insert,
JSON_REMOVE('["a","b","c","d"]', '$[2]') AS remove,
JSON_INSERT('["a","b","c"]', '$[3]', 'd') AS insert_2,
JSON_REPLACE('["a","b","c"]', '$[1]', 'x') AS replace;
-- JSON array functions
SELECT
JSON_LENGTH('["a","b","c"]') AS array_length,
JSON_CONTAINS('["a","b","c"]', '"b"') AS contains_b,
JSON_SEARCH('["a","b","c"]', 'one', 'b') AS search_b;Conditionally update rows using CASE in UPDATE.
- CASE:
UPDATE employees SET salary = CASE WHEN performance = 'excellent' THEN salary * 1.10 WHEN performance = 'good' THEN salary * 1.05 ELSE salary END - Complexity: O(n)
-- JSON_TABLE (MySQL 8.0+)
SELECT *
FROM JSON_TABLE(
'[{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]',
'$[*]' COLUMNS (
name VARCHAR(100) PATH '$.name',
age INT PATH '$.age'
)
) AS jt;
-- JSON_TABLE with nested data
SELECT *
FROM JSON_TABLE(
'{"users": [{"name": "Alice", "hobbies": ["reading", "gaming"]}]}',
'$.users[*]' COLUMNS (
name VARCHAR(100) PATH '$.name',
hobbies JSON PATH '$.hobbies'
)
) AS jt;Insert or update using INSERT ... ON DUPLICATE KEY UPDATE or REPLACE.
- INSERT ON DUPLICATE:
INSERT INTO users (id, name, age) VALUES (1, 'Alice', 25) ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age) - REPLACE:
REPLACE INTO users (id, name, age) VALUES (1, 'Alice', 25) - Complexity: O(1)
-- JSON validation (MySQL 5.7+)
SELECT
JSON_VALID('{"name": "Alice"}') AS valid_json,
JSON_VALID('invalid') AS invalid_json;
-- JSON schema validation (MySQL 8.0+)
SELECT
JSON_SCHEMA_VALID(
'{"type": "object", "properties": {"name": {"type": "string"}}}',
'{"name": "Alice"}'
) AS valid_schema;
-- Using JSON_VALID in constraint
ALTER TABLE users
ADD CONSTRAINT check_json_valid
CHECK (JSON_VALID(data));Insert multiple rows efficiently.
- Multiple VALUES:
INSERT INTO users (name, age) VALUES ('Alice', 25), ('Bob', 30), ('Charlie', 35) - INSERT FROM SELECT:
INSERT INTO archive_users SELECT * FROM users WHERE created_at < '2023-01-01' - Complexity: O(n)
-- GROUP_CONCAT
SELECT
department_id,
GROUP_CONCAT(name) AS employees,
GROUP_CONCAT(name ORDER BY name) AS employees_sorted,
GROUP_CONCAT(name ORDER BY name SEPARATOR '; ') AS employees_separated,
COUNT(*) AS employee_count
FROM employees
GROUP BY department_id;
-- GROUP_CONCAT with DISTINCT
SELECT
department_id,
GROUP_CONCAT(DISTINCT title ORDER BY title) AS unique_titles
FROM employees
GROUP BY department_id;
-- GROUP_CONCAT with CONCAT
SELECT
department_id,
GROUP_CONCAT(CONCAT(name, ' (', title, ')') SEPARATOR ', ') AS employees_info
FROM employees
GROUP BY department_id;Copy table structure with or without data.
- Structure only:
CREATE TABLE new_table LIKE existing_table - Structure and data:
CREATE TABLE new_table AS SELECT * FROM existing_table - Complexity: O(n)
-- Random number generation
SELECT
RAND() AS random_float,
RAND(123) AS random_seeded,
FLOOR(RAND() * 100) AS random_int_0_99,
FLOOR(RAND() * 100) + 1 AS random_int_1_100,
ROUND(RAND() * 100) AS rounded_random;
-- Random order
SELECT * FROM users ORDER BY RAND() LIMIT 5;
-- Random sample (MySQL 8.0+)
SELECT * FROM users ORDER BY RAND() LIMIT 5;
-- Random sample using TABLESAMPLE (MySQL 8.0+)
SELECT * FROM users TABLESAMPLE SYSTEM(10);Modify table structure using ALTER TABLE.
- ADD COLUMN:
ALTER TABLE users ADD COLUMN phone VARCHAR(20) - MODIFY COLUMN:
ALTER TABLE users MODIFY phone VARCHAR(15) - DROP COLUMN:
ALTER TABLE users DROP COLUMN phone - RENAME COLUMN:
ALTER TABLE users RENAME COLUMN phone TO mobile - Complexity: O(n)
-- Row number using ROW_NUMBER (MySQL 8.0+)
SELECT
*,
ROW_NUMBER() OVER (ORDER BY id) AS row_num
FROM users;
-- Row number with partition
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) AS rank_in_dept
FROM employees;
-- Row number using user variables (older MySQL)
SELECT
*,
@row_num := @row_num + 1 AS row_num
FROM users, (SELECT @row_num := 0) r
ORDER BY id;
-- Row number with reset
SELECT
*,
@row_num := IF(@prev_dept = department_id, @row_num + 1, 1) AS row_num,
@prev_dept := department_id
FROM employees
CROSS JOIN (SELECT @row_num := 0, @prev_dept := NULL) r
ORDER BY department_id, salary DESC;Add and manage foreign key constraints.
- CREATE:
CREATE TABLE orders (id INT PRIMARY KEY, user_id INT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE) - ALTER ADD:
ALTER TABLE orders ADD CONSTRAINT fk_orders_users FOREIGN KEY (user_id) REFERENCES users(id) - DROP:
ALTER TABLE orders DROP FOREIGN KEY fk_orders_users - Complexity: O(n)
-- Conditional aggregation
SELECT
department_id,
COUNT(*) AS total_employees,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) AS active_count,
SUM(CASE WHEN status = 'inactive' THEN 1 ELSE 0 END) AS inactive_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
AVG(CASE WHEN status = 'active' THEN salary ELSE NULL END) AS avg_active_salary
FROM employees
GROUP BY department_id;
-- Conditional aggregation with multiple conditions
SELECT
department_id,
SUM(CASE WHEN gender = 'M' AND status = 'active' THEN 1 ELSE 0 END) AS active_males,
SUM(CASE WHEN gender = 'F' AND status = 'active' THEN 1 ELSE 0 END) AS active_females
FROM employees
GROUP BY department_id;Use CHECK constraints for data validation.
- CREATE:
CREATE TABLE users (id INT PRIMARY KEY, age INT CHECK (age >= 0 AND age <= 150)) - ALTER ADD:
ALTER TABLE users ADD CONSTRAINT check_age CHECK (age >= 0 AND age <= 150) - Complexity: O(1)
-- Filtered aggregation (MySQL 8.0+)
SELECT
department_id,
COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'active') AS active_count,
COUNT(*) FILTER (WHERE status = 'inactive') AS inactive_count,
AVG(salary) FILTER (WHERE status = 'active') AS avg_active_salary
FROM employees
GROUP BY department_id;
-- Multiple filters
SELECT
department_id,
COUNT(*) FILTER (WHERE gender = 'M') AS male_count,
COUNT(*) FILTER (WHERE gender = 'F') AS female_count,
AVG(salary) FILTER (WHERE gender = 'M') AS avg_male_salary,
AVG(salary) FILTER (WHERE gender = 'F') AS avg_female_salary
FROM employees
GROUP BY department_id;Set default values for columns.
- CREATE:
CREATE TABLE users (id INT PRIMARY KEY, status VARCHAR(20) DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) - ALTER:
ALTER TABLE users ALTER COLUMN status SET DEFAULT 'active' - Complexity: O(1)
-- GREATEST and LEAST
SELECT
col1,
col2,
col3,
GREATEST(col1, col2, col3) AS max_value,
LEAST(col1, col2, col3) AS min_value
FROM mytable;
-- GREATEST with NULL handling
SELECT
col1,
col2,
col3,
GREATEST(COALESCE(col1, 0), COALESCE(col2, 0), COALESCE(col3, 0)) AS max_with_default
FROM mytable;
-- Find max of values from different tables
SELECT
u.id,
GREATEST(u.salary, COALESCE(b.bonus, 0)) AS total_compensation
FROM users u
LEFT JOIN bonuses b ON u.id = b.user_id;Drop tables with caution using DROP TABLE.
- DROP:
DROP TABLE users - DROP IF EXISTS:
DROP TABLE IF EXISTS users - TRUNCATE:
TRUNCATE TABLE users - Complexity: O(1)
-- Bitwise operations
-- Example table: permissions with bit flags
-- 1 = read, 2 = write, 4 = execute, 8 = delete
-- Check if flag is set
SELECT
permissions,
(permissions & 1) AS can_read,
(permissions & 2) AS can_write,
(permissions & 4) AS can_execute,
(permissions & 8) AS can_delete
FROM user_permissions;
-- Set flag
UPDATE user_permissions
SET permissions = permissions | 4
WHERE user_id = 1;
-- Clear flag
UPDATE user_permissions
SET permissions = permissions & ~4
WHERE user_id = 1;
-- Toggle flag
UPDATE user_permissions
SET permissions = permissions ^ 4
WHERE user_id = 1;
-- Check multiple flags
SELECT * FROM user_permissions
WHERE (permissions & 3) = 3; -- Has read AND writeUse temporary tables for complex queries.
- CREATE TEMPORARY:
CREATE TEMPORARY TABLE temp_users SELECT * FROM users WHERE status = 'active' - DROP TEMPORARY:
DROP TEMPORARY TABLE temp_users - Complexity: O(n)
-- Type conversion
-- CAST
SELECT
CAST('123' AS SIGNED) AS int_val,
CAST('123.45' AS DECIMAL(10,2)) AS dec_val,
CAST('2024-01-01' AS DATE) AS date_val,
CAST(123 AS CHAR) AS char_val,
CAST(123.45 AS UNSIGNED) AS unsigned_val;
-- CONVERT
SELECT
CONVERT('123', SIGNED) AS int_val,
CONVERT('123.45', DECIMAL(10,2)) AS dec_val,
CONVERT('2024-01-01', DATE) AS date_val,
CONVERT(123, CHAR) AS char_val;
-- Converting binary
SELECT
CONVERT('Hello' USING utf8mb4) AS utf8_string,
CAST('Hello' AS BINARY) AS binary_data;Show table information using various commands.
- DESCRIBE:
DESCRIBE users - SHOW CREATE TABLE:
SHOW CREATE TABLE users - SHOW TABLE STATUS:
SHOW TABLE STATUS LIKE 'users' - Complexity: O(1)
-- Conditional UPDATE
-- Update with CASE
UPDATE employees
SET salary = CASE
WHEN performance_rating = 5 THEN salary * 1.20
WHEN performance_rating = 4 THEN salary * 1.15
WHEN performance_rating = 3 THEN salary * 1.10
WHEN performance_rating = 2 THEN salary * 1.05
ELSE salary
END
WHERE status = 'active';
-- Update with IF
UPDATE employees
SET status = IF(active_years > 5, 'senior', 'junior')
WHERE status = 'active';
-- Update with multiple conditions
UPDATE products
SET price = CASE
WHEN category = 'electronics' THEN price * 0.90
WHEN category = 'clothing' THEN price * 0.85
WHEN category = 'books' THEN price * 0.80
ELSE price
END;Get database and server information.
- Database:
SELECT DATABASE() as current_db - Server version:
SELECT VERSION() as mysql_version - Connection info:
SELECT CONNECTION_ID() as connection_id - Complexity: O(1)
-- INSERT ON DUPLICATE KEY UPDATE
INSERT INTO users (id, name, age, email)
VALUES (1, 'Alice', 25, 'alice@example.com')
ON DUPLICATE KEY UPDATE
name = VALUES(name),
age = VALUES(age),
email = VALUES(email),
updated_at = NOW();
-- REPLACE (delete + insert)
REPLACE INTO users (id, name, age, email)
VALUES (1, 'Alice', 25, 'alice@example.com');
-- INSERT with multiple rows
INSERT INTO users (id, name, age)
VALUES
(1, 'Alice', 25),
(2, 'Bob', 30),
(3, 'Charlie', 35)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
age = VALUES(age);Essential query optimization tips for better performance.
- Use EXPLAIN: Analyze query execution plans
- Create indexes: On columns used in WHERE, JOIN, ORDER BY
- Limit results: Use LIMIT for pagination
- Avoid SELECT *: Specify only needed columns
- Use joins instead of subqueries: When possible
- Use EXISTS instead of IN: For large datasets
- Use UNION ALL instead of UNION: When duplicates are acceptable
- Batch operations: Use bulk inserts and updates
-- Query optimization tips
-- 1. Use EXPLAIN to analyze queries
EXPLAIN SELECT * FROM users WHERE name = 'Alice';
EXPLAIN FORMAT=JSON SELECT * FROM users WHERE name = 'Alice';
-- 2. Create indexes on frequently used columns
CREATE INDEX idx_users_name ON users(name);
CREATE INDEX idx_users_email_age ON users(email, age);
-- 3. Use LIMIT for pagination
SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20;
-- 4. Avoid SELECT *
SELECT id, name, email FROM users WHERE active = 1;
-- 5. Use EXISTS instead of IN when possible
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- 6. Use JOIN instead of subqueries
SELECT u.*, o.total
FROM users u
JOIN orders o ON u.id = o.user_id;
-- 7. Use UNION ALL instead of UNION when duplicates are acceptable
SELECT id, name FROM users
UNION ALL
SELECT id, name FROM archived_users;
-- 8. Use appropriate data types
-- VARCHAR for variable length, CHAR for fixed length
-- INT vs BIGINT based on range needed
-- 9. Use batch operations
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com');
-- 10. Use prepared statements for repeated queries
PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?';
SET @id = 1;
EXECUTE stmt USING @id;