Esta é a 9ª parte de uma série sobre expressões de tabelas nomeadas. Na Parte 1, forneci o pano de fundo para expressões de tabela nomeada, que incluem tabelas derivadas, expressões de tabela comum (CTEs), exibições e funções com valor de tabela em linha (iTVFs). Na Parte 2, Parte 3 e Parte 4, concentrei-me em tabelas derivadas. Na Parte 5, Parte 6, Parte 7 e Parte 8, concentrei-me em CTEs. Como expliquei, tabelas derivadas e CTEs são expressões de tabela nomeada com escopo de instrução. Uma vez que a declaração que os define termina, eles se vão.
Agora estamos prontos para prosseguir com a cobertura de expressões de tabelas nomeadas reutilizáveis. Ou seja, aqueles que são criados como um objeto no banco de dados e permanecem lá permanentemente, a menos que sejam descartados. Como tal, eles são acessíveis e reutilizáveis para todos que têm as permissões corretas. Visualizações e iTVFs se enquadram nesta categoria. A diferença entre os dois é principalmente que o primeiro não suporta parâmetros de entrada e o último sim.
Neste artigo começo a cobertura de visualizações. Como fiz antes, primeiro vou focar nos aspectos lógicos ou conceituais e, posteriormente, prosseguir para os aspectos de otimização. Com o primeiro artigo sobre exibições, quero começar com leveza, focando no que é uma exibição, usando a terminologia correta e comparar as considerações de design das exibições com as das tabelas derivadas e CTEs discutidas anteriormente.
Em meus exemplos, usarei um banco de dados de exemplo chamado TSQLV5. Você pode encontrar o script que o cria e o preenche aqui, e seu diagrama ER aqui.
O que é uma visualização?
Como de costume ao discutir a teoria relacional, nós, praticantes de SQL, muitas vezes nos dizem que a terminologia que estamos usando está errada. Então, nesse espírito, vou começar dizendo que quando você usa o termo tabelas e visualizações , está errado. Eu aprendi isso com Chris Date.
Lembre-se de que uma tabela é a contrapartida do SQL para uma relação (simplificando um pouco a discussão sobre valores e variáveis). Uma tabela pode ser uma tabela base definida como um objeto no banco de dados ou pode ser uma tabela retornada por uma expressão — mais especificamente, uma expressão de tabela. Isso é semelhante ao fato de que uma relação pode ser retornada de uma expressão relacional. Uma expressão de tabela pode ser uma consulta.
Agora, o que é uma visão? É uma expressão de tabela nomeada, assim como um CTE é uma expressão de tabela nomeada. É como eu disse, uma visão é uma expressão de tabela nomeada reutilizável que é criada como um objeto no banco de dados e é acessível para aqueles que têm as permissões corretas. Isso é tudo para dizer, uma visão é uma mesa. Não é uma mesa base, mas mesmo assim uma mesa. Então, assim como dizer “um retângulo e um quadrado” ou “um uísque e um Lagavulin” pareceria estranho (a menos que você tivesse muito Lagavulin!), usar “tables and views” também é impróprio.
Sintaxe
Aqui está a sintaxe T-SQL para uma instrução CREATE VIEW:
CREATE [ OR ALTER ] VIEW [
[ COM
AS
[ COM OPÇÃO DE VERIFICAÇÃO ]
[; ]
A instrução CREATE VIEW deve ser a primeira e única instrução no lote.
Observe que a parte CREATE OR ALTER foi introduzida no SQL Server 2016 SP1, portanto, se você estiver usando uma versão anterior, precisará trabalhar com instruções CREATE VIEW e ALTER VIEW separadas, dependendo se o objeto já existe ou não. Como você provavelmente sabe, alterar um objeto existente mantém as permissões atribuídas. Essa é uma das razões pelas quais geralmente é sensato alterar um objeto existente em vez de descartá-lo e recriá-lo. O que pega algumas pessoas de surpresa é que alterar uma visão não retém os atributos de visão existentes; eles precisam ser especificados novamente se você quiser mantê-los.
Aqui está um exemplo de uma definição de visualização simples que representa os clientes dos EUA:
USE TSQLV5; GO CREATE OR ALTER VIEW Sales.USACustomers AS SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA'; GO
E aqui está uma declaração que questiona a visão:
SELECT custid, companyname FROM Sales.USACustomers;
Entre a instrução que cria a visão e a instrução que a consulta, você encontrará os mesmos três elementos envolvidos em uma instrução em uma tabela derivada ou CTE:
- A expressão da tabela interna (a consulta interna da visualização)
- O nome da tabela atribuída (o nome da visualização)
- A instrução com a consulta externa em relação à visualização
Aqueles de vocês com um olho afiado devem ter notado que na verdade existem duas expressões de tabela envolvidas aqui. Há o interno (a consulta interna da exibição) e o externo (a consulta na instrução em relação à exibição). Na instrução com a consulta na exibição, a consulta em si é uma expressão de tabela e, quando você adiciona o terminador, ela se torna uma instrução. Isso pode parecer exigente, mas se você entender isso e chamar as coisas pelos nomes certos, isso refletirá no seu conhecimento. E não é ótimo quando você sabe que sabe?
Além disso, todos os requisitos da expressão de tabela em tabelas derivadas e CTEs que discutimos anteriormente na série se aplicam à expressão de tabela na qual a exibição se baseia. Lembrando que os requisitos são:
- Todas as colunas da expressão de tabela devem ter nomes
- Todos os nomes de coluna da expressão de tabela devem ser exclusivos
- As linhas da expressão de tabela não têm ordem
Se você precisar atualizar sua compreensão do que está por trás desses requisitos, consulte a seção “Uma expressão de tabela é uma tabela” na Parte 2 da série. Certifique-se de entender especialmente a parte "sem pedido". Como um breve lembrete, uma expressão de tabela é uma tabela e, como tal, não tem ordem. É por isso que você não pode criar uma visualização com base em uma consulta com uma cláusula ORDER BY, a menos que essa cláusula exista para oferecer suporte a um filtro TOP ou OFFSET-FETCH. E mesmo com essa exceção permitindo que a consulta interna tenha uma cláusula ORDER BY, você quer lembrar que, se a consulta externa na exibição não tiver sua própria cláusula ORDER BY, você não terá garantia de que a consulta retornará as linhas em qualquer ordem particular, não importa o comportamento observado. Isso é super importante entender!
Aninhamento e várias referências
Ao discutir as considerações de design de tabelas derivadas e CTEs, comparei os dois em termos de aninhamento e referências múltiplas. Agora vamos ver como as visualizações se saem nesses departamentos. Vou começar com o aninhamento. Para isso, compararemos o código que retorna anos em que mais de 70 clientes fizeram pedidos usando tabelas derivadas, CTEs e visualizações. Você já viu o código com tabelas derivadas e CTEs anteriormente na série. Aqui está o código que lida com a tarefa usando tabelas 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;
Salientei que a principal desvantagem que vejo com tabelas derivadas aqui é o fato de você aninhar definições de tabelas derivadas, e isso pode levar à complexidade na compreensão, manutenção e solução de problemas desse código.
Aqui está o código que lida com a mesma tarefa usando CTEs:
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;
Eu apontei que para mim isso parece um código muito mais claro devido à falta de aninhamento. Você pode ver cada etapa da solução do início ao fim separadamente em sua própria unidade, com a lógica da solução fluindo claramente de cima para baixo. Portanto, vejo a opção CTE como uma melhoria em relação às tabelas derivadas a esse respeito.
Agora para visualizações. Lembre-se, um dos principais benefícios das visualizações é a reutilização. Você também pode controlar as permissões de acesso. O desenvolvimento das unidades envolvidas é um pouco mais parecido com os CTEs no sentido de que você pode focar sua atenção em uma unidade por vez do início ao fim. Além disso, você tem a flexibilidade de decidir se deseja criar uma exibição separada por unidade na solução ou, talvez, apenas uma exibição baseada em uma consulta envolvendo expressões de tabela nomeada com escopo de instrução.
Você iria com o primeiro quando cada uma das unidades precisa ser reutilizável. Aqui está o código que você usaria nesse caso, criando três visualizações:
-- 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
Você pode consultar cada uma das visualizações de forma independente, mas aqui está o código que você usaria para retornar o que a tarefa original buscava.
SELECT orderyear, numcusts FROM Sales.YearlyCustCountsAbove70;
Se houver um requisito de reutilização apenas para a parte mais externa (o que a tarefa original exigia), não há necessidade real de desenvolver três visualizações diferentes. Você pode criar uma exibição com base em uma consulta envolvendo CTEs ou tabelas derivadas. Veja como você faria isso com uma consulta envolvendo CTEs:
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
A propósito, se não fosse óbvio, as CTEs nas quais a consulta interna da exibição é baseada podem ser recursivas.
Vamos prosseguir para os casos em que você precisa de várias referências à mesma expressão de tabela da consulta externa. A tarefa para este exemplo é calcular a contagem anual de pedidos por ano e comparar a contagem em cada ano com o ano anterior. A maneira mais fácil de conseguir isso é usar a função de janela LAG, mas usaremos uma junção entre duas instâncias de uma expressão de tabela representando contagens de pedidos anuais apenas para comparar um caso de várias referências entre as três ferramentas.
Este é o código que usamos anteriormente na série para lidar com a tarefa com tabelas 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;
Há uma desvantagem muito clara aqui. Você precisa repetir a definição da expressão de tabela duas vezes. Você está basicamente definindo duas expressões de tabela nomeadas com base no mesmo código de consulta.
Aqui está o código que lida com a mesma tarefa usando CTEs:
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;
Há uma vantagem clara aqui; você define apenas uma expressão de tabela nomeada com base em uma única instância da consulta interna e faz referência a ela duas vezes na consulta externa.
As visualizações são mais semelhantes às CTEs nesse sentido. Você define apenas uma visualização com base em apenas uma cópia da consulta, assim:
CREATE OR ALTER VIEW Sales.YearlyOrderCounts AS SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate); GO
Mas melhor do que com CTEs, você não está limitado a reutilizar a expressão de tabela nomeada apenas na instrução externa. Você pode reutilizar o nome da visualização quantas vezes quiser, com qualquer número de consultas não relacionadas, desde que tenha as permissões corretas. Aqui está o código para realizar a tarefa usando várias referências à exibição:
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 as visualizações são mais parecidas com CTEs do que com tabelas derivadas, com a funcionalidade extra de ser uma ferramenta mais reutilizável, com capacidade de controlar permissões. Ou, para mudar isso, provavelmente é apropriado pensar em um CTE como uma visão com escopo de instrução. Agora, o que poderia ser realmente maravilhoso é se também tivéssemos uma expressão de tabela nomeada com um escopo mais amplo que o de um CTE, mais estreito que o de uma visão. Por exemplo, não seria ótimo se tivéssemos uma expressão de tabela nomeada com escopo no nível de sessão?
Resumo
Eu amo este tópico. Há muito nas expressões de tabela que está enraizado na teoria relacional, que por sua vez está enraizada na matemática. Adoro saber quais são os termos certos para as coisas e, geralmente, ter certeza de que tenho os fundamentos cuidadosamente descobertos, mesmo que para alguns possa parecer exigente e pedante. Olhando para trás no meu processo de aprendizado ao longo dos anos, posso ver um caminho muito claro entre insistir em uma boa compreensão dos fundamentos, usar a terminologia correta e realmente conhecer suas coisas mais tarde, quando chegar às coisas muito mais avançadas e complexas.
Então, quais são as peças críticas quando se trata de visualizações?
- Uma visualização é uma tabela.
- É uma tabela derivada de uma consulta (uma expressão de tabela).
- É dado um nome que para o usuário aparece como um nome de tabela, pois é um nome de tabela.
- Ele é criado como um objeto permanente no banco de dados.
- Você pode controlar as permissões de acesso na visualização.
As visualizações são semelhantes aos CTEs de várias maneiras. No sentido de que você desenvolve suas soluções de forma modular, focando em uma unidade por vez do início ao fim. Também no sentido de que você pode ter várias referências ao nome da exibição da consulta externa. Mas melhor do que os CTEs, as visualizações não se limitam apenas ao escopo da instrução externa, mas são reutilizáveis até serem removidas do banco de dados.
Há muito mais a dizer sobre visualizações, e continuarei a discussão no próximo mês. Enquanto isso, eu quero deixar você com um pensamento. Com tabelas derivadas e CTEs, você pode defender SELECT * em uma consulta interna. Veja o caso que fiz para ele na Parte 3 da série para obter detalhes. Você poderia fazer um caso semelhante com visualizações, ou é uma má ideia com elas?