
-- Create database (engine-specific)
CREATE DATABASE shop;
-- Switch database
-- MySQL: USE shop; SQL Server: USE shop;
-- Create table with constraints
CREATE TABLE customers (
id INT AUTO_INCREMENT PRIMARY KEY, -- MySQL
-- SQL Server: id INT IDENTITY(1,1) PRIMARY KEY
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Alter table
ALTER TABLE customers ADD COLUMN phone VARCHAR(20);
ALTER TABLE customers MODIFY COLUMN phone VARCHAR(30); -- MySQL
-- SQL Server: ALTER TABLE customers ALTER COLUMN phone VARCHAR(30);
ALTER TABLE customers RENAME COLUMN phone TO mobile; -- MySQL 8.0+
-- SQL Server: sp_rename 'customers.phone', 'mobile', 'COLUMN';
ALTER TABLE customers DROP COLUMN mobile;
-- Drop vs Truncate
DROP TABLE customers; -- remove definition + data
TRUNCATE TABLE customers; -- fast delete all rows (resets identities)
-- MySQL RANGE partitioning (by year)
CREATE TABLE metrics (
ts DATETIME NOT NULL,
value DOUBLE
) PARTITION BY RANGE (YEAR(ts)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION pMAX VALUES LESS THAN MAXVALUE
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
status VARCHAR(20) CHECK (status IN ('pending','paid','shipped','cancelled')),
total_amount NUMERIC(10,2) NOT NULL CHECK (total_amount >= 0),
CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id)
REFERENCES customers(id)
ON UPDATE CASCADE
ON DELETE RESTRICT
);
-- SELECT
SELECT id, name, email FROM customers
WHERE email IS NOT NULL
AND name LIKE 'A%'
ORDER BY created_at DESC, name ASC
LIMIT 10 OFFSET 20;
-- INSERT
INSERT INTO customers (name, email) VALUES ('Alice','alice@example.com');
INSERT INTO customers (name, email) VALUES
('Bob','bob@example.com'), ('Carol',NULL);
-- INSERT ... SELECT
INSERT INTO orders (customer_id, order_date, status, total_amount)
SELECT id, CURDATE(), 'pending', 0
FROM customers WHERE email IS NULL;
-- UPDATE / DELETE
UPDATE orders
SET status = CASE WHEN total_amount > 0 THEN 'paid' ELSE 'pending' END
WHERE order_date < CURDATE();
DELETE FROM orders WHERE customer_id = 123;
Upsert:
-- INNER
SELECT c.id, c.name, o.id AS order_id, o.total_amount
FROM customers c
JOIN orders o ON o.customer_id = c.id;
-- LEFT/RIGHT
SELECT c.id, c.name, o.id AS order_id
FROM customers c LEFT JOIN orders o ON o.customer_id = c.id;
-- FULL OUTER (SQL Server only)
SELECT c.id, c.name, o.id
FROM customers c FULL OUTER JOIN orders o ON o.customer_id = c.id;
-- CROSS
SELECT c.name, s.status
FROM customers c
CROSS JOIN (SELECT DISTINCT status FROM orders) s;
-- Aggregates
SELECT
COUNT(*) AS order_count,
SUM(total_amount) AS revenue,
AVG(total_amount) AS avg_order,
MIN(total_amount) AS min_order,
MAX(total_amount) AS max_order
FROM orders
WHERE status = 'paid';
-- GROUP BY & HAVING
SELECT customer_id, COUNT(*) AS orders, SUM(total_amount) AS spend
FROM orders
GROUP BY customer_id
HAVING SUM(total_amount) > 1000
ORDER BY spend DESC;
-- GROUPING SETS / ROLLUP / CUBE (SQL Server)
SELECT region, product, SUM(amount) AS amt
FROM sales
GROUP BY GROUPING SETS ((region, product), (region), ());
SELECT
customer_id,
order_date,
total_amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date) AS rn,
RANK() OVER (ORDER BY total_amount DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY total_amount DESC) AS drnk,
SUM(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_spend,
LAG(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS prev_amount,
LEAD(total_amount) OVER (PARTITION BY customer_id ORDER BY order_date) AS next_amount
FROM orders;
Advanced:
-- Subquery
SELECT * FROM customers
WHERE id IN (
SELECT customer_id FROM orders
GROUP BY customer_id
HAVING SUM(total_amount) > 500
);
-- CTE
WITH paid_orders AS (
SELECT * FROM orders WHERE status = 'paid'
)
SELECT customer_id, COUNT(*) FROM paid_orders GROUP BY customer_id;
-- Recursive CTE (SQL Server)
WITH org AS (
SELECT id, manager_id, name, 0 AS depth
FROM employees WHERE manager_id IS NULL
UNION ALL
SELECT e.id, e.manager_id, e.name, o.depth + 1
FROM employees e JOIN org o ON e.manager_id = o.id
)
SELECT * FROM org;
-- CASE
SELECT id,
CASE
WHEN total_amount = 0 THEN 'free'
WHEN total_amount < 50 THEN 'small'
ELSE 'regular'
END AS order_size
FROM orders;
-- COALESCE / NULLIF
SELECT COALESCE(email, 'unknown@example.com') FROM customers;
SELECT NULLIF(status, 'pending') FROM orders;
-- Vendor equivalents:
-- MySQL: IFNULL(expr, alt)
-- SQL Server: ISNULL(expr, alt)
Watch-outs:
SELECT email FROM customers
UNION
SELECT contact_email FROM partners;
SELECT email FROM customers
UNION ALL
SELECT contact_email FROM partners;
-- INTERSECT/EXCEPT (SQL Server)
SELECT email FROM customers
INTERSECT
SELECT contact_email FROM partners;
SELECT email FROM customers
EXCEPT
SELECT contact_email FROM partners;
Examples (dialect varies):
-- MySQL JSON_TABLE example
SELECT jt.product_id, jt.qty
FROM orders o,
JSON_TABLE(o.items, '$[*]' COLUMNS (product_id INT PATH '$.id', qty INT PATH '$.qty')) AS jt;
-- SQL Server JSON example
SELECT JSON_VALUE(data, '$.email') AS email
FROM users_json
WHERE JSON_VALUE(data, '$.active') = 'true';
CREATE VIEW customer_summary AS
SELECT c.id, c.name, COUNT(o.id) AS orders, COALESCE(SUM(o.total_amount),0) AS spend
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;
-- MySQL procedure
DELIMITER $$
CREATE PROCEDURE set_order_paid(IN p_order_id INT)
BEGIN
UPDATE orders SET status = 'paid' WHERE id = p_order_id;
END$$
DELIMITER ;
-- SQL Server procedure
CREATE PROCEDURE set_order_paid @p_order_id INT
AS
BEGIN
UPDATE orders SET status = 'paid' WHERE id = @p_order_id;
END;
-- Trigger (MySQL)
CREATE TRIGGER trg_order_status
AFTER UPDATE ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_audit(order_id, old_status, new_status)
VALUES (OLD.id, OLD.status, NEW.status);
END;
-- Trigger (SQL Server)
CREATE TRIGGER trg_order_status
ON orders
AFTER UPDATE
AS
BEGIN
INSERT INTO order_audit(order_id, old_status, new_status)
SELECT i.id, d.status, i.status FROM inserted i JOIN deleted d ON i.id = d.id;
END;
BEGIN; -- MySQL: START TRANSACTION; SQL Server: BEGIN TRANSACTION;
-- work ...
SAVEPOINT sp1;
-- more ...
ROLLBACK TO sp1; -- partial undo
COMMIT; -- or ROLLBACK;
EXPLAIN
SELECT c.id, COUNT(o.id)
FROM customers c LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id;
CREATE USER 'analyst'@'localhost' IDENTIFIED BY 'strongpass';
GRANT SELECT ON shop.* TO 'analyst'@'localhost';
REVOKE SELECT ON shop.* FROM 'analyst'@'localhost';
CREATE LOGIN analyst WITH PASSWORD = 'strongpass';
CREATE USER analyst FOR LOGIN analyst;
GRANT SELECT ON dbo.customers TO analyst;