J'ai une colonne: standard BOOLEAN NOT NULL
Je voudrais appliquer une ligne True et toutes les autres False. Il n'y a pas de FK ou autre chose en fonction de cette contrainte. Je sais que je peux l'accomplir avec plpgsql, mais cela ressemble à un marteau. Je préférerais quelque chose comme une contrainte CHECK
ou UNIQUE
. Plus c'est simple, mieux c'est.
Une ligne doit être True, elles ne peuvent pas toutes être False (donc la première ligne insérée doit être True).
La ligne devra être mise à jour, ce qui signifie que je dois attendre pour vérifier les contraintes jusqu'à ce que les mises à jour soient terminées, car toutes les lignes peuvent être définies sur False en premier et une ligne sur True après.
Il y a un FK entre products.tax_rate_id
et tax_rate.id
, mais cela n'a rien à voir avec le taux de taxe par défaut ou standard, sélectionnable par l'utilisateur pour faciliter la création de nouveaux produits.
PostgreSQL 9.5 si cela est important.
Le tableau est le taux d'imposition. L'un des taux de taxe est la valeur par défaut (standard
car la valeur par défaut est une commande Postgres). Lorsqu'un nouveau produit est ajouté, le taux de taxe standard est appliqué au produit. S'il n'y a pas de standard
, la base de données doit faire une supposition ou toutes sortes de vérifications inutiles. La solution simple, je pensais, était de s'assurer qu'il y avait un standard
.
Par "par défaut" ci-dessus, je veux dire pour la couche de présentation (UI). Il existe une option utilisateur pour modifier le taux de taxe par défaut. Je dois soit ajouter des vérifications supplémentaires pour m'assurer que l'interface graphique/l'utilisateur n'essaie pas de définir le tax_rate_id sur NULL, soit simplement définir un taux de taxe par défaut.
Puisque tout ce dont vous avez besoin est une seule colonne avec standard = true
, définissez la norme sur NULL dans toutes les autres lignes. Alors une contrainte UNIQUE
simple fonctionne, puisque les valeurs NULL ne la violent pas:
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
, standard bool DEFAULT true
, CONSTRAINT standard_true_or_null CHECK (standard) -- yes, that's the whole constraint
, CONSTRAINT standard_only_1_true UNIQUE (standard)
);
DEFAULT
est un rappel facultatif que la première ligne entrée doit devenir la valeur par défaut. Ce n'est pas en vigueur quoi que ce soit. Bien que vous ne puissiez pas définir plusieurs lignes sur standard = true
, vous pouvez toujours définir toutes les lignes NULL. Il n'y a aucun moyen propre d'empêcher cela avec seulement contraintes dans une seule table. CHECK
les contraintes ne prennent pas en compte les autres lignes (sans astuces sales).
En relation:
restreindre deux valeurs de colonne spécifiques d'exister en même temps
Ajouter une contrainte pour rendre la colonne unique par groupe de lignes
Mettre à jour:
BEGIN;
UPDATE taxrate SET standard = NULL WHERE standard;
UPDATE taxrate SET standard = TRUE WHERE taxrate = 2;
COMMIT;
Pour autoriser une commande comme (où la contrainte n'est satisfaite qu'à la fin de l'instruction):
WITH kingdead AS (
UPDATE taxrate
SET standard = NULL
WHERE standard
)
UPDATE taxrate
SET standard = TRUE
WHERE taxrate = 1;
.. la contrainte UNIQUE
devrait être DEFERRABLE
. Voir:
dbfiddle ici
Avoir un deuxième tableau avec une seule ligne comme:
Créez ceci en tant que superutilisateur:
CREATE TABLE taxrate (
taxrate int PRIMARY KEY
);
CREATE TABLE taxrate_standard (
taxrate int PRIMARY KEY REFERENCES taxrate
);
CREATE UNIQUE INDEX taxrate_standard_singleton ON taxrate_standard ((true)); -- singleton
REVOKE DELETE ON TABLE taxrate_standard FROM public; -- can't delete
INSERT INTO taxrate (taxrate) VALUES (42);
INSERT INTO taxrate_standard (taxrate) VALUES (42);
Maintenant, il y a toujours une seule ligne pointant vers la norme (dans ce cas simple représentant également directement le taux standard). Seul un superutilisateur pouvait le casser. Vous pouvez également interdire cela avec un déclencheur BEFORE DELETE
.
dbfiddle ici
En relation:
Vous pouvez ajouter un VIEW
pour voir la même chose que dans variante 1:
CREATE VIEW taxrate_combined AS
SELECT t.*, (ts.taxrate = t.taxrate) AS standard
FROM taxrate t
LEFT JOIN taxrate_standard ts USING (taxrate);
Dans les requêtes où tout ce que vous voulez est le taux standard, utilisez (uniquement) taxrate_standard.taxrate
directement.
Vous avez ajouté plus tard:
Il y a un FK entre
products.tax_rate_id
ettax_rate.id
Une implémentation du pauvre de la variante 2 consisterait simplement à ajouter une ligne à products
(ou tout autre tableau similaire) pointant vers la taxe standard taux; un produit factice que vous pourriez appeler "Taux de taxe standard" - si votre configuration le permet.
Les contraintes FK imposent l'intégrité référentielle. Pour le terminer, appliquez tax_rate_id IS NOT NULL
pour la ligne (si ce n'est pas le cas pour la colonne en général). Et interdire sa suppression. Les deux pourraient être mis en déclencheurs. Pas de table supplémentaire, mais moins élégante et moins fiable.
Vous pouvez utiliser un index filtré
create table test ( id int primary key, foo bool );
CREATE UNIQUE INDEX only_one_row_with_column_true_uix ON test (foo) WHERE (foo); --> where foo is true
insert into test values (1, false); insert into test values (2, true); insert into test values (3, false); insert into test values (4, false); insert into test values (5, true);
ERREUR: la valeur de clé en double viole la contrainte unique "only_one_row_with_column_true_uix" DÉTAIL: La clé (foo) = (t) existe déjà.
dbfiddle ici
Mais comme vous l'avez dit, la première ligne doit être vraie, alors vous pouvez utiliser une contrainte CHECK, mais même en utilisant une fonction, vous pouvez supprimer la première ligne plus tard.
create function check_one_true(new_foo bool) returns int as $$ begin return ( select count(*) + (case new_foo when true then 1 else 0 end) from test where foo = true ); end $$ language plpgsql stable;
alter table test add constraint ck_one_true check(check_one_true(foo) = 1);
insert into test values (1, true); insert into test values (2, false); insert into test values (3, false); insert into test values (4, false);
insert into test values (5, true);
ERREUR: une nouvelle ligne pour la relation "test" viole la contrainte de vérification "ck_one_true" DÉTAIL: La ligne défaillante contient (5, t).
select * from test;
id | foo -: | : - 1 | t 2 | f 3 | f 4 | F
delete from test where id = 1;
dbfiddle ici
Vous pouvez le résoudre en ajoutant un déclencheur BEFORE DELETE pour vous assurer que la première ligne (foo est vrai) n'est jamais supprimée.
create function dont_delete_foo_true() returns trigger as $x$ begin if old.foo then raise exception 'Can''t delete row where foo is true.'; end if; return old; end; $x$ language plpgsql;
create trigger trg_test_delete before delete on test for each row execute procedure dont_delete_foo_true();
delete from test where id = 1;
ERREUR: impossible de supprimer la ligne où foo est vrai.
dbfiddle ici