Drapeaux dans une base de données lignes, les meilleures pratiques
Je demande cela par curiosité. Fondamentalement, ma question est quand vous avez une base de données qui a besoin d'une entrée de ligne pour avoir des choses qui agissent comme des drapeaux, Quelle est la meilleure pratique? Un bon exemple de ceci serait les badges sur stack overflow, ou le champ système d'exploitation dans bugzilla. Tout sous-ensemble des indicateurs peut être défini pour une entrée donnée.
Habituellement, je fais du travail c et c++, donc ma réaction intestinale est d'utiliser un champ entier non signé comme un ensemble de bits qui peuvent être retournés... Mais je sais que n'est pas une bonne solution pour plusieurs raisons. Le plus évident est la capacité d'échelle, il y aura une limite supérieure dure sur le nombre de drapeaux que je peux avoir.
Je peux aussi penser à quelques autres solutions qui évolueraient mieux mais qui auraient des problèmes de performance car elles nécessiteraient plusieurs sélections pour obtenir toutes les informations.
Alors, quelle est la "bonne" façon de faire cela?
7 réponses
Si vous avez vraiment besoin d'une sélection illimitée à partir d'un ensemble fermé de drapeaux (par exemple, les badges stackoverflow), la "manière relationnelle" serait de créer une table de drapeaux et une table séparée qui relie ces drapeaux à vos entités cibles. Ainsi, les utilisateurs, les drapeaux et les usersToFlags.
Cependant, si l'efficacité de l'espace est une préoccupation sérieuse et que la capacité de requête ne l'est pas, un masque non signé fonctionnerait presque aussi bien.
D'une manière générale, j'évite les champs de masque de bits. Ils sont difficiles à lire à l'avenir et ils nécessitent une connaissance beaucoup plus approfondie des données à comprendre.
La solution relationnelle a été proposée précédemment. Compte tenu de l'exemple que vous avez décrit, je créerais quelque chose comme ceci (dans SQL Server):
CREATE TABLE Users (
UserId INT IDENTITY(1, 1) PRIMARY KEY,
FirstName VARCHAR(50),
LastName VARCHAR(50),
EmailAddress VARCHAR(255)
);
CREATE TABLE Badges (
BadgeId INT IDENTITY(1, 1) PRIMARY KEY,
[Name] VARCHAR(50),
[Description] VARCHAR(255)
);
CREATE TABLE UserBadges (
UserId INT REFERENCES Users(UserId),
BadgeId INT REFERENCES Badges(BadgeId)
);
Dans de nombreux cas, cela dépend de beaucoup de choses - comme votre backend de base de données. Si vous utilisez MySQL, par exemple, le SET datatype est exactement ce que vous voulez.
Fondamentalement, c'est juste un masque de bits, avec des valeurs assignées à chaque bit. MySQL prend en charge jusqu'à des valeurs 64 bits (ce qui signifie 64 bascules différents). Si vous avez seulement besoin de 8, alors il ne faut qu'un octet par ligne, ce qui est une économie assez impressionnante.
Si vous avez honnêtement plus de 64 valeurs dans un seul champ, votre champ peut être de plus en plus complexes. Vous voudrez peut-être étendre ensuite au type de données BLOB, qui est juste un ensemble brut de bits dont MySQL n'a aucune compréhension inhérente. En utilisant cela, vous pouvez créer un nombre arbitraire de champs de bits que MySQL est heureux de traiter comme des valeurs binaires, hexadécimales ou décimales, comme vous le souhaitez. Si vous avez besoin de plus de 64 options, Créez autant de champs que vous le souhaitez pour votre application. L'inconvénient est qu'il est difficile de rendre le champ lisible par l'homme. Le type de données BIT est également limité à 64.
Une Approche Très Relationnelle
Pour les bases de données sans le type de jeu, vous pouvez ouvrir une nouvelle table pour représenter l'ensemble des entités pour lesquelles chaque indicateur est défini.
Par exemple, pour une Table "Students", vous pourriez avoir des tables "RegisteredStudents", "SickStudents", TroublesomeStudents etc. Chaque table n'aura qu'une seule colonne: student_id. Ce serait en fait très rapide si tout ce que vous voulez savoir est quels étudiants sont "inscrits" ou "malades", et fonctionnerait de la même manière dans chaque SGBD.
Si les indicateurs ont des significations très différentes et sont utilisés directement dans les requêtes SQL ou les vues, utilisez plusieurs colonnes de type BOOLEAN
ça pourrait être une bonne idée.
Mettez chaque drapeau dans une colonne supplémentaire, car vous les lirez et les modifierez de toute façon séparément. Si vous voulez regrouper les drapeaux, donnez simplement un préfixe commun à leurs noms de colonnes, c'est-à-dire au lieu de:
CREATE TABLE ... (
warnings INTEGER,
errors INTEGER,
...
)
Vous devriez utiliser:
CREATE TABLE ... (
warning_foo BOOLEAN,
warning_bar BOOLEAN,
warning_...
error_foo BOOLEAN,
error_bar BOOLEAN,
error_... BOOLEAN,
...
)
Bien que MySQL N'ait pas de type booléen, vous pouvez utiliser le quasi standard TINYINT(1) à cet effet, et définissez-le uniquement sur 0 ou 1.
S'il y a plus que quelques drapeaux, ou susceptibles de l'être à l'avenir, je vais utiliser un tableau distinct de drapeaux et plusieurs-à-plusieurs table entre eux.
S'il y a une poignée de drapeaux et que je ne les utiliserai jamais dans un WHERE, j'utiliserai un SET() ou bitfield ou autre. Ils sont faciles à lire et plus compacts, mais une douleur à interroger et parfois même un mal de tête avec un ORM.
S'il n'y a que quelques drapeaux - et que seulement VA être quelques drapeaux-alors Je vais juste faire quelques colonnes BIT/BOOLEAN/etc.
Je recommande d'utiliser un type de données booléen si votre base de données le supporte.
Sinon, la meilleure approche consiste à utiliser NUMBER(1) ou equivalent, et à mettre une contrainte check sur la colonne qui limite les valeurs valides à (0,1) et peut-être NULL si vous en avez besoin. S'il n'y a pas de type intégré, l'utilisation d'un nombre est moins ambiguë que l'utilisation d'une colonne de caractères. (Quelle est la valeur pour true? "T" ou "Y" ou "t")
La bonne chose à ce sujet est que vous pouvez utiliser SUM () pour compter le nombre de VÉRITABLE lignes.
SELECT COUNT(1), SUM(ActiveFlag)
FROM myusers;