É sempre bom usar listas em um banco de dados relacional?

88

Eu tenho tentado criar um banco de dados com um conceito de projeto e me deparo com o que parece ser um assunto muito debatido. Eu li alguns artigos e algumas respostas do Stack Overflow afirmam que nunca (ou quase nunca) é permitido armazenar uma lista de IDs ou similares em um campo - todos os dados devem ser relacionais, etc.

O problema que estou enfrentando, porém, é que estou tentando fazer um atribuidor de tarefas. As pessoas criarão tarefas, as designarão para várias pessoas e serão salvas no banco de dados.

É claro que, se eu salvar essas tarefas individualmente em "Person", terei que ter dúzias de colunas "TaskID" fictícias e gerenciá-las em micro, porque pode haver de 0 a 100 tarefas designadas a uma pessoa, digamos.

Então, novamente, se eu salvar as tarefas em uma tabela de "Tarefas", terei que ter dúzias de colunas "PersonID" fictícias e gerenciá-las em micro - o mesmo problema de antes.

Para um problema como este, não há problema em salvar uma lista de IDs usando uma forma ou outra, ou simplesmente não estou pensando em outra maneira de conseguir isso sem quebrar princípios?

    
por linus72982 14.11.2018 / 05:25
fonte

9 respostas

242

A palavra-chave e o conceito-chave que você precisa investigar são banco de dados normalização .

O que você faria, em vez de adicionar informações sobre as atribuições à pessoa ou às tabelas de tarefas, é adicionar uma nova tabela a essa informação de atribuição, com relacionamentos relevantes.

Exemplo, você tem as seguintes tabelas:

Pessoas:

+−−−−+−−−−−−−−−−−+
| ID |    Name   |
+====+===========+
|  1 |  Alfred   |
|  2 |  Jebediah |
|  3 |  Jacob    |
|  4 |  Ezekiel  |
+−−−−+−−−−−−−−−−−+

Tarefas:

+−−−−+−−−−−−−−−−−−−−−−−−−−+
| ID |        Name        |
+====+====================+
|  1 |  Feed the Chickens |
|  2 |  Plow              |
|  3 |  Milking Cows      |
|  4 |  Raise a barn      |
+−−−−+−−−−−−−−−−−−−−−−−−−−+

Você criaria uma terceira tabela com Atribuições. Esta tabela modelaria a relação entre as pessoas e as tarefas:

+−−−−+−−−−−−−−−−−+−−−−−−−−−+
| ID |  PersonId |  TaskId |
+====+===========+=========+
|  1 |         1 |       3 |
|  2 |         3 |       2 |
|  3 |         2 |       1 |
|  4 |         1 |       4 |
+−−−−+−−−−−−−−−−−+−−−−−−−−−+

Em seguida, teríamos uma restrição de Chave Estrangeira, de modo que o banco de dados imporia que os IDs de PersonId e TaskIds tivessem que ser IDs válidos para esses itens estrangeiros. Para a primeira linha, podemos ver PersonId is 1 , então Alfred é atribuído a TaskId 3 , Vacas leiteiras .

O que você deve conseguir ver aqui é que você pode ter o mínimo ou o número de tarefas por tarefa ou por pessoa que quiser. Neste exemplo, Ezekiel não é atribuído a nenhuma tarefa, e Alfred é atribuído 2. Se você tiver uma tarefa com 100 pessoas, fazer SELECT PersonId from Assignments WHERE TaskId=<whatever>; produzirá 100 linhas, com uma variedade de diferentes pessoas designadas. Você pode WHERE no PersonId para encontrar todas as tarefas atribuídas a essa pessoa.

Se você quiser retornar as consultas substituindo os IDs pelos Nomes e pelas tarefas, você poderá aprender a juntar as tabelas.

    
por 14.11.2018 / 05:47
fonte
35

Você está fazendo duas perguntas aqui.

Primeiro, você pergunta se está ok armazenar listas serializadas em uma coluna. Sim, está bem. Se o seu projeto exige isso. Um exemplo pode ser ingredientes de produtos para uma página de catálogo, onde você não deseja rastrear cada ingrediente individualmente.

Infelizmente, sua segunda pergunta descreve um cenário em que você deve optar por uma abordagem mais relacional. Você precisará de 3 mesas. Uma para as pessoas, uma para as tarefas e outra que mantém a lista de qual tarefa é atribuída a quais pessoas. Essa última seria uma combinação vertical, uma linha por pessoa / tarefa, com colunas para sua chave primária, id de tarefa e identificação de pessoa.

    
por 14.11.2018 / 05:48
fonte
21

O que você está descrevendo é conhecido como um relacionamento "muitos para muitos", no seu caso entre Person e Task . Ele é tipicamente implementado usando uma terceira tabela, às vezes chamada de tabela "link" ou "referência cruzada". Por exemplo:

create table person (
    person_id integer primary key,
    ...
);

create table task (
    task_id integer primary key,
    ...
);

create table person_task_xref (
    person_id integer not null,
    task_id integer not null,
    primary key (person_id, task_id),
    foreign key (person_id) references person (person_id),
    foreign key (task_id) references task (task_id)
);
    
por 14.11.2018 / 05:46
fonte
12

... it's never (or almost never) okay to store a list of IDs or the like in a field

A única vez que você pode armazenar mais de um item de dados em um único campo é quando esse campo é somente usado como uma única entidade e é < em> nunca considerado como sendo constituído por esses elementos menores. Um exemplo pode ser uma imagem armazenada em um campo BLOB. É composto de muitos e pequenos elementos (bytes), mas estes que significam nada para o banco de dados e só podem ser usados todos juntos (e ficam bonitos para um usuário final).

Já que uma "lista" é, por definição, composta de elementos menores (itens), este não é o caso aqui e você deve normalizar os dados.

... if I save these tasks individually in "Person", I'll have to have dozens of dummy "TaskID" columns ...

Não. Você terá algumas linhas em uma Tabela de Interseções (também conhecida como Entidade Fraca) entre Pessoa e Tarefa. Bancos de dados são realmente bons em trabalhar com muitas linhas; eles são realmente muito ruins em trabalhar com muitas colunas [repetidas].

Um bom exemplo claro dado por whatsisname.

    
por 14.11.2018 / 13:02
fonte
4

Pode ser legítimo em determinados campos pré-calculados.

Se algumas de suas consultas forem caras e você decidir ir com campos pré-calculados atualizados automaticamente usando acionadores de banco de dados, pode ser legítimo manter as listas dentro de uma coluna.

Por exemplo, na interface do usuário, você deseja mostrar essa lista usando a visualização de grade, onde cada linha pode abrir detalhes completos (com listas completas) após clicar duas vezes:

REGISTERED USER LIST
+------------------+----------------------------------------------------+
|Name              |Top 3 most visited tags                             |
+==================+====================================================+
|Peter             |Design, Fitness, Gifts                              |
+------------------+----------------------------------------------------+
|Lucy              |Fashion, Gifts, Lifestyle                           |
+------------------+----------------------------------------------------+

Você está mantendo a segunda coluna atualizada por acionador quando o cliente visita um novo artigo ou por uma tarefa agendada.

Você pode disponibilizar esse campo mesmo para pesquisas (como texto normal).

Para tais casos, manter listas é legítimo. Você só precisa considerar o caso de exceder o comprimento máximo do campo.

Além disso, se você estiver usando o Microsoft Access, será oferecido campos de valores múltiplos são outro caso de uso especial. Eles lidam com suas listas em um campo automaticamente.

Mas você sempre pode voltar ao formato normalizado padrão mostrado em outras respostas.

Resumo: As formas normais de banco de dados são um modelo teórico necessário para entender aspectos importantes da modelagem de dados. Mas é claro que a normalização não leva em conta o desempenho ou outro custo de recuperar os dados. Está fora do escopo desse modelo teórico. Mas o armazenamento de listas ou outros duplicados pré-calculados (e controlados) é frequentemente exigido pela implementação prática.

Em vista do exposto acima, na implementação prática, nós preferiríamos que a consulta confiasse na forma normal perfeita e executasse 20 segundos ou consultas equivalentes confiando em valores pré-calculados que levam 0,08 s? Ninguém gosta que seu produto de software seja acusado de lentidão.

    
por 15.11.2018 / 01:21
fonte
1

Você está pegando o que deveria ser outra mesa, girando-a 90 graus e colocando-a em outra mesa.

É como ter uma tabela de pedidos em que você tem itemProdcode1, itemQuantity1, itemPrice1 ... itemProdcode37, itemQuantity37, itemPrice37. Além de ser difícil de lidar programaticamente, você pode garantir que amanhã alguém vai querer encomendar 38 itens.

Eu só faria do seu jeito se a "lista" não fosse realmente uma lista, ou seja, onde ela fica como um todo e cada item de linha individual não se refere a alguma entidade clara e independente. Nesse caso, basta preencher tudo em algum tipo de dados que seja grande o suficiente.

Assim, uma ordem é uma lista, uma lista de materiais é uma lista (ou uma lista de listas, o que seria ainda mais um pesadelo para implementar "lateralmente"). Mas uma nota / comentário e um poema não são.

    
por 15.11.2018 / 00:06
fonte
0

Dadas duas tabelas; vamos chamá-los de Pessoa e Tarefa, cada um com seu próprio ID (PersonID, TaskID) ... a idéia básica é criar uma terceira tabela para vinculá-los. Vamos chamar essa tabela de PersonToTask. No mínimo, ele deve ter seu próprio ID, bem como os outros dois Então, quando se trata de atribuir alguém a uma tarefa; você não precisará mais ATUALIZAR a tabela Person, basta inserir uma nova linha no PersonToTaskTable. E a manutenção se torna mais fácil - a necessidade de excluir uma tarefa acaba se tornando uma DELETE baseada em TaskID, não mais atualizando a tabela Person e sua análise associada

CREATE TABLE dbo.PersonToTask (
    pttID INT IDENTITY(1,1) NOT NULL,
    PersonID INT NULL,
    TaskID   INT NULL
)

CREATE PROCEDURE dbo.Task_Assigned (@PersonID INT, @TaskID INT)
AS
BEGIN
    INSERT PersonToTask (PersonID, TaskID)
    VALUES (@PersonID, @TaskID)
END

CREATE PROCEDURE dbo.Task_Deleted (@TaskID INT)
AS
BEGIN
    DELETE PersonToTask  WHERE TaskID = @TaskID
    DELETE Task          WHERE TaskID = @TaskID
END

Que tal um relatório simples ou quem é atribuído a uma tarefa?

CREATE PROCEDURE dbo.Task_CurrentAssigned (@TaskID INT)
AS
BEGIN
    SELECT PersonName
    FROM   dbo.Person
    WHERE  PersonID IN (SELECT PersonID FROM dbo.PersonToTask WHERE TaskID = @TaskID)
END

Você pode fazer muito mais; um TimeReport poderia ser feito se você adicionasse campos DateTime para TaskAssigned e TaskCompleted. Tudo depende de você

    
por 14.11.2018 / 20:26
fonte
0

Pode funcionar se você tiver chaves primárias legíveis e quiser uma lista de tarefas sem ter que lidar com a natureza vertical de uma estrutura de tabela. ou seja, muito mais fácil de ler a primeira tabela.

------------------------  
Employee Name | Task 
Jack          |  1,2,5
Jill          |  4,6,7
------------------------

------------------------  
Employee Name | Task 
Jack          |  1
Jack          |  2
Jack          |  5
Jill          |  4
Jill          |  6
Jill          |  7
------------------------
A pergunta seria: a lista de tarefas deve ser armazenada ou gerada sob demanda, o que dependeria em grande parte de requisitos como: com que frequência a lista é necessária, a precisão de quantas linhas de dados existem, como os dados serão usados , etc ... após o que a análise dos trade offs para a experiência do usuário e atender aos requisitos deve ser feita.

Por exemplo, comparar o tempo que seria necessário para recuperar as duas linhas ao executar uma consulta que geraria as duas linhas. Se demorar muito e o usuário não precisar da lista mais atualizada (* esperando menos de 1 alteração por dia), ela poderá ser armazenada.

Ou, se o usuário precisar de um registro histórico de tarefas atribuídas a eles, também faria sentido se a lista fosse armazenada. Então, isso realmente depende do que você está fazendo, nunca diga nunca.

    
por 14.11.2018 / 20:46
fonte
0

Se não for "ok", é bastante ruim que todo site Wordpress tenha uma lista em wp_usermeta com wp_capabilities em uma linha, dismissed_wp_pointers em uma linha e outras ...

De fato, em casos como esse, pode ser melhor para velocidade, pois você quase sempre desejará a lista . Mas o Wordpress não é conhecido por ser o exemplo perfeito das melhores práticas.

    
por 18.11.2018 / 07:27
fonte