← Все курсы · Курс SQL

Одна ячейка, много значений: когда столбец содержит список

Форма столбца, которая заставляет DISTINCT лгать · Часть Analyst Prep Kit

К , аналитик данных · Обновлено

Вы запрашиваете у таблицы список категорий, и ответ — 2923. Вы ожидали около тридцати. Ничто не допустило ошибок, вас ничего не предупредило, и номер не является ошибкой. Это правильный ответ на вопрос, который вы не хотели задавать. Это происходит, когда в столбце хранится список внутри единственной клетки, и она тихонько ломается DISTINCT, COUNT, и GROUP BY все сразу. Вот как определить это менее чем за минуту, почему число вернулось неправильно, а также три способа работы со столбцом, самый простой из них.

Версия из одного предложения. Если ячейка содержит Action,Indie,RPG вместо одного значения, то DISTINCT считается уникальным комбинации, а не уникальные значения, и каждый счетчик, построенный на его основе, наследует ошибку.
Что здесь
  1. Признак: отчетливое число, которое слишком велико.
  2. Посмотрите на значения, прежде чем считать их
  3. Оцените, насколько это распространено
  4. Почему DISTINCT дал вам неправильный ответ
  5. Ценности, которые никогда не появляются в одиночку
  6. Три способа работы с ним: первый самый простой
  7. Какой из них выбрать
  8. Запишите это как ограничение
  9. Почему данные выглядят так

Проработанный пример — общедоступный набор данных игр Пар, состоящий из 125 855 строк с Genres столбец. Каждое число ниже — это реальный результат, в том числе и неправильное.

Признак: отчетливое число, которое слишком велико.

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

SELECT COUNT(DISTINCT Genres) AS genre_count
FROM games_raw;

Ответ вернулся 2,923. На цифровой витрине нет 2923 жанров. В нем есть меню из нескольких десятков, из которых выбирает разработчик.

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

Посмотрите на значения, прежде чем считать их

Прежде чем теоретизировать, посмотрите. Счет говорит вам, сколько, а не что:

SELECT DISTINCT Genres
FROM games_raw
LIMIT 20;
жанры
Приключение
Повседневный
Казуальная игра, Инди, Симулятор
Экшен,Ранний доступ
Боевики, Приключения
Симуляторы,Стратегии

Вот оно. Некоторые ячейки содержат один жанр, а некоторые — три, разделенные запятыми. В столбце не хранится жанр, в нем хранится список жанров в виде фрагмента текста.

Привычка, которую стоит украсть. Сначала посмотрите, потом измерьте. Если вы сначала измерите, вы сможете подтвердить или опровергнуть только то, что вы уже подозревали, а то, о чем вы не подозревали, останется невидимым. Двадцать строк наблюдения — самый дешевый шаг в анализе, и он выявляет проблемы, о которых не сообщает сводная статистика. Исследовательский анализ данных руководство строит остальную часть вступительной процедуры.

Оцените, насколько это распространено

Двадцать рядов — это впечатление. Превратите это в доказательство, потому что от ответа зависит, сколько работы вам предстоит. Если несколько строк содержат списки, это небольшая очистка. Если почти все из них так делают, то проблема заключается в форме колонны:

SELECT COUNT(DISTINCT Genres) AS values_with_a_comma
FROM games_raw
WHERE Genres LIKE '%,%';

2,898 из 2923 различных значений содержат запятую. Это 99 процентов. Это не крайний случай в данных, это сами данные.

(LIKE '%,%' читается как «что угодно, потом запятая, потом что угодно». Если этот синтаксис новый, НРАВИТСЯ и подстановочные знаки прикрывает это.)

Почему DISTINCT дал вам неправильный ответ

DISTINCT сделал именно то, что обещает. Он возвращал каждое отдельное значение в столбце один раз. Проблема в том, что «значение» здесь представляет собой целую строку, разделенную запятыми, и он понятия не имеет, что запятые что-то означают:

Содержимое ячейкиЧто видит DISTINCT
Indieодно значение
Casual,IndieДругая ценность
Casual,Indie,SimulationТретье, другое значение

Три строки в результате и слово Indie есть во всех трёх. Так что 2923 – это не количество жанров. Это количество уникальных комбинации жанров, которые использует хотя бы одна игра. Это реальный факт, просто это не тот факт, о котором кто-то просил.

То же самое искажение присутствует в каждом агрегате, который вы строите на столбце. GROUP BY Genres составляет 2923 группы, в большинстве из которых есть несколько игр. Гистограмму невозможно прочитать. Вычисленная на основе этого процентная доля неверна, поскольку игра, помеченная тремя жанрами, учитывается один раз в отдельной группе, а не один раз для каждого жанра.

Ценности, которые никогда не появляются в одиночку

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

SELECT DISTINCT Genres
FROM games_raw
WHERE Genres NOT LIKE '%,%'
ORDER BY Genres;

Это возвращает 24 ряды, и они выглядят правильно. Экшены, Приключения, Казуальные игры, Инди, Ролевые игры, Гонки, Симуляторы, Спортивные игры, Стратегии и так далее. Соблазнительно назвать это списком жанров и двигаться дальше.

Оно неполное, и ничто в результате об этом не говорит. Значение, которое применяется только когда-либо рядом еще один будет находиться в 2898 списках и ни разу не появится сам по себе. Зонд для одного:

SELECT DISTINCT Genres
FROM games_raw
WHERE Genres LIKE '%Gore%';

132 комбинации возвращаются, и ни одна из них не Gore само собой. Gore реален, он используется, и в списке из 24 строк он полностью отсутствует. Правильное разделение столбца оказывается 33 различные ценности, поэтому девять из них никогда не остаются одинокими.

Общее правило. Фильтрация столбца списка по строкам с одним элементом дает значения, которые может появляются сами по себе, а не ценности, которые существовать. Это разные наборы, и разница молчалива. Относитесь к числу 24 как к первому варианту для проверки, а не как к ответу.

Три способа работы с ним: первый самый простой

1. Не разделяйте, отфильтруйте с помощью LIKE

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

SELECT Name, Price
FROM games_raw
WHERE Genres LIKE '%RPG%';

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

SELECT Name, Price
FROM games_raw
WHERE ',' || Genres || ',' LIKE '%,RPG,%';

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

2. Сопоставьте список со списком, который у вас уже есть.

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

SELECT g.genre, COUNT(*) AS games
FROM genre_list AS g
JOIN games_raw AS r
  ON ',' || r.Genres || ',' LIKE '%,' || g.genre || ',%'
GROUP BY g.genre
ORDER BY games DESC;

Это дает правильный подсчет по жанрам без каких-либо операций со строками, и у этого есть тихое преимущество: поскольку список принадлежит вам, жанр с нулевым количеством игр все равно отображается, когда вы переключаетесь на LEFT JOIN, о чем раскол никогда не сможет вам сказать.

Подходит для: известный, устойчивый набор категорий. Не подходит для: обнаружение категорий, о существовании которых вы не знали, и это та же проблема, что и в списке из 24 строк.

3. Разделите список на одну строку для каждого значения.

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

WITH RECURSIVE split(genre, rest) AS (
  SELECT '', Genres || ','
  FROM games_raw
  WHERE Genres IS NOT NULL AND Genres <> ''
  UNION ALL
  SELECT substr(rest, 1, instr(rest, ',') - 1),
         substr(rest, instr(rest, ',') + 1)
  FROM split
  WHERE rest <> ''
)
SELECT trim(genre) AS genre,
       COUNT(*) AS games
FROM split
WHERE trim(genre) <> ''
GROUP BY lower(trim(genre))
ORDER BY games DESC;

Читая словами: каждую игру начинайте с пустого жанра и его полного списка с приклеенной в конце запятой, чтобы последний элемент тоже имел разделитель. Затем несколько раз вырезайте первую запятую, сохраняя кусок перед ней и перенося остальную часть вперед, пока ничего не останется. instr находит положение запятой и substr обрезает текст. SQL CTE объясняет WITH заблокировать себя.

Это возвращает 33 жанра с количеством игр в каждом, и именно эту версию вы должны указать в отчете.

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

Большинство баз данных имеют ярлык. Рекурсивная версия, приведенная выше, работает везде, поэтому ее стоит увидеть один раз, но она вам понадобится редко: в PostgreSQL есть unnest(string_to_array(genres, ',')), SQL Server имеет STRING_SPLIT(genres, ','), MySQL 8 имеет JSON_TABLE, у Большой запрос есть UNNEST(SPLIT(genres, ','))и у DuckDB есть unnest(str_split(genres, ',')). Проверьте свой, прежде чем писать длинную форму.

Какой из них выбрать

Ваш вопрос Использовать
Покажите мне строки в одной категорииНРАВИТСЯ фильтр
Сколько строк в каждой из нескольких известных категорийНРАВИТСЯ фильтр или объединение списка
Сколько строк в каждой категорииРазделить или объединить список
Какие категории здесь вообще существуютСплит, он единственный, кто обнаруживает
График доли категорийРазделите и укажите в заголовке, что строки учитываются один раз для каждой категории.

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

Запишите это как ограничение

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

В этом конкретном наборе данных есть еще одна вещь, на которую стоит обратить внимание, и это та деталь, которая отличает настоящий анализ от учебного пособия. Эти 33 ценности относятся не ко всем жанрам. Gore, Violent, Nudity и Sexual Content являются предупреждениями о содержании. Accounting, Photo Editing и Video Production это категории программного обеспечения, а вовсе не игры. В этом поле в одном столбце смешаны несколько типов ярлыков, поэтому заголовок «Основной жанр» является вводящим в заблуждение, если вы не решите, какой из 33 учитывается, и не скажете, какой вы удалили. Документирование ограничений данных охватывает написание этого раздела, не подрывая при этом вашу собственную работу.

Почему данные выглядят так

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

game_idЖанр
101Действие
101Инди
101RPG
102Повседневный

С этой таблицей подсчет по жанрам является обычным делом. GROUP BY и ничего из этого руководства не нужно. Так почему же плоская версия продолжает появляться? Потому что аналитики получают данные, а не проектируют их. Экспорт стабилизируется. Таблицы печатаются вручную. API возвращают массив, и тот, кто написал CSV, соединил его запятыми. Инструменты для проведения опросов, естественно, записывают ответы «выберите все подходящие» в одно поле.

Эту форму столбца вы встретите в тегах, навыках, категориях, списках регионов, опциях продукта и вопросах опроса с множественным выбором. Если вы узнали его один раз, вы узнаете его повсюду, и первое, что вы сделаете с любым новым категориальным столбцом, — это просмотрите двадцать строк, прежде чем пересчитать их.

Подробнее об этом и еще что-нибудь подобное. Все инструкции находятся в одном месте на индекс направляющих: SQL, Tableau, миграция данных и связанные с ними рабочие привычки.
DISTINCT вернул 2923 категории, реальный ответ — 33, и ничто не указывало на разницу.

SQL для аналитиков составляет 458 страниц рабочих ответов, при этом нечетные случаи прописаны, а не пропущены.

SQL для аналитиков, 19 $ →
Сделайте WHERE, GROUP BY, JOIN и все остальное привычным.

тот Курс SQL обучает основам аналитического SQL на рабочих примерах, интерактивной лаборатории JOIN и Агрегация, которую вы запускаете в браузере, флэш-карточках и пробном экзамене. Нечего устанавливать.

Откройте набор SQL →

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