四巨头第十一周作业 翻译

通往T-SQL的阶梯:超越基本的4级:使用视图简化你的查询

系列

这篇文章是楼梯系列的一部分:T-SQL的阶梯:超越基础。

从他的阶梯到T-SQL DML,格雷戈里·拉森涵盖了T-SQL语言的更高级的方面,比如子查询。

在这个阶梯级别,我将要讨论如何使用数据库视图来简化Transact-SQL (T-SQL)代码。通过了解如何使用视图,你将能够更好地支持编写T-SQL代码以满足复杂的业务需求。在本文中,我将讨论数据库视图是什么,然后提供一些示例来帮助你理解如何使用视图来实现不同的编码场景。

观点是什么?

视图是由行和列组成的虚拟表。数据可以来自单个表,也可以来自多个表。查询一个视图,就像正常的表一样。视图是用CREATE view语句创建的,并存储在创建它的数据库中。

下面是一些视图可以帮助你编写代码逻辑的情况:

l  你不希望将表的所有列公开给查询该表的用户。

l  你的数据库模式设计非常复杂,因此您可以构建视图来简化用户访问。

l  你希望更改数据库模式设计,但希望保持向后兼容性,因此不需要重写现有代码。

要更好地理解如何使用视图,最好的方法是通过一些使用视图的例子来满足不同的业务需求。

样本数据

为了演示视图如何工作以及如何简化T-SQL代码,需要一些测试数据来支持这些视图。与其创建我自己的测试数据,我的大多数示例将要使用AdventureWorks2008R2数据库。如果你想在你的环境中跟踪和运行我的示例,那么你可以从这里下载AdventureWorks2008R2数据库:http://msftdbprodsamples.codeplex.com/releases/view/93587。

使用视图简化SQL代码的示例

通过使用视图,可以返回列的列表,这些列是表列的子集,一组来自多个表的列,一组基于某些标准的约束列,或者一些其他不同的需求。在本节中,我将提供一些不同的使用视图来满足不同业务需求的示例。

对于我的第一个示例,我们假设有一个要求,即不要将单个表中的所有列都呈现给应用程序或临时查询。对于本例,我们假设你只想从HumanResource返回非个人信息。Employee表如清单1所示。(注意这张表已经在AdventureWorks2008R2数据库中出现了;这里列出的定义仅供参考。

CREATE TABLE [HumanResources].[Employee](

       [BusinessEntityID] [int] NOT NULL,

       [NationalIDNumber] [nvarchar](15) NOT NULL,

       [LoginID] [nvarchar](256) NOT NULL,

       [OrganizationNode] [hierarchyid] NULL,

       [OrganizationLevel]  AS ([OrganizationNode].[GetLevel]()),

       [JobTitle] [nvarchar](50) NOT NULL,

       [BirthDate] [date] NOT NULL,

       [MaritalStatus] [nchar](1) NOT NULL,

       [Gender] [nchar](1) NOT NULL,

       [HireDate] [date] NOT NULL,

       [SalariedFlag] [dbo].[Flag] NOT NULL,

       [VacationHours] [smallint] NOT NULL,

       [SickLeaveHours] [smallint] NOT NULL,

       [CurrentFlag] [dbo].[Flag] NOT NULL,

       [rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,

       [ModifiedDate] [datetime] NOT NULL);

清单1:人力资源的表定义;员工表

应用程序和临时用户需要的非个人信息包括以下列:BusinessEntityId、NationalIDNumber、LoginID、OrganizationNode、OrganizationLevel、JobTitle和HireDate。

为了创建一个视图,它只返回来自人力资源的列的子集。Employee表我将使用清单2中的代码。

CREATE VIEW [HumanResources].[EmployeeInfo]

AS

       SELECT [BusinessEntityID]

             ,[NationalIDNumber] 

             ,[LoginID] 

             ,[OrganizationNode]     

             ,[OrganizationLevel]

             ,[JobTitle]

             ,[HireDate] 

             ,[CurrentFlag] 

    FROM [HumanResources].[Employee];

清单2:从人力资源创建非个人信息视图的脚本。员工表

通过查看清单2中的CREATE VIEW语句,你可以看到它非常简单。视图的代码只是一个简单的SELECT语句,其中包含了我希望在选择条件中公开的视图。一旦我创建了这个视图,我就可以像普通表一样查询它。清单3中的脚本演示了两个不同的SELECT语句,它们从人力资源中检索数据。Employee表使用清单2中的代码创建的视图。

SELECT * FROM [HumanResources].[EmployeeInfo];

SELECT * FROM [HumanResources].[EmployeeInfo]

       WHERE JobTitle like '%Manager%';

清单3:使用视图返回数据的两个SELECT语句。

通过查看清单3中的代码,你可以看到在FROM子句后面引用的对象是我在清单2中创建的视图的名称。我在SELECT语句中引用了视图,就像引用表一样。清单3中的第一个SELECT语句返回了我的人力资源中的所有行。Employee表,但只返回我视图中SELECT子句中的非个人列。清单3中的第二个SELECT语句表明,我可以使用WHERE语句来约束返回的行,就像在引用表时所做的那样。

有时,数据库设计相当复杂,这会使构建查询变得复杂,从而访问数据库中所需的数据。这些复杂的设计可能需要复杂的多表连接来实际返回数据。这就是视图可以提供帮助的地方。通过使用视图,可以在视图中构建复杂的多表连接,然后使用视图来查询数据。通过这样做,可以简化代码来查询数据库,并在视图中隐藏数据库设计的复杂性。为了演示这一点,我创建了一个视图清单4,它检索包含在多个表中的销售订单数据。

CREATE VIEW SalesOrderCombined2005

AS

SELECT

        OH.SalesOrderID

       ,OH.OrderDate

       ,OH.ShipDAte

       ,ST.Name AS TerritoryName

       ,BTA.City AS BillToCity

       ,STA.City AS ShiptToCity

       ,OH.TotalDue

FROM Sales.SalesOrderHeader OH

        JOIN Sales.SalesTerritory ST

        ON OH.TerritoryID = ST.TerritoryID

        JOIN Person.Address BTA

        ON OH.BillToAddressID = BTA.AddressID

        JOIN Person.Address STA

        ON OH.ShipToAddressID = STA.AddressID

WHERE YEAR(OH.OrderDate) = 2005;

清单4:包含多表连接的视图。

清单4中的SalesOrderCombined2005视图将多个表连接在一起,只返回这些表中的列的一个子集。另外,视图有WHERE子句。WHERE子句只返回与2005年被放置的销售订单相关的数据。此视图消除了理解如何使用不同的键列连接多个表的需要。通过执行针对SalesOrderCombined2005的SELECT语句,所有这些连接都是在不需要在SELECT语句中引用它们的情况下完成的。通过在视图中放置复杂的连接语法,可以简化从复杂数据库设计中检索数据的代码。另外,这些类型的视图确保对数据库的所有查询都使用相同的连接语法。通过提供和使用一个视图来查询数据,可以消除连接标准存在的可能性。

有些时候,希望随着时间的推移发展数据库设计,但是不希望破坏现有的代码。视图可以处理满足这一业务需求。为了演示这一点,请查看清单5中的代码。

--- Begin Old Schema

CREATE TABLE DateDimOld (

ID INT IDENTITY,

DT DATE,

DOWTitle varchar(10));

GO

-- Populate DateDimOld

INSERT INTO DateDimOld(DT, DOWTitle) VALUES

  ('12/1/2013',DATENAME(DW,'12/1/2013')),

  ('12/2/2013',DATENAME(DW,'12/2/2013')),

  ('12/3/2013',DATENAME(DW,'12/3/2013'));

GO

SELECT * FROM DateDimOld;

GO

--- End Old Schema 

--  Begin New Schema 

CREATE TABLE DOWTitle (

DowTitleID INT IDENTITY PRIMARY KEY,

DOWTitle VARCHAR(10));

GO

CREATE TABLE DateDimNew (

ID INT IDENTITY,

DT DATE,

DOWTitleID INT);

GO

ALTER TABLE DateDimNew  WITH CHECK ADD  CONSTRAINT [FK_DateDimNew_DOWTitle_DOWTitleID] FOREIGN KEY(DOWTitleID)

REFERENCES DOWTitle (DOWTitleID)

GO

-- Populate DOWTitle

INSERT INTO DOWTitle (DOWTitle) VALUES

  (DATENAME(DW,'12/1/2013')),

  (DATENAME(DW,'12/2/2013')),

  (DATENAME(DW,'12/3/2013'));

GO

-- Populate DateDimNew

INSERT INTO DateDimNew (DT,DOWTitleID) VALUES

  ('12/1/2013', 1),

  ('12/2/2013', 2),

  ('12/3/2013', 3);

GO

-- Remove Old Schema

DROP TABLE DateDimOld

GO

-- Create view to similate Old Schema

CREATE VIEW DateDimOld AS

SELECT DDN.ID, DDN.DT, DOWT.DOWTitle

       FROM DateDimNew AS DDN

       JOIN DOWTitle AS DOWT

       ON DDN.DOWTitleID = DOWT.DowTitleID;

GO

-- Show how VIEW and Simulate Old Schema

SELECT * FROM DateDimOld

-- End New Schema

清单5:新旧模式结构。

通过回顾清单5中的代码,可以看到代码有两个不同的部分。在第一节中,我定义、填充并显示了一些来自旧模式的数据,其中有一个名为datedimodel的表。这个表包含一个名为DT的日期列,以及一个名为DOWTitle的星期列,并将这些列关联到ID列。在第二部分中,我定义了一个新的模式来替换第一部分中的旧模式。在第二部分中,我创建了两个表。第一个表名为DOWTitle,其中包含DOWTitle和DOWTitleID列。第二个表名为DateDimNew。此表包含ID、DT和DOWTitleID列。DOWTitleID列是进入DOWTitle表的外键列。这个新模式是一个规范化的模式,而旧的模式是一个非规范化的模式。在代码的第二部分中,我实际上删除了在第一部分代码中创建的表,并创建了一个同名的视图,datedimodel。datedimodel视图允许我查询新的规范化模式,就像在旧模式中查询datedimodel表一样。这个新的视图数据模型允许我为我可能构建的使用旧模式设计的代码提供向后兼容性。

正如你所看到的,可以使用许多不同的方法视图。在我的例子中,我只展示了从视图中选择数据。视图也可以用来更新表。此外,在创建视图时还可以使用其他选项。

更新视图的基础表

视图也可以用来更新表中的数据。为了演示这一点,我将运行清单6中的代码。

INSERT INTO DateDimOld (DOWTitle) 
VALUES (DATENAME(DW,'12/4/2013'));

清单6:使用视图将数据插入到基础表中。

清单6中的代码并没有真正更新datedimodeltable(已被删除),而是更新底层表DOWTitle,这是datedimodel的视图定义的一部分。在运行清单6中的INSERT语句之后,在DOWTitle表中创建了一个row,其中包含DOWTitle列中的“周三”值。由于datedimodel是我的规范化日期维度表的视图,所以我需要在表DateDimNew中放置另一行,以便查看datedimodel以显示“周三”值。为此,我运行清单7中的代码。

INSERT INTO DateDimNew (DT, DOWTitleID) 
   SELECT '12/4/2013', DOWTitleID FROM DOWTitle 
       WHERE DOWTitle = DATENAME(DW,'12/4/2013');

清单7:向DateDimNew表添加一行。

因为列DOWTitleID不是datedimodel视图的一部分,所以我不能使用视图来更新DateDimNew表。相反,我必须编写清单7中的代码,直接引用底层视图表。

使用视图更新视图的基础表有一些限制。这里有那些限制:

l  视图中只有一个底层表可以被更新。

l  正在更新的列必须在视图中直接引用,而不需要对它们进行任何计算。

l  被修改的列不能被一个组、不同的或有子句影响。

l  当使用CHECK选项时,你的视图不包含TOP子句。

有关限制的更多信息,请参阅联机文档。

确保视图不受其他表更改或更新的影响

在我已经向你展示的CREATE VIEW语句中,所创建的视图不会限制你对底层表所做的操作。你可以对基础表进行一些更改,以便查看可能会破坏视图的视图,或者返回意外的结果。一个这样的更改会破坏一个视图,即删除一个视图引用的列。有些情况下,你可能想要确保你的观点不受这些问题的影响。当创建一个视图时,你可以在create视图或SELECT语句中添加一些额外的子句,以帮助消除这些恼人的潜在问题。

你可以做的第一件事是绑定你的视图底层的表模式。通过将表绑定到底层模式,可以限制任何可能会破坏视图的表更改。为了演示,让我运行清单8中的代码。

ALTER VIEW DateDimOld WITH SCHEMABINDING AS 
SELECT DDN.ID, DDN.DT, DOWT.DOWTitle 
       FROM dbo.DateDimNew AS DDN
       JOIN dbo.DOWTitle AS DOWT
       ON DDN.DOWTitleID = DOWT.DowTitleID;
GO

清单8:使用模式绑定创建视图。

在清单8中,我删除并重新创建了datedimodel视图。当我重新创建它时,我添加了SCHEMABINDING子句。这创建了一个模式绑定视图。当我做出这个改变时,我还需要稍微修改一下视图中的SELECT语句。我所做的更改是为所有表有两个部分的名称。建议你在引用SQL Server表时总是使用两部分命名,而不管SQL Server在技术上是否需要它。这个要求意味着我必须添加“dbo”。在我的原始视图的两个表名前面。除此之外,这一观点与最初的观点完全一致。

为了说明模式绑定如何限制我对底层表的操作,让我运行清单9中的代码。

ALTER TABLE dbo.DateDimNew 
  ALTER COLUMN DT INT;

清单9:尝试使用模式绑定修改表。

当运行清单9中的代码时,我将得到报告1中显示的错误。

Msg 5074, Level 16, State 1, Line 1

The object 'DateDimOld' is dependent on column 'DT'.

Msg 4922, Level 16, State 9, Line 1

ALTER TABLE ALTER COLUMN DT failed because one or more objects access this column.

报告1:更改模式绑定视图的列时收到的错误。

通过检查报告1中的输出,您可以看到数据库引擎阻止了我修改DT列,它包含在视图定义中。通过创建一个模式绑定视图,我确保某人不会出现并修改可能影响我的datedimodel视图的任何部分。

创建视图的另一个选项是with CHECK选项。带有CHECK选项的选项允许您对视图进行约束,以确保对基础表进行的任何更新都可以使用视图进行选择。为了向您展示使用CHECK选项的方式,请查看清单10中的代码。

CREATE TABLE DayOfTheWeek(DayOfTheWeek varchar (10), 
              DayOfTheWeekNum int);
INSERT INTO  DayOfTheWeek VALUES
    ('Monday',0),
    ('Tuesday',1),
    ('Wednesday',2),
    ('Thursday',3),
    ('Friday',4);
GO
CREATE VIEW DisplayDayOfTheWeek 
AS 
SELECT DayOfTheWeek, DayOfTheWeekNum FROM DayOfTheWeek
WHERE DayOfTheWeekNum < 5
       WITH CHECK OPTION;

清单10:使用CHECK选项创建视图。

在清单10的代码中,您可以看到我创建了一个表并填充了一个名为DayOfTheWeek的表。我还创建了一个名为DisplayDayOfTheWeek的视图,它限制了使用WHERE子句返回的天数,并添加了WITH CHECK选项。通过添加WITH CHECK选项,SQL Server将不允许我使用DisplayDayOfTheWeek视图插入或更新一个行,除非weeknum值小于5。为了测试这个,我可以运行清单11中的代码。

INSERT INTO  DisplayDayOfTheWeek VALUES
    ('Saturday',5);
UPDATE DisplayDayOfTheWeek
SET DayOfTheWeekNum = 5
WHERE DayOfTheWeek = 'Friday';

清单11:用CHECK选项测试代码。

当清单11中的代码尝试插入一个值大于5的新行,或者将我现有的周五行更新为大于5的DayOfTheWeekNum值时,我得到了报告2中所示的错误。实际上,清单11中的代码将两次生成此消息,一次用于插入,一次用于更新。

The attempted insert or update failed because the target view either specifies WITH CHECK OPTION or spans a view that specifies WITH CHECK OPTION and one or more rows resulting from the operation did not qualify under the CHECK OPTION constraint.
The statement has been terminated.

报告2:用CHECK选项测试代码。

通过查看消息,您可以看到带有CHECK选项的消息,导致我的INSERT和UPDATE语句在清单11中失败。如果您想实际插入或更新这些行,您有两个选项。一个选项是用CHECK选项删除。这将允许您通过视图更改基础表,但是从视图中选择仍然不会显示那些符合视图定义条件的值。如果您想要插入和更新这些行,并让视图显示它们,那么第二个选项将是更改视图中的WHERE条件,以允许选择新的值。(请记住,使用CHECK选项只适用于通过视图进行的更改;is不阻止直接向底层表的更新或插入。

如果您想要控制可能影响您的视图的语句类型,那么您应该考虑使用模式绑定和/或WITH CHECK选项。

使用视图时的性能考虑。

使用视图有性能问题吗?与大多数SQL Server问题一样,答案是“看情况”。

视图的性能取决于视图在做什么。一个简单的视图,它读取一个没有连接子句的表,它很可能与只引用单个表的查询执行类似的操作。但是,如果您有一个视图,它引用了一个引用视图的视图,而这些视图包含多个连接子句,该怎么办呢?通过简单的SELECT语句实际执行的底层查询可能会被扩展成一个非常复杂的SELECT语句,其中包含多个连接子句,并且可能会比您预期的做更多的工作。

其他值得一提的关于视图性能问题的东西是,当一个视图包含多个表连接在一起时,但您只想从视图中的单个表返回数据。在这种情况下,SQL Server仍然需要连接视图中的所有表,以从单个表返回数据。这可能导致SQL Server的额外工作加入到视图中所有的表中,而对于那些只希望从视图中的单个表返回数据的查询的响应时间较慢。如果您发现只从一个表中返回数据,并且性能很重要,那么您最好将查询写在单个表上,而不是使用包含多个表连接的视图。

CVews是一种简化代码并隐藏数据库模式复杂性的好方法。但是隐藏这种复杂性会导致严重的性能问题。如果您打算使用视图,请确保您知道视图在幕后做什么。理解查询引擎必须执行的工作,以执行针对您的视图的查询将帮助您开发性能良好的代码。

使用视图保护数据。

人们使用视图的另一个原因是访问表中的某些列。假设您有一个业务需求,允许用户对包含机密数据的表进行报告,比如社会保险号或信用卡号。您可能不希望他们访问这些机密列。确保他们不能读取这些机密数据列的一种方法是创建一个表的视图,该视图不包括那些机密列,并且不提供用户在底层表上的选择权限。

总结

视图是实现安全性、简化查询复杂数据库模式和/或提供向后功能的好方法。但是,如果您在不理解这可能导致的性能影响的情况下开始嵌套视图,那么就会有一个负面的视图。当您看到一个给定的业务需求需要一个T-SQL解决方案时,请将视图视为您可能能够用于实现解决方案的众多工具中的一个。

问答

在本节中,您可以通过回答下列问题,来回顾您对使用视图查询数据库的理解程度。

问题1

一个视图可以帮助您实现哪些好的业务需求?

需要保持应用程序或特定查询访问表中的底层列。

需要简化查询复杂数据库结构所需的代码。

需要提供向后兼容性。

上面所有的

问题2

你需要确保当一个列值被更新或插入时,通过视图可以选择它。哪个子句提供了这个功能?

创建视图

与SCHEMABINDING

检查选项

以上都不是

问题3

您需要在表中限制对机密数据的访问。可以使用什么方法来限制对这些数据的访问?

使用WITH CHECK选项创建视图。

创建一个使用带有SCHEMABINDING选项的视图。

创建一个视图,该视图不包含表中的机密列,并且不能被证明选择访问该表。

创建一个视图,该视图排除表中的机密列,并证明选择访问表。

答案

问题1

答案是d。在直接查询中使用视图有很多原因。a、b和c是其中的一些原因。

问题2

正确的答案是c. CREATE VIEW不是提供任何额外数据完整性检查的子句。WITH SCHEMABINDING子句确保任何ALTER TABLE语句不会在视图的底层表结构发生更改时引起视图问题。它是带有CHECK选项的,它确保您不能更新基础表,除非更改立即可以使用视图进行查询。

问题3

正确答案是c.答案a和b没有特别限制对机密列的访问,因为他们没有提到从视图中排除机密列。答案d是不正确的,因为如果人们可以访问包含机密数据的底层表,那么他们仍然可以通过编写直接针对表的查询来选择机密列。

猜你喜欢

转载自www.cnblogs.com/hawking-520/p/9065300.html