Usando uma tabela de configuração de linha única no banco de dados do SQL Server. Péssima ideia?


145

Ao desenvolver um aplicativo de carrinho de compras, descobri que precisava salvar as configurações com base nas preferências e requisitos do administrador. Essas informações podem incluir informações da empresa, IDs de conta de remessa, chaves da API do PayPal, preferências de notificação etc.

Parece altamente inapropriado criar uma tabela para armazenar uma única linha em um sistema de banco de dados relacional.

Qual é a maneira apropriada de armazenar essas informações?

Nota: meu DBMS é o SQL Server 2008 e a camada de programação é implementada com o ASP.NET (em C #).

Respostas:


189

Eu fiz isso de duas maneiras no passado - uma tabela de linha única e uma tabela de pares de chave / valor - e há pontos positivos e negativos em cada abordagem.

Fila unica

  • positivo: os valores são armazenados no tipo correto
  • positivo: é mais fácil lidar com o código (devido ao acima)
  • positivo: valores padrão podem ser dados para cada configuração individualmente
  • negativo: é necessária uma alteração no esquema para adicionar uma nova configuração
  • negativo: a tabela pode ficar muito ampla se houver muitas configurações

Par de chave / valor

  • positivo: adicionar novas configurações não requer uma alteração no esquema
  • positivo: o esquema da tabela é estreito, com linhas extras sendo usadas para novas configurações
  • negativo: cada configuração tem o mesmo valor padrão (nulo / vazio?)
  • negativo: tudo deve ser armazenado como uma string (por exemplo, nvarchar)
  • negativo: ao lidar com as configurações no código, você precisa saber que tipo de configuração é e transmiti-la

A opção de linha única é de longe a mais fácil de se trabalhar. Isso ocorre porque você pode armazenar cada configuração em seu tipo correto no banco de dados e não precisa armazenar os tipos das configurações, bem como suas chaves de pesquisa no código.

Uma coisa que me preocupou em usar essa abordagem foi ter várias linhas na tabela "especial" de configurações de linha única. Eu superei isso por (no SQL Server):

  • adicionando uma nova coluna de bits com um valor padrão de 0
  • criando uma restrição de verificação para garantir que esta coluna tenha um valor 0
  • criando uma restrição exclusiva na coluna de bits

Isso significa que apenas uma linha pode existir na tabela porque a coluna de bits precisa ter um valor 0, mas só pode haver uma linha com esse valor devido à restrição exclusiva.


5
Fazemos a coisa de linha única em nosso aplicativo LOB. Os valores são do tipo correto, o que torna muito mais simples usá-los no aplicativo. Nosso esquema é versionado junto com o aplicativo, portanto, uma alteração na configuração é gerenciada como qualquer revisão de aplicativo.
DaveE

17
Única linha positiva: pode ter FK definido em algumas colunas!
Wqw

8
Você sempre pode fazer um par de chave / valor com um identificador de tipo para determinar qual coluna tem o valor em seu tipo de valor. Isso oferece o melhor dos dois mundos e você pode usar um processo armazenado para obter o valor quando necessário.
Middletone

19
Uma coisa que pode realmente arruinar seu dia após a implementação da solução de linha única é quando você está mais tarde a tarefa de "vamos também acompanhar a última vez que cada valor foi alterado e que mudou isto ...."
Dave Mateer

6
Outra vantagem da solução de linha única, que descobri em um caso: eu tinha um aplicativo criado para um cliente, com uma tabela de linha única para "configurações". Mais tarde, recebi outros dois clientes que queriam usar o mesmo aplicativo, mas desejavam configurações diferentes: tudo o que eu precisava fazer era adicionar uma PK "client_id" à tabela para manter um conjunto de configurações separado para cada cliente. (Isto é, quando você percebe que estes "ajustes" são realmente apenas atributos para uma entidade de nível superior você ainda não modelado.)
Jeffrey Kemp

10

Você deve criar uma tabela com uma coluna para o tipo de informação e o valor da informação (pelo menos). Dessa forma, você evita a necessidade de criar novas colunas sempre que uma nova informação é adicionada.


1
Simples e arrumado. Basta trabalhar com uma lista de pares de valores-chave a partir daí. Você pode querer pensar sobre o padrão valores um pouco, depende do contexto de uso ...
Paul Kohler

4
Por que é um problema criar novas colunas? Eu sei que existem situações em que os desenvolvedores devem evitá-lo por causa de problemas políticos com a atualização de esquemas SQL, mas não há menção disso na questão.
finnw

6

Uma única linha funcionará bem; terá até tipos fortes:

show_borders    bit
admin_name      varchar(50)
max_users       int

Uma desvantagem é que requer uma alteração de esquema ( alter table) para adicionar uma nova configuração. Uma alternativa é normalizar, onde você acaba com uma tabela como:

pref_name       varchar(50) primary key
pref_value      varchar(50) 

Isso tem tipos fracos (tudo é um varchar), mas adicionar uma nova configuração é apenas adicionar uma linha, algo que você pode fazer apenas com acesso de gravação ao banco de dados.


4

Pessoalmente, eu o armazenaria em uma única linha, se é isso que funciona. Excesso de armazenamento em uma tabela SQL? provavelmente, mas não há nenhum dano real ao fazê-lo.


4

Como você adivinhou, e, exceto nas situações mais simples, colocar todos os parâmetros de configuração em uma única linha tem muitas desvantagens. Isso é uma má idéia...

Uma maneira conveniente de armazenar informações sobre o tipo de configuração e / ou preferência do usuário é em XML . Muitos DBMSs suportam o tipo de dados XML. A sintaxe XML permite que você gaste a "linguagem" e a estrutura que descrevem a configuração à medida que essa configuração evolui. Uma vantagem do XML é o suporte implícito à estrutura hierárquica, permitindo, por exemplo, armazenar pequenas listas de parâmetros de configuração sem precisar nomeá-los com um sufixo numerado. Uma possível desvantagem do formato XML é que a pesquisa e a modificação geral desses dados não são tão diretas quanto outras abordagens (nada complicado, mas não tão simples / natural)

Se você deseja permanecer mais próximo do modelo relacional , o modelo de Entidade-Atributo-Valor é provavelmente o que você precisa, pelo qual os valores individuais são armazenados em uma tabela que normalmente se parece com:

EntityId     (foreign key to the "owner" of this attribute)
AttributeId  (foreign key to the "metadata" table where the attribute is defined)
StringValue  (it is often convenient to have different columns of different types
IntValue      allowing to store the various attributes in a format that befits 
              them)

Em que o AttributeId é uma chave estrangeira para uma tabela na qual cada atributo possível ("parâmetro de configuração" no seu caso) é definido, por exemplo

AttributeId  (Primary Key)
Name
AttributeType     (some code  S = string, I = Int etc.)
Required          (some boolean indicating that this is required)
Some_other_fields   (for example to define in which order these attributes get displayed etc...)

Finalmente, o EntityId permite identificar alguma entidade que "possui" esses vários atributos. No seu caso, pode ser um UserId ou mesmo implícito se você tiver apenas uma configuração para gerenciar.

Além de permitir que a lista de possíveis parâmetros de configuração cresça à medida que a aplicação evolui, o modelo EAV coloca os "metadados", ou seja, os dados pertencentes ao próprio Atributo, nas tabelas de dados, evitando assim toda a codificação codificada dos nomes de colunas comumente vistos quando os parâmetros de configuração são armazenados em uma única linha.


3
Parece exagero para a maioria dos usos de uma tabela de configuração.
JerryOL

Eu acho que a idéia geral por trás dessa abordagem é ótima. Mas por que XML? Basta escolher um formato simples de intercâmbio de dados como JSON ou YAML e você poderá obter as vantagens de ambas as outras variações.
schlamar

1
O EAV é relacional, mas não é normalizado. Certamente existem casos de uso para ele (por exemplo, sistemas ORM parecem amá-los), mas o argumento de que os metadados estão no banco de dados para o EAV não é um motivo convincente para usá-los. Todos os RDBMS contêm metadados nas tabelas do sistema de qualquer maneira que você possa descobrir, portanto, as tabelas de linha única também possuem metadados no banco de dados. Os nomes de colunas codificados também não são um problema. Se você usar chaves para entidades e atributos, terá uma tabela de pesquisa codificada em outro lugar que as define (ou pior, é na sua camada de apresentação).
Davos

3

Você certamente não precisa alterar seu esquema ao adicionar um novo parâmetro de configuração na abordagem normalizada, mas provavelmente ainda está alterando seu código para processar o novo valor.

A adição de uma "tabela alterada" à sua implantação não parece ser uma grande desvantagem pela simplicidade e segurança de tipo da abordagem de linha única.


2

Um par de Chave e Valor é semelhante a um .Net App.Config que pode armazenar definições de configuração.

Então, quando você deseja recuperar o valor, pode:

SELECT value FROM configurationTable
WHERE ApplicationGroup = 'myappgroup'
AND keyDescription = 'myKey';

1

Uma maneira comum de fazer isso é ter uma tabela de "propriedades" semelhante a um arquivo de propriedades. Aqui você pode armazenar todas as constantes do seu aplicativo, ou coisas não tão constantes que você só precisa ter por perto.

Você pode pegar as informações desta tabela conforme necessário. Da mesma forma, como você encontra alguma outra configuração para salvar, pode adicioná-la. Aqui está um exemplo:

property_entry_table

[id, scope, refId, propertyName, propertyValue, propertyType] 
1, 0, 1, "COMPANY_INFO", "Acme Tools", "ADMIN"  
2, 0, 1, "SHIPPING_ID", "12333484", "ADMIN"  
3, 0, 1, "PAYPAL_KEY", "2143123412341", "ADMIN"   
4, 0, 1, "PAYPAL_KEY", "123412341234123", "ADMIN"  
5, 0, 1, "NOTIF_PREF", "ON", "ADMIN"  
6, 0, 2, "NOTIF_PREF", "OFF", "ADMIN"   

Dessa forma, você pode armazenar os dados que possui e os que terá no próximo ano e ainda não sabe :).

Neste exemplo, seu escopo e refId podem ser usados ​​para o que você quiser no back-end. Portanto, se o propertyType "ADMIN" tiver um escopo 0 refId 2, você saberá qual é a preferência.

O tipo de propriedade é fornecido quando, um dia, você precisar armazenar informações não administrativas aqui também.

Observe que você não deve armazenar dados do carrinho dessa maneira ou fazer pesquisas para esse assunto. No entanto, se os dados forem específicos do sistema , você certamente poderá usar esse método.

Por exemplo: se você deseja armazenar seu DATABASE_VERSION , use uma tabela como esta. Dessa forma, quando você precisar atualizar o aplicativo, poderá verificar a tabela de propriedades para ver qual versão do seu software o cliente possui.

O ponto é que você não deseja usar isso para coisas que pertencem ao carrinho. Mantenha sua lógica de negócios em tabelas relacionais bem definidas. A tabela de propriedades é apenas para informações do sistema.


@finnw Concordo plenamente que esse método não deve ser usado para pesquisas, especialmente quando existem muitos tipos diferentes de pesquisas. Talvez eu tenha entendido mal a pergunta. Parecia que ele precisava de uma tabela para constantes e propriedades do sistema. Nesse caso, por que ter 10 tabelas diferentes?
Stephano

nota: ele disse "Salvar configurações e configurações", e não "eu preciso para salvar carrinho de dados relacional"
Stephano

Minha objeção a isso é que você está ignorando a digitação do SQL e outros mecanismos de restrição para evitar a atualização do esquema SQL ao adicionar novos atributos. Como você diz "dados que você terá no próximo ano e que ainda não conhece". Sim, você terá novos dados no próximo ano, mas o que deve impedir você de criar novas colunas SQL (digitadas), CHECK e possivelmente restrições de FOREIGN KEY para eles no momento em que são adicionados?
finnw

Meu primeiro instinto é simplesmente adicionar esses dados a um arquivo simples. E você está correto, esse processo de usar uma tabela, na verdade, contornará os mecanismos de restrição do DBMS. No entanto, eu diria que, se você tentar demais seguir técnicas apropriadas de banco de dados, estará perdendo o objetivo. Confira a primeira resposta; mais votado no SO: stackoverflow.com/questions/406760/…
Stephano

2
Eu usaria um par de valores-chave, despejaria tudo em um dicionário na inicialização e você classificaria.
Paul Creasey

0

Não tenho certeza se uma única linha é a melhor implementação para configuração. É melhor ter uma linha por item de configuração com duas colunas (configName, configValue), embora isso exija a conversão de todos os seus valores para strings e vice-versa.

Independentemente disso, não há mal nenhum em usar uma única linha para a configuração global. As outras opções para armazená-lo no DB (variáveis ​​globais) são piores. Você pode controlá-lo inserindo sua primeira linha de configuração e desativando as inserções na tabela para evitar várias linhas.


0

Você pode fazer o par de chave / valor sem conversões adicionando uma coluna para cada tipo principal e uma coluna informando em qual coluna os dados estão.

Portanto, sua tabela seria algo como:

id, column_num, property_name, intValue, floatValue, charValue, dateValue
1, 1, weeks, 51, , ,
2, 2, pi, , 3.14159, , 
3, 4, FiscYearEnd, , , , 1/31/2015
4, 3, CompanyName, , , ACME, 

Ele ocupa um pouco mais de espaço, mas no máximo você está usando algumas dezenas de atributos. Você pode usar uma declaração de caso do valor column_num para obter / ingressar no campo direito.


0

Desculpe eu venho, anos depois. De qualquer forma, o que faço é simples e eficaz. Eu simplesmente crio uma tabela com três () colunas:

ID - int (11)

nome - varchar (64)

valor - texto

O que eu faço antes de criar uma nova coluna de configuração, atualizá-la ou ler é serializar o "valor"! Dessa forma, tenho certeza do tipo (Bem, php é :))

Por exemplo:

b: 0; é para B OOLEAN ( falso )

b: 1; é para B OOLEAN ( verdadeiro )

i: 1988; é para eu NT

s: 5: "Kader"; é para um S TRING de 5 caracteres

Eu espero que isso ajude :)


1
Por que não criar apenas uma nova coluna para o tipo? i:1988parece que você está tentando recolher duas informações em uma única coluna.
Maksymiuk

@maksymiuk simplesmente porque uma vez desserializado de obter o tipo exato em vez de usar um loop depois (se ou switch) ... etc
Kader Bouyakoub

sem necessidade de loops ou comutadores ou qualquer coisa, isso removeria a etapa de analisar as informações de cada linha, enquanto que, se você tivesse uma coluna extra para o tipo, as informações de tipo já estarão disponíveis para o componente que extrai as informações. sem ter que fazer quaisquer outras medidas que apenas a consulta inicial
Maksymiuk

Você quer dizer algo como echo (int) $varum número inteiro e outros para outros tipos?
Kader Bouyakoub

0

Tenha uma coluna de chave como varchar e uma coluna de valor como JSON. 1é numérico enquanto "1"é uma sequência. truee falsesão ambos booleanos. Você pode ter objetos também.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.