2009-12-18 1 views
20

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

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

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

+0

Вам нужно беспокоиться об этом? Внешние ключи управляют вставкой/обновлением данных - они не проверяются при выборе. –

+1

Почему вы не фильтруете оба вида? –

+2

Не могли бы вы привести пример? Я не понимаю, как таблицы чтения могут влиять на ссылочную целостность? Может быть, несколько таблиц с некоторыми данными, мнениями и проблемами, которые у вас есть? –

ответ

7

Питер уже попал на это, но самое лучшее решение заключается в следующем:

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

+0

Да, я сделал бы это так, если бы мне нужна была такая функция. –

1

Нечто подобное в View2, вероятно, ваш лучший выбор:

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

Да, я знаю об этом решении, но надеялся, что этого можно избежать. Благодарю. – BuschnicK

+0

Если у T-SQL гипотетически была такая возможность для фильтрации одного представления на основе другого, я думаю, что ответ Тома будет самым близким к тому, что SQL-сервер будет делать для получения результатов. – AaronLS

2

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

+0

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

0

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

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

Ограничения не выполняют никакой фильтрации, они только предотвращают недействительные данные или каскадные изменения ключа и удаляются.

0

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

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 

против:

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

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

Итак, первые две таблицы; 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'. 

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

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

+0

Проблема сформулирована отлично. И да, если потребительские запросы, используемые внутренними объединениями, не будут проблемой. Однако они этого не делают. Поскольку внешний ключ может быть нулевым, используются левые соединения. Это приводит к тому, что вы можете получить пустые столбцы из-за того, что внешний ключ является нулевым (который предназначен) или нулевым столбцом из-за ссылочного вида, не содержащего запрошенные строки (не предназначенные и что я имел в виду под «неудачей соединения»). – BuschnicK

14

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

Итак, возникает большой вопрос, можете ли вы сделать 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 столбце

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'. 

Если я бросаю отфильтрованный индекс и создать нефильтрованное уникальный, не CLU я могу создать dbo.thirdtable без каких-либо проблем.

Так что я боюсь, ответ до сих пор, кажется, быть №

+2

С точки зрения целостности данных вы в конечном итоге застреваете с триггерами и ограничениями. С точки зрения оптимизации, я не уверен, что вы можете многое сделать. Я пишу блог для вас сейчас на msmvps.com/blogs/robfarley. Он будет опубликован завтра. –

0

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

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

Например:

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

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

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

1

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

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

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

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

1

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

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