30 SQL-запросов, объяснённых через их эквиваленты Pandas

В мире, где с 1974 года доминирует SQL, в 2008 году появился Pandas, предлагающий привлекательные функции, такие как встроенная визуализация и гибкая обработка данных. Pandas быстро стал популярным инструментом для исследования данных, затмив собой SQL.
Но SQL по-прежнему держит свои позиции. Это второй по востребованности и третий по скорости роста язык для Data science (см. здесь ). Таким образом, в то время как Pandas привлекает всеобщее внимание, SQL остаётся жизненно важным навыком для любого специалиста по данным.
Давайте узнаем, как легко выучить SQL, если вы уже знаете Pandas.
https://t.me/sqlhub – изучение sql в одном канале
Подключение к базе данных
Настройка рабочей области SQL и подключение базы данных может быть настоящей головной болью. Во-первых, вам нужно установить предпочитаемый вариант SQL (PostgreSQL, MySQL и т. д.) и загрузить SQL IDE. Выполнение их здесь увело бы нас от цели статьи, поэтому мы воспользуемся сокращением.
В частности, мы будем напрямую запускать SQL-запросы в Jupyter Notebook без дополнительных шагов. Всё, что нам нужно сделать, это установить пакет ipython-sql с помощью pip:
pip install ipython-sql
После установки запустите новый сеанс Jupyter и выполните эту команду в блокноте:
%load_ext sql
и всё готово!
Чтобы проиллюстрировать, как работают основные операторы SQL, мы будем использовать базу данных Chinook SQLite, которая имеет 11 таблиц.
Чтобы загрузить набор данных и его 11 таблиц как отдельные переменные в нашу среду, мы можем запустить:
%sql sqlite:///data/chinook.db
Оператор начинается со встроенной волшебной команды %sql, которая сообщает интерпретатору блокнота, что мы будем запускать команды SQL. За ней следует путь, по которому находится загруженная база данных Chinook.
Допустимые пути всегда должны начинаться с префикса sqlite:/// для баз данных SQLite. Выше мы подключаемся к базе данных, хранящейся в папке «data» текущего каталога. Если вы хотите передать абсолютный путь, префикс должен начинаться с четырех косых черт:sqlite:////
Если вы хотите подключиться к другой разновидности базы данных, вы можете обратиться к этой отличной статье .
Первый взгляд на таблицы
Первое, что мы всегда делаем в Pandas, — это используем функцию .head() для первого взгляда на данные. Давайте узнаем, как это сделать в SQL:
%%sql
SELECT * FROM customers
LIMIT 5

Первое ключевое слово в приведённом выше запросе — SELECT. Оно эквивалентно оператору скобок в Pandas, где мы выбираем определённые столбцы. Но за ключевым словом SELECT следует * (звездочка). * — оператор SQL, который выбирает всё (все строки и столбцы) из таблицы, указанной после ключевого слова FROM. LIMIT используется для минимизации возвращаемого результата. Таким образом, приведённый выше запрос эквивалентен функции df.head().
Если вы не хотите выбирать все столбцы, вы можете указать одно или несколько имён столбцов после ключевого слова SELECT:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
LIMIT 10

Эквивалентная операция Pandas:
tracks[['Name', 'Composer', 'UnitPrice']].head(10)
Другое полезное ключевое слово в SQL — DISTINCT. Добавление этого ключевого слова перед именем любого столбца возвращает его уникальные значения:
%%sql
SELECT DISTINCT FirstName
FROM employees
-- equivalent to `.unique()`

Комментарии в SQL пишутся с двойным дефисом.
Подсчёт количества строк
Точно так же, как у Pandas есть атрибут .shape для своих DataFrames, у SQL есть функция COUNT для отображения количества строк в таблице:
%%sql
SELECT COUNT(*) FROM tracks

Более полезной информацией будет подсчёт количества уникальных значений в определённом столбце. Мы можем сделать это, добавив ключевое слово DISTINCT в COUNT:
%%sql
SELECT COUNT(DISTINCT FirstName) FROM employees
-- equivalent to `employees['FirstName'].nunique()`

Фильтрация результатов с помощью предложений WHERE
Просто смотреть и считать строки может любой. Давайте посмотрим, как мы можем фильтровать строки на основе условий.
Во-первых, давайте посмотрим на песни, которые стоят больше доллара:
%%sql
SELECT * FROM tracks
WHERE UnitPrice > 1.0
LIMIT 10
--tracks[tracks['UnitPrice' > 1.0]]

Условные операторы записываются в предложении WHERE, которое всегда следует после FROM и перед ключевыми словами LIMIT. Использование условий очень похоже на то, как мы это делаем в Pandas, но я осмелюсь сказать, что версия SQL более читабельна.
Вы также можете использовать функцию COUNT при использовании условных выражений. Например, давайте посмотрим количество песен с ценой от 1 до 10 долларов:
%%sql
SELECT COUNT(*) FROM tracks
WHERE UnitPrice > 1.0 AND UnitPrice < 10
-- tracks[(tracks['UnitPrice'] > 1) & (tracks['UnitPrice'] < 10)]

Выше мы связали два условия логическим оператором AND. Аналогично используются и другие логические операторы (OR, NOT).
Теперь давайте посмотрим все счета-фактуры, в которых в качестве города выставления счётов указан Париж или Берлин:
%%sql
SELECT BillingAddress, BillingCity, Total
FROM invoices
WHERE BillingCity = 'Paris' OR BillingCity = 'Berlin'
LIMIT 5
--invoices[(invoices['BillingCity'] == 'Paris') |
-- (invoices['BillingCity'] == 'Berlin')]

Оператор равенства в SQL требует только одного знака «=» (равно). Оператор неравенства представлен операторами ‘!=’ или ‘<>’:
%%sql
SELECT BillingAddress, BillingCity, Total
FROM invoices
WHERE BillingCity != 'Paris' AND BillingCity <> 'Berlin'
LIMIT 5

Упрощённая фильтрация с помощью BETWEEN и IN
Подобные условные операторы используются очень часто, и их запись с помощью простых логических значений становится громоздкой. Например, в Pandas есть функция .isin(), которая проверяет, принадлежит ли значение списку значений.
Если бы мы хотели выбрать счета для пяти городов, нам пришлось бы написать пять связанных условий. К счастью, SQL поддерживает аналогичный оператор IN, поэтому .isin() нам не нужно:
%%sql
SELECT *
FROM invoices
WHERE BillingCity IN ('Berlin', 'Paris', 'New York', 'Boston', 'London')
LIMIT 5
--invoices[invoices['BillingCity'].isin(
-- ('Berlin', 'Paris', 'New York', 'Boston', 'London')
--)]

Список значений после IN следует указывать в виде кортежа, а не списка. Вы также можете отменить условие с помощью ключевого слова NOT:
%%sql
SELECT *
FROM invoices
WHERE BillingCity NOT IN ('Berlin', 'Paris', 'New York', 'Boston', 'London')
LIMIT 5
--invoices[~invoices['BillingCity'].isin(
-- ('Berlin', 'Paris', 'New York', 'Boston', 'London')
--)]

Другой распространённой операцией фильтрации числовых столбцов является выбор значений в пределах диапазона. Для этого можно использовать ключевое слово BETWEEN, которое эквивалентно pd.Series.between():
%%sql
SELECT BillingCity, Total
FROM invoices
WHERE Total BETWEEN 5 AND 15
LIMIT 5
--invoices[invoices['Total'].between(5, 15)]

Проверка на нули
В каждом источнике данных есть пропущенные значения, и базы данных не являются исключением. Точно так же, как есть несколько способов исследовать отсутствующие значения в Pandas, существуют определённые ключевые слова, которые проверяют наличие нулевых значений в SQL. Следующий запрос подсчитывает количество строк с пропущенными значениями в BillingState:
%%sql
SELECT COUNT(*) FROM
invoices
WHERE BillingState IS NULL
--invoices['BillingState'].isnull().sum()

Вы можете добавить ключевое слово NOT между IS и NULL, чтобы отбросить пропущенные значения определённого столбца:
%%sql
SELECT InvoiceDate, BillingCountry
FROM invoices
WHERE Total IS NOT NULL
LIMIT 10

Лучшее сопоставление строк с LIKE
В предложении WHERE мы отфильтровали столбцы на основе точных текстовых значений. Но часто нам может понадобиться отфильтровать текстовые столбцы на основе шаблона. В Pandas и чистом Python мы использовали бы регулярные выражения для сопоставления с образцом, которые очень эффективны, но для освоения регулярных выражений требуется время.
В качестве альтернативы SQL предлагает подстановочный знак ‘%’ в качестве заполнителя для соответствия любому символу 0 или более раз. Например, строка «gr%» соответствует «greet», «groom», «greed» и «%ex%» соответствует любому тексту с «ex» в середине и т. д. Давайте посмотрим, как использовать её с SQL:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE Name LIKE 'B%'
LIMIT 5
--tracks[tracks['Name'].str.startswith('B')]

Приведённый выше запрос находит все песни, начинающиеся с «B». Строка, содержащая подстановочный знак, должна идти после ключевого слова LIKE.
Теперь давайте найдём все песни, в названии которых есть слово «beautiful»:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE Name LIKE '%beautiful%'
--tracks[tracks['Name'].str.contains('beautiful')]

Вы также можете использовать другие логические операторы рядом с LIKE:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE (Name LIKE 'F%') AND (UnitPrice > 1.0)
--tracks[(tracks['Name'].str.startswith('F')) & (tracks['UnitPrice'] > 1.0)]

В SQL есть много других подстановочных знаков, которые имеют разные функции. Вы можете увидеть полный список и их использование здесь .
Агрегатные функции в SQL
Также возможно выполнять основные арифметические операции над столбцами. Эти операции называются агрегатными функциями в SQL, и наиболее распространёнными из них являются AVG, SUM, MIN, MAX. Их функциональность должна быть ясна из их названий:
%%sql
SELECT SUM(Total), MAX(Total),
MIN(Total), AVG(Total)
FROM invoices

Агрегатные функции дают только один результат для столбца, в котором вы их использовали. Это означает, что вы не можете выполнять агрегирование по одному столбцу и выбирать другие неагрегированные столбцы:
%%sql
SELECT AVG(Total), BillingCity, BillingAddress
FROM invoices;
-- the result will be a single row because
-- of the presence of aggregate functions

Вы можете так же легко комбинировать агрегатные функции с условными операторами, используя предложения WHERE:
%%sql
SELECT AVG(Total), BillingCity
FROM invoices
WHERE BillingCity = 'Paris'
-- invoices[invoices['BillingCity']]['Total'].mean()

Также можно использовать арифметические операторы, такие как +, -, *, / в столбцах и простые числа. При использовании со столбцами операция выполняется поэлементно:
%%sql
SELECT SUM(Total) / COUNT(Total)
FROM invoices
-- finding the mean manually

Одно замечание об арифметических операциях: если вы выполняете операции только с целыми числами, SQL считает, что вы ожидаете целое число в качестве ответа:
%%sql
SELECT 10 / 3

Вместо того, чтобы возвращать 3,33…, результат равен 3. Чтобы получить результат с плавающей запятой, вы должны использовать в запросе хотя бы одно число с плавающей запятой или использовать все числа с плавающей запятой, чтобы быть в безопасности:
%%sql
SELECT 10.0 / 3.0

Используя эти знания, рассчитаем среднюю продолжительность песни в минутах:
%%sql
SELECT Milliseconds / 1000.0 / 60.0
FROM tracks
LIMIT 10

Если вы обратите внимание на приведённый выше столбец, его имя написано как «запрос, используемый для создания этого столбца ». Из-за этого поведения использование длинных вычислений, таких как определение стандартного отклонения или дисперсии столбца, может быть проблемой, поскольку имя столбца будет таким же большим, как и сам запрос.
Чтобы избежать этого, SQL допускает использование псевдонимов, подобно тому, как операторы импорта имеют псевдонимы в Python. Например:
%%sql
SELECT SUM(Total) as sum_total
FROM invoices

Использование ключевого слова as после одного элемента в операторе SELECT сообщает SQL, что мы используем псевдоним. Вот ещё примеры:
%%sql
SELECT SUM(Total) as sum_total,
MAX(Total) as max_total,
MIN(Total) as min_total,
SUM(Total) / COUNT(Total) as mean_total
FROM invoices

Вы можете так же легко использовать псевдонимы для столбцов с длинными именами.
Упорядочивание результатов в SQL
Так же, как у Pandas есть метод sort_values, SQL поддерживает упорядочение столбцов через предложение ORDER BY. Передача имени столбца после предложения сортирует результаты в порядке возрастания:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE Composer <> 'None'
ORDER BY Composer
LIMIT 10
-- tracks.sort_values('Compose')[['Name', 'Compose', 'UnitPrice']]

Упорядочим таблицу треков по возрастанию имени композитора. Обратите внимание, что оператор ORDER BY всегда должен стоять после предложения WHERE. Также можно передать два или более столбца в ORDER BY:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE Composer <> 'None'
ORDER BY UnitPrice, Composer, Name
LIMIT 10
-- tracks.sort_values(['UnitPrice', 'Composer', 'Name'])\
-- [['Name', 'Compose', 'UnitPrice']]

Вы также можете изменить порядок, передав ключевое слово DESC после имени каждого столбца:
%%sql
SELECT Name, Composer, UnitPrice
FROM tracks
WHERE Composer != 'None'
ORDER BY UnitPrice DESC, Composer DESC, Name ASC
LIMIT 10
-- tracks.sort_values(['UnitPrice', 'Composer', 'Name'])\
-- [['Name', 'Compose', 'UnitPrice']]

Приведённый выше запрос возвращает три столбца после упорядочения UnitPrice и Compose в порядке убывания и имени в порядке возрастания ( ключевое слово ASC по умолчанию).
Группировка в SQL
Одной из самых мощных функций Pandas является расширение groupby. Вы можете использовать его, чтобы преобразовать таблицу практически в любую форму, которую вы хотите. Его очень близкий родственник в SQL – GROUP BY, который можно использовать для достижения той же функциональности. Например, следующий запрос подсчитывает количество песен в каждом жанре:
%%sql
SELECT GenreID, COUNT(*) as genre_count
FROM tracks
GROUP BY GenreId
LIMIT 10
-- tracks.groupby('GenreId')['GenreID'].count()

Разница между GROUP BY в SQL и groupby в Pandas заключается в том, что SQL не позволяет выбирать столбцы, которые не были указаны в предложении GROUP BY. Например, добавление дополнительного свободного столбца в приведённый выше запрос приводит к ошибке:
%%sql
SELECT Name, GenreID, COUNT(*) as genre_count
FROM tracks
GROUP BY GenreId
syntax error at or near "GROUP"
LINE 1: GROUP BY GenreId;
Однако вы можете выбрать столько столбцов в операторе SELECT, сколько хотите, если вы используете для них какой-либо тип агрегатной функции:
%%sql
SELECT GenreId, AlbumId,
COUNT(*) as genre_count,
AVG(Milliseconds) / 1000.0 / 60.0 as avg_duration,
AVG(UnitPrice) as avg_price
FROM tracks
GROUP BY GenreId, AlbumId
LIMIT 10

Приведённый выше запрос включает почти все темы, которые мы изучили до этого момента. Мы группируем как по идентификатору альбома, так и по идентификатору жанра, и для каждой группы мы рассчитываем среднюю продолжительность и цену песни. Мы также эффективно используем псевдонимы.
Мы можем сделать запрос ещё более мощным, упорядочив его по средней продолжительности и количеству жанров:
%%sql
SELECT GenreId, AlbumId,
COUNT(*) as genre_count,
AVG(Milliseconds) / 1000.0 / 60.0 as avg_duration,
AVG(UnitPrice) as avg_price
FROM tracks
GROUP BY GenreId, AlbumId
ORDER BY avg_duration DESC, genre_count DESC
LIMIT 10

Обратите внимание на то, как мы используем псевдонимы агрегатных функций в предложении ORDER BY. После того как вы назовёте столбец или результат агрегатной функции псевдонимом, вы сможете ссылаться на них только по псевдониму для остальной части запроса.
Использование условных выражений с HAVING
По умолчанию SQL не разрешает условную фильтрацию с использованием агрегатных функций в предложении WHERE. Например, мы хотим выбрать только те жанры, в которых количество песен превышает 100. Попробуем сделать это с предложением WHERE:
%%sql
SELECT GenreId
FROM tracks
GROUP BY GenreId
WHERE COUNT(GenreId) > 10
syntax error at or near "WHERE"
LINE 1: where COUNT(GenreId) > 10
Правильный способ фильтрации строк на основе результатов агрегатных функций — использование предложения HAVING:
%%sql
SELECT GenreId
FROM tracks
GROUP BY GenreId
HAVING COUNT(GenreId) > 100

Предложение HAVING обычно используется с GROUP BY. Всякий раз, когда вы хотите отфильтровать строки с помощью агрегатных функций, используйте предложение HAVING!
Краткое содержание
К настоящему времени вы должны были понять, насколько мощным может быть SQL и насколько более читабельным он становится по сравнению с Pandas. Несмотря на то, что мы узнали тонну, мы едва лишь поцарапали поверхность.
Для практических задач я рекомендую Data Lemur или LeetCode, если вы любите приключения.



