tabelas de auto-referência, boas ou más? [fechadas]

37

Representando localizações geográficas em um aplicativo, o design do modelo de dados subjacente sugere duas opções claras (ou talvez mais?).

Uma tabela com uma coluna parent_id auto-referente uk - london (london parent id = UK id)

ou duas tabelas, com um relacionamento de um para muitos, usando uma chave estrangeira.

Minha preferência é por uma tabela de auto-referência, pois ela facilmente se estende a tantas sub-regiões quanto necessário.

Em geral, as pessoas se desviam das tabelas de auto-referência ou estão OK?

NimChimpsky
fonte

Respostas:

40

Nada de errado com as tabelas de auto-referência.

É o padrão de design de banco de dados comum para hierarquias profundamente aninhadas (infinito?).

Oded
fonte
@NimChimpsky - Como o conceito de recursão, essa ideia é difícil para alguns.
Oded
2
(Pelo menos) a Oracle ainda possui um contrato SQL especial, a cláusula "START WITH - CONNECT BY", para lidar com tabelas de auto-referência.
user281377
11
@ user281377 - E o SQL Server introduziu o hierarchyidtipo.
Oded
usign hibernação por isso vai ter o seu próprio molho especial
Nim Chimpsky
4
@NimChimpsky - Considere considerar o "Nested Set Model" como uma alternativa à coluna "parent_id" - ele fornece a mesma funcionalidade, mas melhor desempenho e consultas mais fáceis para extrair as hierarquias. en.wikipedia.org/wiki/Nested_set_model A série de livros de Joe Celko, "SQL For Smarties", possui ótimos exemplos de SQL em relação a conjuntos aninhados.
perfil completo de Keith Palmer Jr.
7

Necromante.
A resposta correta é: depende de qual mecanismo de banco de dados e de qual ferramenta de gerenciamento.

Vamos dar um exemplo:
temos uma tabela de relatórios,
e um relatório pode ter um pai (ponto de menu, como categoria),
e esse pai pode ter um pai (por exemplo, centro de lucro)
e assim por diante ad infinitum.

O exemplo mais simples de um relacionamento recursivo padrão, como em qualquer entidade / hierarquia auto-referente.

A tabela resultante do SQL-Server é:

IF  EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'dbo.FK_T_FMS_Reports_T_FMS_Reports') AND parent_object_id = OBJECT_ID(N'dbo.T_FMS_Reports'))
ALTER TABLE dbo.T_FMS_Reports DROP CONSTRAINT FK_T_FMS_Reports_T_FMS_Reports
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'dbo.T_FMS_Reports') AND type in (N'U'))
DROP TABLE dbo.T_FMS_Reports 
GO



CREATE TABLE dbo.T_FMS_Reports 
( 
     RE_UID uniqueidentifier NOT NULL 
    ,RE_RE_UID uniqueidentifier NULL 
    ,RE_Text nvarchar(255) NULL 
    ,RE_Link nvarchar(400) NULL 
    ,RE_Sort int NOT NULL 
    ,RE_Status int NOT NULL 
    ,PRIMARY KEY CLUSTERED ( RE_UID ) 
); 

GO

ALTER TABLE dbo.T_FMS_Reports  WITH CHECK ADD  CONSTRAINT FK_T_FMS_Reports_T_FMS_Reports FOREIGN KEY(RE_RE_UID) 
REFERENCES dbo.T_FMS_Reports (RE_UID) 
-- ON DELETE CASCADE -- here, MS-SQL has a problem 
GO

ALTER TABLE dbo.T_FMS_Reports CHECK CONSTRAINT FK_T_FMS_Reports_T_FMS_Reports 
GO

Mas você tem um problema:
quando você precisa excluir um ponto de menu com todos os seus submenus, NÃO PODE definir a cascata de exclusão, porque o Microsoft SQL-Server não suporta exclusões em cascata recursivas (por outro lado, o PostGreSQL sim [mas somente se o o gráfico não é cíclico], enquanto o MySQL não gosta desse tipo de estrutura de tabela, porque não suporta CTEs recursivos).

Então você meio que explode integridade / funcionalidade de exclusão, tornando obrigatório implementar essa funcionalidade em seu próprio código ou em um procedimento armazenado (se o seu RDBMS suportar procedimentos armazenados).

Isso sem dúvida explodirá qualquer tipo de importação / exportação de dados dinâmicos totalmente automática, porque você não pode simplesmente executar uma instrução de exclusão para todas as tabelas de acordo com relacionamentos de chave externa (sem referência própria), nem fazer uma seleção simples * e crie uma inserção para cada linha em uma ordem arbitrária.

Por exemplo, quando você cria um script INSERT usando SSMS, o SSMS não obtém a chave estrangeira e, de fato, cria instruções de inserção que inserem entradas com dependências, antes de inserir o pai da dependência, que falhará com um erro , porque a chave estrangeira está no lugar.

No entanto, em sistemas de gerenciamento de banco de dados adequados (como o PostgreSQL), com ferramentas adequadas, isso não deve ser um problema. Apenas entenda que, apenas porque você paga muito pelo seu RDBMS (estou olhando para você, Microsoft; Oracle =?) E / ou seu cinto de ferramentas, isso não significa que está programado corretamente. E o OpenSource (por exemplo, MySQL) também não o torna imune a essas minúcias maravilhosas.

O diabo está nos detalhes, como diz o velho ditado.

Agora, não que você não possa solucionar esses problemas, mas eu realmente não recomendaria se o seu sistema fosse complexo (por exemplo, mais de 200 tabelas).
Além disso, em um ambiente comercial habitual (como retratado por Dilbert), você simplesmente não terá esse tempo.

Uma abordagem muito melhor, embora mais difícil, seria uma tabela de fechamento.
Isso teria o bônus adicional de que ele também funciona no MySQL.
Depois de implementar a funcionalidade de fechamento uma vez, você a trabalhará em locais adicionais em quase nenhum momento.

Dilema
fonte
3
+1 em chamar a atenção das tabelas de fechamento (pelo menos a terminologia, já conhecia o conceito). Aqui está um bom artigo para outros interessados. coderwall.com/p/lixing/closure-tables-for-browsing-trees-in-sql
Outfast Source