Como identificar um relacionamento individual (um para?)
-
22-09-2019 - |
Pergunta
O domínio do problema http://www.freeimagehosting.net/uploads/6e7aa06096.png
Então, aqui está o problema.
Em um exemplo concreto, imagine que um simples dTO - fk_jointable_parenttable seria renderizado como uma coleção de articulares no objeto ParentTable, enquanto fk_basetable_ineritedtable seria feita como um objeto herdado no objeto Basetable (a herdeira foi uma má escolha, por exemplo, eu sei, eu sei, mas não voltar).
O melhor que posso criar é um para muitos, o mesmo que fk_jointable_parenttable. Eu tentei muitas abordagens, incluindo (tentando) comparar as chaves e estou ficando aquém.
Aqui está o script. O problema é, usando as visualizações info_schema ou sys, identifique fk_jointable_parenttable e fk_jointable_child como um para muitos e fk_basetable_ineritedtable como um a um/nenhum.
O decredentado é capaz de diferenciar FK_BASETABLE_INHERITEDTABLE FROM FK_JONTABLE_PARENTABLE
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]
Solução
Editar: Erro pequeno corrigido na consulta Key estrangeira que não afeta a resposta, mas retorna a tabela exclusiva errada
A conclusão original que cheguei, mas nunca fui capaz de executar adequadamente é:
Se as colunas do lado base da restrição se encaixam em qualquer uma das restrições exclusivas da tabela lateral base e a contagem de colunas for a mesma, é um para um a um/nenhum.
O mesmo poderia ser dito se todas as colunas da FK se encaixam em uma das restrições únicas que possuem mais colunas se todas essas colunas compreendem mais uma FK, mas isso está fora do escopo da questão em questão.
Foi uma questão de execução da minha parte.
Encontrei uma solução que produz resultados corretos, mas, pois não sou guru do SQL, temo que seja bastante agitado. Talvez eu faça isso como outra pergunta para revisão.
Enfim - este script identificará todos os 1:? Relacionamentos no banco de dados.
/*
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
Para dar uma olhada nos resultados em um teste mais elaborado db, consulte TSQL: Identifique um 1:? relação