Tasks and solutions for the PostgreSQL fighter

  • Tutorial

Greetings to all SQL lovers!

On the Internet, I rarely met articles that cover different working points and subtleties associated with data processing in SQL .
I like it when you can learn a lot from one article at once, even if in general terms.
Therefore, I decided to write my article containing various tasks and answers with explanations to them.
Suitable for those who have mastered all the basic skills and wants to develop further.

The answers provided are suitable for PostgreSQL ( most of the tasks are suitable for other DBMSs , but the results and solutions may be different. Even wondering where the differences will arise)

Try to answer yourself before opening the spoiler.

Go!


I will try to mark with an asterisk, something especially for PostgreSQL * (there are not many such moments)

1. A bit about numeric operations


1.1 Will these queries be executed? What results will they return?

-- А) Начнем с простого запросаSELECT3/2;
-- Б)SELECTmin('Какой-то текст'::TEXT), avg('Какой-то текст'::TEXT);
-- В)* Почему данный запрос может вернуть FALSE, возможно ли такое поведение СУБД?SELECT7.2 = (3.8::FLOAT + 3.4)
-- Г)SELECT (20/25)*25.0;


Answers to 1.1
А) Ответ: 1
Будет показана только целая часть, т.к. при операции используются целые числа. Такое часто встречается и в других языках.

Б) Ответ: запрос не выполнится.

avg выдаст ошибку, т.к. принимает только числа и временные интервалы*

Однако функция min / max может выполняться на текстовых данных (в соответствии с алфавитной сортировкой в БД).
Иногда это может быть полезно, когда нужно хотя бы посмотреть на столбец, который не перечислен в GROUP BY
Или когда к числам нужно применить алфавитную сортировку, при которой '10' < '2'

В) Ответ: FALSE

Может показаться странным, но такое допустимо, т.к. это особенность представления компьютером некоторых чисел с плавающей точкой, число может принять вид 7.1(9)
Вспоминается, как когда-то я долго разбирался с запросом, не зная этого

Г) Ответ: 0. подвох в том, что выражение в скобках будет =0

SELECT (20/25.0)*25 отработал бы более корректно


1.2 Given the table " table_2 " (with a single column " value " (INTEGER)) consisting of the following 5 lines:
value
five
five
Null
five
five

What result will return the query:
SELECT (avg(value)*count(*)) - sum(value) FROM table_2;

Answer Options
  • -4
  • 0
  • NULL
  • 5
  • Вызовет ошибку, т.к. не указан GROUP BY
  • Ни один из перечисленных


Answer 1.2
ответ: 5

Агрегатные функции, примененные к конкретному столбцу, игнорируют NULL, однако count(*) посчитает все строки
5 * 5 — 20


2. General issues


2.1 When can a query return not the entire contents of a table? ( parent_id INTEGER, the table is filled with various data)

SELECT * FROM any_table WHERE parent_id = parent_id;

And how will the query behave below? What data will it display? * PostgreSQL

SELECT * FROM any_table WHERE parent_id ISNOTDISTINCTFROM parent_id;

Answers to 2.1
Первый запрос покажет все записи, кроме тех, где parent_id является NULL

Второй запрос покажет все записи таблицы. IS DISTINCT FROM по логике похож на оператор != в котором NULL идентичен NULL
IS NOT DISTINCT FROM логически обратит неравенство в равенство

2.2. What is the result of the query?

-- А)SELECT * FROM (
    SELECT1UNION ALL
    SELECT1
    ) x(y)
UNION
(
    SELECT2UNION ALL
    SELECT2
);

Answer to 2.2
Результатом будет 2 строки со значениями 1 и 2, UNION удалит все дубликаты в результирующей выборке, а не только между двумя объединяемыми таблицами. Замечал, что не для всех это очевидно.

2.3 Write a request that will show tomorrow's date.

Answer to 2.3
SELECTCAST((now()+ INTERVAL'1 DAY') ASDATE)

Не все часто работают с датами, но какой-то минимум освоить стоит
*Решение для Postgres, но думаю другие СУБД не сильно отличаются

Если работа с датами Вам в новинку, то советую поэкспериментировать с запросом
Например:
— заменить DAY на (week, month, year и т.д.)
— заменить +1 на -9000
— заменить DATE на TIME
— убрать CAST
— оставить только NOW()
и т.д.

И, вдохновившись какими-то результатами, отправляйтесь читать MANUAL, все темы там подробно раскрыты


2.4 The UPDATE , DELETE , INSERT and MERGE statements are designed to manipulate data in tables. Is the execution of SELECT .. "safe"? Can any query affect the data in the table?
Answer 2.4
Вопрос может показаться примитивным, однако…

В самом начале изучения SQL, у меня складывалось мнение, что этот оператор может только показывать данные, но:

Помимо того, что SELECT способен заблокировать таблицу на изменение (BEGIN; SELECT… FOR UPDATE) *
SELECT способен вызывать функции, которые могут выполнять практически любые манипуляции.

Новичкам нужно это понимать сразу, а не после выполнения «маленького информационного» запроса на Production сервере


3. Only PostgreSQL


3.1 Describe what will happen when executing this query in the SQL dialog:

SELECT * INTO wtf FROM pg_stat_activity;

Answer to 3.1
Обычно SELECT INTO используется в функциях plpgsql, для записи значения в переменную.

Вне plpgsql эффект команды будет аналогичен запросу ниже:

CREATETABLE wtf ASSELECT * FROM pg_stat_activity;


3.2 what this “simple” query will show

SELECT wtf_ FROM pg_stat_activity AS wtf_ ;

Answer to 3.2
pg_stat_activity системное представление (VIEW) активных процессов в базе.

Особенность запроса в том, что будет выведен один столбец со строками (ROW) имеющими TYPE pg_stat_activity (или другой таблицы). Знать это нужно скорее тем, кто пишет функции, подробнее можно почитать в мануале
Вопрос добавил потому, что новичок может легко по ошибке получить такой результат, и не понимать в чем дело

4. Work with the text. Regular expressions


I think you need to be able not only to build queries, but also to present the results in the right form.
Regular expressions are a separate huge topic, with many quality articles. Therefore, I will only show examples, without detailed explanations.

4.1. Suppose there is a table " table_5 " with a text column " X " and a variety of different rows. What query can I get any last 10 characters of each line?

The answer to 4.1
SQL позволяет придумать массу решений одной и той же задачи, к примеру:
самое простое, что приходит на ум — это right(X,10)
можно использовать регулярное выражение: substring( X, '.{0,10}$' )
можно даже накостылять «извертеться»(во всех смыслах) так: reverse(substring(reverse(X) for 10))


4.2 There is a table “table_6” with a text column “X”. The table contains one row (all text is in English and Russian only):
'Lorem 3 Ipsum 23 standard 7 dummy 17 text Ultimate Answer of Life ?? 777'

A) Write a query that returns the characters from the 42nd to the 68th of this line.
B) How can I pull out the CAPITAL (Russian or English) letters in a line using SQL?
C) How to calculate the sum of numbers ( not numbers ) in a row using SQL

SQL outline
WITH table_6(X) AS(
    SELECT'Lorem 3 Ipsum 23 standard 7 dummy 17 text Ultimate Answer of Life ?? 777'::TEXT
    )
SELECT X FROM table_6

Answers to 4.2
-- ТУТ должен быть WITH из "SQL наброска"-- А)SELECTSUBSTRING(LEFT(X,68) FROM42 ) FROM table_6 -- 1 вариантSELECTSUBSTRING(X, 42, (68-42)+1) FROM table_6   -- 2 вариант-- 3 вариант с Вас-- Б) Все просто, заменяем всё кроме заглавных букв на пустотуSELECT regexp_replace(X,'[^A-ZА-ЯЁ]', '','g') FROM table_6 
-- Буква 'Ё' обычно не входит в диапазон А-Я-- без параметра 'g' замена произойдет лишь 1 раз-- В) Без регулярных выражений задача может показаться кошмаром-- С помощью regexp_matches и жадного поиска** получаем массивы чисел в столбик, и суммируем вытащив из массиваSELECTsum(x[1]::INT) 
    FROM (
              SELECT regexp_matches(X,'[0-9]+','g') FROM table_6
              ) AS y(x)
-- * в других СУБД функции могут иначе называться-- ** если уберем +, то получим сумму цифр (будет браться лишь 1 символ, а не вся максимальная последовательность)


4.3 How to replace in the text (cell of the table) all double (triple or more) spaces with a single space? (by tradition: table " table_7 " with column " X ") (PS it is enough to write SELECT that returns the desired result, and not UPDATE table_7 ... )

Answer to 4.3
WITH table_7(X) AS (SELECT'Lorem     3    Ipsum 23  standard  7     
  dummy    11
   text'::TEXT)
-- 1 вариант. Заменяем только пробелы (2 и более подряд)SELECT regexp_replace(X, '( ){2,}', ' ', 'g') FROM table_7
-- 2 вариант. Заменяем все пробельные символы (табуляция, неразрывный пробел, перевод строки и т.д.) на один пробел, даже если эти символы чередуютсяSELECT regexp_replace(X, '\s+', ' ', 'g') FROM table_7 
-- Отчаянный вариант! Думаю те, кто когда-либо искал решение подобной задачи, натыкались на такое "изящное" решение. Без использования регулярных выражений..-- Работает весьма хитро, на любом количестве пробелов, главное, чтобы текст не содержал используемых подстановочных символов-- Не рекомендую такое использовать, но для разминки ума стоит понять, как оно работаетSELECTreplace(replace(replace(X, ' ', '<>'), '><', ''), '<>', ' ') FROM table_7


4.4 There is a string " X " in which typos are made. Instead of Russian letters (e, o, c, C), outwardly similar to them characters of the English alphabet were used. Replace these characters with SQL.

PS The line should contain only Russian characters, and you should not worry about a possible change of English words.

(If it is difficult to replace all characters, then replace at least one)

Example string:

X = 'Cтрoитeльствo или рeкoнcтрукция oбъeкта'

Answer to 4.4
-- Несомненно, Replace(Replace(Replace(.. потрясающее решение, но-- специальная функция гораздо изящнее для такого случая (1 символ на 1 символ)SELECTTRANSLATE('Cтрoитeльствo или рeкoнcтрукция oбъeкта', 'Cceo', 'Ссео')

4.5 Write a query that converts the string:
'IVANOV IVAN IVANOVICH' to the form 'Ivanov Ivan Ivanovich'

Answer 4.5
-- Все просто, когда имеется такая функцияSELECT initcap('иВАнОв  ИВан  иВановиЧ')
*Возможно в других СУБД имеются аналоги

Bonus task for those who managed
Здорово, если есть готовая функция
А сможете преобразовать наоборот? (желательно не теряя отступов).
Возможно задача не типичная, но для развития будет полезна.

'иВАнОв ИВан иВановиЧ' преобразовать к 'иВАНОВ иВАН иВАНОВИЧ'
а инвертировать регистр?

The answer to the bonus task
SELECT string_agg(LOWER(LEFT(x,1)) || UPPER(SUBSTRING(x from2)), ''ORDERBY rn) 
    FROM 
  (SELECT * FROM regexp_split_to_table('  иВАнОв  ИВан  иВановиЧ
                           4 TesT', '\y') WITHORDINALITY y(x, rn) ) AS z
-- *Решение для PostgreSQL, но смысл везде такой же-- Придумал на скорую руку, без использования процедурного языка-- возможно неуклюжее и не производительное решение, но для данного примера подходит-- сохраняет все пробельные символы между словами.-- WITH ORDINALITY нужен для сохранения порядка символов (доступен с версии 9.4)-- Разбираем строку по границам слова-- получаем отдельно строки со словами и разделяющими их символами-- обрабатываем..

5. A bit of transaction


Transactions are very important thing in a DBMS, it is quite important to understand the main points. Let

me try to simulate an example:

Suppose there is a table “goods” with which two users are going to work.
It has an integer column discount equal to 10 for all rows.
Database settings are standard (READ COMMITTED - reading of captured data).

User User_1 opens a transaction, the following query:

BEGIN;
UPDATE goods
SET discount = discount + 5;

A second later, another user ( User_2 ) executes
almost the same query without opening a transaction:
UPDATE goods
SET discount = discount + 10;

What do you think will happen in the following scenario:

A) What result will User_2 get if User_1 leaves the transaction open (i.e., does not confirm the transaction / does not roll back the changes)?
What User_1 will see when prompted:

SELECT discount FROM goods LIMIT1;

B) What happens if User_1 does a ROLLBACK? What results will User_2 get?

Q) What happens if User_1 makes a COMMIT? What results will User_2 get?

Answers
Насколько я знаю READ UNCOMMITTED не поддерживается в PostgreSQL, и «грязные» (не подтвержденные) данные прочитать не получится

Ответы будут следующими:

А) Запрос User_2 будет ожидать COMMIT или ROLLBACK от User_1. (запрос словно подвиснет)
User_1 в своей транзакции будет видеть свою версию снимка базы, где discount уже равняется 15

Б) Если User_1 сделает ROLLBACK, то значение discount останется прежним, а следом выполнится запрос User_2, который прибавит 10 к discount и discount будет равен 20

В) Если User_1 сделает COMMIT, то значение discount увеличится на 5, а следом выполнится запрос User_2, который прибавит 10 к discount и discount будет равен 25

Another version of this task
Немного другая версия задачи 13 от пользователя kirill_petrov на особенность READ COMMITTED
-- Имеется таблица с двумя строкамиCREATETABLE goods (discount) AS
 (SELECT10::INTUNION ALL
 SELECT15);
-- 1. User_1 выполняет запрос (транзакция остается открыта):BEGIN;
UPDATE goods
SET discount = discount + 5;
--2. User_2 выполняет запрос:UPDATE goods
SET discount = discount + 100WHERE discount = 15--3. User_1 выполняет COMMIT;
Какие данные окажутся в таблице?

Conclusion


I think I touched on some interesting points.

I hope the tasks will help to motivate beginners, because it’s boring to learn anything without specific goals / objectives / directions.

I can be glad for those who were easy to answer all the questions. And those who had difficulties, I hope, got a kick in the direction of development. Those who have understood little, but want to master SQL, I invite you to my last article, The Course of a Young PostgreSQL Fighter .

I am waiting for any additions, solutions of particularly interesting problems (you can own) and other comments!

Thanks for attention! I wish you success in learning SQL!

Only registered users can participate in the survey. Sign in , please.

Was the article helpful?


Also popular now: