Podmínkou pro získání zápočtu je vypracovat protokol obsahující:
Databázové jazyky mohou sloužit pro
CREATE, ALTER, DROP;INSERT, UPDATE, DELETE, SELECT;GRANT, REVOKE, BEGIN, COMMIT, ROLLBACK;Databázový jazyk musí obsahovat:
V historii měl hodně verzí. Mezi implementacemi se liší.
Všechno je strukturováno ve formě tabulek. Data jsou uložena nezávisle na formě tabulek.
INTEGER, BIGINT, SMALLINT, TINYINT, …FLOAT, DOUBLE PRECISION, …BOOLEANCHAR(n), VARCHAR(n), …DATE, TIME, DATETIME, TIMESTAMP, …TEXT, TINYTEXT, MEDIUMTEXT, LONGTEXTBLOB, TINYBLOB, MEDIUMBLOB, LONGBLOBNULL28, 31, 123, …'Lenka', 'Nováková', …'2013-02-25', '12:45:01', …+, -, *, /=, >, <, >=, <=, <>/!=, IS NULL, IS NOT NULL, BETWEEN AND, NOT BETWEEN AND, IN, NOT IN, LIKE, NOT LIKENOT, AND, OR||Přednost operátorů:
||- (unární)*, /+, -=, <>, >, <, >=, <=NOTANDORV některých dialektech SQL je možné vytvářet domény: vlastní datové typy určené omezením nějakého jiného typu. Vytváří se příkazem:
CREATE DOMAIN name
  AS type
  [DEFAULT value]
  [NOT NULL]
  [CHECK(condition)]
  [COLLATE collation];CREATE DOMAIN identification AS INTEGER NOT NULL CHECK(VALUE > 0);M/F CREATE DOMAIN sex AS CHAR(1) NOT NULL CHECK(VALUE IN ('M', 'F'));CREATE DOMAIN my_text AS VARCHAR(60) CHARACTER SET UTF8
  CHECK(VALUE NOT LIKE ' %' AND VALUE NOT LIKE '% ')
  COLLATE UTF8;Domény jde také měnit (ALTER DOMAIN) a mazat (DROP DOMAIN).
Tabulky se vytváří příkazem:
CREATE TABLE name (
  column_definitions
  [CONSTRAINT constraint] PRIMARY KEY (columns)
  [[CONSTRAINT constraint] UNIQUE (columns)]*
  [[CONSTRAINT constraint] FOREIGN KEY (columns)
    REFERENCES table [(columns)]
    [ON DELETE action]
    [ON UPDATE action]]*
  [, [CONSTRAINT constraint] CHECK column_constraint]*
);Sloupce jsou specifikovány jménem a typem/doménou. Místo typu je možné použít COMPUTED BY value, což vypočte hodnotu sloupce na základě jiných sloupců. Dají se přidat další věci:
DEFAULT valueNOT NULLPRIMARY KEYREFERENCES table [column]CHECK (condition)UNIQUEČást PRIMARY KEY specifikuje, které sloupce jsou primární klíč.
Části UNIQUE specifikují, které skupiny sloupců musí obsahovat dohromady různé hodnoty.
Tabulky jde také upravovat příkazem ALTER TABLE nebo odstraňovat příkazem DROP TABLE.
CREATE DOMAIN identification AS INTEGER NOT NULL CHECK(VALUE > 0);
CREATE DOMAIN valid_date AS DATE NOT NULL CHECK (VALUE >= '1900-01-01');
CREATE DOMAIN text35 AS VARCHAR(35) CHARACTER SET UTF8
  NOT NULL CHECK(VALUE NOT LIKE ' %' AND VALUE NOT LIKE '% ') COLLATE UTF8;
CREATE DOMAIN text20 AS VARCHAR(20) CHARACTER SET UTF8
  NOT NULL CHECK(VALUE NOT LIKE ' %' AND VALUE NOT LIKE '% ') COLLATE UTF8;
CREATE TABLE game_type (
  rowid identification,
  name text35,
  CONSTRAINT game_type_pk PRIMARY KEY(rowid),
  CONSTRAINT game_type_uniq_name UNIQUE (name)
);
CREATE TABLE game (
  rowid identification,
  name text35,
  version text20,
  released valid_date,
  type identification,
  CONSTRAINT game_pk PRIMARY KEY (rowid),
  CONSTRAINT game_uniq UNIQUE (name, version)
);
CREATE TABLE score (
  player identification,
  game identification,
  day valid_date,
  order identification,
  score identification,
  CONSTRAINT score_pk PRIMARY KEY (player, game, day, order)
);
CREATE TABLE player (
  rowid identification,
  name text20,
  surname text35,
  birthday valid_date,
  CONSTRAINT player_pk PRIMARY KEY (rowid)
);
ALTER TABLE game ADD CONSTRAINT game_fk_type
  FOREIGN KEY (type) REFERENCES game_type(rowid) ON UPDATE CASCADE;
ALTER TABLE score ADD CONSTRAINT score_fk_player
  FOREIGN KEY (player) REFERENCES player(rowid) ON UPDATE CASCADE;
ALTER TABLE score ADD CONSTRAINT score_fk_game
  FOREIGN KEY (game) REFERENCES game(rowid) ON UPDATE CASCADE;CREATE TABLE game_type (name, CONSTRAINT game_type_uniq_name UNIQUE (name));
CREATE TABLE game (name, version, released, type, CONSTRAINT game_uniq UNIQUE (name, version), CONSTRAINT game_fk_type FOREIGN KEY (type) REFERENCES game_type(rowid) ON UPDATE CASCADE);
CREATE TABLE score (player, game, day, ord, score, CONSTRAINT score_pk PRIMARY KEY (player, game, day, ord), CONSTRAINT score_fk_game FOREIGN KEY (game) REFERENCES game(rowid) ON UPDATE CASCADE, CONSTRAINT game_fk_player FOREIGN KEY (player) REFERENCES player(rowid) ON UPDATE CASCADE);
CREATE TABLE player (name, surname, birthday);INSERT INTO table [(columns)] VALUES (values);Pokud není uveden seznam sloupců, berou se v pořadí, v jakém byly deklarovány. Pokud má sloupec definovanou výchozí hodnotu nebo povoluje prázdné hodnoty, je možné ho vynechat.
Vložení selže, pokud by byla porušena doménová, entitní nebo referenční integrita.
V některých implementacích je také možné „nasypat“ data do tabulky z jiné tabulky (místo VALUES se napíše příkaz SELECT) nebo přidat více řádků najednou (více závorek za VALUES oddělených čárkou).
Některé implementace umožňují automatickou generaci primárního klíče zvyšováním čísla.
CREATE SEQUENCE g_player;
CREATE SEQUENCE g_game;
CREATE SEQUENCE g_game_type;
INSERT INTO game_type VALUES (NEXT VALUE FOR g_game_type, 'puzzle');
INSERT INTO game_type VALUES (NEXT VALUE FOR g_game_type, 'action');
INSERT INTO game_type VALUES (NEXT VALUE FOR g_game_type, 'strategy');
INSERT INTO game_type VALUES (NEXT VALUE FOR g_game_type, 'adventure');
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Tic-Tac-Toe', '8.4', '2009-06-23', 1);
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Zoo Tycoon', '2', '2008-04-17', 3);
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Sudoku', '1.01', '2008-05-02', 1);
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Doom', '1.9', '2002-09-02', 2);
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Paintball', '2', '2006-07-29', 2);
INSERT INTO game VALUES (NEXT VALUE FOR g_game, 'Age of Empires', '2', '2000-12-06', 3);
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'Petr', 'Synek', '2000-11-20');
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'Ivana', 'Malá', '1999-10-10');
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'René', 'Nový', '2002-05-31');
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'Pavel', 'Srb', '2003-10-30');
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'Petr', 'Klíč', '2001-11-16');
INSERT INTO player VALUES (NEXT VALUE FOR g_player, 'Zita', 'Stará', '2001-11-03');
INSERT INTO score VALUES (1, 3, CAST('today' AS DATE), 1, 4021);      -- OK
INSERT INTO score VALUES (1, 3, CAST('today' AS DATE), 2, 4093);      -- OK
INSERT INTO score VALUES (3, 8, CAST('today' AS DATE) - 4, 1, 3245);  -- chyba referenční integrity
INSERT INTO score VALUES (3, 3, 2010, 4, 4213);                       -- chyba doménové integrity
INSERT INTO score VALUES (1, 4, '2022-02-30', 1, 17850);              -- chyba doménové integrity
INSERT INTO score VALUES (1, 4, CAST('today' AS DATE) - 1, 1, 17850); -- OK
INSERT INTO score VALUES (1, 4, CAST('today' AS DATE) - 1, 1, 25321); -- chyba entitní integrity
INSERT INTO score VALUES (1, 4, CAST('today' AS DATE) - 1, 2, 25321); -- OK
INSERT INTO score VALUES (3, 3, CAST('today' AS DATE), 1, 3245);      -- OK
INSERT INTO score VALUES (3, 3, CAST('today' AS DATE), 2, 4137);      -- OK
INSERT INTO score VALUES (3, 3, CAST('today' AS DATE), 3, 3981);      -- OK
INSERT INTO score VALUES (6, 3, CAST('today' AS DATE) - 3, 1, 3401);  -- OK
INSERT INTO score VALUES (6, 3, CAST('today' AS DATE) - 3, 2, 3998);  -- OK
INSERT INTO score VALUES (6, 3, CAST('today' AS DATE) - 3, 3, 3728);  -- OK
INSERT INTO score VALUES (6, 3, CAST('today' AS DATE) - 3, 4, 4137);  -- OK
INSERT INTO score VALUES (2, 3, CAST('today' AS DATE) - 5, 1, 1891);  -- OK
INSERT INTO score VALUES (2, 3, CAST('today' AS DATE) - 5, 2, 4150);  -- OKINSERT INTO game_type VALUES ('puzzle');
INSERT INTO game_type VALUES ('action');
INSERT INTO game_type VALUES ('strategy');
INSERT INTO game_type VALUES ('adventure');
INSERT INTO game VALUES ('Tic-Tac-Toe', '8.4', '2009-06-23', 1);
INSERT INTO game VALUES ('Zoo Tycoon', '2', '2008-04-17', 3);
INSERT INTO game VALUES ('Sudoku', '1.01', '2008-05-02', 1);
INSERT INTO game VALUES ('Doom', '1.9', '2002-09-02', 2);
INSERT INTO game VALUES ('Paintball', '2', '2006-07-29', 2);
INSERT INTO game VALUES ('Age of Empires', '2', '2000-12-06', 3);
INSERT INTO player VALUES ('Petr', 'Synek', '2000-11-20');
INSERT INTO player VALUES ('Ivana', 'Malá', '1999-10-10');
INSERT INTO player VALUES ('René', 'Nový', '2002-05-31');
INSERT INTO player VALUES ('Pavel', 'Srb', '2003-10-30');
INSERT INTO player VALUES ('Petr', 'Klíč', '2001-11-16');
INSERT INTO player VALUES ('Zita', 'Stará', '2001-11-03');
INSERT INTO score VALUES (1, 3, DATE('now'), 1, 4021);
INSERT INTO score VALUES (1, 3, DATE('now'), 2, 4093);
INSERT INTO score VALUES (1, 4, DATE('now', '-1 day'), 1, 17850);
INSERT INTO score VALUES (1, 4, DATE('now', '-1 day'), 2, 25321);
INSERT INTO score VALUES (3, 3, DATE('now'), 1, 3245);
INSERT INTO score VALUES (3, 3, DATE('now'), 2, 4137);
INSERT INTO score VALUES (3, 3, DATE('now'), 3, 3981);
INSERT INTO score VALUES (6, 3, DATE('now', '-3 day'), 1, 3401);
INSERT INTO score VALUES (6, 3, DATE('now', '-3 day'), 2, 3998);
INSERT INTO score VALUES (6, 3, DATE('now', '-3 day'), 3, 3728);
INSERT INTO score VALUES (6, 3, DATE('now', '-3 day'), 4, 4137);
INSERT INTO score VALUES (2, 3, DATE('now', '-5 day'), 1, 1891);
INSERT INTO score VALUES (2, 3, DATE('now', '-5 day'), 2, 4150);UPDATE table SET column1=value1 [, columnK=valueK]* [WHERE condition]Použijeme-li WHERE, změní se jen ty řádky, které splňují podmínku. Pokud podmínku neuvedeme, změní se všechny řádky tabulky.
DELETE FROM table [WHERE condition]Smažou se všechny řádky vyhovující podmínce. Vynecháme-li podmínku, smažou se úplně všechny.
SELECT [DISTINCT | ALL] projection
  FROM source
  [WHERE restriction]
  [GROUP BY aggregation [HAVING restriction]]
  [ORDER BY criterion]
  [row_limit];Výsledkem dotazu je vždy tabulka. Počet sloupců závisí na projekci, počet řádků na restrikcích.
Specifikujeme-li DISTINCT, duplicitní záznamy jsou vynechány.
SELECT DISTINCT CAST('today' AS DATE) FROM game_type AS today;SELECT DATE('now') AS today;SELECT DISTINCT name FROM player ORDER BY name;SELECT * FROM player ORDER BY surname;SELECT * FROM player ORDER BY birthday DESC, surname;SELECT EXTRACT(YEAR FROM birthday) AS birth_year, name, surname FROM player ORDER BY birthday DESC, surname;SELECT strftime('%Y', birthday) AS birth_year, name, surname FROM player ORDER BY birthday DESC, surname;SELECT (CAST('today' AS DATE) - birthday)/365.25 AS age, name, surname FROM player ORDER BY birthday DESC, surname;SELECT (julianday() - julianday(birthday))/365.25 AS age, name, surname FROM player ORDER BY birthday DESC, surname;SELECT DISTINCT player FROM score ORDER BY player;SELECT DISTINCT game FROM score ORDER BY game;SELECT MIN(day) AS first_day FROM score;SELECT MAX(day) AS first_day FROM score;SELECT name, version, rowid FROM game WHERE released >= '2006-01-01' ORDER BY released DESC;SELECT name, version, rowid, type FROM game WHERE type = 1 ORDER BY name DESC;table0 CROSS JOIN table1table0 [INNER] JOIN table1 ON conditionPodmínka slouží k realizaci vztahu mezi tabulkami.
Lze spojit jen dvě tabulky, při větším množství je potřeba více JOIN klauzulí.
Systém si sám vybere, jaký algoritmus pro spojení použije. V případě, že je to nutné specifikovat ručně, existuje rozšířená syntaxe.
table0 (LEFT | RIGHT | FULL) [OUTER] JOIN table1 ON conditionSELECT DISTINCT game.rowid, game.name FROM game JOIN score ON score.game = game.rowid WHERE player = 1 ORDER BY game.name;SELECT player.* FROM player LEFT JOIN score ON score.player = player.rowid WHERE score.game IS NULL ORDER BY player.surname, player.name, player.birthday;SELECT game.* FROM game LEFT JOIN score ON score.game = game.rowid WHERE score.player IS NULL AND game.released < '2008-01-01';SELECT game.*, game_type.name FROM game JOIN game_type ON game.type = game_type.rowid;SELECT score, game.name, player.name, player.surname FROM score JOIN game ON score.game = game.rowid JOIN player ON score.player = player.rowid ORDER BY score.day, score.player, score.ord;SELECT DISTINCT player.rowid, player.name, player.surname, game.rowid, game.name FROM player LEFT JOIN score ON score.player = player.rowid LEFT JOIN game ON score.game = game.rowid ORDER BY player.surname, player.name;Agregační funkce pracují s celou množinou záznamů a vracejí jednu hodnotu. Prázdné hodnoty jsou ignorovány (s výjimkou funkce COUNT). Pro prázdný vstup vracejí NULL.
Použitím modifikátoru DISTINCT je možné zpracovat pouze různé hodnoty.
func([DISTINCT | ALL] expr)COUNTSUMAVGMINMAXMějme tabulku zaměstnanců, kde jsou uvedeny jejich platy s hodnotami 12540, 13820, 11958, 13820, 14051, 19085, 20340, 13820, 14051:
CREATE TABLE employee(name, salary);
INSERT INTO employee VALUES ('Agáta', 12540), ('Bořek', 13820), ('Cindy', 11958), ('David', 13820), ('Ema', 14051), ('Filip', 19085), ('Gabriela', 20340), ('Hynek', 13820), ('Iva', 14051);Poté na ní můžeme vyzkoušet různé agregační funkce:
SELECT COUNT(salary), COUNT(DISTINCT salary), SUM(salary), SUM(DISTINCT salary), AVG(salary), AVG(DISTINCT salary), MIN(salary), MIN(DISTINCT salary), MAX(salary), MAX(DISTINCT salary) FROM employee;Agregační funkce se dají využívat:
SELECT;WHERE a HAVING příkazu SELECT;ORDER BY příkazu SELECT;Klauzule GROUP BY vytvoří „souhrnné“ řádky, kde každý obsahuje informace o všech původních řádcích, které mají nějaké dané atributy stejné.
K získání skalární hodnoty v ostatních sloupcích je nutné použít agregační funkce.
Podmínka s použitím WHERE se týká původních dat. Pokud chceme nějak filtrovat podle seskupených dat, musíme použít HAVING.
SELECT (SELECT COUNT(*) FROM game_type) as game_type_count, (SELECT COUNT(*) FROM game) as game_count, (SELECT COUNT(*) FROM player) as player_count, (SELECT COUNT(*) FROM score) as score_count;SELECT player FROM score GROUP BY player HAVING COUNT(DISTINCT game) >= 2;SELECT player.rowid, player.name, player.surname, COUNT(DISTINCT game) as game_count FROM player JOIN score ON score.player = player.rowid GROUP BY player.rowid ORDER BY game_count DESC, surname;SELECT player.rowid, player.name, player.surname, COUNT(DISTINCT game) as game_count FROM player LEFT JOIN score ON score.player = player.rowid GROUP BY player.rowid ORDER BY game_count DESC, surname;SELECT player.rowid, player.name, player.surname, game.rowid, game.name, COUNT(DISTINCT day) AS day_count FROM player JOIN score ON score.player = player.rowid JOIN game ON score.game = game.rowid GROUP BY player.rowid, game.rowid ORDER BY player.surname, game.name;SELECT player.rowid, player.name, player.surname, game.rowid, game.name, COUNT(DISTINCT day) AS day_count, MAX(day) AS last_day FROM player JOIN score ON score.player = player.rowid JOIN game ON score.game = game.rowid GROUP BY player.rowid, game.rowid ORDER BY player.surname, game.name;SELECT game.rowid, game.name, COUNT(*) AS play_count FROM game JOIN score ON score.game = game.rowid GROUP BY game.rowid HAVING play_count >= 5 ORDER BY game.name;SELECT game.rowid, game.name, COUNT(DISTINCT player) AS player_count FROM game JOIN score ON score.game = game.rowid GROUP BY game.rowid HAVING player_count >= 2 ORDER BY game.name;SELECT game.rowid, game.name, score.player, COUNT(*) AS play_count FROM game JOIN score ON score.game = game.rowid GROUP BY game.rowid, score.player HAVING play_count >= 2 ORDER BY game.name, score.player;SELECT day, COUNT(DISTINCT game) as game_count FROM score GROUP BY day ORDER BY day DESC;SELECT player.rowid, player.name, player.surname, player.birthday, game.rowid, game.name, AVG(score.score) AS average_score FROM player LEFT JOIN score ON score.player = player.rowid LEFT JOIN game ON score.game = game.rowid GROUP BY player.rowid, game.rowid;Do jistých příkazů je možné vnořit příkaz SELECT. Konkrétně pokud vnořujeme do dalšího příkazu SELECT, můžeme ho použít v podmínce WHERE, v projekci nebo ve zdroji dat (odvozená tabulka). Poddotazy podporují jen některé systémy.
SELECT rowid, name, surname FROM player WHERE rowid NOT IN (SELECT player FROM score) ORDER BY surname, name, birthday;INSERT INTO game VALUES (NEXT VALUE FOR g_hra, 'Zuma Deluxe', '1.0', '2010-07-07', (SELECT rowid FROM game_type WHERE name='puzzle'));
SELECT * FROM game;INSERT INTO game VALUES ('Zuma Deluxe', '1.0', '2010-07-07', (SELECT rowid FROM game_type WHERE name='puzzle'));
SELECT * FROM game;Co můžeme s výsledkem poddotazu dělat, závisí na jeho tvaru:
EXISTS a NOT EXISTS.ANY, SOME, IN, NOT IN, ALL, EXISTS, NOT EXISTS. Například 0 IN (SELECT something) je ekvivalentní 0 = SOME(SELECT something).WHERE nejde použít.WHERE nejde použít.Poddotaz musí být v kulatých závorkách. Uvnitř WHERE se musí psát napravo od operátoru. Některé poddotazy lze přeformulovat jako spojení, což většinou zvýší efektivitu.
Poddotaz může používat atributy z vnějšího dotazu, čímž vznikne korelovaný poddotaz. Ten ovšem může být hodně pomalý, protože se musí provádět zvlášť pro každý řádek vnějšího dotazu. Zato vnější dotaz nesmí používat atributy z vnitřního dotazu. Dřív byly korelované dotazy hodně významné, ale dnes má SQL nové funkcionality, které většinou umožňují se jim vyhnout.
SELECT rowid, name, surname, (SELECT COUNT(DISTINCT game) FROM score WHERE player = player.rowid) AS game_count FROM player ORDER BY surname, name, birthday;JOIN a GROUP BY.SELECT * FROM game WHERE rowid NOT IN (SELECT game FROM score) ORDER BY name;SELECT game.* FROM game LEFT JOIN score ON score.game = game.rowid WHERE player IS NULL;SELECT rowid, name, released FROM game WHERE released = (SELECT MIN(released) FROM game) ORDER BY name;SELECT rowid, name FROM game_type WHERE 3 <= (SELECT COUNT(*) FROM game WHERE type = game_type.rowid) ORDER BY name;select1 UNION [ALL] select2 (UNION [ALL] selectk)*Není-li použito ALL, sjednocení vynechá duplicitní řádky.
Všechny dotazy musí mít stejný počet a typy sloupců. Názvy se řídí podle prvního dotazu.
Sjednocení lze seřadit přidáním ORDER BY za poslední dotaz.
CREATE VIEW name [(columns)]
AS select_query
[WITH CHECK OPTION];Jméno pohledu musí být unikátní v rámci tabulek a pohledů.
Neuvedeme-li názvy sloupců, použijí se ty z dotazu. Je nutné je uvést, pokud v dotazu chybí aliasy pro některé vypočtené výrazy.
V pohledu není efektivní používat ORDER BY. Lepší je seřadit až výsledek po použití pohledu.
Klauzuli WITH CHECK OPTION můžeme použít u aktualizovatelných pohledů, pokud nechceme, aby do tabulky vkládaly řádky, které nesplňují vyhledávací podmínku.
Definici pohledu lze měnit:
ALTER VIEW name [(columns)]
AS select_query
[WITH CHECK OPTION];Pohled lze také odstranit příkazem DROP VIEW name;.
Pohled je aktualizovatelný (lze ho použít také k úpravě tabulky), pokud
NOT NULL a nemají výchozí hodnotu;CREATE VIEW score_all
AS SELECT player.name AS player_name, surname, birthday, game.name AS game_name, game.version, game.released, game_type.name AS type, score.day, score.ord, score.score
FROM score JOIN player ON score.player = player.rowid JOIN game ON score.game = game.rowid JOIN game_type ON game.type = game_type.rowid;
SELECT * FROM score_all ORDER BY player_name, game_name, day, ord;CREATE VIEW game_stats
AS SELECT game.name, game.version, game.released, game_type.name AS type, MAX(score) AS max_score, MIN(score) AS min_score, AVG(score) AS avg_score, COUNT(*) as play_count, COUNT(DISTINCT player) AS player_count, MIN(day) AS first_played, MAX(day) AS last_played
FROM game JOIN score ON score.game = game.rowid JOIN game_type ON game.type = game_type.rowid
GROUP BY game;
SELECT * FROM game_stats ORDER BY name;