2

編集:私が禁止しようとしているサンプルデータに追加されました。

この質問はこれに似ています:同じテーブルを 2 回参照しているため、View に CLUSTERED INDEX を作成できません。回避策はありますか? しかし、そこにある答えは私を助けません。私は一意性を強制しようとしているので、代替手段なしで「それをしないでください」という答えは、私の進歩に役立ちません.

問題の例 (簡略化):

CREATE TABLE [dbo].[Object]
(
   Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
   OrgId UNIQUEIDENTIFIER
)

CREATE TABLE [dbo].[Attribute]
(
   Id INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
   Name NVARCHAR(256) NOT NULL
)

CREATE TABLE [dbo].[ObjectAttribute]
(
   Id INT NOT NULL IDENTITY(1, 1),
   ObjectId INT NOT NULL,
   AttributeId INT NOT NULL,
   Value NVARCHAR(MAX) NOT NULL,

   CONSTRAINT FK_ObjectAttribute_Object FOREIGN KEY (ObjectId) REFERENCES [Object] (Id),
   CONSTRAINT FK_ObjectAttribute_Attribute FOREIGN KEY (AttributeId) REFERENCES Attribute (Id)
)
GO

CREATE UNIQUE INDEX IUX_ObjectAttribute ON [dbo].[ObjectAttribute] ([ObjectId], [AttributeId])
GO

CREATE VIEW vObject_Uniqueness
WITH SCHEMABINDING
AS
SELECT
    ObjectBase.OrgId
    , CAST(OwnerValue.Value AS NVARCHAR(256)) AS OwnerValue
    , CAST(NameValue.Value AS NVARCHAR(50)) AS NameValue
FROM [dbo].[Object] ObjectBase
INNER JOIN [dbo].ObjectAttribute OwnerValue
    INNER JOIN [dbo].Attribute OwnerAttribute
        ON OwnerAttribute.Id = OwnerValue.AttributeId
        AND OwnerAttribute.Name = 'Owner'
    ON OwnerValue.ObjectId = ObjectBase.Id
INNER JOIN [dbo].ObjectAttribute NameValue
    INNER JOIN [dbo].Attribute NameAttribute
        ON NameAttribute.Id = NameValue.AttributeId
        AND NameAttribute.Name = 'Name'
    ON NameValue.ObjectId = ObjectBase.Id
GO

/*
Cannot create index on view "[Database].dbo.vObject_Uniqueness". The view contains a self join on "[Database].dbo.ObjectAttribute".
*/
CREATE UNIQUE CLUSTERED INDEX IUX_vObject_Uniqueness
ON vObject_Uniqueness (OrgId, OwnerValue, NameValue)
GO

DECLARE @Org1 UNIQUEIDENTIFIER = NEWID();
DECLARE @Org2 UNIQUEIDENTIFIER = NEWID();

INSERT [dbo].[Object]
(
    OrgId
)
VALUES
    (@Org1) -- Id: 1
    , (@Org2) -- Id: 2
    , (@Org1) -- Id: 3

INSERT [dbo].[Attribute]
(
    Name
)
VALUES
    ('Owner') -- Id: 1
    , ('Name') -- Id: 2
    --, ('Others')

-- Acceptable data.
INSERT [dbo].[ObjectAttribute]
(
    AttributeId
    , ObjectId
    , Value
)
VALUES
    (1, 1, 'Jeremy Pridemore') -- Owner for object 1 (Org1).
    , (2, 1, 'Apple') -- Name for object 1 (Org1).
    , (1, 2, 'John Doe') -- Owner for object 2 (Org2).
    , (2, 2, 'Pear') -- Name for object 2 (Org2).

-- Unacceptable data.
-- Org1 already has an abject with an owner value of 'Jeremy' and a name of 'Apple'
INSERT [dbo].[ObjectAttribute]
(
    AttributeId
    , ObjectId
    , Value
)
VALUES
    (1, 3, 'Jeremy Pridemore') -- Owner for object 3 (Org1).
    , (2, 3, 'Apple') -- Name for object 3 (Org1).

-- This is the bad data. I want to disallow this.
SELECT
    OrgId, OwnerValue, NameValue
FROM vObject_Uniqueness
GROUP BY OrgId, OwnerValue, NameValue
HAVING COUNT(*) > 1

DROP VIEW vObject_Uniqueness
DROP TABLE ObjectAttribute
DROP TABLE Attribute
DROP TABLE [Object]

この例では、次のエラーが発生します。

Msg 1947, Level 16, State 1, Line 2 Cannot create index on view "TestDb.dbo.vObject_Uniqueness". The view contains a self join on "TestDb.dbo.ObjectAttribute".

これが示すように、2 つのテーブルを持つ属性システムを使用して、1 つのオブジェクトとその値を表しています。オブジェクトの存在とオブジェクトの OrgId はメイン テーブルにあり、残りの値はセカンダリ テーブルの属性です。

まず第一に、なぜこれが自己結合があると言っているのか理解できません。ObjectからObjectAttribute2回参加しています。ONいいえ、句内のテーブルから同じテーブルに移動する場所はありません。

第二に、これを機能させる方法はありますか?または、私が行っている独自性を強制する方法はありますか? 私が望む最終結果は、によって、同じ「所有者」と「名前」の値を提供するそれらを参照するレコードを持つObject.OrgId2つのObject行がないことです。ObjectAttributeそのため、OrgId、Owner、および Name の値は、任意の に対して一意である必要がありますObject

4

3 に答える 3

2

このためのヘルパーテーブルを作成できると思います:

CREATE TABLE [dbo].[ObjectAttributePivot]
(
   Id int primary key,
   OwnerValue  nvarchar(256),
   NameValue nvarchar(50)
)
GO

次に、データの同期を維持するためのヘルパー トリガーを作成します。

create view vw_ObjectAttributePivot
as
    select
        o.Id,
        cast(ov.Value as nvarchar(256)) as OwnerValue,
        cast(nv.Value as nvarchar(50)) as NameValue
    from dbo.Object as o
        inner join dbo.ObjectAttribute as ov on ov.ObjectId = o.Id
        inner join dbo.Attribute as ova on ova.Id = ov.AttributeId and ova.Name = 'Owner'
        inner join dbo.ObjectAttribute as nv on nv.ObjectId = o.Id
        inner join dbo.Attribute as nva on nva.Id = nv.AttributeId and nva.Name = 'Name'
GO

create trigger utr_ObjectAttribute on ObjectAttribute
after update, delete, insert
as
begin
    declare @temp_objects table (Id int primary key)

    insert into @temp_objects
    select distinct ObjectId from inserted
    union
    select distinct ObjectId from deleted

    update ObjectAttributePivot set
        OwnerValue = vo.OwnerValue,
        NameValue = vo.NameValue
    from ObjectAttributePivot as o
        inner join vw_ObjectAttributePivot as vo on vo.Id = o.Id
    where
        o.Id in (select t.Id from @temp_objects as t)

    insert into ObjectAttributePivot (Id, OwnerValue, NameValue)
    select vo.Id, vo.OwnerValue, vo.NameValue
    from vw_ObjectAttributePivot as vo
    where
        vo.Id in (select t.Id from @temp_objects as t) and
        vo.Id not in (select t.Id from ObjectAttributePivot as t)

    delete ObjectAttributePivot
    from ObjectAttributePivot as o
    where
        o.Id in (select t.Id from @temp_objects as t) and
        o.Id not in (select t.Id from vw_ObjectAttributePivot as t)
end
GO

その後、独自のビューを作成できます。

create view vObject_Uniqueness
with schemabinding
as
    select
        o.OrgId,
        oap.OwnerValue,
        oap.NameValue
    from dbo.ObjectAttributePivot as oap
        inner join dbo.Object as o on o.Id = oap.Id
GO

CREATE UNIQUE CLUSTERED INDEX IUX_vObject_Uniqueness
ON vObject_Uniqueness (OrgId, OwnerValue, NameValue)
GO

sql fiddle demo

于 2013-09-30T18:18:04.857 に答える
1

私たちがここで抱えている基本的な問題は、あなたが目指している独自性のタイプを強化することであり、「それが違反になるのはいつですか?」という質問に答えようとすることです。このことを考慮:

  • データベースには、例で参照する最初の 2 つのオブジェクト (Org1 と Org2) がロードされます。
  • ここで、ObjectAttribute(AttributeId, ObjectId, Value) VALUES (1, 3, 'Jeremy Pridemore') を INSERT します。

これは違反ですか?あなたが私に言ったことに基づいて、私は「いいえ」と言うでしょう: INSERT ObjectAttribute(AttributeId, ObjectId, Value) VALUES (2, 3, 'Cantalope') に進むことができます。したがって、次のステートメントがどうなるかがわからない限り、現在のステートメントが有効かどうかはわかりません。しかし、2 番目のステートメントを発行するという保証はありません。確かに、最初のステートメントが OK かどうかを決める時点で、それがどうなるかを知る方法はありません。

では、私が話しているタイプの独立した挿入を禁止する必要があります-「所有者」エントリが挿入されますが、対応する「名前」エントリは同時に挿入されませんか? 私にとって、それはあなたがここでやろうとしていることに対する唯一の実行可能なアプローチであり、そのタイプの制約を強制する唯一の方法はトリガーを使用することです.

このようなもの:

DROP TRIGGER TR_ObjectAttribute_Insert
GO
CREATE TRIGGER TR_ObjectAttribute_Insert ON dbo.ObjectAttribute
AFTER INSERT
AS
    DECLARE @objectsUnderConsideration TABLE (ObjectId INT PRIMARY KEY);
    INSERT INTO @objectsUnderConsideration(ObjectId)
    SELECT DISTINCT ObjectId FROM inserted; 

    DECLARE @expectedObjectAttributeEntries TABLE (ObjectId INT, AttributeId INT);
    INSERT INTO @expectedObjectAttributeEntries(ObjectId, AttributeId)
    SELECT o.ObjectId, a.Id AS AttributeId
    FROM @objectsUnderConsideration o
        CROSS JOIN Attribute a; -- cartisean join, objects * attributes

    DECLARE @totalNumberOfAttributes INT = (SELECT COUNT(1) FROM Attribute);

    -- ensure we got what we expect to get
    DECLARE @expectedCount INT, @actualCount INT;
    SET @expectedCount = (SELECT COUNT(*) FROM @expectedObjectAttributeEntries);
    SET @actualCount = (
        SELECT COUNT(*) 
        FROM @expectedObjectAttributeEntries e 
            INNER JOIN inserted i ON e.AttributeId = i.AttributeId AND e.ObjectId = i.ObjectId
    );  -- if an attribute is missing, we'll have too few; if an object is being entered twice, we'll have too many

    IF @expectedCount < @actualCount
    BEGIN 
        RAISERROR ('Invalid insertion: incomplete set of attribute values', 16, 1);
        ROLLBACK TRANSACTION;
        RETURN 
    END
    ELSE IF @expectedCount > @actualCount
    BEGIN 
        RAISERROR ('Invalid insertion: multiple entries for same object', 16, 1);
        ROLLBACK TRANSACTION;
        RETURN 
    END

    -- passed the check that we have all the necessary attributes; now check for duplicates
    ELSE 
    BEGIN
        -- for each object, count exact duplicate preexisting entries; reject if every attribute is a dup
        DECLARE @duplicateAttributeCount TABLE (ObjectId INT, DupCount INT);
        INSERT INTO @duplicateAttributeCount(ObjectId, DupCount)
        SELECT o.ObjectId, (
            SELECT COUNT(1)
            FROM inserted i
                INNER JOIN ObjectAttribute oa
                     ON i.AttributeId = oa.AttributeId
                    AND i.ObjectId = oa.ObjectId
                    AND i.Value = oa.Value
                    AND i.Id <> oa.Id
            WHERE oa.ObjectId = o.ObjectId
        )
        FROM @objectsUnderConsideration o

        IF EXISTS ( 
            SELECT 1
            FROM @duplicateAttributeCount d
            WHERE d.DupCount = @totalNumberOfAttributes
        )
        BEGIN
            RAISERROR ('Invalid insertion: duplicates pre-existing entry', 16, 1);
            ROLLBACK TRANSACTION;
            RETURN 
        END
    END
GO

上記はテストされていません。考えてみると、Object に参加して、ObjectId ではなく OrgId でテストを整理する必要があるかもしれません。UPDATE と DELETE にも同等のトリガーが必要です。しかし、うまくいけば、これで少なくとも開始するのに十分です。

于 2013-09-28T02:09:34.957 に答える
-3

どの Sql Sever エディションを使用するかを検討する必要があります。これには、インデックス付きビューに制限があります。参照: http://msdn.microsoft.com/en-us/library/cc645993(SQL.110).aspx#RDBMS_mgmt インデックス付きビューの直接クエリを参照してください。次の手順は、インデックス付きビューを作成するために必要であり、インデックス付きビューを正常に実装するために重要です。

1 - ビューで参照されるすべての既存のテーブルに対して SET オプションが正しいことを確認します。

2-新しいテーブルとビューを作成する前に、セッションの SET オプションが正しく設定されていることを確認します。

3-ビュー定義が決定論的であることを確認します。

4-WITH SCHEMABINDING オプションを使用してビューを作成します。

5-ビューに一意のクラスター化インデックスを作成します。

インデックス付きビューに必要な SET オプション クエリの実行時に異なる SET オプションがアクティブになっていると、データベース エンジンで同じ式を評価しても異なる結果が生成される可能性があります。たとえば、SET オプション CONCAT_NULL_YIELDS_NULL が ON に設定された後、式 'abc ' + NULL は値 NULL を返します。ただし、CONCAT_NULL_YIEDS_NULL を OFF に設定すると、同じ式で「abc」が生成されます。

于 2013-09-27T06:33:08.130 に答える