СУБД. Лекция 3

СУБД
Навроцкий Артем
Лекция 3

Выборка данных (продолжение)

Регистронезависимый поиск

Регистронезависимый поиск

COLLATION

Простая сортировка обычно даёт не тот результат, который ожидается пользователем.

ucs_basic (C) en_US.utf8 (ISO-14651) ru_RU.utf8
_ель ґвалт джаз
Есенин джаз ґвалт
джаз ель ель
ель _ель _ель
жаркое Есенин Есенин
ёжик ёжик ёжик
ґвалт жаркое жаркое
http://collation-charts.org/mysql60/mysql604.utf8_general_ci.european.html

COLLATION

В разных языках разные преобразования в верхний/нижний регистр

SELECT
    LOWER('WINDOWS' COLLATE "en_US.utf8"),
    UPPER('linux' COLLATE "en_US.utf8")
lower upper
windows LINUX
SELECT
    LOWER('WINDOWS' COLLATE "tr_TR.utf8"),
    UPPER('linux' COLLATE "tr_TR.utf8")
lower upper
wındows LİNUX

COLLATION

В PostgreSQL поддержка COLLATION реализована на базе C-функции strcoll_l.

Это порождает ряд проблем:

С PostgreSQL 10 (окт 2019) появилась поддержка COLLATION на базе библиотеки ICU.

LOWER(name)

CREATE TABLE person (
    id SERIAL PRIMARY KEY,
    name TEXT COLLATE "ru_RU.utf8",
    ...
);
CREATE UNIQUE INDEX uniq_name ON person (LOWER(name));

SELECT * FROM person WHERE LOWER(name) = LOWER('Jack Sparrow');

Проблемы:

CITEXT

CREATE EXTENSION IF NOT EXISTS citext;
CREATE TABLE person (
    id SERIAL PRIMARY KEY,
    name CITEXT COLLATE "ru_RU.utf8",
    ...
);
CREATE UNIQUE INDEX uniq_name ON person (name);

SELECT * FROM person WHERE name = 'Jack Sparrow';

Выборка данных

SELECT

[ WITH [ RECURSIVE ] with_query [, ...] ]
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
    [ * | expression [ [ AS ] output_name ] [, ...] ]
    [ FROM from_item [, ...] ]
    [ WHERE condition ]
    [ GROUP BY grouping_element [, ...] ]
    [ HAVING condition [, ...] ]
    [ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ]
    [ ORDER BY expression [ ASC | DESC | USING operator ] [, ...] ]
    [ LIMIT { count | ALL } ]
    [ OFFSET start [ ROW | ROWS ] ]
    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
    [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE }
        [ OF table_name [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]

SUBQUERIES


SELECT
    col0,
    (SELECT col1 FROM table1 WHERE table1.id = table0.id),
    (SELECT col2 FROM table1 WHERE table1.id = table0.id)
FROM
    table0;

SELECT *
FROM t1
WHERE column1 = (SELECT MAX(column2) FROM t2);

SELECT *
FROM t1 AS t
WHERE 2 = (SELECT COUNT(*) FROM t1 WHERE t1.id = t.id);

SUBQUERIES: Expressions

SELECT s1 FROM t1 WHERE s1 > ANY (SELECT s1 FROM t2);
 
SELECT s1 FROM t1 WHERE s1 = ANY (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 IN (SELECT s1 FROM t2);
 
SELECT s1 FROM t1 WHERE s1 > ALL (SELECT s1 FROM t2);
 
SELECT s1 FROM t1 WHERE s1 <> ALL (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 NOT IN (SELECT s1 FROM t2);

ROW SUBQUERIES

SELECT *
FROM t1
WHERE (col1, col2)
	= (SELECT col3, col4 FROM t2 WHERE id = 10);
 
SELECT *
FROM t1
WHERE ROW (col1, col2)
	= (SELECT col3, col4 FROM t2 WHERE id = 10);

SELECT column1, column2, column3
FROM t1
WHERE (column1, column2, column3)
    IN (SELECT column1, column2, column3 FROM t2);

[NOT] EXISTS

SELECT DISTINCT store_type
FROM stores
WHERE EXISTS (
    SELECT *
    FROM cities_stores
    WHERE cities_stores.store_type = stores.store_type
);
 
SELECT DISTINCT store_type
FROM stores
WHERE NOT EXISTS (
    SELECT *
    FROM cities_stores
    WHERE cities_stores.store_type = stores.store_type
);

SUBQUERIES in FROM

SELECT ... FROM (subquery) [AS] name ...
 
SELECT AVG(sum_column1)
FROM (
    SELECT SUM(column1) AS sum_column1
    FROM t1
    GROUP BY column1
) AS t1;

Оконные функции

SELECT
  ROW_NUMBER() OVER (ORDER BY age ASC) AS rownumber,
  person_id, person_name, age
FROM person;

SELECT
  ROW_NUMBER() OVER w AS rownumber,
  RANK() OVER w AS ranking,
  person_id, person_name, age
FROM person
WINDOW w AS (ORDER BY age ASC);

SELECT
  count(*) AS unfiltered,
  count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);

Оконные функции

Функция Описание
row_number() номер текущей строки в её разделе, начиная с 1
rank() ранг текущей строки с пропусками; то же, что и row_number для первой родственной ей строки
dense_rank() ранг текущей строки без пропусков; эта функция считает группы родственных строк
percent_rank() относительный ранг текущей строки:
(rank - 1) / (общее число строк - 1)
first_value(any) возвращает значение, вычисленное для первой строки в рамке окна
last_value(any) возвращает значение, вычисленное для последней строки в рамке окна
nth_value(any, n) возвращает значение, вычисленное в н-ой строке в рамке окна (считая с 1), или NULL, если такой строки нет.

Оконные функции

SELECT DISTINCT
  department_id,
  FIRST_VALUE(person_name) OVER w,
  FIRST_VALUE(age) OVER w
FROM person
WINDOW w as (PARTITION BY department_id);

SELECT DISTINCT ON (department_id)
  department_id,
  person_name,
  age
FROM person;

UNION

SELECT ...
UNION [ALL | DISTINCT]
SELECT ...
[UNION [ALL | DISTINCT]
SELECT ...]
person amount
Joe 1000
Alex 2000
Bob 5000
person amount
Joe 2000
Alex 2000
Zach 35000
SELECT * FROM sales2010
UNION
SELECT * FROM sales2011;
person amount
Alex 2000
Bob 5000
Joe 1000
Joe 2000
Zach 35000
SELECT * FROM sales2010
UNION ALL
SELECT * FROM sales2011;
person amount
Joe 1000
Alex 2000
Bob 5000
Joe 2000
Alex 2000
Zach 35000

Объединения таблиц (JOIN)

Набор данных

Employee
LastName DepartmentID
Rafferty 31
Jones 33
Steinberg 33
Robinson 34
Smith 34
John
Department
DepartmentID DepartmentName
31 Sales
33 Engineering
34 Clerical
35 Marketing

CROSS JOIN

SELECT * FROM Employee CROSS JOIN Department;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 31 Sales
Steinberg 33 31 Sales
Robinson 34 31 Sales
Smith 34 31 Sales
John 31 Sales
Rafferty 31 33 Engineering
Jones 33 33 Engineering
Steinberg 33 33 Engineering
Robinson 34 33 Engineering
Smith 34 33 Engineering
John 33 Engineering
Rafferty 31 34 Clerical
Jones 33 34 Clerical
Steinberg 33 34 Clerical
Robinson 34 34 Clerical
Smith 34 34 Clerical
John 34 Clerical
Rafferty 31 35 Marketing
Jones 33 35 Marketing
Steinberg 33 35 Marketing
Robinson 34 35 Marketing
Smith 34 35 Marketing
John 35 Marketing

CROSS JOIN

SELECT * FROM Employee, Department;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 31 Sales
Steinberg 33 31 Sales
Robinson 34 31 Sales
Smith 34 31 Sales
John 31 Sales
Rafferty 31 33 Engineering
Jones 33 33 Engineering
Steinberg 33 33 Engineering
Robinson 34 33 Engineering
Smith 34 33 Engineering
John 33 Engineering
Rafferty 31 34 Clerical
Jones 33 34 Clerical
Steinberg 33 34 Clerical
Robinson 34 34 Clerical
Smith 34 34 Clerical
John 34 Clerical
Rafferty 31 35 Marketing
Jones 33 35 Marketing
Steinberg 33 35 Marketing
Robinson 34 35 Marketing
Smith 34 35 Marketing
John 35 Marketing

INNER JOIN

SELECT *
FROM Employee
INNER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 33 Engineering
Steinberg 33 33 Engineering
Robinson 34 34 Clerical
Smith 34 34 Clerical

INNER JOIN

SELECT *
FROM Employee, Department
WHERE Employee.DepartmentID = Department.DepartmentID;

Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 33 Engineering
Steinberg 33 33 Engineering
Robinson 34 34 Clerical
Smith 34 34 Clerical

NATURAL JOIN

SELECT *
FROM Employee
NATURAL JOIN Department;

DepartmentID Employee.
LastName
Department.
DepartmentName
31 Rafferty Sales
33 Jones Engineering
33 Steinberg Engineering
34 Robinson Clerical
34 Smith Clerical

JOIN USING

SELECT *
FROM Employee
JOIN Department USING (DepartmentID);

DepartmentID Employee.
LastName
Department.
DepartmentName
31 Rafferty Sales
33 Jones Engineering
33 Steinberg Engineering
34 Robinson Clerical
34 Smith Clerical

LEFT OUTER JOIN

SELECT *
FROM Employee
LEFT OUTER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 33 Engineering
Steinberg 33 33 Engineering
John
Robinson 34 34 Clerical
Smith 34 34 Clerical

RIGHT OUTER JOIN

SELECT Employee.*, Department.*
FROM Department
RIGHT OUTER JOIN Employee
ON Employee.DepartmentID = Department.DepartmentID;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 33 Engineering
Steinberg 33 33 Engineering
John
Robinson 34 34 Clerical
Smith 34 34 Clerical

FULL OUTER JOIN

SELECT *
FROM Employee
FULL OUTER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID;
Employee.
LastName
Employee.
DepartmentID
Department.
DepartmentID
Department.
DepartmentName
Rafferty 31 31 Sales
Jones 33 33 Engineering
Steinberg 33 33 Engineering
John
Robinson 34 34 Clerical
Smith 34 34 Clerical
35 Marketing

FULL OUTER JOIN

SELECT *
FROM Employee
FULL OUTER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID;

SELECT *
FROM Employee
LEFT OUTER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID;
 
UNION ALL
 
SELECT *
FROM Employee
RIGHT OUTER JOIN Department
ON Employee.DepartmentID = Department.DepartmentID
WHERE Employee.DepartmentID IS NULL;

SELF-JOIN

SELECT F.EmployeeID, F.LastName, S.EmployeeID, S.LastName, F.Country
FROM Employee F
INNER JOIN Employee S ON F.Country = S.Country
WHERE F.EmployeeID < S.EmployeeID
ORDER BY F.EmployeeID, S.EmployeeID;
EmployeeID LastName Country DepartmentID
123 Rafferty 31 Australia
124 Jones 33 Australia
145 Steinberg 33 Australia
201 Robinson 34 United States
305 Smith 34 Germany
306 John Germany

SELF-JOIN

EmployeeID LastName DepartmentID Country
123 Rafferty 31 Australia
124 Jones 33 Australia
145 Steinberg 33 Australia
201 Robinson 34 United States
305 Smith 34 Germany
306 John Germany
F.EmployeeID F.LastName S.EmployeeID S.LastName F.Country
123 Rafferty 124 Jones Australia
123 Rafferty 145 Steinberg Australia
124 Jones 145 Steinberg Australia
305 Smith 306 John Germany

SUBQUERIES vs JOIN

Коррелирующий подзапрос

SELECT E.*
FROM Employee E WHERE EXISTS (
  SELECT *
  FROM Department D WHERE D.DepartmentID = E.DepartmentID
);

Не коррелирующий подзапрос

SELECT E.*
FROM Employee E WHERE E.DepartmentID IN (
  SELECT DepartmentID
  FROM Department D
);

JOIN

SELECT E.*
FROM Employee E
JOIN Department D ON (E.DepartmentID = D.DepartmentID);

FAKE TABLE

n
1
2
3
4
5
SELECT 1 AS n
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5;

FAKE TABLE

n
1
2
3
4
5
WITH RECURSIVE series(n) AS (
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM series WHERE n < 5
)
SELECT * FROM series ORDER BY n;

SELECT n FROM unnest(ARRAY[
    1, 2, 3, 4, 5
]) n ORDER BY n;

SELECT n
FROM generate_series(1, 5) n;

FAKE TABLE

a b
1 foo
2 bar
3 baz
SELECT * FROM unnest(ARRAY[
    1, 2, 3
], ARRAY[
    'foo', 'bar', 'baz'
]) n (a, b) ORDER BY a;

SELECT * FROM (
  VALUES (1, 'foo'), (2, 'bar'), (3, 'baz')
) n (a, b)

Получение идентификаторов до вставки

CREATE TABLE city (
    id bigserial NOT NULL PRIMARY KEY,
    region_id bigint NOT NULL,
    name varchar(128) NOT NULL
);

SELECT nextval(pg_get_serial_sequence('city', 'id'));

SELECT nextval(pg_get_serial_sequence('city', 'id'))
FROM generate_series(1, 100);

SELF UNION

SELECT col1 FROM tbl
UNION ALL
SELECT col2 FROM tbl;

SELECT unnest(array[col1, col2]) as col1 FROM tbl;

WITH RECURSIVE

WITH RECURSIVE recursetree (id, path, name) AS (
    SELECT id, array[id], name FROM tree
    WHERE parent_id = 0
  UNION ALL
    SELECT t.id, array_append(path, t.id), t.name
    FROM tree t
    JOIN recursetree rt ON rt.id = t.parent_id
  )
SELECT id, array_to_string(path, '.') as path, name FROM recursetree
ORDER BY path;
id path name
1 1 Россия
3 1.3 Москва
6 1.3.6 Бутово
2 2 США
4 2.4 Нью Йорк
5 2.5 Вашингтон

Еще немного про WITH

Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.

CREATE TABLE avatar (
    id        SERIAL PRIMARY KEY,
    name      VARCHAR(64)
);
CREATE TABLE quest (
    id        BIGSERIAL PRIMARY KEY,
    quest_rid INT,
    avatar_id INT REFERENCES avatar (id) ON DELETE CASCADE
);
CREATE TABLE mail (
    id           SERIAL PRIMARY KEY,
    recipient_id INT REFERENCES avatar (id) ON DELETE CASCADE,
    mail_rid     INT
);
CREATE TABLE item (
    id           BIGSERIAL PRIMARY KEY,
    mail_id      INT REFERENCES mail (id) ON DELETE CASCADE,
    item_rid     INT
);

Еще немного про WITH

Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.

-- Получить всех, кто выполнил квест
SELECT * FROM quest q WHERE q.quest_rid = 42;

-- Создать письма
INSERT INTO mail (recipient_id, mail_rid)
SELECT q.avatar_id, 17
FROM quest q WHERE q.quest_rid = 42;

-- Создать предметы в письмах
INSERT INTO item (mail_id, item_rid)
SELECT mail_id, 7291
FROM mail m WHERE ???;

Еще немного про WITH

Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.

--- Сохраняем номер последнего добавленного письма
CREATE TEMPORARY TABLE tmp_mail ON COMMIT DROP AS
    SELECT GREATEST(MAX(id), 0) as last_id FROM mail;

-- Создать письма
INSERT INTO mail (recipient_id, mail_rid)
SELECT q.avatar_id, 17
FROM quest q WHERE q.quest_rid = 42;

-- Создать предметы в письмах
INSERT INTO item (mail_id, item_rid)
SELECT m.id, 7291
FROM mail m WHERE m.id > (SELECT last_id FROM tmp_mail);

Еще немного про WITH

Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.

-- Создать письма и сохранить их идентификаторы
CREATE TEMPORARY TABLE tmp_mail ON COMMIT DROP AS
WITH new_mail AS (
  INSERT INTO mail (recipient_id, mail_rid)
  SELECT q.avatar_id, 17
  FROM quest q WHERE q.quest_rid = 42
  RETURNING id
)
SELECT * FROM new_mail;

-- Создать предметы в письмах
INSERT INTO item (mail_id, item_rid)
SELECT m.id, 7291
FROM mail m WHERE m.id > (SELECT last_id FROM tmp_mail);

Еще немного про WITH

Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.

-- Создать письма и предметы в письмах
WITH new_mail AS (
  INSERT INTO mail (recipient_id, mail_rid)
  SELECT q.avatar_id, 17
  FROM quest q WHERE q.quest_rid = 42
  RETURNING id
)
INSERT INTO item (mail_id, item_rid)
SELECT m.id, 7291
FROM new_mail m;

Составление запросов

Составление запроса. Пример 1

Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.

CREATE TABLE highschooler (
    id    BIGSERIAL PRIMARY KEY,
    name  VARCHAR(128),
    grade INT
);

CREATE TABLE friend (
    id1 INT REFERENCES highschooler (id) ON DELETE CASCADE,
    id2 INT REFERENCES highschooler (id) ON DELETE CASCADE
);

Дружба взаимная, если есть запись (123, 456), то есть и (456, 123).

Составление запроса. Пример 1

Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.

Составление запроса. Пример 1

Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.

Есть в том же классе и нет в других классах
==
Есть друзья и нет в других классах

Составление запроса. Пример 1

Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.


    SELECT H.name, H.grade
FROM highschooler AS H
JOIN friend AS F ON (H.Id = F.Id1)
LEFT JOIN highschooler AS O ON (F.Id2 = O.Id AND H.Grade <> O.Grade)

SELECT H.name, H.grade
FROM highschooler AS H
JOIN friend AS F ON (H.Id = F.Id1)
LEFT JOIN highschooler AS O ON (F.Id2 = O.Id AND H.Grade <> O.Grade)
GROUP BY H.grade, H.name, H.id
HAVING COUNT(O.Id) = 0
ORDER BY H.grade, H.name
    

Составление запроса. Пример 2

Найти разницу между средней оценкой фильмов выпущенных до 1980 года, и средней оценкой фильмов выпущенных после 1980 года.

CREATE TABLE movie (
    mID      SERIAL PRIMARY KEY,
    title    VARCHAR(128),
    year     INT,
    director VARCHAR(128)
);

CREATE TABLE rating (
    rID        SERIAL PRIMARY KEY,
    mID        INT REFERENCES movie (mID) ON DELETE CASCADE,
    stars      INT,
    ratingDate TIMESTAMP
);

Составление запроса. Пример 2

Найти разницу между средней оценкой фильмов выпущенных до 1980 года, и средней оценкой фильмов выпущенных после 1980 года.


SELECT
    AVG(CASE WHEN year > 1980 THEN stars ELSE NULL END) AS after,
    AVG(CASE WHEN year < 1980 THEN stars ELSE NULL END) AS before
FROM (
    SELECT M.mID, M.year, AVG(R.stars) as stars
    FROM movie M
    JOIN rating R USING (mID)
    GROUP BY M.mID, M.year
) AS R

SELECT
    AVG(CASE WHEN year > 1980 THEN stars ELSE NULL END) AS after,
    AVG(CASE WHEN year < 1980 THEN stars ELSE NULL END) AS before
FROM (
    SELECT M.mID, M.year, AVG(R.stars) as stars
    FROM Movie M
    JOIN Rating R USING (mID)
    GROUP BY M.mID, M.year
) AS R

Составление запроса. Пример 2

Найти разницу между средней оценкой фильмов выпущенных до 1980 года, и средней оценкой фильмов выпущенных после 1980 года.


SELECT
    AVG(CASE WHEN year > 1980 THEN stars ELSE NULL END) -
    AVG(CASE WHEN year < 1980 THEN stars ELSE NULL END) AS result
FROM (
    SELECT M.mID, M.year, AVG(R.stars) as stars
    FROM movie M
    JOIN rating R USING (mID)
    GROUP BY M.mID
) AS R

Составление запроса. Пример 2

Найти разницу между средней оценкой фильмов выпущенных до 1980 года, и средней оценкой фильмов выпущенных после 1980 года.


SELECT AVG(R.s_after) - AVG(R.s_before) AS result
FROM (
    SELECT
        AVG(CASE WHEN year > 1980 THEN stars ELSE NULL END) AS s_after,
        AVG(CASE WHEN year < 1980 THEN stars ELSE NULL END) AS s_before
    FROM movie M 
    JOIN rating R USING (mID)
    GROUP BY M.mID
) AS R

Представления (VIEW)

VIEW

Представление (VIEW)
Объект базы данных, являющийся результатом выполнения запроса к базе данных, определенного с помощью оператора SELECT, в момент обращения к представлению.
CREATE
    [ OR REPLACE ]
    [ TEMP | TEMPORARY ]
    [ RECURSIVE ]
VIEW name [ ( column_name [, ...] ) ]
    [ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
    AS query
    [ WITH [ CASCADED | LOCAL ] CHECK OPTION ];

Преимущества VIEW

Ограничения VIEW

Особенности VIEW

CREATE VIEW v AS
    SELECT a.id, b.id FROM a, b;
-- ERROR: column "id" specified more than once
-- SQL-состояние: 42701

CREATE VIEW v (a_id, b_id) AS
    SELECT a.id, b.id FROM a,b;
CREATE VIEW v AS
    SELECT a.id a_id, b.id b_id FROM a,b;

Особенности VIEW

Изменяемые VIEW

Изменение VIEW

CREATE TABLE films (
    imdb        varchar(16) PRIMARY KEY,
    title       varchar(40) NOT NULL,
    kind        varchar(10)
);

CREATE OR REPLACE VIEW comedies AS
    SELECT *
    FROM films
    WHERE kind = 'Comedy'
    WITH CASCADED CHECK OPTION;

INSERT INTO comedies (imdb, title, kind)
    VALUES ('tt0114709', 'Toy Story 1', 'Animation');
-- ERROR:  new row violates check option for view "comedies"
-- DETAIL:  Failing row contains (tt0114709, Toy Story 1, Animation).

INSERT INTO comedies (imdb, title, kind)
    VALUES ('tt1156398', 'Zombieland', 'Comedy');
Навроцкий Артем
E-mail: bozaro@yandex.ru
Спасибо за внимание!