iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Views

A MySQL view is a stored query that acts like a table. Useful for query reuse, security (hide columns), and abstraction. Can be updatable for simple cases; ALGORITHM = MERGE / TEMPTABLE controls execution strategy.

CREATE VIEW, updatable, security

EXAMPLE
-- 1) Basic view
CREATE VIEW active_users AS
SELECT id, email, name, created_at
FROM   users
WHERE  deleted_at IS NULL AND status = 'active';

-- Query like a table
SELECT * FROM active_users WHERE created_at >= NOW() - INTERVAL 30 DAY;

-- 2) Replace + drop
CREATE OR REPLACE VIEW active_users AS
SELECT id, email, name, created_at FROM users WHERE status = 'active';

DROP VIEW IF EXISTS active_users;

-- 3) View with joins + computed columns
CREATE VIEW order_summary AS
SELECT
    o.id,
    o.user_id,
    u.email                     AS user_email,
    o.total,
    o.status,
    COUNT(i.id)                 AS item_count,
    o.created_at
FROM   orders o
JOIN   users u  ON u.id = o.user_id
LEFT JOIN order_items i ON i.order_id = o.id
GROUP BY o.id;

SELECT * FROM order_summary WHERE total >= 100;

-- 4) Algorithm — MERGE vs TEMPTABLE vs UNDEFINED
CREATE VIEW v1 ALGORITHM = MERGE AS SELECT * FROM users WHERE active;
CREATE VIEW v2 ALGORITHM = TEMPTABLE AS SELECT * FROM users WHERE active;

-- MERGE: query rewritten to use base table (fast, uses indexes)
-- TEMPTABLE: result materialised into a temp table (forced when DISTINCT, GROUP BY, etc.)
-- UNDEFINED (default): MySQL chooses

-- 5) Updatable views — INSERT / UPDATE / DELETE through them
CREATE VIEW recent_users AS
SELECT id, email, name, created_at
FROM   users
WHERE  created_at >= NOW() - INTERVAL 30 DAY;

-- Works:
UPDATE recent_users SET name = 'New' WHERE id = 1;

-- Doesn't work (multiple tables, GROUP BY, aggregates, DISTINCT):
UPDATE order_summary SET item_count = 5;   -- ERROR

-- 6) WITH CHECK OPTION — enforce view's WHERE on writes
CREATE VIEW active_admins AS
SELECT id, email, role
FROM   users
WHERE  status = 'active' AND role = 'admin'
WITH   CHECK OPTION;

-- INSERT INTO active_admins (email, role) VALUES ('x@x.com', 'user');
-- ERROR — violates WHERE clause; can't insert non-admin via this view

-- 7) Security — hide columns from users
REVOKE ALL ON users FROM analyst@'%';

CREATE VIEW user_public AS
SELECT id, name, created_at
FROM   users;

GRANT SELECT ON user_public TO analyst@'%';
-- Analyst reads names, can't see emails / passwords

-- 8) View dependencies
SHOW CREATE VIEW order_summary;

-- View definition
SELECT view_definition
FROM   information_schema.views
WHERE  table_schema = 'mydb' AND table_name = 'order_summary';

-- List all views
SELECT table_name FROM information_schema.views WHERE table_schema = 'mydb';

-- 9) DEFINER + INVOKER — security context
CREATE
    DEFINER = 'admin'@'%'
    SQL SECURITY INVOKER
VIEW user_public AS
SELECT id, name, created_at FROM users;

-- DEFINER (default): runs with creator's permissions
-- INVOKER: runs with caller's permissions

-- 10) Cascading drop
DROP VIEW user_public, order_summary;

-- View depending on dropped table → becomes invalid; CHECK TABLE shows error
CHECK TABLE my_view;

-- 11) View limitations
--   ❌ No CHECK constraints (use WITH CHECK OPTION instead)
--   ❌ No triggers ON the view (triggers fire on base tables)
--   ❌ Can't index views directly (use base-table indexes)
--   ❌ ALGORITHM = TEMPTABLE can't be updatable
--   ❌ Subqueries in FROM, UNION → not updatable

-- 12) No materialised views in MySQL!
-- Workarounds:
--   • Cached result table refreshed via event scheduler
--   • Application-side cache (Redis)
--   • Switch to Postgres / Oracle for true materialised views

-- DIY materialised view via table + event
CREATE TABLE daily_revenue (
    day DATE PRIMARY KEY,
    revenue DECIMAL(10, 2),
    order_count INT
);

CREATE EVENT refresh_daily_revenue
ON SCHEDULE EVERY 15 MINUTE
DO
    REPLACE INTO daily_revenue
    SELECT DATE(created_at) AS day,
           SUM(total)        AS revenue,
           COUNT(*)           AS order_count
    FROM   orders
    WHERE  status = 'paid'
    GROUP BY day;

-- Or via stored procedure called by external scheduler

-- 13) Common patterns

-- a) API shape — hide internal fields
CREATE VIEW api_users AS
SELECT
    id,
    email,
    name,
    created_at,
    (SELECT COUNT(*) FROM orders WHERE user_id = u.id) AS order_count
FROM users u;

-- b) Backwards compatibility
ALTER TABLE orders RENAME COLUMN amt TO total;

CREATE OR REPLACE VIEW orders_compat AS
SELECT id, user_id, total AS amt, total, status, created_at
FROM orders;

-- c) Department-restricted view
CREATE VIEW my_team_orders AS
SELECT *
FROM   orders
WHERE  team_id = (SELECT team_id FROM users WHERE id = CURRENT_USER());

-- 14) Performance
-- ALGORITHM = MERGE: rewrites query to use base table (preferred when possible)
-- ALGORITHM = TEMPTABLE: materialises view first, then queries it (slower, no index reuse)
-- Use EXPLAIN to see how a view query gets executed:
EXPLAIN SELECT * FROM order_summary WHERE total > 100;

-- 15) Common bugs
--   ❌ Forgetting WITH CHECK OPTION on updatable views → bad data lands in base table
--   ❌ Using DEFINER with overly-broad role → privilege escalation risk
--   ❌ Many nested views → optimiser struggles
--   ❌ Assuming materialised behaviour → MySQL re-runs the underlying query
--   ❌ INSERT through view with required columns missing in view definition

-- 16) When to use views vs alternatives
-- View              : query reuse, security, abstraction
-- Stored procedure  : business logic with side effects
-- Generated columns : derived values stored alongside the row
-- Application cache : when MySQL alone can't make it fast enough
--                      (Redis / Memcached / app-side memoisation)

-- 17) Updatable view limits — must satisfy ALL:
--   • SELECT from ONE table
--   • No DISTINCT, GROUP BY, HAVING
--   • No aggregate functions (SUM, COUNT, etc.)
--   • No subqueries in SELECT list
--   • No UNION / UNION ALL
--   • No JOIN (in most cases)
--   • No LEFT JOIN with rows missing on the joined side
--   • Columns to be inserted/updated must come from base table

-- 18) Best practices
--   ✅ Document views with COMMENT in version control (MySQL doesn't store comments on views)
--   ✅ Use SQL SECURITY INVOKER unless you specifically need DEFINER for delegation
--   ✅ ALGORITHM = MERGE when the view CAN be merged — better index reuse
--   ✅ Use views for security (column-level) — pair with REVOKE on base table
--   ✅ Don't over-nest — 3+ levels of views slow the optimiser
--   ✅ EXPLAIN the underlying query — same indexes still apply
--   ✅ For aggregate caching, use a real table + event scheduler — no native materialised view
--   ✅ Consider switching to Postgres if you need real materialised views

Why it matters

MySQL views are great for query reuse + security but DON’T materialise — every query re-runs the underlying SELECT. For expensive aggregates, build a cache table + event scheduler, or switch to Postgres for true materialised views.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
CREATE VIEW active_users AS
    SELECT * FROM users WHERE last_login > NOW() - INTERVAL 30 DAY;
Try it Yourself »

Discussion

Loading…