Простая сортировка обычно даёт не тот результат, который ожидается пользователем.
| ucs_basic (C) | en_US.utf8 (ISO-14651) | ru_RU.utf8 | 
|---|---|---|
| _ель | ґвалт | джаз | 
| Есенин | джаз | ґвалт | 
| джаз | ель | ель | 
| ель | _ель | _ель | 
| жаркое | Есенин | Есенин | 
| ёжик | ёжик | ёжик | 
| ґвалт | жаркое | жаркое | 
В разных языках разные преобразования в верхний/нижний регистр
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 | 
В PostgreSQL поддержка COLLATION реализована на базе C-функции strcoll_l.
Это порождает ряд проблем:
С PostgreSQL 10 (окт 2019) появилась поддержка COLLATION на базе библиотеки ICU.
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');
    Проблемы:
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';
[ 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 ] [...] ]
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);
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);
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);
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
);
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) | возвращает значение, вычисленное для последней строки в рамке окна | 
| 
                 | 
            возвращает значение, вычисленное в н-ой строке в рамке окна (считая с 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;
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 | 
| 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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;
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 | 
| 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 | 
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
);
    SELECT E.*
FROM Employee E
JOIN Department D ON (E.DepartmentID = D.DepartmentID);
| 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;
| 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;
| 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);
SELECT col1 FROM tbl
UNION ALL
SELECT col2 FROM tbl;
    
SELECT unnest(array[col1, col2]) as col1 FROM tbl;
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 | Вашингтон | 
Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.
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
);
Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.
-- Получить всех, кто выполнил квест
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 ???;
    Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.
--- Сохраняем номер последнего добавленного письма
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);
    Надо всем аватарам, у которых сдан квест на "Кащея" отправить "Меч кладенец" по игровой почте.
-- Создать письма и сохранить их идентификаторы
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 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;
    Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.
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).
Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.
Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.
        Есть в том же классе и нет в других классах
==
Есть друзья и нет в других классах
    
Найти имена и классы, которые имеют друзей только в том же классе. Вернуть результат, отсортированный по классу, затем имени в классе.
    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
    
    
Найти разницу между средней оценкой фильмов выпущенных до 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
);
Найти разницу между средней оценкой фильмов выпущенных до 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
Найти разницу между средней оценкой фильмов выпущенных до 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
Найти разницу между средней оценкой фильмов выпущенных до 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
CREATE
    [ OR REPLACE ]
    [ TEMP | TEMPORARY ]
    [ RECURSIVE ]
VIEW name [ ( column_name [, ...] ) ]
    [ WITH ( view_option_name [= view_option_value] [, ... ] ) ]
    AS query
    [ WITH [ CASCADED | LOCAL ] CHECK OPTION ];
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;
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');