Sloupcová omezení, Indexy

Pokud vytváříte tabulku (příkazem CREATE TABLE), můžete mít na data, která se budou ukládat do sloupců, speciální požadavky. Nejdůležitější z nich proberu v této kapitole a v kapitole o relacích.

V této kapitole se bude vytvářet hodně tabulek, které už později nebudou potřeba, tak si je na konci můžete smazat příkazem DROP TABLE.

Integritní omezení

Integritní omezení (Integrity constraints) je správnější název než soupcová omezení. Existují totiž omezení, která se netýkají jen hodnot (jednoho) sloupce, ale i vazeb mezi sloupci. (Viz relace v další kapitole).

Integritní omezení říkají, jak mají vypadat data ukládaná do tabulky (sloupce).

Mezi integritní omezení patří (neformálně) už datový typ sloupce. Když si vytvoříte datový sloupec typu INTEGER, už do něj nemůžete vložit text. (O to se stará DBMS.)

Formálně je integritním omezením i defaultní hodnota (ikdyž to ve skutečnosti není žádné omezení). Defaultní (implicitní) hodnota říká, co se má uložit za hodnotu do sloupečku, když při INSERTU hodnotu sloupečku neurčíte explicitně.

Další constrainty jsou: NOT NULL, UNIQUE, CHECK, a pro relaci PRIMARY KEY (primární klíč) a FOREIGN KEY (cizí klíč).

Defaultní hodnoty

Defaultní hodnota sloupce je taková hodnota, která se do sloupce dosadí automaticky, pokud neurčíte jeho hodnotu. Pokud defaultní hodnotou neurčíte, ani neurčíte hodnotu sloupce v příkazu INSERT, je automaticky dosazena hodnota NULL (NULL je defaultní hodnota pro defaultní hodnotu :-).

Defaultní hodnoty vám mohou ušetřit trochu toho psaní a navíc vám zajistí, že ve sloupci nebudete mít tu ošklivou hodnotu NULL. (Typicky datový typ BOOL většinou není dobré nechávat nevyplněný).

Příklad: Dejme tomu, že pravidelně sázím sportku za 60 Kč. Vytvořím si tabulku, kde si budu ukládat, kolik jsem vsadil, kdy a kolik jsem vyhrál. Záznam o sázce vložím vždy ten den, kdy vsadím. Záznam o výhře budu měnit dodatečně až po tahu sportky.

Vytvořím tedy defaultní hodnotu pro sázku (60 Kč), ale i pro výhru (0 Kč). Když pak nevyhraji, nemusím se obtěžovat záznam měnit a nebudu mít nikde ve sloupci výher hodnotu NULL (pokud si jí tam schválně nevložím :-)). Protože sázky i výhry budou v celých korunách, použiji jako typ celé číslo (integer).

Poznámka pro později narozené: Tenhle tutoriál vznikal v době, kdy ještě existovali halíře :-).
CREATE TABLE sportka (
        datum DATE,
        vsazeno INTEGER DEFAULT 60,
        vyhra INTEGER DEFAULT 0
);

Klíčové slovo DEFAULT s hodnotou se píší vždy až za datový typ sloupečku.

Doposud jsem při INSERTu vkládal vždy hodnoty pro všechny sloupečky tabulky. Aby měla hodnota DEFAULT nějaký smysl, musí existovat způsob, jak vložit hodnoty jen pro některé sloupečky. A ten teď ukážu v příkladu: za jménem tabulky se do závorek vypíší jména sloupců, do kterých chci vložit nějakou hodnotu explicitně (do ostatních se vloží DEFAULT hodnota).

Nyní vložím do tabulky sportka několik „sázek“:

INSERT INTO sportka(datum) VALUES (TO_DATE('2002-09-22','YYYY-MM-DD'));
INSERT INTO sportka(datum) VALUES (TO_DATE('2002-09-23','YYYY-MM-DD'));
INSERT INTO sportka(datum, vsazeno) VALUES (TO_DATE('2002-09-24','YYYY-MM-DD'), 120);

Nechme stranou, že jsem tak trochu gambler. Výsledek je:

rimmer1=> SELECT * FROM sportka;
   datum    | vsazeno | vyhra
------------+---------+-------
 2002-09-22 |      60 |     0
 2002-09-23 |      60 |     0
 2002-09-24 |     120 |     0
(3 rows)

Defaultní hodnotou může být také výraz. Opět ukážu na příkladě:

Představte si, že vedete malý obchod s cukrovím. Jedno ze zboží, které objednáváte, je „Sladký rohlík“. Většinou jich objednáváte 100 Kusů a jejich trvanlivost je 5 dní. Ale pokud si je objednáte ve speciálním balení, bude jejich trvanlivost delší. Musíte také vědět, kdo vám rohlíky dovezl (kvůli případné reklamaci). Váš obvyklý závozník se jmenuje Tomáš Tlustý (ale někdy se může stát, že potřebujete větší množství a tak si objednáte i u někoho jiného). Přehled o zboží si pak budete udržovat v takovéto tabulce:

CREATE TABLE sladky_rohlik (
        zavoznik VARCHAR(30) DEFAULT 'Tomáš Tlustý',
        pocet INTEGER DEFAULT 100,
        dovezeno DATE DEFAULT current_date,
        spotrebovat_do DATE DEFAULT current_date+5
);
INSERT INTO sladky_rohlik(pocet) VALUES(100);

Poznámka:  current_date je funkce, která vrací hodnotu aktuálního datumu. Funkcemi se budu zabývat později.

Bohužel nemůžete ve výrazu použít hodnotu sloupce (nejde použít jako defaultní hodnotu ve sloupci spotrebovat_do dovezeno + 5). Další malou „nepříjemností“ je to, že příkaz INSERT musí obdržet jako argument hodnotu alespoň jednoho sloupce (i když mají všechny sloupce svou defaultní hodnotu). Vyzkoušejte.

Dobrá zpráva je, že můžete pro hodnotu sloupce použít klíčové slovo DEFAULT. Je to určitě lepší, než psát explicitně defaultní hodnotu, zvlášť když tento SQL dotaz používáte častěji (máte ho třeba někde uložený) a defaultní hodnota se v tabulce může změnit.

INSERT INTO sladky_rohlik(pocet) VALUES(DEFAULT);
rimmer1=> SELECT * FROM sladky_rohlik;
   zavoznik   | pocet |  dovezeno  | spotrebovat_do
--------------+-------+------------+----------------
 Tomáš Tlustý |   100 | 2013-11-28 | 2013-12-03
 Tomáš Tlustý |   100 | 2013-11-28 | 2013-12-03
(2 řádky)
 

Více o DEFAULT.

Podmínka CHECK

Za klíčovým slovem CHECK následuje podmínka, podobně jako u WHERE (Jen si odmyslete podmínky s NULL, IN a ALL). Podmínka se uvádí za klíčovým slovem CHECK v závorce. Podmínka (constraint) určí, jaké hodnoty budete moci do tabulky vložit (pouze takové, co podmínce vyhoví).

Příklad: budete udržovat databázi zboží na skladě. Na zboží máte následující požadavky:

  1. Nemůžete mít na skladě záporné množství zboží, tak si v databázi určíte podmínku, že množství zboží nesmí být menší než 0.
  2. Máte také omezené místo ve skladu, proto nesmí žádné zboží přesáhnout počet 500 kusů (tolik zboží byste ani nestihli do příští objednávky prodat).
  3. Když se pak pokusíte odečíst z databáze více zboží, než máte na skladě, nebo naopak více zboží objednat, než kolik můžete přijmout, nepůjde to.
  4. Zboží musí mít také svou cenu.
  5. Cena by měla být vyšší než 0.

Tabulka s takovými omezeními může vypadat následovně:

CREATE TABLE zbozi (
        nazev VARCHAR(20),
        pocet INTEGER CHECK (pocet >= 0 AND pocet <= 500),
        cena NUMERIC(8,2) CHECK (cena > 0)
);

Teď se pokusím vložit nějaké hodnoty do tabulky:

rimmer1=> INSERT INTO zbozi VALUES ('Kulomety', 500, 1499.90);
INSERT 0 1
rimmer1=> INSERT INTO zbozi VALUES ('Tarasnice', 0, 500);
INSERT 0 1
rimmer1=> UPDATE zbozi SET pocet = pocet + 100;
ERROR:  new row for relation "zbozi" violates check constraint "zbozi_pocet_check"
DETAIL:  Failing row contains (Kulomety, 600, 1499.90).
rimmer1=> INSERT INTO zbozi VALUES ('Samopaly', 501, 1499.90);
ERROR:  new row for relation "zbozi" violates check constraint "zbozi_pocet_check"
DETAIL:  Failing row contains (Samopaly, 501, 1499.90).
rimmer1=> INSERT INTO zbozi(nazev, pocet) VALUES ('Samopaly', 300);
INSERT 0 1
rimmer1=> INSERT INTO zbozi(nazev, pocet, cena) VALUES ('Samopaly', 300, -1000);
ERROR:  new row for relation "zbozi" violates check constraint "zbozi_cena_check"
DETAIL:  Failing row contains (Samopaly, 300, -1000.00).

Všimněte si, jaké si vymyslel Postgres pro „check constraint“ názvy.

Záznamy o Kulometech a Tarasnicích jsem vložil bez problémů. Splňovali všechny podmínky.
Pokus o zvýšení všech položek o 100 kusů neprošel, neboť jedna položka (Kulomety) by nesplnila sloupcové omezení pocet <= 500;. Příkaz UPDATE se neprovedl a nebyla změněná žádná řádka v databázi.

rimmer1=> SELECT * FROM zbozi;
   nazev   | pocet |  cena  
-----------+-------+---------
 Kulomety  |   500 | 1499.90
 Tarasnice |     0 |  500.00
 Samopaly  |   300 |        
(3 řádky)

Zajímavý je předposlední příkaz INSERT. Po jeho provedení existuje položka, jejíž cena nebyla udána a je tedy NULL. Ale v požadavcích na tabulku bylo, aby byla u každého zboží cena! Tento problém vyřeší podmínka NOT NULL.

Podmínka NOT NULL

Toto sloupcové omezení zabrání vytvoření sloupce v řádku s hodnotou NULL.

Problém s cenou z předchozího příkladu se vyřeší kombinací podmínek CHECK a NOT NULL. Název zboží i počet by také neměli být nikdy prázdné, takže i k nim přidám podmínku NOT NULL.
Tabulku vytvořím znova, proto ji nejdřív smažu:

DROP TABLE IF EXISTS zbozi;
CREATE TABLE zbozi (
        nazev VARCHAR(20) NOT NULL,
        pocet INTEGER CHECK (pocet >= 0 AND pocet <= 500) NOT NULL,
        cena NUMERIC(8,2) NOT NULL CHECK (cena >= 0)
);

Integritní omezení se píší za datovým typem, ale je jedno v jakém pořadí.

rimmer1=> INSERT INTO zbozi(nazev, pocet) VALUES ('Samopaly', 300);
ERROR:  null value in column "cena" violates not-null constraint
DETAIL:  Failing row contains (Samopaly, 300, null).

Podmínka UNIQUE

Podmínka UNIQUE zajišťuje, že v sloupci budete mít jen jedinečné hodnoty (pro všechny řádky tabulky). V sloupci tedy nebudou dvě hodnoty stejné, ale může tam být několik položek s hodnotou NULL (nepoužijete-li sloupcové omezení NOT NULL).

NULL totiž znamená „nevím co je tam za hodnotu“, NULL se ničemu nerovná, proto se bere jako unikátní hodnota.

Ukáži na příkladu, jak se dají sloupcové podmínky kombinovat. Řekněme, že chci mít tabulku, kam smí být proveden jen jeden zápis denně:

rimmer1=> CREATE TABLE zapis (
        jmeno_zadatele VARCHAR(20) NOT NULL,
        text_zapisu TEXT,
        datum_zapisu DATE UNIQUE NOT NULL CHECK (datum_zapisu >= current_date)
        DEFAULT current_date
);
NOTICE:  CREATE TABLE / UNIQUE will create implicit index 'zapis_datum_zapisu_key' for table 'zapis'
CREATE TABLE
Poznámka:  current_date je funkce, která vrací hodnotu aktuálního datumu. Funkcemi se budu zabývat později.

Index a DROP INDEX

Určitě jste si v předchozím příkladě všimli, že se kromě tabulky ještě vytvořil index zapis_datum_zapisu_key. Jak si Postgres vytvořil jeho jméno je asi jasné.

Co je index

Index je nepovinná datová struktura (databázový objekt) asociovaná s tabulkou. Hlavní cíle indexu jsou:

  1. Zajistit integritu dat
    • unikátnost (unique, primary key)
    • odkazy (references – cizí klíče)
  2. Umožnit rychlejší přístup k datům
    • optimalizace rychlosti provádění dotazů
    • snazší řazení dat dle obsahu sloupce

Zatím jediným databázovým objektem se kterým jste přímo pracovali byla tabulka. Tabulka je v databázích to nejdůležitější, kolem čeho se všechno točí. Ale existují i další databázové objekty, jako je například zmíněný index.

Index funguje podobně jako rejstřík na konci knihy. Umožní DBMS rychle najít v datech to, co potřebuje.

Index pro podmínku UNIQUE slouží k tomu, aby mohl Postgres kontrolovat, že jsou data v sloupci datum_zapisu jedinečná. Pomocí indexu DBMS rychle zjistí, zda nová vkládaná hodnota někde ve sloupci už je nebo ne. O to, jak přesně to DBMS s indexem dělá se nemusíte moc starat. Důležité je jen vědět, že index na jednu stranu urychluje práci při hledání dat, na druhou stranu zabírá místo na disku a zpomaluje INSERT a UPDATE (protože se musí aktualizovat nejen tabulka, ale i index). Z tohoto důvodu se nevytváří index u každého sloupečku, ale jenom tam, kde se dá očekávat, že bude potřeba.

Další využití indexů (krom UNIQUE) bude popsáno v dalších kapitolách.

Zrušení indexu

Příkaz pro zrušení indexu je jednoduchý:

DROP INDEX nazev_indexu;

Ze syntaxe DROP INDEX se dá uhádnout, že název indexu musí být unikátní pro celou databází (resp. schéma).

Indexy, které máte v databázi, zjistíte metapříkazem \di.

rimmer1=> \d zapis
                              Tabulka "public.zapis"
    Sloupec     |          Typ          |              Modifikátory              
----------------+-----------------------+-----------------------------------------
 jmeno_zadatele | character varying(20) | not null
 text_zapisu    | text                  |
 datum_zapisu   | date                  | not null implicitně ('now'::text)::date
Indexy:
    "zapis_datum_zapisu_key" UNIQUE CONSTRAINT, btree (datum_zapisu)
Kontrolní pravidla:
    "zapis_datum_zapisu_check" CHECK (datum_zapisu >= 'now'::text::date)
 
rimmer1=> \di
                        Seznam relací
 Schéma |         Jméno          |  Typ  | Vlastník | Tabulka
--------+------------------------+-------+----------+---------
 public | zapis_datum_zapisu_key | index | petr     | zapis
(1 řádka)

Pokud zrušíte index, podmínka UNIQUE bude z tabulky odstraněna.
Index nelze smazat, pokud existuje podmínka (constraint), která jej využívá.

rimmer1=> DROP INDEX zapis_datum_zapisu_key;
ERROR:  cannot drop index zapis_datum_zapisu_key because constraint zapis_datum_zapisu_key on table zapis requires it
DOPORUČENÍ:  You can drop constraint zapis_datum_zapisu_key on table zapis instead.

Jak se dá odstranit podmínka (drop constraint) proberu v kapitole o ALTER TABLE.

Smazáním tabulky se smažou i její indexy.

Další informace lze najít v odstavci o vytváření indexu.

MySQL/MariaDB

Typy tabulek

V kapitole o CREATE TABLE jsem vám zatajil o MySQL jednu důležitou věc. V MySQL existuje několik databázových „strojů“ (engine), které se starají o fyziké vykonávání SQL příkazů. Mezi nejpoužívanější patří MyISAM a InnoDB.

Engine MyISAM byl dříve defaultní. Je velmi rychlý rychle ukládá i načítá data, ale nevynucuje integritní omezení. Když vytvoříte tabulku s Engine = MyISAM, můžete sice uvádět integritní omezení, ale MyISAM je potichu ignoruje.

Engine InnoDB je pomalejší, protože naopak integritní omezení respektuje. Dnes je to v databázi defaultní engine (tj. když při vytváení tabulky neuvedete engine, použije se InnoDB). Defaultní engine se ale dá v databázi nastavit, takže se nemůžete na 100% spolehnout na to, který je defaultní. Proto je rozumné vždycky uvádět, že chcete InnoDB.

MySQL se hodně používá na webových hostinzích, které používali amatéři na své webové stránky. Ti většinou vůbec netušili nic o integritních omezeních, proto bylo zbytečné, aby vytvářeli tabulky s engine InnoDB. Navíc chtěli poskytovatelé ušetřit nějaký ten procesorový čas, proto bylo obyklé používat MyISAM. Dneska už kvalita železa natolik postoupila, že rozdíl v rychlostech enginů není až tak podstatný a mnohem důležitější je integrita dat v databázi.

Změny

V MySQL není funkce TO_DATE, takže INSERTy vypadají jednoduše takto:

INSERT INTO sportka(datum) VALUES ('2002-09-22');
INSERT INTO sportka(datum) VALUES ('2002-09-23');
INSERT INTO sportka(datum, vsazeno) VALUES ('2002-09-24', 120);

MySQL umí jako defaultní hodnotu aktuálního času použít jen s datovým typem TIMESTAMP, který ukládá datum i čas. Navíc je omezen jen na jednu takovou defaultní hodnotu na tabulku.
Tabulku sladky_rohlik můžete vytvořit takto:

CREATE TABLE sladky_rohlik (
    zavoznik VARCHAR(30) DEFAULT 'Tomáš Tlustý',
    pocet INTEGER DEFAULT 100,
    dovezeno TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    spotrebovat_do DATE DEFAULT 0
);

Do tabulky se pak musí hodnota do sloupce spotrebovat_do explicitně vložit, jinak v něm bude 0 ('0000-00-00').

mysql> INSERT INTO sladky_rohlik(pocet, spotrebovat_do)
VALUES(DEFAULT, DATE_ADD(NOW(), INTERVAL +5 DAY));
Query OK, 1 row affected, 1 warning (0.08 sec)

mysql> SHOW WARNINGS;
+-------+------+-----------------------------------------------------+
| Level | Code | Message                                             |
+-------+------+-----------------------------------------------------+
| Note  | 1265 | Data truncated for column 'spotrebovat_do' at row 1 |
+-------+------+-----------------------------------------------------+
1 row in set (0.00 sec)
Funkce NOW() vrací datum i čas. Při ukládání do tabulky se do sloupce spotrebovat_do typu DATE ukládá jen datum, takže čas je „oříznut“. Varováním se tak netřeba znepokojovat, je to úplně normální.
TIMESTAMP má ještě jednu zvláštnost. Pokud mu neurčíte defaultní hodnotu, bude mít automaticky jako defaultní hodnotu aktuální čas. Pokud bych chtěl mít v tabulce více typů TIMESTAMP, musí všechny, až na jeden, mít definovanou „statickou“ defaultní hodnotu (třeba '2015-12-24').

MySQL vám sice umožní zapsat podmínku CHECK, ale za 1. jí musíte uvést pří definici sloupce až jako poslední (to pro Postgres neplatí) a za 2. jí úplně ignoruje.

CREATE TABLE zbozi (
    nazev VARCHAR(20),
    pocet INTEGER CHECK (pocet >= 0 AND pocet >= 500),
    cena NUMERIC(8,2) CHECK (cena > 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci;

INSERT INTO zbozi VALUES ('Samopaly', 501, 1499.90);

INSERT nezpůsobí žádnou chybu.

Jak už jsem psal, CHECK se musí psát v MySQL až za ostatní integritní omezení (a MySQL ho ignoruje). (MySQL umožňuje zapsat CHECK čistě kvůli větší přenositelnosti SQL příkazů mezi různými databázemi.)

mysql> DROP TABLE IF EXISTS zbozi;
mysql> CREATE TABLE zbozi (
        nazev VARCHAR(20) NOT NULL,
        pocet INTEGER NOT NULL CHECK (pocet >= 0 AND pocet <= 500),
        cena NUMERIC(8,2) NOT NULL CHECK (cena > 0)
);

mysql> INSERT INTO zbozi(nazev, pocet) VALUES ('Samopaly', 300);
Query OK, 1 row affected, 1 warning (0.03 sec)

mysql> SHOW WARNINGS;
+---------+------+-------------------------------------------+
| Level   | Code | Message                                   |
+---------+------+-------------------------------------------+
| Warning | 1364 | Field 'cena' doesnt  have a default value |
+---------+------+-------------------------------------------+
1 row in set (0.00 sec)

mysql> select * from zbozi;
+----------+-------+------+
| nazev    | pocet | cena |
+----------+-------+------+
| Samopaly |   300 | 0.00 |
+----------+-------+------+
1 row in set (0.01 sec)

MySQL si stěžuje, že sloupeček cena nemá definovanou hodnotu. NULL má zakázáno, tak si domyslel 0.00.

Další změna definice tabulky je kvůli omezení TIMESTAMP (jen jeden sloupeček TIMESTAMP může mít v tabulce jako defaultní hodnotu aktuální čas). A CHECK se musí napsat až na konci integritních omezení …

CREATE TABLE zapis (
        jmeno_zadatele VARCHAR(20) NOT NULL,
        text_zapisu TEXT,
        datum_zapisu TIMESTAMP UNIQUE NOT NULL
                DEFAULT current_timestamp
                CHECK (datum_zapisu >= current_date)
);

SHOW CREATE TABLE zapis;
CREATE TABLE `zapis` (
  `jmeno_zadatele` varchar(20) COLLATE utf8mb4_czech_ci NOT NULL,
  `text_zapisu` text COLLATE utf8mb4_czech_ci,
  `datum_zapisu` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY `datum_zapisu` (`datum_zapisu`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci;

SHOW CREATE TABLE ukazuje dvě zajímavé věci. Za 1. zmizela podmínka CHECK (já vám to říkal že jí MySQL ignoruje) a za 2. podmínka UNIQUE se transformovala do indexu (UNIQUE KEY) jménem datum_zapisu (nad sloupečkem datum_zapisu, který je napsaný na konci v závorkách). Shodnost názvu indexu a jména sloupečku je čistě „náhodná“ (existuje způsob, jak si jméno indexu definovat, ale o tom až jindy).

V MySQL mají indexy platnost v rámci tabulky. Takže můžete mít indexy stejného jména, pokud se týkají různých tabulek. Z toho taky plyne, že když pracujete s indexy, musíte říct nejen jaký index (jeho jméno) ale i z jaké tabulky.

SHOW INDEXES FROM zapis;
-- výsledkem je jedna dlouhá řádka, takže jí sem celou nevypíšu.
-- zajímavý je jen sloupeček Key_name, kde najdete jméno indexu (datum_zapisu)
+-------+------------+--------------+--------------+--------------+-- ...
| Table | Non_unique | Key_name     | Seq_in_index | Column_name  | C ...
+-------+------------+--------------+--------------+--------------+-- ...
| zapis |          0 | datum_zapisu |            1 | datum_zapisu | A ...
+-------+------------+--------------+--------------+--------------+-- ...
 

Smazáním UNIQUE indexu zrušíte integritní omezení UNIQUE.

DROP INDEX datum_zapisu ON zapis;

SHOW CREATE TABLE zapis;
CREATE TABLE `zapis` (
  `jmeno_zadatele` varchar(20) COLLATE utf8mb4_czech_ci NOT NULL,
  `text_zapisu` text COLLATE utf8mb4_czech_ci,
  `datum_zapisu` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci

Všimněte si, že DROP INDEX zmiňuje nejen jaký index, ale i z jaké tabulky smazat.

SQLite

SQLite je na tom kupodivu o mnoho lépe než MySQL. Sice také nemá funkci TO_DATE, ale nedělá mu problém vytvořit dva sloupce s defaultní hodnotou “aktuální čas“ a dokonce rozumí podmínce CHECK! (Styď se MySQL, styď se!)

INSERT INTO sportka(datum) VALUES ('2002-09-22');
INSERT INTO sportka(datum) VALUES ('2002-09-23');
INSERT INTO sportka(datum, vsazeno) VALUES ('2002-09-24', 120);

Podmínka DEFAULT která obsahuje nějaký početní výraz musí být uzavřená v závorkách (ikdyž je to jedna funkce). (To se týká poslední DEFAULT podmínky v následujícím příkladě.)

CREATE TABLE sladky_rohlik (
        zavoznik VARCHAR(30) DEFAULT 'Tomáš Tlustý',
        pocet INTEGER DEFAULT 100,
        dovezeno DATE DEFAULT current_date,
        spotrebovat_do DATE DEFAULT (date(current_date,'+5 days'))
);

SQLite nerozumí klíčovému slovu DEFAULT při INSERTu (nebo UPDATu):

sqlite> INSERT INTO sladky_rohlik(pocet) VALUES(DEFAULT);
Error: near "DEFAULT": syntax error
sqlite> INSERT INTO sladky_rohlik(pocet) VALUES(100);
sqlite> select * FROM sladky_rohlik;
zavoznik         pocet       dovezeno    spotrebovat_do
---------------  ----------  ----------  --------------
Tomáš Tlustý  100         2013-11-28  2013-12-03

K zobrazení indexů z tabulky se používá metapříkaz .indices:

sqlite> .indices zapis
sqlite_autoindex_zapis_1

Neukončujte metapříkaz .indices středníkem, SQLite by si myslelo že je součást názvu tabulky.

K mazání indexů se používá DROP INDEX, ale nelze smazat index svázaný s integritním omezením UNIQUE (nebo primarním klíčem):

sqlite> DROP INDEX sqlite_autoindex_zapis_1;
Error: index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped

Oracle

U Oracle Database moc rozdílů tentokrát není. První drobností je, že DROP TABLE nemá část IF EXISTS.

DROP TABLE zbozi;

Jak si s tím poradit ukáži příště.

V Oracle neexistuje datový typ TEXT, místo něj použijte CLOB. A také byste všude měli používat datový typ VARCHAR2 místo VARCHAR, který je zastaralý.

Hlavním překvápkem je nemožnost použití current_date v podmínce CHECK. Oracle fylozofie je totiž taková, že podmínka CHECK musí pro data v tabulce být neustále pravdivá. Jenže když dáte do podmínky, že musí být datum větší než „dnešní“ datum, tak vložená hodnota může dnes podmínce vyhovět, ale za týden už ne.

Je to vcelku logický a správný požadavek, jen pro nás uživatele je to trošku pruda. Řešení sice existuje (TRIGGERY mohou automaticky vkládat do sloupečků hodnotu aktuálního času (resp. jakoukoliv hodnotu)), ale triggery jsou zatím nad rámec tohoto kurzu.

CREATE TABLE zapis (
    jmeno_zadatele VARCHAR2(20) NOT NULL,
    text_zapisu CLOB,
    datum_zapisu DATE UNIQUE NOT NULL
);

Vypsání indexů tabulky taky není v Oracle žádný med. Dá se to udělat následujícím SQL příkazem, který zatím nebudu vysvětlovat a vy se jej nesnažte pochopit (na JOIN příjde řada v některé z dalších kapitol).

SELECT user_tables.table_name, user_indexes.index_name,
user_ind_columns.column_name
FROM user_tables
JOIN user_indexes on user_indexes.table_name = user_tables.table_name
JOIN user_ind_columns ON user_indexes.index_name = user_ind_columns.index_name
WHERE user_tables.table_name = 'ZAPIS'
ORDER BY user_tables.table_name,user_indexes.index_name;

Všimněte si, že jméno tabulky zapis je v podmínce WHERE zapsáno velkými písmeny.

Zobrazení indexu

Zobrazení indexu v Oracle

Indexy mají platnost v rámci schématu, takže jejich mazání je stejné jako v PostgreSQL. A taky platí, že index pro UNIQUE omezení ani primární klíč nemůžete smazat (můžete odstranit podmínku UNIQUE nebo primární klíč a tím smazat i index, ale o tom zase až později).

oracle> DROP INDEX SYS_C007191;
ORA-02429: cannot drop index used for enforcement of unique/primary key

Oracle interně převádí prázdný řetězec '' na hodnotu NULL. Lze sice definovat sloupec takto:

sloupec VARCHAR2(255) DEFAULT '' NOT NULL,

Ale protože se '' převádí na NULL, nemůže se defaultní hodnota u NOT NULL sloupečku '' nikdy použít, čili je na prd. Nemůžete ani INSERTnout ''. Takže se v Oracle u textových sloupečků (CHAR, VARCHAR2, CLOB) podmínce NOT NULL raději vyhněte.

Oracle je přecitlivělý na pořadí integritních pomínek a defaultních hodnot. Takže:

-- toto funguje
sloupec VARCHAR2(255) DEFAULT '-' NOT NULL,
-- toto zahlásí chybu ORA-00907: missing right parenthesis
sloupec VARCHAR2(255) NOT NULL DEFAULT '-',
Komentář Hlášení chyby
Created: 11.9.2005
Last updated: 6.9.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..