Vistas en MySQL: qué son, cómo se crean y para qué sirven

Una vista en MySQL (una view) es una consulta SELECT guardada con nombre dentro de la base de datos, que después consultas como si fuera una tabla. La vista no guarda filas: cada vez que haces SELECT * FROM mi_vista, MySQL ejecuta la consulta guardada contra las tablas y te devuelve los datos que hay en ese momento.
Sirve para tres cosas concretas: esconder un JOIN largo detrás de un nombre corto, dejar que un usuario lea algunas columnas de una tabla sin darle acceso a la tabla entera, y cambiar el esquema sin romper las consultas que ya usa la aplicación. Todo lo que sigue corresponde a MySQL 8.4.
Qué es una vista en MySQL
Una vista se comporta como una tabla virtual. Tiene nombre y columnas, aparece en la lista de tablas de la base de datos y la consultas con SELECT igual que a una tabla, pero sus filas salen de una consulta.
El ejemplo más corto es el del propio manual:
CREATE TABLE t (qty INT, price INT);
INSERT INTO t VALUES (3, 50);
CREATE VIEW v AS SELECT qty, price, qty * price AS value FROM t;
SELECT * FROM v;+------+-------+-------+
| qty | price | value |
+------+-------+-------+
| 3 | 50 | 150 |
+------+-------+-------+La tabla t no tiene una columna value: ese valor se calcula cada vez que alguien consulta la vista. Si mañana cambias price en t, la próxima consulta a v ya devuelve el resultado nuevo.
Dos cosas que conviene saber desde el principio:
- Tablas y vistas comparten el mismo espacio de nombres dentro de una base de datos. No puedes tener una tabla
clientesy una vistaclientesen el mismo esquema. - No se puede crear un índice sobre una vista. Según cómo la procese MySQL, la consulta puede usar los índices de las tablas base o no (lo vemos en la sección de rendimiento).
Cómo crear una vista en MySQL con CREATE VIEW
La sintaxis completa de CREATE VIEW es esta:
CREATE
[OR REPLACE]
[ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
[DEFINER = user]
[SQL SECURITY { DEFINER | INVOKER }]
VIEW view_name [(column_list)]
AS select_statement
[WITH [CASCADED | LOCAL] CHECK OPTION]Casi siempre basta con CREATE VIEW nombre AS SELECT .... Para los ejemplos uso una tienda con dos tablas, clientes y pedidos:
CREATE TABLE clientes (
id INT PRIMARY KEY AUTO_INCREMENT,
nombre VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL,
pais CHAR(2) NOT NULL,
activo TINYINT(1) NOT NULL DEFAULT 1
);
CREATE TABLE pedidos (
id INT PRIMARY KEY AUTO_INCREMENT,
cliente_id INT NOT NULL,
total DECIMAL(10, 2) NOT NULL,
estado VARCHAR(20) NOT NULL,
creado_en DATETIME NOT NULL,
FOREIGN KEY (cliente_id) REFERENCES clientes (id)
);Supón que la aplicación pide en varios lugares "los pedidos pagados, con el nombre y el país del cliente". En vez de repetir el JOIN en cada consulta, lo guardas una vez como vista:
CREATE VIEW pedidos_pagados AS
SELECT
p.id,
p.total,
p.creado_en,
c.nombre AS cliente,
c.pais
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.estado = 'pagado';Desde ese momento la consultas como a cualquier tabla, y puedes agregarle tu propio WHERE, ORDER BY o JOIN:
SELECT cliente, total
FROM pedidos_pagados
WHERE pais = 'MX'
ORDER BY creado_en DESC;Nombrar las columnas de la vista
Por defecto, las columnas de la vista se llaman igual que las del SELECT. Para usar otros nombres, ponlos entre paréntesis después del nombre de la vista. Tiene que haber exactamente un nombre por cada columna que devuelve el SELECT, y ninguno puede repetirse:
CREATE VIEW ventas_por_pais (pais, pedidos, facturado) AS
SELECT c.pais, COUNT(*), SUM(p.total)
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.estado = 'pagado'
GROUP BY c.pais;Los nombres de columna de una vista no pueden pasar de 64 caracteres, que es el máximo para un nombre de columna. En un SELECT normal un alias puede llegar a 256, pero dentro de una vista se aplica el límite de 64.
Qué no puede llevar el SELECT de una vista
El manual enumera estas restricciones:
- No puede usar variables de sistema ni variables de usuario (
@algo). - Dentro de un procedimiento almacenado, no puede usar sus parámetros ni sus variables locales.
- No puede usar parámetros de una sentencia preparada.
- No puede leer de una tabla
TEMPORARY, y tampoco se pueden crear vistas temporales. - No se le pueden asociar triggers.
- Todas las tablas y vistas que menciona tienen que existir cuando la creas.
- Puede mencionar como máximo 61 tablas.
Fuera de eso, el SELECT puede llevar JOIN, UNION, subconsultas, funciones de agregación y GROUP BY.
La definición de una vista queda fija al crearla
Al crear una vista, MySQL guarda la lista de columnas tal como es en ese momento, y los cambios posteriores en las tablas no la actualizan. La documentación dice que la definición queda "congelada". Donde más se nota es con SELECT *: MySQL expande el asterisco al crear la vista y guarda las columnas que había entonces.
Eso tiene tres consecuencias:
- Si después agregas una columna a la tabla, la vista no la incluye.
- Si borras de la tabla una columna que la vista usaba, consultar la vista da error.
- Si borras o modificas con
DROP TABLEoALTER TABLEuna tabla que la vista usa, MySQL no te avisa. El error aparece después, la primera vez que alguien consulta la vista.
Para encontrar vistas que quedaron rotas después de un cambio de esquema, usa CHECK TABLE:
CHECK TABLE pedidos_pagados;Si agregaste columnas a la tabla y quieres que la vista las muestre, tienes que volver a crearla con CREATE OR REPLACE VIEW.
ORDER BY dentro de una vista
La definición de una vista puede llevar ORDER BY, pero MySQL lo ignora si la consulta que lee la vista tiene su propio ORDER BY. Con LIMIT y otras cláusulas el manual no promete nada: se combinan con las de la consulta exterior y el resultado queda indefinido. Lo más seguro es poner el orden y la paginación en la consulta que lee la vista, no en la vista.
Modificar, listar y borrar vistas en MySQL
Cambiar una vista: CREATE OR REPLACE VIEW y ALTER VIEW
Las dos sentencias reemplazan la definición de una vista. La diferencia es que CREATE OR REPLACE VIEW crea la vista si no existe, y ALTER VIEW falla:
-- Crea la vista si no existe, la reemplaza si existe
CREATE OR REPLACE VIEW pedidos_pagados AS
SELECT p.id, p.total, p.creado_en, c.nombre AS cliente, c.pais, c.email
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.estado = 'pagado';
-- Cambia una vista que ya existe (falla si no existe)
ALTER VIEW pedidos_pagados AS
SELECT p.id, p.total, p.creado_en, c.nombre AS cliente, c.pais
FROM pedidos p
JOIN clientes c ON c.id = p.cliente_id
WHERE p.estado = 'pagado';CREATE OR REPLACE VIEW requiere los privilegios CREATE VIEW y DROP sobre la vista. ALTER VIEW requiere esos mismos dos y, además, solo puede ejecutarlo la cuenta que creó la vista o una cuenta con SET_ANY_DEFINER o ALLOW_NONEXISTENT_DEFINER.
Ver la definición de una vista
SHOW CREATE VIEW pedidos_pagados;Para esto necesitas el privilegio SHOW VIEW, y MySQL no lo da automáticamente a quien tiene CREATE VIEW. Puede pasarte que crees una vista y no puedas ver su definición hasta que un administrador te conceda SHOW VIEW.
La definición también está en INFORMATION_SCHEMA.VIEWS, que tiene columnas como VIEW_DEFINITION, CHECK_OPTION, IS_UPDATABLE, DEFINER y SECURITY_TYPE:
SELECT TABLE_NAME, IS_UPDATABLE, SECURITY_TYPE, VIEW_DEFINITION
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_SCHEMA = 'tienda';VIEW_DEFINITION no muestra el texto tal como lo escribiste. MySQL guarda la definición normalizada y, por ejemplo, elimina los comentarios que había antes del SELECT.
Listar las vistas de una base de datos
Con el modificador FULL, SHOW TABLES agrega una columna que indica BASE TABLE para cada tabla y VIEW para cada vista. Filtrando por esa columna te quedas solo con las vistas:
SHOW FULL TABLES FROM tienda WHERE Table_type = 'VIEW';Borrar una vista con DROP VIEW
DROP VIEW IF EXISTS pedidos_pagados, ventas_por_pais;DROP VIEW borra solo la definición; los datos siguen en las tablas. Si vienes de una versión anterior, ojo con un cambio de comportamiento: en MySQL 8.4, si alguna de las vistas de la lista no existe, DROP VIEW da error y no borra ninguna. Hasta MySQL 8.3 también daba error, pero borraba las que sí existían. Con IF EXISTS el comando funciona igual en las dos versiones: borra las que existen y, por cada una que falta, deja una nota que puedes ver con SHOW WARNINGS. RESTRICT y CASCADE aparecen en la sintaxis, pero MySQL las ignora.
Vistas actualizables: INSERT, UPDATE y DELETE sobre una vista
También puedes escribir a través de una vista. Si cada fila de la vista corresponde a una fila concreta de una tabla, MySQL acepta UPDATE, DELETE y, con algunas condiciones más, INSERT, y aplica el cambio en la tabla base.
CREATE VIEW clientes_activos AS
SELECT id, nombre, email, pais
FROM clientes
WHERE activo = 1;
UPDATE clientes_activos SET email = 'ana@ejemplo.com' WHERE id = 7;Ese UPDATE modifica la fila 7 de clientes. Una vista deja de ser actualizable si tiene cualquiera de estos elementos:
- Funciones de agregación o de ventana (
SUM(),COUNT(),MAX()...). DISTINCT,GROUP BYoHAVING.UNIONoUNION ALL.- Una subconsulta en la lista del
SELECT. Si la subconsulta no es correlacionada, solo impide elINSERT. - Una subconsulta en el
WHEREque lee una tabla que también está en elFROM. - Ciertos tipos de
JOIN. - Una vista no actualizable en el
FROM. - Solo valores literales, sin ninguna tabla.
ALGORITHM = TEMPTABLE.
Por eso ventas_por_pais es de solo lectura: tiene GROUP BY y SUM(). Si alguien cambiara el total de un país, MySQL no tendría forma de saber qué filas de pedidos modificar.
Para aceptar además INSERT, la vista tiene que cumplir tres condiciones: no repetir nombres de columna, incluir todas las columnas de la tabla que no tienen valor por defecto y que todas sus columnas sean columnas de la tabla, no expresiones como col1 + 3 o UPPER(col2). Una columna calculada solo bloquea el UPDATE de esa columna; las demás se pueden modificar:
CREATE VIEW v AS SELECT col1, 1 AS col2 FROM t;
UPDATE v SET col1 = 0; -- funciona
UPDATE v SET col2 = 0; -- falla: col2 es una expresiónPara saber si MySQL considera actualizable una vista, mira la columna IS_UPDATABLE de INFORMATION_SCHEMA.VIEWS.
WITH CHECK OPTION: que la vista no acepte filas que no puede ver
Una vista actualizable permite escribir filas que después no muestra. Si clientes_activos incluyera la columna activo, un UPDATE clientes_activos SET activo = 0 haría que ese cliente desapareciera de la vista. También podrías insertar a través de ella un cliente con activo = 0, que la vista nunca mostraría. WITH CHECK OPTION impide las dos cosas: rechaza los INSERT de filas que no cumplen el WHERE de la vista y los UPDATE que dejarían una fila fuera de ella.
CREATE TABLE t1 (a INT);
CREATE VIEW v1 AS SELECT * FROM t1 WHERE a < 2
WITH CHECK OPTION;
CREATE VIEW v2 AS SELECT * FROM v1 WHERE a > 0
WITH LOCAL CHECK OPTION;
CREATE VIEW v3 AS SELECT * FROM v1 WHERE a > 0
WITH CASCADED CHECK OPTION;mysql> INSERT INTO v2 VALUES (2);
ERROR 1369 (HY000): CHECK OPTION failed 'test.v2'
mysql> INSERT INTO v3 VALUES (2);
ERROR 1369 (HY000): CHECK OPTION failed 'test.v3'Los dos INSERT fallan porque 2 no cumple la condición de v1 (a < 2), aunque el mensaje de error nombra la vista en la que intentaste insertar. LOCAL y CASCADED se diferencian cuando una vista está construida sobre otra:
- Con
LOCAL, MySQL revisa elWHEREde la vista y después pasa a las vistas sobre las que está construida, aplicando en cada una su propia opción. Si una de esas vistas no tieneCHECK OPTION, suWHEREno se revisa. - Con
CASCADED, MySQL revisa elWHEREde la vista y el de todas las vistas sobre las que está construida, tengan o no su propioCHECK OPTION.
Si escribes WITH CHECK OPTION sin LOCAL ni CASCADED, MySQL usa CASCADED.
Vistas y seguridad: dar acceso a parte de una tabla
Para mí este es el mejor motivo para usar vistas. Imagina una tabla empleados con el sueldo de cada persona, y un usuario de reportes que necesita ver nombres y departamentos pero no sueldos:
CREATE DEFINER = 'admin'@'localhost'
SQL SECURITY DEFINER
VIEW empleados_publico AS
SELECT id, nombre, departamento
FROM empleados
WHERE activo = 1;
GRANT SELECT ON rrhh.empleados_publico TO 'reportes'@'%';El usuario reportes puede consultar empleados_publico sin tener ningún permiso sobre empleados. Como la vista no incluye la columna de sueldo ni las filas de empleados inactivos, nunca los ve: el SELECT decide qué columnas se muestran y el WHERE, qué filas.
Esto funciona por la cláusula SQL SECURITY:
- Con
DEFINER(el valor por defecto), la vista se ejecuta con los privilegios de la cuenta indicada enDEFINER. Quien la consulta solo necesitaSELECTsobre la vista; sus propios privilegios sobre las tablas no cuentan. - Con
INVOKER, la vista se ejecuta con los privilegios de quien la consulta. Si ese usuario no puede leerempleados, la consulta falla.
Si no escribes DEFINER, MySQL usa como definidor la cuenta que ejecuta el CREATE VIEW. El manual recomienda SQL SECURITY INVOKER siempre que sea posible, para que una vista no le dé a nadie acceso a datos que no podría leer directamente. DEFINER se justifica en casos como el del ejemplo, donde quieres justamente eso: mostrar una parte de la tabla a quien no tiene permiso sobre la tabla completa.
Un riesgo con DEFINER: si se borra la cuenta que figura como definidora, la vista queda huérfana y cualquier consulta a ella da error. Para evitarlo, en MySQL 8.4 DROP USER y RENAME USER fallan si van a dejar objetos huérfanos, salvo que la cuenta que los ejecuta tenga el privilegio ALLOW_NONEXISTENT_DEFINER. Para ver qué cuentas figuran como definidoras de tus vistas:
SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.VIEWS;Rendimiento de las vistas en MySQL: MERGE y TEMPTABLE
El costo de consultar una vista depende de cómo la procesa MySQL, y para eso tiene dos algoritmos.
Con MERGE, MySQL combina tu consulta con la definición de la vista y ejecuta una sola consulta contra las tablas. Con esta vista:
CREATE ALGORITHM = MERGE VIEW v_merge (vc1, vc2) AS
SELECT c1, c2 FROM t WHERE c3 > 100;La consulta SELECT * FROM v_merge WHERE vc1 < 100; se convierte en:
SELECT c1, c2 FROM t WHERE (c3 > 100) AND (c1 < 100);El resultado es la misma consulta que habrías escrito sin la vista, así que MySQL puede usar los índices de t como siempre.
Con TEMPTABLE, MySQL ejecuta primero la consulta de la vista, guarda el resultado en una tabla temporal y después ejecuta tu consulta sobre esa tabla temporal. El manual advierte dos efectos: tu consulta ya no puede usar los índices de las tablas base (la consulta que llena la tabla temporal sí puede), y la vista deja de ser actualizable.
UNDEFINED deja la elección a MySQL, que usa MERGE siempre que puede. Si no escribes ALGORITHM, el comportamiento depende del flag derived_merge de optimizer_switch, que viene activado por defecto.
MySQL no puede usar MERGE si la vista contiene alguno de estos elementos:
- Funciones de agregación o de ventana.
DISTINCT,GROUP BY,HAVINGoLIMIT.UNIONoUNION ALL.- Subconsultas en la lista del
SELECT. - Asignaciones a variables de usuario.
- Solo valores literales.
Si pides ALGORITHM = MERGE en una vista que no lo permite, MySQL emite una advertencia y usa UNDEFINED.
En la práctica, una vista que solo filtra y une tablas cuesta lo mismo que escribir la consulta a mano. Una vista con GROUP BY se procesa con una tabla temporal cada vez que la consultas. Si necesitas ese resultado ya calculado, CREATE VIEW no tiene una opción para guardarlo: tendrás que crear una tabla de resumen y actualizarla tú.
Beneficios de las vistas en MySQL
- Reutilizar consultas: el
JOINlargo se escribe una vez y se usa en todas partes con un nombre que dice qué devuelve. - Limitar qué datos ve cada usuario: le das
SELECTsobre la vista y no sobre la tabla, y solo ve las columnas y filas que la vista incluye. - Proteger a la aplicación de los cambios de esquema. Si divides una tabla en dos, puedes crear una vista con el nombre y las columnas de la tabla original, y las consultas de lectura que ya existían siguen funcionando mientras migras el resto.
- Validar escrituras: una vista actualizable con
WITH CHECK OPTIONrechaza losINSERTyUPDATEque dejarían filas fuera de suWHERE. - Simplificar reportes: quien consulta
ventas_por_paisno necesita saber cómo se relacionanpedidosyclientes.
También tienen límites: no admiten índices, las que agrupan se procesan con una tabla temporal en cada consulta, su lista de columnas no se actualiza cuando cambian las tablas, y un cambio en una tabla puede romperlas sin que nadie se entere hasta que alguien las consulta.
Preguntas frecuentes sobre vistas en MySQL
¿Qué es una view en MySQL?
Es una consulta SELECT guardada con nombre que se consulta como si fuera una tabla. No almacena datos: los lee de las tablas cada vez que la consultas.
¿Cuál es la diferencia entre una tabla y una vista en MySQL?
Una tabla guarda filas y puede tener índices. Una vista solo guarda la definición de una consulta y no admite índices. Las dos comparten el espacio de nombres de la base de datos, así que una tabla y una vista no pueden llamarse igual.
¿Una vista en MySQL ocupa espacio?
Solo ocupa lo que ocupa su definición; los datos siguen en las tablas. Si MySQL la procesa con TEMPTABLE, crea una tabla temporal mientras se ejecuta la consulta.
¿Se puede hacer INSERT o UPDATE en una vista en MySQL?
Sí, si la vista es actualizable: sin agregaciones, sin DISTINCT, GROUP BY, HAVING ni UNION, y con cada fila de la vista correspondiendo a una fila concreta de una tabla. Para INSERT también tiene que incluir todas las columnas que no tienen valor por defecto. La columna IS_UPDATABLE de INFORMATION_SCHEMA.VIEWS te dice si lo es.
¿Las vistas en MySQL son más rápidas?
No. Una vista procesada con MERGE rinde igual que la misma consulta escrita a mano, y una con GROUP BY o DISTINCT se procesa con una tabla temporal cada vez que la consultas.
¿MySQL tiene vistas materializadas?
La sintaxis de CREATE VIEW en MySQL 8.4 no tiene ninguna opción para guardar el resultado. Si necesitas datos ya calculados, la alternativa es una tabla de resumen que actualizas tú.
¿Cómo ver las vistas de una base de datos en MySQL?
Con SHOW FULL TABLES FROM tu_base WHERE Table_type = 'VIEW'; o consultando INFORMATION_SCHEMA.VIEWS. Para ver la definición de una vista concreta, usa SHOW CREATE VIEW nombre;, que requiere el privilegio SHOW VIEW.
¿Cómo eliminar una vista en MySQL?
Con DROP VIEW IF EXISTS nombre;. Borra solo la definición, no los datos. En MySQL 8.4, sin IF EXISTS, si alguna de las vistas de la lista no existe no se borra ninguna.
El WHERE y el HAVING que pones dentro de una vista siguen las mismas reglas que en cualquier consulta. Si quieres repasarlas, en la diferencia entre WHERE y HAVING están explicadas a partir del orden de ejecución.
Fuentes
- MySQL 8.4 Reference Manual: CREATE VIEW Statement
- MySQL 8.4 Reference Manual: ALTER VIEW Statement
- MySQL 8.4 Reference Manual: DROP VIEW Statement
- MySQL 8.4 Reference Manual: View Processing Algorithms
- MySQL 8.4 Reference Manual: Updatable and Insertable Views
- MySQL 8.4 Reference Manual: The View WITH CHECK OPTION Clause
- MySQL 8.4 Reference Manual: Restrictions on Views
- MySQL 8.4 Reference Manual: Stored Object Access Control
- MySQL 8.4 Reference Manual: Optimizing Derived Tables, View References, and Common Table Expressions
- MySQL 8.4 Reference Manual: The INFORMATION_SCHEMA VIEWS Table
- MySQL 8.4 Reference Manual: SHOW TABLES Statement
¿Tienes un proyecto en mente?
Trabajo con Laravel, WordPress, SEO técnico y servidores MCP. El primer paso es una llamada de descubrimiento, sin costo ni compromiso, donde me cuentas qué necesitas y te digo con honestidad si puedo ayudarte.