TSQL外键视图?

时间:2009-12-18 14:00:59

标签: sql sql-server tsql views foreign-keys

我有一个SQL-Server 2008数据库和一个使用外键约束来强制引用完整性的模式。按预期工作。现在,用户创建原始表的视图以仅处理数据的子集。我的问题是过滤某些表中的某些数据集而不是其他表中的某些数据集会违反外键约束 想象一下两个表“一”和“两个”。 “one”只包含值为1,2,3的id列。 “两个”引用“一个”。现在,您可以在两个表上创建视图。表“one”的视图不会过滤任何内容,而表“one”的视图会删除除第一行之外的所有行。你最终会在第二个视图中找到无处可见的条目。

有什么方法可以避免这种情况吗?你能在视图之间有外键约束吗?

一些澄清以回应一些评论:
我知道即使通过视图插入,底层约束也将确保数据的完整性。我的问题在于使用视图的语句。这些语句是在考虑原始表的情况下编写的,并假设某些连接不会失败。使用表格时,此假设始终有效 - 但视图可能会破坏它 首先创建视图时加入/检查所有约束是因为大量的引用表而导致的。因此,我希望避免这种情况。

11 个答案:

答案 0 :(得分:14)

我喜欢你的问题。它对查询优化器非常熟悉,以及如果它们没有任何用途可以看到某些连接是多余的,或者如果它可以简化某些知道在连接的另一端最多只有一次命中的东西。

所以,最重要的问题是你是否可以针对索引视图的CIX制作FK。答案是否定的。

create table dbo.testtable (id int identity(1,1) primary key, val int not null);
go
create view dbo.testview with schemabinding as
select id, val
from dbo.testtable
where val >= 50
;
go
insert dbo.testtable
select 20 union all
select 30 union all
select 40 union all
select 50 union all
select 60 union all
select 70 
go
create unique clustered index ixV on dbo.testview(id);
go
create table dbo.secondtable (id int references dbo.testview(id));
go

除最后一个语句外,所有这些都有效,错误包括:

Msg 1768, Level 16, State 0, Line 1
Foreign key 'FK__secondtable__id__6A325CF7' references object 'dbo.testview' which is not a user table.

因此外键必须引用用户表。

但是......接下来的问题是你是否可以引用在SQL 2008中过滤的唯一索引,以实现类似FK的视图。

答案仍然是否定的。

create unique index ixUV on dbo.testtable(val) where val >= 50;
go

这成功了。

但是现在如果我尝试创建一个引用val

的表
create table dbo.thirdtable (id int identity(1,1) primary key, val int not null check (val >= 50) references dbo.testtable(val));

(我希望与过滤索引中的过滤器匹配的检查约束可能有助于系统理解FK应该保留)

但是我收到一个错误说:

There are no primary or candidate keys in the referenced table 'dbo.testtable' that matching the referencing column list in the foreign key 'FK__thirdtable__val__0EA330E9'.

如果我删除过滤后的索引并创建一个未过滤的唯一非聚集索引,那么我可以毫无问题地创建dbo.thirdtable。

所以我担心答案似乎仍然是否定。

答案 1 :(得分:10)

我花了一些时间来弄清楚这里的误会 - 不确定我是否仍然完全理解,但现在是这样。 我将使用一个靠近你的例子,但有一些数据 - 我更容易用这些术语思考。

所以前两张桌子; A =部门B =员工

CREATE TABLE Department
  ( 
   DepartmentID int PRIMARY KEY
  ,DepartmentName varchar(20)
  ,DepartmentColor varchar(10)
  )
GO 
CREATE TABLE Employee
  ( 
   EmployeeID int PRIMARY KEY
  ,EmployeeName varchar(20)
  ,DepartmentID int FOREIGN KEY REFERENCES Department ( DepartmentID )
  )
GO 

现在我将在

中丢弃一些数据
INSERT  INTO Department
  ( DepartmentID, DepartmentName, DepartmentColor )
 SELECT 1, 'Accounting', 'RED' UNION
 SELECT 2, 'Engineering', 'BLUE' UNION
 SELECT 3, 'Sales', 'YELLOW'  UNION
 SELECT 4, 'Marketing', 'GREEN' ;

INSERT  INTO Employee
  ( EmployeeID, EmployeeName, DepartmentID )
 SELECT 1, 'Lyne', 1 UNION
 SELECT 2, 'Damir', 2 UNION
 SELECT 3, 'Sandy', 2 UNION
 SELECT 4, 'Steve', 3 UNION
 SELECT 5, 'Brian', 3 UNION
 SELECT 6, 'Susan', 3 UNION
    SELECT 7, 'Joe', 4 ;

所以,现在我将在第一个表上创建一个视图来过滤一些部门。

CREATE VIEW dbo.BlueDepartments
AS
SELECT * FROM dbo.Department
WHERE DepartmentColor = 'BLUE'
GO

返回

DepartmentID DepartmentName       DepartmentColor
------------ -------------------- ---------------
2            Engineering          BLUE

根据您的示例,我将为第二个表添加一个不过滤任何内容的视图。

CREATE VIEW dbo.AllEmployees
AS
SELECT * FROM dbo.Employee
GO

返回

EmployeeID  EmployeeName         DepartmentID
----------- -------------------- ------------
1           Lyne                 1
2           Damir                2
3           Sandy                2
4           Steve                3
5           Brian                3
6           Susan                3
7           Joe                  4

在我看来,你认为员工5号,部门ID = 3指向无处?

  

“你最终会得到条目   第二种观点无处可寻。“

嗯,它指向DepartmentDepartmentID = 3,与外键一起指定。即使您尝试加入视图,也不会出现任何问题:

SELECT  e.EmployeeID
       ,e.EmployeeName
       ,d.DepartmentID
       ,d.DepartmentName
       ,d.DepartmentColor
FROM    dbo.AllEmployees AS e
        JOIN dbo.BlueDepartments AS d ON d.DepartmentID = e.DepartmentID
        ORDER BY e.EmployeeID

返回

EmployeeID  EmployeeName         DepartmentID DepartmentName       DepartmentColor
----------- -------------------- ------------ -------------------- ---------------
2           Damir                2            Engineering          BLUE
3           Sandy                2            Engineering          BLUE   

所以没有任何内容被破坏,加入根本找不到DepartmentID <> 2的匹配记录这实际上就像我加入表格然后包含过滤器如第一个视图中所示:

SELECT  e.EmployeeID
       ,e.EmployeeName
       ,d.DepartmentID
       ,d.DepartmentName
       ,d.DepartmentColor
FROM    dbo.Employee AS e
        JOIN dbo.Department AS d ON d.DepartmentID = e.DepartmentID
        WHERE d.DepartmentColor = 'BLUE'
     ORDER BY e.EmployeeID

再次返回:

EmployeeID  EmployeeName         DepartmentID DepartmentName       DepartmentColor
----------- -------------------- ------------ -------------------- ---------------
2           Damir                2            Engineering          BLUE
3           Sandy                2            Engineering          BLUE

在这两种情况下,连接都不会失败,它们只是按预期执行。

现在我将尝试通过视图(没有DepartmentID = 127)来破坏参照完整性

INSERT  INTO dbo.AllEmployees
      ( EmployeeID, EmployeeName, DepartmentID )
VALUES( 10, 'Bob', 127 )

这导致:

Msg 547, Level 16, State 0, Line 1
The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Employee__Depart__0519C6AF". The conflict occurred in database "Tinker_2", table "dbo.Department", column 'DepartmentID'.

如果我尝试通过视图删除部门

DELETE FROM dbo.BlueDepartments
WHERE DepartmentID = 2

结果是:

Msg 547, Level 16, State 0, Line 1
The DELETE statement conflicted with the REFERENCE constraint "FK__Employee__Depart__0519C6AF". The conflict occurred in database "Tinker_2", table "dbo.Employee", column 'DepartmentID'.

因此对基础表的约束仍然适用。

希望这会有所帮助,但也许我误解了你的问题。

答案 2 :(得分:8)

彼得已经对此有所了解,但最好的解决方案是:

  1. 创建一次“主”逻辑(过滤引用的表)。
  2. 让相关表格的所有视图都加入为(1)创建的视图,而不是原始表格。
  3. 即,

    CREATE VIEW v1 AS SELECT * FROM table1 WHERE blah
    
    CREATE VIEW v2 AS SELECT * FROM table2 WHERE EXISTS
      (SELECT NULL FROM v1 WHERE v1.id = table2.FKtoTable1)
    

    当然,将一个表上的视图的过滤器传播到下级表上的视图的语法糖会很方便,但是,它不是SQL标准的一部分。也就是说,这个解决方案仍然足够好 - 高效,直接,可维护,并保证消费代码的所需状态。

答案 3 :(得分:2)

如果您尝试通过视图插入,更新或删除数据,则基础表约束仍然适用。

答案 4 :(得分:1)

View2中的这类内容可能是您最好的选择:

CREATE VIEW View2
AS
     SELECT
          T2.col1,
          T2.col2,
          ...
     FROM
          Table2 T2
     INNER JOIN Table1 T1 ON
          T1.pk = T2.t1_fk

答案 5 :(得分:1)

如果滚动表以使Identity列不会发生冲突,则有一种可能性是使用通过Identity和表引用引用不同数据表的查找表。

此表上的外键可用于引用表格。

这在很多方面都很昂贵 必须使用触发器强制执行查找表上的参照完整性。 除了数据表之外还包括查找表和索引的附加存储。 数据读取几乎肯定会涉及存储过程或三个来执行过滤的UNION。 查询计划评估也会产生开发成本。

列表继续,但它可能适用于某些情况。

答案 6 :(得分:1)

使用Rob Farley的架构:

CREATE TABLE dbo.testtable(
id int IDENTITY(1,1) PRIMARY KEY,
val int NOT NULL); 
go
INSERT dbo.testtable(val)
VALUES(20),(30),(40),(50),(60),(70);
go 
CREATE TABLE dbo.secondtable(
id int NOT NULL,
CONSTRAINT FK_SecondTable FOREIGN KEY(id) REFERENCES dbo.TestTable(id)); 
go

CREATE TABLE z(n tinyint PRIMARY KEY);
INSERT z(n)
VALUES(0),(1);
go
CREATE VIEW dbo.SecondTableCheck WITH SCHEMABINDING AS
SELECT 1 n
FROM dbo.TestTable AS t JOIN dbo.SecondTable AS s ON t.Id = s.Id
CROSS JOIN dbo.z
WHERE t.Val < 50;
go
CREATE UNIQUE CLUSTERED INDEX NoSmallIds ON dbo.SecondTableCheck(n);
go

我必须创建一个小辅助表(dbo.z)才能使其工作,因为索引视图不能有自连接,外连接,子查询或派生表(而TVC计为派生表)。

答案 7 :(得分:0)

您可以将过滤后的表格1数据转移到另一个表格。此临时表的内容是您的视图1,然后通过登台表和表2的连接构建视图2.这样,过滤表1的过程只进行一次,并重用于两个视图。

实际上它归结为视图2不知道你在视图1中执行了什么样的过滤,除非你告诉视图2过滤标准,或者使它以某种方式依赖于视图1的结果,这意味着模拟与view1相同的过滤。

约束不执行任何类型的过滤,它们只能阻止无效数据,或级联密钥更改和删除。

答案 8 :(得分:0)

根据您的要求,另一种方法是使用存储过程返回两个记录集。您传递过滤条件并使用过滤条件查询表1,然后可以使用这些结果将查询过滤到表2,以使其结果也一致。然后你返回两个结果。

答案 9 :(得分:0)

不,您无法在视图上创建外键。

即使你可以,那会离开你?在创建视图后,您仍然需要声明FK。谁会宣布FK,您或用户?如果用户足够复杂以声明FK,为什么他不能在引用的视图中添加内部联接?例如:

create view1 as select a, b, c, d from table1 where a in (1, 2, 3)
go 
create view2 as select a, m, n, o from table2 where a in (select a from view1)
go 

vs:

create view1 as select a, b, c, d from table1 where a in (1, 2, 3)
go 
create view2 as select a, m, n, o from table2
--# pseudo-syntax for fk:
alter view2 add foreign key (a) references view1 (a)
go 

我不知道外键如何简化你的工作。

或者:

将数据子集复制到另一个架构或数据库中。相同的表,相同的密钥,更少的数据,更快的分析,更少的争用。

如果您需要所有表的子集,请使用其他数据库。如果您只需要某些表的子集,请在同一数据库中使用模式。这样你的新表仍然可以引用未复制的表。

然后使用现有视图复制数据。任何FK违规都会引发错误并确定哪些视图需要编辑。如有必要,每天创建一份工作并安排工作。

答案 10 :(得分:0)

从纯粹的数据完整性角度来看(与查询优化器没有任何关系),我考虑了一个索引视图。我想你可以在它上面创建一个唯一的索引,当你试图在基础表中破坏完整性时,可能会破坏它。

但是......我认为你不能很好地绕过索引视图的限制。

例如:

您不能使用外部联接或子查询。这使得很难找到视图中不存在的行。如果你使用聚合,你不能使用HAVING,因此削减了你可以在那里使用的一些选项。如果你有分组(无论你是否使用GROUP BY子句),你甚至不能在索引视图中有常量,所以你甚至不能尝试在常量字段上放置一个索引,这样第二行就会倒下。你不能使用UNION ALL,所以在点击第二个零时计数将打破一个唯一索引的想法将不起作用。

我觉得应该有答案,但我担心你不得不好好看看你的实际设计并找出你真正需要的东西。也许在所涉及的表上触发(和良好的索引),以便任何可能破坏某些内容的更改都可以将其全部推出。

但我真的希望能够提出一些可以利用查询优化器来帮助系统性能的东西,但我认为不行。