2010-01-26 5 views
2

The Problem Domain http://www.freeimagehosting.net/uploads/6e7aa06096.pngComment identifier un à un (biunivoque?) Relation à l'aide des vues INFORMATION_SCHEMA ou sys dans Sql Server

Voici donc le problème .. Utilisation TSQL et INFORMATION_SCHEMA vues ou sys, comment puis-je identifier une relation 1: 0-1, telle que FK_BaseTable_InheritedTable?

Dans un exemple concret, imaginez simple DTO - FK_JoinTable_ParentTable serait rendu comme une collection de joinTable sur l'objet ParentTable, alors que FK_BaseTable_InheritedTable serait soit rendu sous la forme d'un objet InheritedTable sur l'objet BaseTable (héritage était un mauvais choix, par exemple , Je sais, mais ne pas y retourner). Le meilleur que je peux trouver est un-à-plusieurs, même que FK_JoinTable_ParentTable. J'ai essayé beaucoup d'approches, y compris (en essayant de) comparer les clés et je suis à court.

Voici le script. Le problème est de savoir, en utilisant les vues INFO_SCHEMA ou sys, d'identifier FK_JoinTable_ParentTable et FK_JoinTable_Child comme un-à-plusieurs et FK_BaseTable_InheritedTable comme one-to-one/none.

Le tournesol est d'être capable de faire la différence FK_BaseTable_InheritedTable de FK_JoinTable_ParentTable

CREATE TABLE [dbo].[Child](
[ChildId] [int] NOT NULL, 
CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED 
(
[ChildId] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

CREATE TABLE [dbo].[ParentTable](
[ParentId] [int] NOT NULL, 
CONSTRAINT [PK_ParentTable] PRIMARY KEY CLUSTERED 
(
[ParentId] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 


CREATE TABLE [dbo].[JoinTable](
[PId] [int] NOT NULL, 
[CId] [int] NOT NULL, 
CONSTRAINT [PK_JoinTable] PRIMARY KEY CLUSTERED 
(
[PId] ASC, 
[CId] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

CREATE TABLE [dbo].[InheritedTable](
[InheritedId] [int] NOT NULL, 
CONSTRAINT [PK_InheritedTable] PRIMARY KEY CLUSTERED 
(
[InheritedId] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

CREATE TABLE [dbo].[BaseTable](
[BaseId] [int] NOT NULL, 
CONSTRAINT [PK_BaseTable] PRIMARY KEY CLUSTERED 
(
[BaseId] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 

ALTER TABLE [dbo].[JoinTable] WITH CHECK ADD CONSTRAINT [FK_JoinTable_Child] FOREIGN KEY([CId]) 
REFERENCES [dbo].[Child] ([ChildId]) 

ALTER TABLE [dbo].[JoinTable] CHECK CONSTRAINT [FK_JoinTable_Child] 

ALTER TABLE [dbo].[JoinTable] WITH CHECK ADD CONSTRAINT [FK_JoinTable_ParentTable] FOREIGN KEY([PId]) 
REFERENCES [dbo].[ParentTable] ([ParentId]) 

ALTER TABLE [dbo].[JoinTable] CHECK CONSTRAINT [FK_JoinTable_ParentTable] 

ALTER TABLE [dbo].[BaseTable] WITH CHECK ADD CONSTRAINT [FK_BaseTable_InheritedTable] FOREIGN KEY([BaseId]) 
REFERENCES [dbo].[InheritedTable] ([InheritedId]) 

ALTER TABLE [dbo].[BaseTable] CHECK CONSTRAINT [FK_BaseTable_InheritedTable] 
+0

plus élaborée pourquoi est-ce que je vois non des questions idiotes obtenir tant de réponses instantanées et une question sérieuse est tout simplement ignorée? hmmmm –

Répondre

0

EDIT: fixed petite faute de frappe dans la requête clé étrangère qui ne touche pas la réponse, mais renvoie la mauvaise table unique,

La conclusion originale Je suis venu mais n'a jamais été en mesure d'exécuter correctement est:

Si les colonnes latérales de base de la Contraindre t entrer dans l'une des contraintes uniques de la table de base ET le nombre de colonnes est le même que c'est un un-à-un/aucun.

La même chose pourrait être dit que si l'ensemble des colonnes FK correspondre à l'une des contraintes uniques qui a plus de colonnes si toutes ces colonnes comprennent encore une autre FK, mais cela est en dehors de la portée de la question à portée de main.

C'était un problème d'exécution de ma part.

J'ai mis au point une solution qui produit des résultats corrects mais en ce que je ne suis pas un gourou SQL, je crains que ce soit plutôt kludgy. Peut-être que je vais le mettre en place comme une autre question à examiner.

Quoi qu'il en soit - ce script va identifier tous les 1 :? relations dans la DB.

/* 
    Will identify immediate 1:? fk relationships 

*/ 
-- TODO: puzzle: work out the set-based equivalent 

SET NOCOUNT ON 




BEGIN -- Get a table full of PK and UQ columns 
    DECLARE @unique_keys TABLE 
     (
      -- contains PK and UQ indexes 
      [schema_name] NVARCHAR(128), 
      table_name NVARCHAR(128), 
      index_name NVARCHAR(128), 
      column_id INT, 
      column_name NVARCHAR(128), 
      is_primary_key BIT, 
      is_unique_constraint BIT, 
      is_unique BIT 
     ) 
    INSERT INTO @unique_keys 
      (
       [schema_name], 
       table_name, 
       index_name, 
       column_id, 
       column_name, 
       is_primary_key, 
       is_unique_constraint, 
       is_unique 
      ) 
     -- selects PK and UQ indexes 
      SELECT S.name AS [schema_name], 
        T.name AS table_name, 
        IX.name AS index_name, 
        IC.column_id, 
        C.name AS column_name, 
        IX.is_primary_key, 
        IX.is_unique_constraint, 
        IX.is_unique 
      FROM sys.tables AS T 
        INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id 
        INNER JOIN sys.indexes AS IX ON T.object_id = IX.object_id 
        INNER JOIN sys.index_columns AS IC ON IX.object_id = IC.object_id 
                  AND IX.index_id = IC.index_id 
        INNER JOIN sys.columns AS C ON IC.column_id = C.column_id 
                AND IC.object_id = C.object_id 
      WHERE (IX.is_unique = 1) 
        AND (T.name <> 'sysdiagrams') 
        AND IX.is_unique = 1 
      ORDER BY schema_name, 
        table_name, 
        index_name, 
        C.column_id 
END 



BEGIN -- Get a table full of FK columns 

    DECLARE @foreign_key_columns TABLE 
     (
      constraint_name NVARCHAR(128), 
      base_schema_name NVARCHAR(128), 
      base_table_name NVARCHAR(128), 
      base_column_id INT, 
      base_column_name NVARCHAR(128), 
      unique_schema_name NVARCHAR(128), 
      unique_table_name NVARCHAR(128), 
      unique_column_id INT, 
      unique_column_name NVARCHAR(128) 
     ) 
    INSERT INTO @foreign_key_columns 
      (
       constraint_name, 
       base_schema_name, 
       base_table_name, 
       base_column_id, 
       base_column_name, 
       unique_schema_name, 
       unique_table_name, 
       unique_column_id, 
       unique_column_name 
      ) 
      SELECT FK.name AS constraint_name, 
        S.name AS base_schema_name, 
        T.name AS base_table_name, 
        C.column_id AS base_column_id, 
        C.name AS base_column_name, 
        US.name AS unique_schema_name, 
        UT.name AS unique_table_name, 
        UC.column_id AS unique_column_id, 
        UC.name AS unique_column_name 
      FROM sys.tables AS T 
        INNER JOIN sys.schemas AS S ON T.schema_id = S.schema_id 
        INNER JOIN sys.foreign_keys AS FK ON T.object_id = FK.parent_object_id 
        INNER JOIN sys.foreign_key_columns AS FKC ON FK.object_id = FKC.constraint_object_id 
        INNER JOIN sys.columns AS C ON FKC.parent_object_id = C.object_id 
                AND FKC.parent_column_id = C.column_id 
        INNER JOIN sys.columns AS UC ON FKC.referenced_object_id = UC.object_id 
                AND FKC.referenced_column_id = UC.column_id 
        INNER JOIN sys.tables AS UT ON FKC.referenced_object_id = UT.object_id 
        INNER JOIN sys.schemas AS US ON UT.schema_id = US.schema_id 
      WHERE (T.name <> 'sysdiagrams') 
      ORDER BY base_schema_name, 
        base_table_name 
END 


DECLARE @constraint_name NVARCHAR(128), 
    @base_schema_name NVARCHAR(128), 
    @base_table_name NVARCHAR(128), 
    @unique_schema_name NVARCHAR(128), 
    @unique_table_name NVARCHAR(128) 

-- The foreign key side of the constraint is always singular, we need to check from the perspective 
-- of the unique side of the constraint. 

-- for each FK constraint in DB 
DECLARE tmpC CURSOR READ_ONLY 
    FOR SELECT DISTINCT 
       constraint_name, 
       base_schema_name, 
       base_table_name, 
       unique_schema_name, 
       unique_table_name 
     FROM @foreign_key_columns 

OPEN tmpC 
FETCH NEXT FROM tmpC INTO @constraint_name, @base_schema_name, @base_table_name, @unique_schema_name, @unique_table_name 
WHILE @@FETCH_STATUS = 0 
    BEGIN 
     -- get the columns in the base side of the FK constraint 
     DECLARE @fkc TABLE 
      (
       column_name NVARCHAR(128) 
      ) 
     DELETE FROM @fkc 

     INSERT INTO @fkc (column_name) 
       SELECT base_column_name 
       FROM @foreign_key_columns 
       WHERE constraint_name = @constraint_name 

     -- check for one to one/none 
     -- If the base side columns of the constraint fit into any one of the base side tables unique constraints 
     -- AND the column count is the same then we have a one-to-one/none and should be realized as a singular 
     -- object reference 

     -- I realize that if the base side unique constraint has more columns than the unique side unique constraint 
     -- AND all of those columns DO represent a 1:? that would actually qualify but it seems like an edge case and 
     -- beyond the scope of this question. 

     DECLARE @uk_schema_name NVARCHAR(128), 
      @uk_table_name NVARCHAR(128), 
      @uk_index_name NVARCHAR(128), 
      @is_may_have_a BIT 
     SET @is_may_have_a = 0 

     -- have to open another cursor over the unique keys of the base table - i want 
     -- a distinct list of unique constraints for the base table 

     DECLARE cKey CURSOR READ_ONLY 
      FOR SELECT DISTINCT 
         [schema_name], 
         table_name, 
         index_name 
       FROM @unique_keys 
       WHERE [schema_name] = @base_schema_name 
         AND table_name = @base_table_name 

     OPEN cKey 
     FETCH NEXT FROM cKey INTO @uk_schema_name, @uk_table_name, @uk_index_name 
     WHILE @@FETCH_STATUS = 0 
      BEGIN 

       -- get the unique constraint columns 
       DECLARE @pkc TABLE 
        (
         column_name NVARCHAR(128) 
        ) 
       DELETE FROM @pkc 

       INSERT INTO @pkc (column_name) 
         SELECT column_name 
         FROM @unique_keys 
         WHERE [schema_name] = @uk_schema_name 
           AND table_name = @uk_table_name 
           AND index_name = @uk_index_name 

       -- if count is same and columns are same 
       DECLARE @count1 INT, @count2 INT 
       SELECT @count1 = COUNT(*) FROM @fkc 
       SELECT @count2 = COUNT(*) FROM @pkc 

       IF @count1 = @count2 
        BEGIN 
         -- select all from both on name and exclude mismatches 
         SELECT @count1 = COUNT(*) 
         FROM @fkc F 
           FULL OUTER JOIN @pkc P ON f.column_name = p.column_name 
         WHERE NOT p.column_name IS NULL AND NOT f.column_name IS NULL 

         IF @count1 = @count2 
          BEGIN 
           -- the base side of the fk constraint corresponds exactly to 
           -- at least on unique constraint making it effectively 1:? 
           SET @is_may_have_a = 1 
           BREAK 
          END 
        END 
       FETCH NEXT FROM cKey INTO @uk_schema_name, @uk_table_name, @uk_index_name 
      END 

     CLOSE cKey 
     DEALLOCATE cKey 

     IF @is_may_have_a = 1 
      PRINT 'for ' + @unique_schema_name + '.' + @unique_table_name + ' constraint ' + + @constraint_name + ' is 1:? ' 

     FETCH NEXT FROM tmpC INTO @constraint_name, @base_schema_name, @base_table_name, @unique_schema_name, @unique_table_name 
    END 

CLOSE tmpC 
DEALLOCATE tmpC 

Pour consulter les résultats sur un test db voir TSQL: Identify a 1:? relationship

Questions connexes