FizzBuzz - это распространенная проблема кодирования, которая имеет дело в основном с логикой перехода / условной логики и, в зависимости от языка, манипулированием типом данных. Общая идея такова: для ряда одновременных целочисленных значений (например, от 1 до 100) распечатайте все числа, соответствующие следующим условиям.

  • Если число делится на 3, распечатайте «Fizz».
  • Если число делится на 5, распечатайте «Живая лента».
  • Если число делится на 3 и 5, распечатайте «FizzBuzz».
  • Наконец, если число не делится на 3 или 5, распечатайте его.

Для чисел 1–15 результат (в виде списка, разделенного запятыми) будет выглядеть примерно так:

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz...

Понятно? Если нет, не стесняйтесь комментировать, и я могу объяснить это более подробно 😄

Это обычное дело для выполнения этой задачи на обобщенном языке вычислений / сценариев, таком как C ++, Ruby, JavaScript или Python. Сегодня мы сделаем это с помощью SQL, в частности, с использованием MySQL.

Но почему?

Мне часто задают этот вопрос, особенно когда я начинаю задумываться о важности SQL и обходить построитель запросов Object Relational Mapper другим внутренним инженерам. Если вы имеете дело с данными в какой-то момент, вам нужно будет понять, как манипулировать этими данными и извлекать их. На сегодняшний день реляционные базы данных - это статус-кво для хранения данных и других операций с ними. Как кодировщик, отвечающий за данные, важно понимать детали создаваемого вами запроса и стабильность данных, которые вы обрабатываете. Использование необработанного SQL для взаимодействия с вашими данными - это как можно ближе к данным, не перескакивая через пресловутый каньон, который отделяет вас от таких вещей, как машинный язык, ассемблер и двоичный код 😁

Итак, сегодня мы решим одну из самых распространенных задач кодирования на чистом SQL. Обещаю, будет весело.

Подготовка к работе с MySQL

Прежде всего, вам потребуется установленный MySQL. Мы пойдем быстрым путем ниже, но для получения более подробной информации, пожалуйста, ознакомьтесь с моим сообщением в блоге о начале работы с MySQL.

Шаг 1. Установите MySQL

Давайте продолжим и воспользуемся лучшим менеджером пакетов, созданным до сих пор для UNIX, и установим MySQL с помощью Homebrew. (Если вам нужно установить Homebrew, перейдите сюда и следуйте последним инструкциям по установке).

После того, как вы установили Homebrew, выполните следующую команду в своем терминале, чтобы установить MySQL для использования в командной строке:

brew install mysql

После завершения установки запустите службу:

brew services start mysql

Есть вопросы? Прокомментируйте и дайте мне знать 😄

Шаг 2: доступ к консоли MySQL

Теперь нам нужно войти в MySQL. Мы собираемся войти в систему как root и, честно говоря, пропустить настройку учетной записи пользователя. Как правило, это не очень хорошая идея, поэтому, если вы хотите настроить свою учетную запись пользователя и защитить ее и учетную запись root, ознакомьтесь с моим сообщением в блоге об этом.

Выполните эту команду, чтобы войти в систему как root:

mysql -u root -p

Вам будет предложено ввести пароль, просто нажмите return и войдите в систему. Не должно быть пароля, если вы его не установили. Теперь вы должны войти в систему и увидеть следующее приглашение:

mysql> 

Если вы это видите, то все готово! Если нет, то прокомментируйте и дайте мне знать 😄

Шаг 3. Завершение цели и настройка базы данных

Одна из самых важных вещей, которую нужно понять при выполнении задачи кодирования, - это то, что будет вводить / выводить. Мы уже знаем, что нашим вводом будет диапазон чисел. Предположим, что мы всегда начинаем с единицы и что нам просто предоставляется верхняя граница диапазона. Таким образом, учитывая 30, мы будем FizzBuzz все числа от 1 до 30.

Результатом будет таблица, которую мы создадим, будет таблицей с двумя столбцами. Первый столбец будет автоматически увеличивающимся id, как и первичный ключ в обычной таблице записей, и столбец с именем output, который будет принимать строку длиной не более 80 символов.

В качестве крайнего случая здесь может быть попытка вставить любую цифру длиной более 80 символов (потому что это технически строка), но это немного выходит за рамки этого сообщения. Вот как должен выглядеть конечный результат при максимальном диапазоне 15:

+----+----------+
| id | output   |
+----+----------+
|  1 | 1        |
|  2 | 2        |
|  3 | Fizz     |
|  4 | 4        |
|  5 | Buzz     |
|  6 | Fizz     |
|  7 | 7        |
|  8 | 8        |
|  9 | Fizz     |
| 10 | Buzz     |
| 11 | 11       |
| 12 | Fizz     |
| 13 | 13       |
| 14 | 14       |
| 15 | FizzBuzz |
+----+----------+

Понятно? Если нет, вы знаете, что делать 😄

Давайте создадим базу данных с именем Fizz_Buzz для этого упражнения, а затем переключимся на эту базу данных:

mysql> CREATE DATABASE Fizz_Buzz;
...
mysql> USE Fizz_Buzz;

Не забывайте завершать команды SQL точкой с запятой!

Теперь давайте создадим таблицу, в которой будут храниться наши результаты - поскольку мне нравятся практичность и не запутанные имена баз данных, мы собираемся назвать эту таблицу творческим именем results:

/* In the database Fizz_Buzz */
mysql> CREATE TABLE results (id SERIAL, output CHAR(80));

Термины SERIAL и CHAR(80) являются присвоениями типов данных столбцам id и output соответственно. SERIAL - это псевдоним для множества типов данных, связанных вместе, но он отлично подходит для первичных ключей, потому что некоторые ключевые компоненты включают, что данные являются целыми числами (BIGINT(20), чтобы быть конкретными), беззнаковыми (никогда не отрицательными), автоматически увеличивающимися, не нулевыми и уникальный. CHAR(80) намного проще, это в основном «строка» (на самом деле набор символов) длиной ровно 80 символов.

Боковое примечание: если вы когда-нибудь захотите увидеть все столбцы (поля) и их типы данных, вы можете запустить DESCRIBE <TABLE_NAME_HERE>;, чтобы увидеть эту информацию. В нашем примере: DESCRIBE results;.

Уф! 😅 Хорошо, теперь давайте убедимся, что эта таблица работает, вставив некоторые данные, в частности одну запись, напрямую использующую выходной столбец, чтобы убедиться, что все это работает. Для этого мы воспользуемся синтаксисом INSERT INTO SQL:

mysql> INSERT INTO results (output) VALUES (1);

… А затем приступим к работе:

mysql> SELECT * FROM results;
+----+--------+
| id | output |
+----+--------+
|  1 | 1      |
+----+--------+

Помните: мы использовали тип данных CHAR(80) для вывода, так почему же вставка числа работает? Предполагается, что это символ или набор символов, хотя внешне он выглядит как целое число. Мы используем это в наших интересах, потому что нам нужно будет добавить «числа», а также такие строки, как «Fizz» и «Buzz» в один столбец. Всегда знайте типы данных вашего поля!

Давайте продолжим и очистим эту таблицу с помощью следующей команды (она нам понадобится позже, я обещаю). Это очистит данные этой таблицы и сбросит последовательность автоинкремента для столбца id:

mysql> TRUNCATE TABLE results;

Фантастика! Разве это не здорово? Теперь, когда у нас есть основы, мы готовы погрузиться в концепцию хранимой процедуры.

Шаг 4: понимание хранимых процедур

Хранимые процедуры относятся к общей концепции хранимых процедур и хранимых функций. Сохраненная процедура - это набор логики, сохраняемый в базе данных для использования и запуска как части набора данных этой базы данных. Другими словами, создание хранимой процедуры похоже на определение метода, функции, лямбды или процедуры в большинстве обобщенных языков программирования. Это будет сердцевиной нашей задачи FizzBuzz, поэтому давайте сначала попробуем небольшой пример.

Одно небольшое примечание: разница между хранимыми процедурами и хранимыми функциями заключается в том, что хранимая процедура очень похожа на выражение, где это набор логики, определенный для основного выполнения действия. . В контексте базы данных он предназначен для работы с данными без возврата значения.

Сохраненные функции? Как вы уже догадались, они действительно возвращают значение и поэтому используют гораздо более сложные объявления типов данных. Сегодня мы будем использовать хранимые процедуры, потому что это все, что нам нужно.

Шаг 5: Создание простой, знакомой хранимой процедуры

Помните те две команды, которые мы использовали для создания строки данных (INSERT INTO), а затем в основном сбросили таблицу (TRUNCATE TABLE)? Теперь мы создадим процедуру, которая выполняет эти две команды за нас за одну процедуру.

Если вам это нужно, вот документация MySQL (8.0) для синтаксиса CREATE PROCEDURE.

Мы продолжим и создадим эту процедуру в консоли MySQL. Это может быть сложно из-за способа управления автозавершением кода. Другими словами, когда мы вводим текст в консоли, незавершенным операторам будет предшествовать маленькая стрелка -> вместо приглашения mysql>. Если вы испортили строку и перешли к следующей строке, вам нужно будет использовать команду \c, чтобы очистить ввод и повторить попытку.

Если вы создаете процедуру, а она не работает, вам нужно будет удалить процедуру DROP PROCEDURE <procedure_name_here> и создать ее заново. Хотя эта последняя проблема останется, мы значительно упростим написание кода в следующем разделе, а пока давайте воспользуемся консолью.

Сначала нам нужно изменить «разделитель» или, другими словами, символ, который сообщает консоли, что последовательность завершена. Это сделано для того, чтобы мы могли использовать правильный разделитель точки с запятой для определения процедуры в консоли, а затем изменить ее обратно на точку с запятой позже:

mysql> DELIMITER //

Это просто означает, что нам нужно будет добавить // в конце нашего полного оператора, чтобы сообщить MySQL «ОК, все готово».

Мы собираемся вызвать нашу процедуру goodbye_data_hello_data - она ​​сначала сбросит таблицу, а затем добавит данные:

mysql> CREATE PROCEDURE goodbye_data_hello_data(value CHAR(80))
    -> BEGIN
    ->   TRUNCATE TABLE results;
    ->   INSERT INTO results (output) VALUES (value);
    -> END //

Теперь мы должны иметь возможность вызвать эту процедуру и передать ей любой аргумент, который представляет собой набор из одного или нескольких символов длиной менее 80 символов. Четвертая строка - это действительно динамичная часть этой процедуры. Он использует аргумент value для вставки любого символа (ов), переданного / переданного при вызове процедуры.

Теперь мы можем вернуть наш разделитель и вызвать нашу процедуру, используя синтаксис CALL:

mysql> DELIMITER ;
mysql> CALL goodbye_data_hello_data(2);

После вызова процедуры запрос SELECT * FROM results; должен вернуть нам таблицу с одной строкой, с id 1 и output 2. Попробуйте запустить процедуру еще раз, используя аргумент «Fizz»: CALL goodbye_data_hello_data('Fizz'). Что происходит?

Шаг 6. Выход из консоли по мере усложнения ситуации

Разве не было бы проще, если бы мы могли набрать нашу процедуру и не беспокоиться о том, что испортят одну строку и придется заново набирать все это? Конечно! Давайте воспользуемся текстовым редактором (я собираюсь использовать Visual Studio Code, но Atom by GitHub - тоже отличный вариант).

Давайте создадим в нашем текстовом редакторе новый файл под названием goodbye_data_hello_data.sql. Убедитесь, что вы запомнили, где на вашем компьютере сохранен файл. Этот файл сделает для нас три вещи:

  1. Отбросьте процедуру, если она существует: DROP PROCEDURE IF EXISTS goodbye_data_hello_data;.
  2. Создайте процедуру (тот же синтаксис процедуры, что и выше, без подсказок).
  3. Вызовите процедуру с аргументом: CALL goodbye_data_hello_data('Buzz').

Вот как это будет выглядеть в целом (с соответствующими операторами-разделителями):

DROP PROCEDURE IF EXISTS goodbye_data_hello_data;
DELIMITER //
CREATE PROCEDURE goodbye_data_hello_data(value CHAR(80))
BEGIN
  TRUNCATE TABLE results;
  INSERT INTO results (output) VALUES (value);
END//
DELIMITER ;
CALL goodbye_data_hello_data('Buzz');

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

Отлично, теперь мы можем использовать команду source в консоли MySQL обратно в Терминале для запуска файла. В большинстве IDE есть возможность скопировать абсолютный путь к файлу, используйте его, если он у вас есть, и выполните эту команду:

source <path_to_file>
/* For example */
source /Users/jude/working/fizz_buzz/goodbye_data_hello_data.sql

Не забывайте расширение файла .sql, иначе оно не сработает.

Теперь мы можем создать новый файл и легко изменить наш метод, используя эту новую концепцию исходного файла, вместо того, чтобы потенциально испортить довольно длительный запуск SQL в консоли MySQL.

Шаг 6: вставка чисел с помощью циклов

Сначала настройте исходный файл, аналогичный приведенному выше (создайте новый файл с именем fizz_buzz.sql), исключив код в блоке CREATE PROCEDURE - например, так:

DROP PROCEDURE IF EXISTS fizz_buzz;
DELIMITER //
CREATE PROCEDURE fizz_buzz(max INT UNSIGNED)
  /* Code will go here */
END//
DELIMITER ;
CALL fizz_buzz(15);

Давайте продолжим и заставим эту процедуру делать что-то одно, а затем постепенно улучшать ее. Давайте заставим процедуру складывать все «числа» (не забывайте, что на самом деле это СИМВОЛЫ) из диапазона, созданного заданным аргументом максимального значения. Для этого нам нужно будет использовать цикл, иначе мы не сможем узнать, сколько INSERT INTO операторов нам нужно. WHILE синтаксис в SQL обычно выглядит так:

WHILE <condition> DO
  /* code to execute */
END WHILE;

Звучит неплохо? В приведенном ниже случае мы складываем все числа от 1 до 30:

DROP PROCEDURE IF EXISTS fizz_buzz;
DELIMITER //
CREATE PROCEDURE fizz_buzz(max INT UNSIGNED)
BEGIN  
  DECLARE counter INT UNSIGNED DEFAULT 1;
  TRUNCATE TABLE results;
  WHILE counter <= max DO
    INSERT INTO results (output) VALUES (counter);
    SET counter = counter + 1;
  END WHILE;
END //
DELIMITER ;
CALL fizz_buzz(30);

Это много. Давай распакуем это.

  • Строки 1–6 выглядят знакомо, единственное изменение - добавление аргумента max в виде положительного целого числа вместо значения.
  • DECLARE новый! Так мы объявляем локальные переменные в процедуре. Он принимает тип (ы) данных и значение по умолчанию, которое мы устанавливаем как единое целое в качестве начала нашего диапазона. Локальные переменные должны быть объявлены в начале процедуры, прежде чем что-либо еще.
  • Наш цикл WHILE начинается, и мы вставляем значение counter, добавляем единицу к counter (используя SET) и повторяем, пока counter не станет равным max.

Результатом SELECT * FROM results; должны теперь быть два столбца чисел 1–30 для id и output.

Это сработало для вас? Отлично 😁, можете переходить к следующему шагу! Неа? Прокомментируйте и дайте мне знать 😃.

Шаг 7: FizzBuzz: логика ветвления

Вы сделали это. Мы готовы вставить логику ветвления FizzBuzz и выполнить эту задачу. Используя тот же исходный файл, который вы создали для раздела выше, давайте добавим логику ветвления в наш цикл. Синтаксис логики ветвления в SQL обычно выглядит следующим образом:

IF <condition> THEN
  /* Code Here */
ELSEIF <condition> THEN
  /* Code Here */
ELSE
  /* Code Here */
END IF

Звучит неплохо? Давайте закончим эту задачу:

Нам нужно добавить логику ветвления для FizzBuzz в наш цикл while, чтобы то, что вставлено, было правильным на основе делимости значений, и нам нужно сделать это для всех чисел в указанном диапазоне. В качестве краткого напоминания о правилах:

  • Если число делится на 3, распечатайте «Fizz».
  • Если число делится на 5, распечатайте «Живая лента».
  • Если число делится на 3 и 5, распечатайте «FizzBuzz».
  • Наконец, если число не делится на 3 или 5, распечатайте его.

Вот как это будет выглядеть:

DROP PROCEDURE IF EXISTS fizz_buzz;
DELIMITER //
CREATE PROCEDURE fizz_buzz(max INT UNSIGNED)
BEGIN  
  DECLARE counter INT UNSIGNED DEFAULT 1;
  TRUNCATE TABLE results;
  WHILE counter <= max DO
    IF (counter % 3) = 0 AND (counter % 5) = 0 THEN
      INSERT INTO results (output) VALUES ('FizzBuzz');
    ELSEIF (counter % 3) = 0 THEN
      INSERT INTO results (output) VALUES ('Fizz');
    ELSEIF (counter % 5) = 0 THEN
      INSERT INTO results (output) VALUES ('Buzz');
    ELSE
      INSERT INTO results (output) VALUES (counter);
    END IF;
    
    SET counter = counter + 1;
  END WHILE;
END//
DELIMITER ;

Я оставил оператор CALL отключенным, потому что мы можем вызвать его сразу после вызова нашего исходного файла для создания процедуры, давайте теперь создадим процедуру:

source /Users/jude/working/fizz_buzz/fizz_buzz.sql

Наконец, позвольте вызвать процедуру (используйте любой верхний диапазон, который вы хотите, я буду использовать 30), а затем проверим таблицу результатов:

mysql> CALL fizz_buzz(30);
...
mysql> SELECT * FROM results;

Ваши результаты (если вы использовали 30) должны выглядеть примерно так:

+----+----------+
| id | output   |
+----+----------+
|  1 | 1        |
|  2 | 2        |
|  3 | Fizz     |
|  4 | 4        |
|  5 | Buzz     |
|  6 | Fizz     |
|  7 | 7        |
|  8 | 8        |
|  9 | Fizz     |
| 10 | Buzz     |
| 11 | 11       |
| 12 | Fizz     |
| 13 | 13       |
| 14 | 14       |
| 15 | FizzBuzz |
| 16 | 16       |
| 17 | 17       |
| 18 | Fizz     |
| 19 | 19       |
| 20 | Buzz     |
| 21 | Fizz     |
| 22 | 22       |
| 23 | 23       |
| 24 | Fizz     |
| 25 | Buzz     |
| 26 | 26       |
| 27 | Fizz     |
| 28 | 28       |
| 29 | 29       |
| 30 | FizzBuzz |
+----+----------+

Ура! Вы сделали это 🎉 Это был своего рода марафон, поэтому, если у вас есть какие-либо вопросы или комментарии, не стесняйтесь оставлять комментарии ниже. Удачного кодирования!