Curated questions covering queries, joins, indexes, transactions, stored procedures, normalization, and database optimization techniques.
MySQL is an open-source relational database management system (RDBMS). Key features: ACID compliance, support for SQL standard, multiple storage engines (InnoDB, MyISAM), replication, partitioning, full-text search, JSON support, and wide adoption in web applications (LAMP stack).
SELECT u.name, o.total\nFROM users u\nINNER JOIN orders o ON u.id = o.user_id;\n\nSELECT u.name, o.total\nFROM users u\nLEFT JOIN orders o ON u.id = o.user_id; -- includes users with no ordersSELECT department, COUNT(*) as emp_count, AVG(salary) as avg_sal\nFROM employees\nWHERE salary > 30000 -- filter rows first\nGROUP BY department\nHAVING COUNT(*) > 5; -- filter groups afterDELETE FROM users WHERE id = 1; -- specific rows, rollback-able\nTRUNCATE tl-table temp_logs; -- all rows, fast, no rollback\nDROP tl-table old_table; -- removes tl-table entirelyCREATE tl-table users (\n id INT PRIMARY KEY AUTO_INCREMENT,\n email VARCHAR(255) UNIQUE NOT NULL,\n username VARCHAR(50) UNIQUE\n);-- Primary key creates clustered index automatically\nCREATE tl-table users (id INT PRIMARY KEY, name VARCHAR(100));\n\n-- Non-clustered index\nCREATE INDEX idx_name ON users(name);\nCREATE INDEX idx_email_status ON users(email, status); -- compositeCREATE tl-table example (\n country_code CHAR(2), -- always 2 bytes\n description VARCHAR(500) -- variable length\n);CREATE tl-table events (\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n scheduled_at DATETIME -- no timezone conversion\n);SELECT name FROM customers\nUNION\nSELECT name FROM suppliers; -- removes duplicates\n\nSELECT name FROM customers\nUNION ALL\nSELECT name FROM suppliers; -- keeps duplicates, fasterWindow functions perform calculations across a set of rows related to the current tl-row without collapsing them into groups. Available since MySQL 8.0.
SELECT name, salary, department,\n RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank,\n SUM(salary) OVER (PARTITION BY department) as dept_total,\n LAG(salary) OVER (ORDER BY salary) as prev_salary\nFROM employees;SELECT name, score,\n ROW_NUMBER() OVER (ORDER BY score DESC) as row_num,\n RANK() OVER (ORDER BY score DESC) as rank_val,\n DENSE_RANK() OVER (ORDER BY score DESC) as dense_rank\nFROM students;A stored procedure is a precompiled set of SQL statements stored in the database. Benefits: reusability, reduced network traffic, better performance, and centralized business logic.
DELIMITER //\nCREATE PROCEDURE GetUserById(IN userId INT)\nBEGIN\n SELECT * FROM users WHERE id = userId;\nEND //\nDELIMITER ;\n\nCALL GetUserById(1);-- Function\nCREATE FUNCTION GetFullName(first VARCHAR(50), last VARCHAR(50))\nRETURNS VARCHAR(100)\nDETERMINISTIC\nRETURN CONCAT(first, " ", last);\n\nSELECT GetFullName(first_name, last_name) FROM users;A trigger automatically executes in response to INSERT, UPDATE, or DELETE events on a table. Types: BEFORE and AFTER.
CREATE TRIGGER before_user_update\nBEFORE UPDATE ON users\nFOR EACH ROW\nBEGIN\n INSERT INTO audit_log(user_id, old_email, new_email, changed_at)\n VALUES (OLD.id, OLD.email, NEW.email, NOW());\nEND;CREATE VIEW active_users AS\nSELECT id, name, email\nFROM users\nWHERE status = "active";\n\nSELECT * FROM active_users; -- use like a tableEXPLAIN SELECT * FROM users WHERE email = "alice@example.com";\n-- Check: type (ALL=bad, ref/eq_ref=good), key (index used), rows (estimated)-- JOIN (usually faster)\nSELECT u.name FROM users u\nINNER JOIN orders o ON u.id = o.user_id;\n\n-- Subquery equivalent\nSELECT name FROM users\nWHERE id IN (SELECT user_id FROM orders);-- Non-correlated (executes once)\nSELECT * FROM users WHERE id IN (SELECT user_id FROM orders);\n\n-- Correlated (executes per tl-row - slow)\nSELECT * FROM users u\nWHERE (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) > 5;SET TRANSACTION ISOLATION LEVEL READ COMMITTED;\nSTART TRANSACTION;\n SELECT balance FROM accounts WHERE id = 1;\n UPDATE accounts SET balance = balance - 100 WHERE id = 1;\nCOMMIT;-- Pessimistic\nSTART TRANSACTION;\nSELECT * FROM products WHERE id = 1 FOR UPDATE;\nUPDATE products SET stock = stock - 1 WHERE id = 1;\nCOMMIT;\n\n-- Optimistic\nUPDATE products SET stock = stock-1, version = version+1\nWHERE id = 1 AND version = 5; -- fails if version changedSELECT DISTINCT department FROM employees; -- unique departments\n\nSELECT department, COUNT(*), AVG(salary)\nFROM employees\nGROUP BY department; -- aggregation per groupSELECT\n COUNT(*) as total_rows,\n COUNT(email) as rows_with_email,\n COUNT(DISTINCT email) as unique_emails\nFROM users;SELECT IFNULL(phone, "N/A") FROM users;\nSELECT COALESCE(phone, mobile, "N/A") FROM users;\nSELECT total / NULLIF(count, 0) FROM stats; -- avoid division by zeroSELECT * FROM users WHERE name LIKE "Al%"; -- starts with Al\nSELECT * FROM users WHERE email REGEXP "^[a-z]+@gmail\.com$";STRAIGHT_JOIN forces MySQL to join tables in the order they appear in the query, overriding the optimizer. Use only when you know the optimizer is choosing a suboptimal join order.
-- Force users to be the driving table\nSELECT STRAIGHT_JOIN u.name, o.total\nFROM users u\nINNER JOIN orders o ON u.id = o.user_id;SET NAMES utf8mb4;\nSELECT LENGTH("hello"); -- 5 bytes\nSELECT CHAR_LENGTH("hello"); -- 5 chars\n-- For emoji (4 bytes in UTF-8):\nSELECT LENGTH("hi"); -- 6 bytes\nSELECT CHAR_LENGTH("hi"); -- 3 charsSELECT CONCAT("Hello", " ", "World"); -- "Hello World"\nSELECT CONCAT("Hello", NULL, "World"); -- NULL\nSELECT CONCAT_WS(", ", "Alice", NULL, "NYC"); -- "Alice, NYC"SELECT DATE_FORMAT(NOW(), "%Y-%m-%d %H:%i:%s"); -- "2026-04-22 10:30:00"\nSELECT STR_TO_DATE("22/04/2026", "%d/%m/%Y"); -- 2026-04-22SELECT DATEDIFF("2026-12-31", "2026-01-01"); -- 364 days\nSELECT TIMESTAMPDIFF(MONTH, "2026-01-01", "2026-12-31"); -- 11 months-- Composite index: efficient for WHERE a=? AND b=?\nCREATE INDEX idx_name_dept ON employees(last_name, department);\n\n-- Leftmost prefix rule: can use for:\n-- WHERE last_name = ?\n-- WHERE last_name = ? AND department = ?\n-- Cannot use for: WHERE department = ? aloneSTART TRANSACTION;\n INSERT INTO orders VALUES (1, 100);\n SAVEPOINT after_order;\n INSERT INTO payments VALUES (1, 100);\n ROLLBACK TO SAVEPOINT after_order; -- undo payment only\nCOMMIT; -- commits the orderCREATE tl-table shirts (\n size ENUM("XS","S","M","L","XL"),\n colors SET("red","green","blue","black")\n);\nINSERT INTO shirts VALUES ("M", "red,blue");SELECT * FROM users USE INDEX (idx_email) WHERE email = "a@b.com";\nSELECT * FROM users FORCE INDEX (idx_email) WHERE email = "a@b.com";SHOW PROCESSLIST; -- active connections and queries\nSHOW FULL PROCESSLIST; -- includes full query text\n\nSELECT table_name, table_rows, data_length\nFROM information_schema.tables\nWHERE table_schema = "mydb";-- Create full-text index\nCREATE FULLTEXT INDEX ft_idx ON articles(title, body);\n\n-- Natural language search\nSELECT *, MATCH(title,body) AGAINST("mysql performance") AS score\nFROM articles\nWHERE MATCH(title,body) AGAINST("mysql performance")\nORDER BY score DESC;CREATE tl-table orders (\n id INT PRIMARY KEY,\n user_id INT,\n FOREIGN KEY (user_id) REFERENCES users(id)\n ON DELETE CASCADE\n ON UPDATE CASCADE\n);-- GROUP BY: one tl-row per department\nSELECT department, AVG(salary) FROM employees GROUP BY department;\n\n-- PARTITION BY: all rows preserved with department average\nSELECT name, salary, department,\n AVG(salary) OVER (PARTITION BY department) as dept_avg\nFROM employees;CREATE TEMPORARY tl-table temp_results AS\nSELECT user_id, SUM(amount) as total\nFROM orders\nGROUP BY user_id;\n\nSELECT u.name, t.total\nFROM users u JOIN temp_results t ON u.id = t.user_id;-- CROSS JOIN: 3 * 4 = 12 rows\nSELECT * FROM colors CROSS JOIN sizes;\n\n-- INNER JOIN: only matching rows\nSELECT * FROM users INNER JOIN orders ON users.id = orders.user_id;A SELF JOIN joins a tl-table with itself. Used for hierarchical data (employees and managers) or comparing rows within the same table.
-- Find employees and their managers\nSELECT e.name as employee, m.name as manager\nFROM employees e\nLEFT JOIN employees m ON e.manager_id = m.id;SELECT * FROM orders WHERE amount BETWEEN 100 AND 500;\n-- Equivalent to:\nSELECT * FROM orders WHERE amount >= 100 AND amount <= 500;\n\nSELECT * FROM events WHERE event_date BETWEEN "2026-01-01" AND "2026-12-31";SELECT * FROM users WHERE phone IS NULL; -- correct\nSELECT * FROM users WHERE phone = NULL; -- always returns 0 rows!\nSELECT * FROM users WHERE phone IS NOT NULL; -- has a phone numberSELECT * FROM products ORDER BY id LIMIT 10; -- first 10\nSELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20; -- rows 21-30\nSELECT * FROM products ORDER BY id LIMIT 20, 10; -- same as aboveSELECT CAST("42" AS UNSIGNED); -- 42\nSELECT CAST("2026-04-22" AS DATE); -- date\nSELECT CONVERT("42", UNSIGNED); -- 42\nSELECT CONVERT("hello" USING utf8mb4); -- charset conversionSHOW TABLES; -- simple list\nSHOW TABLES LIKE "user%"; -- filtered\n\nSELECT table_name, engine, table_rows, data_length\nFROM information_schema.tables\nWHERE table_schema = DATABASE()\nORDER BY data_length DESC;Explore 500+ free tutorials across 20+ languages and frameworks.