sql >> Base de Datos >  >> RDS >> Database

Fundamentos de las expresiones de tabla, Parte 9:Vistas, en comparación con tablas derivadas y CTE

Esta es la novena parte de una serie sobre expresiones de tabla con nombre. En la Parte 1, proporcioné los antecedentes de las expresiones de tablas con nombre, que incluyen tablas derivadas, expresiones de tablas comunes (CTE), vistas y funciones con valores de tablas en línea (iTVF). En la Parte 2, Parte 3 y Parte 4 me centré en las tablas derivadas. En la Parte 5, Parte 6, Parte 7 y Parte 8 me centré en CTE. Como expliqué, las tablas derivadas y los CTE son expresiones de tabla con nombre en el ámbito de la declaración. Una vez que termina la declaración que los define, se van.

Ahora estamos listos para continuar con la cobertura de expresiones de tabla con nombre reutilizables. Es decir, los que se crean como un objeto en la base de datos y permanecen allí de forma permanente a menos que se eliminen. Como tales, son accesibles y reutilizables para todos los que tienen los permisos adecuados. Las vistas y los iTVF entran en esta categoría. La diferencia entre los dos es principalmente que el primero no admite parámetros de entrada y el segundo sí.

En este artículo empiezo la cobertura de opiniones. Como hice antes, primero me enfocaré en los aspectos lógicos o conceptuales, y luego procederé a los aspectos de optimización. Con el primer artículo sobre las vistas, quiero comenzar de manera ligera, centrándome en lo que es una vista, usando la terminología correcta y comparando las consideraciones de diseño de las vistas con las de las tablas derivadas y CTE discutidas anteriormente.

En mis ejemplos, usaré una base de datos de muestra llamada TSQLV5. Puede encontrar el script que lo crea y lo completa aquí, y su diagrama ER aquí.

¿Qué es una vista?

Como es habitual cuando se habla de teoría relacional, a los practicantes de SQL se nos suele decir que la terminología que estamos usando es incorrecta. Entonces, con este espíritu, de inmediato, comenzaré diciendo que cuando usa el término tablas y vistas , Está incorrecto. Aprendí esto de Chris Date.

Recuerde que una tabla es la contraparte de SQL a una relación (simplificando un poco la discusión sobre valores y variables). Una tabla podría ser una tabla base definida como un objeto en la base de datos, o podría ser una tabla devuelta por una expresión, más específicamente, una expresión de tabla. Eso es similar al hecho de que una relación podría ser una que se devuelve de una expresión relacional. Una expresión de tabla podría ser una consulta.

Ahora, ¿qué es una vista? Es una expresión de tabla con nombre, al igual que un CTE es una expresión de tabla con nombre. Es solo que, como dije, una vista es una expresión de tabla con nombre reutilizable que se crea como un objeto en la base de datos y es accesible para aquellos que tienen los permisos correctos. Esto es todo para decir, una vista es una tabla. No es una mesa base, sino una mesa al fin y al cabo. Entonces, al igual que decir "un rectángulo y un cuadrado" o "un whisky y un Lagavulin" parecería extraño (¡a menos que haya tomado demasiado Lagavulin!), usar "tablas y vistas" es inadecuado.

Sintaxis

Esta es la sintaxis de T-SQL para una instrucción CREATE VIEW:

CREAR [ O ALTERAR ] VISTA [ . ] [ () ]
[ CON ]
AS

[ CON OPCIÓN DE COMPROBACIÓN ]
[; ]

La declaración CREATE VIEW debe ser la primera y única declaración del lote.

Tenga en cuenta que la parte CREATE OR ALTER se introdujo en SQL Server 2016 SP1, por lo que si está utilizando una versión anterior, deberá trabajar con instrucciones CREATE VIEW y ALTER VIEW independientes, dependiendo de si el objeto ya existe o no. Como probablemente sepa, la modificación de un objeto existente conserva los permisos asignados. Esa es una de las razones por las que generalmente es sensato alterar un objeto existente en lugar de soltarlo y recrearlo. Lo que sorprende a algunas personas es que alterar una vista no conserva los atributos de vista existentes; esos deben volver a especificarse si desea conservarlos.

Aquí hay un ejemplo de una definición de vista simple que representa a los clientes de EE. UU.:

USE TSQLV5;
GO
 
CREATE OR ALTER VIEW Sales.USACustomers
AS
  SELECT custid, companyname
  FROM Sales.Customers
  WHERE country = N'USA';
GO

Y aquí hay una declaración que consulta la vista:

SELECT custid, companyname
FROM Sales.USACustomers;

Entre la declaración que crea la vista y la declaración que la consulta, encontrará los mismos tres elementos que están involucrados en una declaración contra una tabla derivada o un CTE:

  1. La expresión de la tabla interna (la consulta interna de la vista)
  2. El nombre de la tabla asignada (el nombre de la vista)
  3. La declaración con la consulta externa contra la vista

Aquellos de ustedes con buen ojo habrán notado que en realidad hay dos expresiones de tabla involucradas aquí. Está el interno (la consulta interna de la vista) y el externo (la consulta en la declaración contra la vista). En la declaración con la consulta contra la vista, la consulta en sí es una expresión de tabla y, una vez que agrega el terminador, se convierte en una declaración. Esto puede sonar exigente, pero si entiendes esto y llamas a las cosas por su nombre correcto, se refleja en tu conocimiento. ¿Y no es genial cuando sabes que sabes?

Además, todos los requisitos de la expresión de tabla en tablas derivadas y CTE que analizamos anteriormente en la serie se aplican a la expresión de tabla en la que se basa la vista. Como recordatorio, los requisitos son:

  • Todas las columnas de la expresión de la tabla deben tener nombres
  • Todos los nombres de columna de la expresión de la tabla deben ser únicos
  • Las filas de la expresión de la tabla no tienen orden

Si necesita refrescar su comprensión de lo que hay detrás de estos requisitos, consulte la sección "Una expresión de tabla es una tabla" en la Parte 2 de la serie. Asegúrese de comprender especialmente la parte "sin orden". Como breve recordatorio, una expresión de tabla es una tabla y, como tal, no tiene orden. Es por eso que no puede crear una vista basada en una consulta con una cláusula ORDER BY, a menos que esta cláusula esté ahí para admitir un filtro TOP o OFFSET-FETCH. E incluso con esta excepción que permite que la consulta interna tenga una cláusula ORDER BY, debe recordar que si la consulta externa contra la vista no tiene su propia cláusula ORDER BY, no obtendrá una garantía de que la consulta devolverá las filas en cualquier orden particular, sin importar el comportamiento observado. ¡Es muy importante entender esto!

Anidamiento y referencias múltiples

Cuando discutí las consideraciones de diseño de tablas derivadas y CTE, comparé los dos en términos de anidamiento y referencias múltiples. Ahora veamos cómo les va a las vistas en estos departamentos. Comenzaré con la anidación. Para ello, compararemos el código que devuelve años en los que más de 70 clientes realizaron pedidos utilizando tablas derivadas, CTE y vistas. Ya vio el código con tablas derivadas y CTE anteriormente en la serie. Aquí está el código que maneja la tarea usando tablas derivadas:

SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
       FROM ( SELECT YEAR(orderdate) AS orderyear, custid
              FROM Sales.Orders ) AS D1
       GROUP BY orderyear ) AS D2
WHERE numcusts > 70;

Señalé que el principal inconveniente que veo con las tablas derivadas aquí es el hecho de que anida las definiciones de tablas derivadas, y esto puede generar complejidad en la comprensión, el mantenimiento y la resolución de problemas de dicho código.

Aquí está el código que maneja la misma tarea usando CTE:

WITH C1 AS
(
  SELECT YEAR(orderdate) AS orderyear, custid
  FROM Sales.Orders
),
C2 AS
(
  SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  FROM C1
  GROUP BY orderyear
)
SELECT orderyear, numcusts
FROM C2
WHERE numcusts > 70;

Señalé que para mí esto se siente como un código mucho más claro debido a la falta de anidamiento. Puede ver cada paso de la solución de principio a fin por separado en su propia unidad, con la lógica de la solución fluyendo claramente de arriba a abajo. Por lo tanto, veo la opción CTE como una mejora con respecto a las tablas derivadas a este respecto.

Ahora a las vistas. Recuerde, uno de los principales beneficios de las vistas es la reutilización. También puede controlar los permisos de acceso. El desarrollo de las unidades involucradas es un poco más similar a CTE en el sentido de que puede enfocar su atención en una unidad a la vez de principio a fin. Además, tiene la flexibilidad de decidir si desea crear una vista separada por unidad en la solución, o tal vez solo una vista basada en una consulta que involucre expresiones de tabla con nombre en el ámbito de la instrucción.

Iría con el primero cuando cada una de las unidades necesita ser reutilizable. Este es el código que usaría en tal caso, creando tres vistas:

-- Sales.OrderYears
CREATE OR ALTER VIEW Sales.OrderYears
AS 
  SELECT YEAR(orderdate) AS orderyear, custid
  FROM Sales.Orders;
GO
 
-- Sales.YearlyCustCounts
CREATE OR ALTER VIEW Sales.YearlyCustCounts
AS
  SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
  FROM Sales.OrderYears
  GROUP BY orderyear;
GO
 
-- Sales.YearlyCustCountsMin70
CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70
AS
  SELECT orderyear, numcusts
  FROM Sales.YearlyCustCounts
  WHERE numcusts > 70;
GO

Puede consultar cada una de las vistas de forma independiente, pero este es el código que usaría para devolver lo que buscaba la tarea original.

SELECT orderyear, numcusts
FROM Sales.YearlyCustCountsAbove70;

Si hay un requisito de reutilización solo para la parte más externa (lo que requería la tarea original), no hay una necesidad real de desarrollar tres vistas diferentes. Puede crear una vista basada en una consulta que involucre CTE o tablas derivadas. Así es como lo haría con una consulta que involucre CTE:

CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70
AS
  WITH C1 AS
  (
    SELECT YEAR(orderdate) AS orderyear, custid
    FROM Sales.Orders
  ),
  C2 AS
  (
    SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
    FROM C1
    GROUP BY orderyear
  )
  SELECT orderyear, numcusts
  FROM C2
  WHERE numcusts > 70;
GO

Por cierto, si no fuera obvio, los CTE en los que se basa la consulta interna de la vista pueden ser recursivos.

Procedamos a los casos en los que necesita múltiples referencias a la misma expresión de tabla desde la consulta externa. La tarea de este ejemplo es calcular el recuento de pedidos anuales por año y comparar el recuento de cada año con el año anterior. La forma más fácil de lograr esto es usar la función de ventana LAG, pero usaremos una combinación entre dos instancias de una expresión de tabla que representa los recuentos de pedidos anuales solo para comparar un caso de referencia múltiple entre las tres herramientas.

Este es el código que usamos anteriormente en la serie para manejar la tarea con tablas derivadas:

SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS CUR
  LEFT OUTER JOIN
     ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
       FROM Sales.Orders
       GROUP BY YEAR(orderdate) ) AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Hay una desventaja muy clara aquí. Tienes que repetir la definición de la expresión de la tabla dos veces. Básicamente, está definiendo dos expresiones de tabla con nombre basadas en el mismo código de consulta.

Aquí está el código que maneja la misma tarea usando CTE:

WITH OrdCount AS
(
  SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
  FROM Sales.Orders
  GROUP BY YEAR(orderdate)
)
SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM OrdCount AS CUR
  LEFT OUTER JOIN OrdCount AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Aquí hay una clara ventaja; usted define solo una expresión de tabla con nombre basada en una sola instancia de la consulta interna y hace referencia a ella dos veces desde la consulta externa.

Las vistas son más similares a los CTE en este sentido. Usted define solo una vista basada en una sola copia de la consulta, así:

CREATE OR ALTER VIEW Sales.YearlyOrderCounts
AS
  SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
  FROM Sales.Orders
  GROUP BY YEAR(orderdate);
GO

Pero mejor que con CTE, no está limitado a reutilizar la expresión de la tabla nombrada solo en la declaración externa. Puede reutilizar el nombre de la vista tantas veces como desee, con cualquier cantidad de consultas no relacionadas, siempre que tenga los permisos adecuados. Aquí está el código para lograr la tarea usando múltiples referencias a la vista:

SELECT CUR.orderyear, CUR.numorders,
  CUR.numorders - PRV.numorders AS diff
FROM Sales.YearlyOrderCounts AS CUR
  LEFT OUTER JOIN Sales.YearlyOrderCounts AS PRV
    ON CUR.orderyear = PRV.orderyear + 1;

Parece que las vistas son más similares a las CTE que a las tablas derivadas, con la funcionalidad adicional de ser una herramienta más reutilizable, con la capacidad de controlar los permisos. O para darle la vuelta, probablemente sea apropiado pensar en un CTE como una vista con alcance de declaración. Ahora, lo que podría ser realmente maravilloso es si también tuviéramos una expresión de tabla con nombre con un alcance más amplio que el de un CTE, más estrecho que el de una vista. Por ejemplo, ¿no hubiera sido genial si tuviéramos una expresión de tabla con nombre en el ámbito de la sesión?

Resumen

me encanta este tema Hay tanto en las expresiones de tabla que tiene sus raíces en la teoría relacional, que a su vez tiene sus raíces en las matemáticas. Me encanta saber cuáles son los términos correctos para las cosas y, en general, asegurarme de que tengo las bases resueltas con cuidado, incluso si a algunos les puede parecer quisquilloso y demasiado pedante. Mirando hacia atrás en mi proceso de aprendizaje a lo largo de los años, puedo ver un camino muy claro entre insistir en una buena comprensión de los fundamentos, usar la terminología correcta y saber realmente lo que haces más tarde, cuando llegues a cosas mucho más avanzadas y complejas.

Entonces, ¿cuáles son las piezas críticas cuando se trata de vistas?

  • Una vista es una tabla.
  • Es una tabla que se deriva de una consulta (una expresión de tabla).
  • Se le da un nombre que al usuario le parece un nombre de tabla, ya que es un nombre de tabla.
  • Se crea como un objeto permanente en la base de datos.
  • Puede controlar los permisos de acceso contra la vista.

Las vistas son similares a las CTE en varios aspectos. En el sentido de que desarrolla sus soluciones de forma modular, centrándose en una unidad a la vez de principio a fin. También en el sentido de que puede tener varias referencias al nombre de la vista desde la consulta externa. Pero mejor que los CTE, las vistas no se limitan solo al alcance de la declaración externa, sino que son reutilizables hasta que se eliminen de la base de datos.

Hay mucho más que decir sobre las vistas, y continuaré la discusión el próximo mes. Mientras tanto, quiero dejarte con un pensamiento. Con tablas derivadas y CTE, podría defender SELECT * en una consulta interna. Vea el caso que hice para él en la Parte 3 de la serie para obtener más detalles. ¿Podrías hacer un caso similar con vistas, o es una mala idea con esas?