Видимо я сделала какое-то очень плохое зло, поэтому живу во время перемен. Справиться с эмоциями и повысить свою конкурентоспособность на рынке Data Enigneer’ов мне помогает сайт Hackerrank. На пути к решению вообще всех задач по SQL с этого сайта мне попалась задачка на нетривиальные запросы.
В задачке требовалось звёздочками нарисовать прямоугольный треугольник.
Понятно, что можно было сделать как-то так:
SELECT '*'
UNION ALL
SELECT '* *'
UNION ALL
...
Но это дико скучно и некрасиво.
Давайте разберемся, как рисовать с помощью SQL, и при этом ощущать себя настоящим творцом!
Оригинальный текст задачи:
P(R) represents a pattern drawn by Julia in R rows. The following pattern represents P(5):
* * * * *
* * * *
* * *
* *
*
Write a query to print the pattern P(20).
Моё решение здесь и далее на MySQL 8.0:
SET @n = 20;
WITH RECURSIVE seq AS (
SELECT 1 AS val
UNION ALL
SELECT val + 1
FROM seq
WHERE val+1 <= @n
)
SELECT REPEAT('* ', val)
FROM seq
ORDER BY val DESC;
В следующей задачке треугольник надо было перевернуть, что решается удалением ORDER BY.
После этих задач я задумалась: а как не менее изящно нарисовать равнобедренный треугольник?
SET @n = 4;
SET @fill = '8';
SET @sp = '.';
WITH RECURSIVE seq AS (
SELECT 1 AS val
UNION ALL
SELECT val + 1
FROM seq
WHERE val+1 <= @n
)
SELECT CONCAT(REPEAT(@sp, @n - val),
REPEAT(CONCAT(@fill, @sp), val),
REPEAT(@sp, @n - val - 1))
FROM seq;
Здесь я решила использовать как переменные кирпичики нашей картины, чтобы получать более творческие картинки.
Вот результат:
...8...
..8.8..
.8.8.8.
8.8.8.8.
А что насчёт кружочка?
SET @n = 20;
SET @fill = '8';
SET @sp = '.';
SET @r_x = 8;
SET @r_y = 5;
/* Procedure which draws lines by points */
DELIMITER $$
CREATE FUNCTION draw_circle(x_points VARCHAR(255))
RETURNS varchar(255)
NO SQL
BEGIN
DECLARE c INT;
DECLARE str VARCHAR(255);
SET c = 0;
SET str = '';
line_loop: LOOP
IF c > @n THEN LEAVE line_loop; END IF;
IF FIND_IN_SET(CAST(c AS CHAR), x_points)
THEN SET str = CONCAT(str, @fill);
ELSE SET str = CONCAT(str, @sp);
END IF;
SET c = c + 1;
ITERATE line_loop;
END LOOP;
RETURN str;
END $$
DELIMITER ;
/* Here we get points for circle */
WITH RECURSIVE seq AS (
SELECT 0 AS x, 0 AS y, 0 AS val
UNION ALL
SELECT
round(@r_x + @r_x * COS(2 * PI() * val / @n)) as x,
round(@r_y + @r_y * SIN(2 * PI() * val / @n)) as y,
val + 1
FROM seq
WHERE val < @n
)
, points AS (
SELECT GROUP_CONCAT(x) AS xs, y
FROM seq
WHERE val > 0
GROUP BY y
ORDER BY y ASC
)
SELECT
draw_circle(xs)
FROM
points
;
Здесь пришлось попотеть: разобраться с функциями и циклами в MySQL, дискретизировать формулу окружности.
Зато каков результат:
.......8..8..8.......
....8...........8....
..8...............8..
8...................8
8...................8
8...................8
..8...............8..
....8...........8....
.......8..8..8.......
Теперь нарисуем шахматную доску.
SET @n = 8;
SET @fill = '#';
SET @sp = '.';
/* Procedure which draws lines by points */
DROP FUNCTION IF EXISTS draw_chessboard;
DELIMITER $$
CREATE FUNCTION draw_chessboard(i INTEGER)
RETURNS varchar(255)
NO SQL
BEGIN
DECLARE c INT;
DECLARE str VARCHAR(255);
SET c = 0;
SET str = '';
line_loop: LOOP
IF c > @n THEN LEAVE line_loop; END IF;
IF i mod 2 = 0 THEN
IF c mod 2 <> 0
THEN SET str = CONCAT(str, @fill);
ELSE SET str = CONCAT(str, @sp);
END IF;
ELSEIF i mod 2 <> 0 THEN
IF c mod 2 = 0
THEN SET str = CONCAT(str, @fill);
ELSE SET str = CONCAT(str, @sp);
END IF;
END IF;
SET c = c + 1;
ITERATE line_loop;
END LOOP;
RETURN str;
END $$
DELIMITER ;
WITH RECURSIVE seq AS (
SELECT 1 AS val
UNION ALL
SELECT
val + 1 AS val
FROM seq
WHERE val < @n
)
SELECT
draw_chessboard(val)
FROM
seq
;
Результат:
#_#_#_#_#
_#_#_#_#_
#_#_#_#_#
_#_#_#_#_
#_#_#_#_#
_#_#_#_#_
#_#_#_#_#
_#_#_#_#_
Под конец перейдем к чему-то посерьезнее! К сердечку!
SET @n = 40;
SET @fill = '8';
SET @sp = '.';
SET @r_x = 16;
SET @r_y = 16;
/* Procedure which draws lines by points */
DROP FUNCTION IF EXISTS draw_heart;
DELIMITER $$
CREATE FUNCTION draw_heart(x_points VARCHAR(255))
RETURNS varchar(255)
NO SQL
BEGIN
DECLARE c INT;
DECLARE str VARCHAR(255);
SET c = 0;
SET str = '';
line_loop: LOOP
IF c > @r_x * 2 THEN LEAVE line_loop; END IF;
IF FIND_IN_SET(CAST(c AS CHAR), x_points)
THEN SET str = CONCAT(str, @fill);
ELSE SET str = CONCAT(str, @sp);
END IF;
SET c = c + 1;
ITERATE line_loop;
END LOOP;
RETURN str;
END $$
DELIMITER ;
/* Here we get points for heart */
WITH RECURSIVE seq AS (
SELECT 0 AS x, 0 AS y, 0 AS val
UNION ALL
SELECT
round(@r_x + 16 * POWER(SIN(2 * PI() * val / @n), 3)) as x,
round(2 * @r_y - ( 13 * COS(2 * PI() * val / @n) - 5 * COS(4 * PI() * val / @n) - 2 * COS(6 * PI() * val / @n) - COS(8 * PI() * val / @n) ) ) as y,
val + 1
FROM seq
WHERE val < @n
)
, points AS (
SELECT GROUP_CONCAT(x) AS xs, y
FROM seq
WHERE val > 0
GROUP BY y
ORDER BY y ASC
)
SELECT
draw_heart(xs)
FROM
points
;
Формулу для сердца я добыла здесь: http://www.wolframalpha-ru.com/2012/03/blog-post.html, и это было самой сложной частью рисунка.
Результат:
........8.8...........8.8........
.....8.....................8.....
.............8.....8.............
..8............8.8............8..
.8..............8..............8.
................8................
8...............................8
.8.............................8.
..8...........................8..
.....8.....................8.....
........8...............8........
..........8...........8..........
.............8.....8.............
...............8.8...............
................8................
................8................
Вот таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком. Тем не менее, уверена, что мои решения далеки от оптимальности, поэтому жду ваших комментариев и замечаний.
Комментарии (20)
gsaw
08.05.2022 08:18+4Ну если по правде, то это не совсем SQL. Даже в методе Pl/SQL в коде есть "NO SQL" :)
FlyingDutchman2
08.05.2022 09:35А вот еще: решение головоломки Sudoku одним SQL-запросом: https://technology.amis.nl/it/solving-a-sudoku-with-1-sql-statement-the-model-clause/
kubk
08.05.2022 10:26+5Задачи интересные, только вот это не статья, а просто список ваших SQL-запросов. Было бы неплохо разделить решения на несколько подэтапов, объяснить как пришли к решению.
MyraJKee
08.05.2022 10:29+1Как скучно я живу... Изучаю понемногу react и golang, не знаю чё бы такое поделать чтобы интересно было.
BigDflz
08.05.2022 11:15если пишешь
Моё решение здесь и далее на MySQL 8.0:
то используй последние версии...., а использование SET @n = 20; с оконными функциями вообще не дело. Писать статью с deprecated операторами, и ни слова об этом - не красиво.
TimsTims
08.05.2022 11:38с оконными функциями вообще не дело
Почему? Потому-что это работает медленнее, чем могло было быть? Возможно да. Возможно, есть способ сделать это быстрее... на несколько миллисекунд. Здесь же не стоит задачи нарисовать 1 млн сердечек в ограниченное количество времени. А нарисовать одну фигуру, неважно как.
Писать статью с deprecated операторами, и ни слова об этом - не красиво.
Рано или поздно, всё будет deprecated :)
v1000
08.05.2022 13:00таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком
Тот случай, когда имея базу данных, можно нарисовать все что угодно. Буквально.
AWE64
08.05.2022 14:20+1Ждем статью о том как рисковать с помощью SQL.
max851
08.05.2022 14:30+1Да там статья не нужна...
rm -rf /backup_task /all_backups
А потом просто налаждаться чуством медленно растущей вероятности песца.
Areso
09.05.2022 19:32+1Достаточно начать с UPDATE / DELETE и забыть поставить WHERE.
А вообще, про риск с помощью SQL почти каждый DBAшник сможет рассказать много разного :)
drop if exists тоже отличное начало для истории в баре :)
Anarchist
08.05.2022 16:47+1На рисование окружностей есть прекрасный алгоритм Брезенхэма. Я думаю, его было бы тоже интересно воплотить в SQL, используя свойства симметрии окружностей. :)
KvanTTT
08.05.2022 17:19Класс! Только мне кажется это должно быть и в хабе «Ненормальное программирование». Для окружности лучше больше символом использовать.
playermet
08.05.2022 20:37+6Как-то все очень сложно. Для сравнения, код рисующий шар в sqlite и результат. В pixels координата x делится пополам чтобы немного компенсировать высоту символов, которая больше чем ширина. У меня в консоли без междустрочного интервала вообще отлично выглядит.
WITH RECURSIVE counter (i) AS ( SELECT 0 UNION ALL SELECT i + 1 FROM counter ), pixels (x, y) AS ( SELECT i % 60 * 0.5, round(i / 60) FROM counter LIMIT 60*30 ), sdf (x, y, d) AS ( SELECT x, y, (y-15)*(y-15) + (x-15)*(x-15) FROM pixels ) SELECT group_concat(substr('█▓▒░:- ', min(max(d / 30, 1), 7), 1), '') FROM sdf GROUP BY y;
--------------- ------:::::::::::::------ -----:::::::░░░░░░░░░:::::::----- ---:::::░░░░░░░░░░░░░░░░░░░░░:::::--- ---:::░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░:::--- ---:::░░░░▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░░:::--- ---:::░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░:::--- ---::░░░░▒▒▒▒▓▓▓▓▓▓█████████████▓▓▓▓▓▓▒▒▒▒░░░░::--- ---::░░░▒▒▒▒▓▓▓▓▓███████████████████▓▓▓▓▓▒▒▒▒░░░::--- ---::░░░▒▒▒▓▓▓▓▓███████████████████████▓▓▓▓▓▒▒▒░░░::--- --::░░░▒▒▒▓▓▓▓███████████████████████████▓▓▓▓▒▒▒░░░::-- --:::░░▒▒▒▒▓▓▓█████████████████████████████▓▓▓▒▒▒▒░░:::-- --::░░░▒▒▒▓▓▓▓█████████████████████████████▓▓▓▓▒▒▒░░░::-- --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- --::░░░▒▒▒▓▓▓▓█████████████████████████████▓▓▓▓▒▒▒░░░::-- --:::░░▒▒▒▒▓▓▓█████████████████████████████▓▓▓▒▒▒▒░░:::-- --::░░░▒▒▒▓▓▓▓███████████████████████████▓▓▓▓▒▒▒░░░::-- ---::░░░▒▒▒▓▓▓▓▓███████████████████████▓▓▓▓▓▒▒▒░░░::--- ---::░░░▒▒▒▒▓▓▓▓▓███████████████████▓▓▓▓▓▒▒▒▒░░░::--- ---::░░░░▒▒▒▒▓▓▓▓▓▓█████████████▓▓▓▓▓▓▒▒▒▒░░░░::--- ---:::░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░:::--- ---:::░░░░▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░░:::--- ---:::░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░:::--- ---:::::░░░░░░░░░░░░░░░░░░░░░:::::--- -----:::::::░░░░░░░░░:::::::----- ------:::::::::::::------ ---------------
dmitryvolochaev
10.05.2022 19:35А зачем сложности с WITH RECURSIVE? С generate_series() еще проще всё будет
BigDflz
09.05.2022 19:32+1вот все-таки тут куча гадких критиканов собралось, мало того что минусуют, да ещё и карму сливают, вот она свобода высказывания. во всей красе, не все , конечно, но большинство, и читать интересного становится всё меньше, как следствие такого затыкания ртов...
gleb_l
Если на собеседовании телемастера он оказывается радиоинженером - точно надо брать: раз может создать схему - то и телевизор починит.