Видимо я сделала какое-то очень плохое зло, поэтому живу во время перемен. Справиться с эмоциями и повысить свою конкурентоспособность на рынке 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)


  1. gleb_l
    08.05.2022 08:04
    +1

    Если на собеседовании телемастера он оказывается радиоинженером - точно надо брать: раз может создать схему - то и телевизор починит.


  1. gsaw
    08.05.2022 08:18
    +4

    Ну если по правде, то это не совсем SQL. Даже в методе Pl/SQL в коде есть "NO SQL" :)


  1. FlyingDutchman2
    08.05.2022 09:35

    А вот еще: решение головоломки Sudoku одним SQL-запросом: https://technology.amis.nl/it/solving-a-sudoku-with-1-sql-statement-the-model-clause/


  1. kubk
    08.05.2022 10:26
    +5

    Задачи интересные, только вот это не статья, а просто список ваших SQL-запросов. Было бы неплохо разделить решения на несколько подэтапов, объяснить как пришли к решению.


  1. MyraJKee
    08.05.2022 10:29
    +1

    Как скучно я живу... Изучаю понемногу react и golang, не знаю чё бы такое поделать чтобы интересно было.


    1. randomsimplenumber
      08.05.2022 10:45

      Задачки с leetcode.


    1. Areso
      08.05.2022 16:17

      Игру?

      Я вот делаю игры как хобби, ни разу не скучно. Уже много лет :)


  1. BigDflz
    08.05.2022 11:15

    если пишешь
    Моё решение здесь и далее на MySQL 8.0:

    то используй последние версии...., а использование SET @n = 20; с оконными функциями вообще не дело. Писать статью с deprecated операторами, и ни слова об этом - не красиво.


    1. TimsTims
      08.05.2022 11:38

      с оконными функциями вообще не дело

      Почему? Потому-что это работает медленнее, чем могло было быть? Возможно да. Возможно, есть способ сделать это быстрее... на несколько миллисекунд. Здесь же не стоит задачи нарисовать 1 млн сердечек в ограниченное количество времени. А нарисовать одну фигуру, неважно как.

      Писать статью с deprecated операторами, и ни слова об этом - не красиво.

      Рано или поздно, всё будет deprecated :)


  1. olegborzov
    08.05.2022 12:29
    +4


  1. v1000
    08.05.2022 13:00

    таким образом можно почувствовать себя творцом, будучи простым дата-аналитиком

    Тот случай, когда имея базу данных, можно нарисовать все что угодно. Буквально.


  1. AWE64
    08.05.2022 14:20
    +1

    Ждем статью о том как рисковать с помощью SQL.


    1. max851
      08.05.2022 14:30
      +1

      Да там статья не нужна...

      rm -rf /backup_task /all_backups

      А потом просто налаждаться чуством медленно растущей вероятности песца.


    1. Areso
      09.05.2022 19:32
      +1

      Достаточно начать с UPDATE / DELETE и забыть поставить WHERE.

      А вообще, про риск с помощью SQL почти каждый DBAшник сможет рассказать много разного :)

      drop if exists тоже отличное начало для истории в баре :)


  1. Anarchist
    08.05.2022 16:47
    +1

    На рисование окружностей есть прекрасный алгоритм Брезенхэма. Я думаю, его было бы тоже интересно воплотить в SQL, используя свойства симметрии окружностей. :)


  1. KvanTTT
    08.05.2022 17:19

    Класс! Только мне кажется это должно быть и в хабе «Ненормальное программирование». Для окружности лучше больше символом использовать.


  1. Vezyk
    08.05.2022 17:32

    Неплохая визуализация к термину "нефиг делать" )


  1. 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;
                           ---------------                      
                      ------:::::::::::::------                 
                  -----:::::::░░░░░░░░░:::::::-----             
                ---:::::░░░░░░░░░░░░░░░░░░░░░:::::---           
              ---:::░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░:::---         
            ---:::░░░░▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░░:::---       
          ---:::░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░:::---     
         ---::░░░░▒▒▒▒▓▓▓▓▓▓█████████████▓▓▓▓▓▓▒▒▒▒░░░░::---    
        ---::░░░▒▒▒▒▓▓▓▓▓███████████████████▓▓▓▓▓▒▒▒▒░░░::---   
       ---::░░░▒▒▒▓▓▓▓▓███████████████████████▓▓▓▓▓▒▒▒░░░::---  
       --::░░░▒▒▒▓▓▓▓███████████████████████████▓▓▓▓▒▒▒░░░::--  
      --:::░░▒▒▒▒▓▓▓█████████████████████████████▓▓▓▒▒▒▒░░:::-- 
      --::░░░▒▒▒▓▓▓▓█████████████████████████████▓▓▓▓▒▒▒░░░::-- 
      --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- 
      --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- 
      --::░░░▒▒▒▓▓▓███████████████████████████████▓▓▓▒▒▒░░░::-- 
      --::░░░▒▒▒▓▓▓▓█████████████████████████████▓▓▓▓▒▒▒░░░::-- 
      --:::░░▒▒▒▒▓▓▓█████████████████████████████▓▓▓▒▒▒▒░░:::-- 
       --::░░░▒▒▒▓▓▓▓███████████████████████████▓▓▓▓▒▒▒░░░::--  
       ---::░░░▒▒▒▓▓▓▓▓███████████████████████▓▓▓▓▓▒▒▒░░░::---  
        ---::░░░▒▒▒▒▓▓▓▓▓███████████████████▓▓▓▓▓▒▒▒▒░░░::---   
         ---::░░░░▒▒▒▒▓▓▓▓▓▓█████████████▓▓▓▓▓▓▒▒▒▒░░░░::---    
          ---:::░░░░▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒░░░░:::---     
            ---:::░░░░▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒░░░░:::---       
              ---:::░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░:::---         
                ---:::::░░░░░░░░░░░░░░░░░░░░░:::::---           
                  -----:::::::░░░░░░░░░:::::::-----             
                      ------:::::::::::::------                 
                           ---------------                      


    1. dmitryvolochaev
      10.05.2022 19:35

      А зачем сложности с WITH RECURSIVE? С generate_series() еще проще всё будет


  1. BigDflz
    09.05.2022 19:32
    +1

    вот все-таки тут куча гадких критиканов собралось, мало того что минусуют, да ещё и карму сливают, вот она свобода высказывания. во всей красе, не все , конечно, но большинство, и читать интересного становится всё меньше, как следствие такого затыкания ртов...