TSQL внешние ключи на просмотрах? - PullRequest
20 голосов
/ 18 декабря 2009

У меня есть база данных SQL-Server 2008 и схема, которая использует ограничения внешнего ключа для обеспечения ссылочной целостности. Работает как задумано. Теперь пользователь создает представления для исходных таблиц для работы только с подмножествами данных. Моя проблема заключается в том, что фильтрация определенных наборов данных в некоторых таблицах, но не в других, нарушит ограничения внешнего ключа.
Представьте себе две таблицы «один» и «два». «one» содержит только столбец id со значениями 1,2,3. «Две» ссылки «одна». Теперь вы создаете представления для обеих таблиц. Представление для таблицы «два» ничего не фильтрует, в то время как представление для таблицы «один» удаляет все строки, кроме первой. Вы получите записи во втором представлении, которые нигде не указывают.

Есть ли способ избежать этого? У вас могут быть ограничения внешнего ключа между представлениями?

Некоторые уточнения в ответ на некоторые комментарии:
Мне известно, что лежащие в основе ограничения обеспечат целостность данных даже при вставке в представления. Моя проблема заключается в утверждениях, потребляющих взгляды. Эти утверждения были написаны с учетом исходных таблиц и предполагают, что некоторые объединения не могут завершиться неудачей. Это предположение всегда верно при работе с таблицами, но представления могут нарушить его.
Объединение / проверка всех ограничений при создании представлений в первую очередь раздражает из-за большого количества ссылающихся таблиц. Таким образом, я надеялся избежать этого.

Ответы [ 11 ]

14 голосов
/ 04 января 2010

Мне нравится твой вопрос. Он кричит о знакомстве с Оптимизатором запросов и о том, как он может видеть, что некоторые объединения являются избыточными, если они не служат цели, или если он может упростить что-то, зная, что на другой стороне объединения есть не более одного удара.

Итак, большой вопрос заключается в том, можете ли вы создать FK против CIX индексированного представления. И ответ нет.

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 1019 *

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.

Так что я боюсь, что ответ все еще, кажется, нет.

10 голосов
/ 03 января 2010

Мне потребовалось некоторое время, чтобы выяснить недоразумение здесь - не уверен, что я все еще полностью понимаю, но вот оно. Я буду использовать пример, близкий к вашему, но с некоторыми данными - мне проще думать в этих терминах.

Итак, первые две таблицы; 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, DepartmentID = 3 балла в никуда?

"Вы получите записи в Второй взгляд на эту точку никуда. "

Ну, это указывает на таблицу Department DepartmentID = 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'.

Таким образом, ограничения на базовые таблицы все еще применяются.

Надеюсь, это поможет, но тогда, возможно, я неправильно понял вашу проблему.

8 голосов
/ 02 января 2010

Питер уже ударил по этому, но лучшее решение:

  1. Создайте "основную" логику (которая фильтрует ссылочную таблицу) один раз.
  2. Соединить все представления связанных таблиц с представлением , созданным для (1), а не с исходной таблицей.

т.е.,

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. Тем не менее, это решение все еще достаточно хорошее - эффективное, простое, обслуживаемое и гарантирует требуемое состояние для потребляющего кода.

2 голосов
/ 18 декабря 2009

Если вы попытаетесь вставить, обновить или удалить данные через представление, базовые ограничения таблицы по-прежнему будут применяться.

1 голос
/ 28 января 2012

Использование схемы Роба Фарли:

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 считаются производными таблицами).

1 голос
/ 03 июня 2011

Если прокручивать таблицы так, чтобы столбцы идентификаторов не конфликтовали, одной из возможностей было бы использование справочной таблицы, которая ссылалась на разные таблицы данных по идентификатору и ссылке на таблицу.

Внешние ключи в этой таблице будут работать по ссылкам на таблицы.

Это было бы дорого несколькими способами Ссылочная целостность в таблице поиска должна обеспечиваться с помощью триггеров. Дополнительное хранилище таблицы поиска и индексации в дополнение к таблицам данных. Чтение данных почти наверняка потребует хранимой процедуры или трех для выполнения фильтрованного UNION. Оценка плана запроса также будет иметь стоимость разработки.

Список можно продолжить, но он может работать в некоторых сценариях.

1 голос
/ 18 декабря 2009

Что-то вроде этого в View2, вероятно, является лучшим выбором:

CREATE VIEW View2
AS
     SELECT
          T2.col1,
          T2.col2,
          ...
     FROM
          Table2 T2
     INNER JOIN Table1 T1 ON
          T1.pk = T2.t1_fk
0 голосов
/ 04 января 2010

С точки зрения чисто целостности данных (и не имеет ничего общего с оптимизатором запросов) я рассмотрел индексированное представление. Я подумал, что вы можете создать для него уникальный индекс, который может быть нарушен, если вы попытаетесь нарушить целостность ваших базовых таблиц.

Но ... я не думаю, что вы можете обойти ограничения индексированных представлений достаточно хорошо.

Например:

Вы не можете использовать внешние объединения или подзапросы. Это делает очень трудным поиск строк, которые не существуют в представлении. Если вы используете агрегаты, вы не можете использовать HAVING, так что отсекаются некоторые опции, которые вы также можете использовать там. Вы даже не можете иметь константы в индексированном представлении, если у вас есть группировка (независимо от того, используете ли вы предложение GROUP BY), поэтому вы даже не можете попытаться поместить индекс в константное поле, чтобы упала вторая строка. Вы не можете использовать UNION ALL, поэтому идея наличия счетчика, который будет нарушать уникальный индекс при достижении второго нуля, не будет работать.

Я чувствую, что должен быть ответ, но я боюсь, что вам придется внимательно взглянуть на ваш реальный дизайн и решить, что вам действительно нужно. Возможно, триггеры (и хорошие индексы) для задействованных таблиц, так что любые изменения, которые могут что-то сломать, могут все это прокрутить.

Но я очень надеялся, что смогу предложить что-то, что оптимизатор запросов сможет использовать для повышения производительности вашей системы, но я не думаю, что смогу.

0 голосов
/ 02 января 2010

Нет, вы не можете создавать внешние ключи для представлений.

Даже если бы ты мог, где бы ты это оставил? Вам все равно придется объявить 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 

против:

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 вызовут ошибку и определят, какие виды требуют редактирования. Создайте работу и запланируйте ее ежедневно, если необходимо.

0 голосов
/ 31 декабря 2009

Другой подход, в зависимости от ваших требований, заключается в использовании хранимой процедуры для возврата двух наборов записей. Вы передаете ему критерии фильтрации, и он использует критерии фильтрации для запроса к таблице 1, а затем эти результаты можно использовать для фильтрации запроса к таблице 2, чтобы его результаты также были согласованными. Затем вы возвращаете оба результата.

...