it-swarm-eu.dev

Jak mohu vynutit, aby jeden záznam měl skutečnou hodnotu pro booleovský sloupec a všechny ostatní falešnou hodnotu?

Chci vynutit, že pouze jeden záznam v tabulce je považován za „výchozí“ hodnotu pro další dotazy nebo zobrazení, která by k této tabulce mohla přistupovat.

V zásadě chci zaručit, že tento dotaz vždy vrátí přesně jeden řádek:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

Jak bych to udělal v SQL?

20
emaynard

Edit: to je zaměřeno na SQL Server, než jsme věděli MySQL

Existují 4 5 způsobů, jak toho dosáhnout: v pořadí od nejžádanějších po nejméně žádané

Nevíme, co to RDBMS, pokud se tak nemusí vztahovat na všechny

  1. Nejlepší způsob je filtrovaný index. To používá DRI k zachování jedinečnosti.

  2. Počítačový sloupec s jedinečností (viz odpověď Jacka Douglase) (přidáno úpravou 2)

  3. Indexované/materializované zobrazení, které je jako filtrovaný index pomocí DRI

  4. Spouštěč (podle ostatních odpovědí)

  5. Zkontrolujte omezení pomocí UDF. Toto není bezpečné pro izolaci souběžnosti a snímků. Viz OneTwoThreeFour

Všimněte si, že požadavek na „jednu výchozí“ je v tabulce, nikoli uložená procedura. Uložená procedura bude mít stejné problémy se souběžností jako kontrolní omezení s udf

Poznámka: zeptal se na SO mnohokrát:

8
gbn

Poznámka: Tato odpověď byla dána dříve, než bylo jasné, že žadatel chtěl něco konkrétního MySQL. Tato odpověď je zkreslená směrem k serveru SQL.

Použijte ZAŠKRTNĚTE omezení na tabulku, která volá UDF a ujistěte se, že se vrátí hodnota je <= 1. UDF může pouze spočítat řádky v tabulce WHERE isDefault = TRUE. Tím zajistíte, že v tabulce nebude více než 1 výchozí řádek.

Měli byste přidat bitmap nebo filtrováno index do sloupce isDefault (v závislosti na vašem platforomu), abyste se ujistili, že tento UDF běží velmi rychle.

Upozornění

  • Omezení kontroly mají jistéomezení . Konkrétně jsou vyvolány pro každý řádek, který je upraven, i když tyto řádky ještě nebyly v transakci potvrzeny. Pokud tedy zahájíte transakci, nastavte nový řádek jako výchozí a poté starý řádek zrušte, vaše omezení kontroly si stěžuje. Je to proto, že se spustí po vytvoření nového řádku jako výchozího, zjistíte, že nyní existují dvě výchozí hodnoty a selže. Řešením je zajistit, že nejprve nastavíte výchozí řádek před nastavením nového výchozího nastavení, i když tuto práci provádíte v transakci.
  • Jako gbnpoukázáno , UDF mohou vracet nekonzistentní výsledky, pokud používáte izolaci snímků na serveru SQL. Vložený UDF však do tohoto problému nenastane a protože UDF, který právě provádí počet, je inline-schopný, to zde není problém.
  • Síla výpočetního výkonu databázového stroje spočívá v jeho schopnosti pracovat na souborech dat najednou. S omezením kontroly podporovaným UDF bude motor omezen na použití tohoto UDF sériově na každý změněný řádek. Ve vašem případě použití je nepravděpodobné, že budete provádět hromadné aktualizace tabulky PostalCodes, to však zůstává problémem s výkonem pro situace, kdy je pravděpodobná aktivita založená na sadě.

S ohledem na všechna tato upozornění doporučuji místo omezení kontroly použít návrh Gbn filtrovaného jedinečného indexu

10
Nick Chammas

Zde je uložená procedura (MySQL Dialect):

DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;

    SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
    IF FOUND_TRUE = 1 THEN
        SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
        IF NEWID <> OLDID THEN
            UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
            UPDATE PostalCode SET isDefault = TRUE  WHERE ID = NEWID;
        END IF;
    ELSE
        UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
    END IF;
END;
$$
DELIMITER ;

Chcete-li se ujistit, že je tabulka čistá a zda uložená procedura funguje, za předpokladu, že je výchozí hodnota ID 200, spusťte tyto kroky:

ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

A co místo uložené procedury, co třeba trigger?

DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;

Chcete-li se ujistit, že je tabulka čistá a že spouštěč funguje, za předpokladu, že je výchozí hodnota ID 200, spusťte tyto kroky:

DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
4
RolandoMySQLDBA

Pro použití pravidel byste mohli použít spouštěč. Když příkaz UPDATE nebo INSERT nastaví hodnotu IsDefault na True, může SQL ve spouštěči nastavit všechny ostatní řádky na False.

Při použití tohoto postupu budete muset zvážit další situace. Například, co by se mělo stát, když je více než jeden řádek v sadách UPDATE nebo INSERT isDefault to True? Jaká pravidla budou platit, aby nedošlo k porušení pravidla?

Je také možné, že neexistuje výchozí nastavení? Co se stane, když aktualizace nastaví parametr isDefault z True na False.

Jakmile definujete pravidla, můžete je vytvořit ve spouštěči.

Poté, jak je uvedeno v jiné odpovědi, chcete použít omezení kontroly, které pomůže vynutit pravidla.

3
bobs

Následující nastaví vzorovou tabulku a data:

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

CREATE TABLE dbo.MyTable
(
    [id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [IsDefault] BIT DEFAULT 0
)
GO

INSERT dbo.MyTable DEFAULT VALUES
GO 100

Pokud vytvoříte uloženou proceduru pro nastavení příznaku IsDefault a odebrání oprávnění ke změně základní tabulky, můžete to vynutit pomocí dotazu níže. Vyžadovalo by to pokaždé skenování tabulky.

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END 
GO

V závislosti na velikosti tabulky může index v systému IsDefault (DESC) vést k tomu, že se jeden nebo oba níže uvedené dotazy vyhnou úplnému prověřování.

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  IsDefault = 1

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  ([id] != @Id AND IsDefault = 1)

Pokud nemůžete odebrat oprávnění ze základní tabulky, potřebujete vynutit integritu jinými prostředky a používáte SQL2008, můžete použít filtrovaný jedinečný index:

CREATE UNIQUE INDEX IX_MyTable_IsDefault  ON dbo.MyTable (IsDefault) WHERE IsDefault = 1
3

Pro MySQL, která nemá filtrované indexy, byste mohli udělat něco jako následující. Využívá skutečnost, že MySQL umožňuje více jedinečných indexů NULL a používá vyhrazenou tabulku a cizí klíč k simulaci datového typu, který může mít jako hodnoty pouze NULL a 1.

S tímto řešením byste místo true/false použili pouze 1/NULL.

CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);

CREATE TABLE PostalCodes (
    ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    Zip VARCHAR (32) NOT NULL,
    isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
    CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
    UNIQUE KEY (isDefault)
);

INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1   );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1   );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */

/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);

/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2   );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/

Jediná věc, která se může pokazit, je, pokud někdo přidá řádek do tabulky DefaultFlag. Myslím, že se nejedná o velký problém, a pokud to opravdu chcete, můžete to vždy opravit pomocí povolení.

1
djfm
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

To vám v podstatě způsobovalo problémy, protože jste pole postavili na špatné místo a to je vše.

Mejte tabulku „Výchozí“, ve které je v ní PSČ, který obsahuje ID, které hledáte. Obecně je také dobré pojmenovat tabulky podle jednoho záznamu, takže název vaší původní tabulky by měl být lépe nazván 'PostalCode'. Výchozí nastavení bude jedna řádková tabulka, dokud nebudete muset své výchozí nastavení specializovat na jinou konfiguraci (například na historiku).

Poslední požadovaný dotaz je následující:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
0
Konchog