Цели статьи

Качество данных - обязательное условие перехода на более продвинутые уровни
Качество данных - обязательное условие перехода на более продвинутые уровни

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

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

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

Почему именно таблица? И как она может быть плоской?

Я работаю в сфере бизнес-консалтинга. Со временем мне удалось выделить три "структуры данных", которые играют ключевую роль в нашей сфере:

  1. Плоская таблица - самая фундаментальная структура данных в бизнесе, по моей оценке

  2. Иерархия (дерево) - по сути, это частный случай графа, но имеет много особенностей, поэтому выделяется отдельно. В области бизнеса так удобнее

  3. Граф

Если вы откроете почти любой Excel файл, там будет нечто похожее на таблицы. В Word, Power Point также частенько вставляют таблицы. Реляционные БД - чистые плоские таблицы. Кубические БД - можно логически представить в виде таблицы. Даже иерархии и графы часто физически хранятся в табличном виде. Именно поэтому, в бизнесе, самый большой из трёх китов - плоская таблица. В этой статье мы подробнее рассмотрим именно её.

Термин "плоская таблица" подчеркивает разницу с просто таблицами:

Плоская таблица - это классическая таблица (а-ля SQL table) в первой нормальной форме (1NF). Однострочная шапка, атомарные типы данных. В общем красота. Когда вам дают такую таблицу, вы сразу можете приступить к интересной и полезной стадии анализа.

Таблица (в общем случае) - это всё, что человек считает таблицей. Таблицы бывают матричные, двойные матричные, с многострочными шапками, неатомарными типами данных в теле и т.д. Если вам дали такую таблицу, вы потратите от 15 минут до нескольких дней на перевод её в понятный, плоский вид. Иногда этот перевод вообще невозможен, по причине архитектурных ошибок или качества исходных данных.

Для наглядности два фото:

Это НЕ плоская таблица :(
Смотреть приятно, но никакие инструменты анализа применить нельзя. Сложно понять какие сущности присутствуют в таблице. Как оказалось, в столбце №2 есть четыре сущности: предприятие, месторождение, объект и показатель :)
Смотреть приятно, но никакие инструменты анализа применить нельзя.
Сложно понять какие сущности присутствуют в таблице. Как оказалось, в столбце №2 есть четыре сущности: предприятие, месторождение, объект и показатель :)

А вот это плоская таблица :)
Визуально менее приятно, но зато в этом кроется гигантский аналитический потенциал
Визуально менее приятно, но зато в этом кроется гигантский аналитический потенциал

Для перевода вышепоказанной таблицы в плоский вид потребовалась не одна строчка кода. Код перевода можно посмотреть в статье тут.

Архитектура плоской таблицы

Введем основные термины.

Потенциальные ключи таблицы (candidate key) - это наборы столбцов, которые обеспечивают уникальность каждой строчки таблицы. Больше техническое понятие. Полный набор потенциальных ключей интереса не представляет.

Первичный ключ таблицы (primary key) - это то, про что рассказывает плоская таблица. Каждая строчка содержит один объект. Каждая строчка рассказывает что-то про этот объект. Первичный ключ обеспечивает уникальность каждой строчки. Т.е. первичный ключ - это один из потенциальных ключей. К сожалению, далеко не все, кто проектируют таблицы, задумываются о первичном ключе. А без этого никуда.

Функциональные зависимости (functional dependencies). Приведу пример. Есть справочник должности, а есть справочник сотрудники. Допустим, что каждый сотрудник имеет строго одну должность. Таким образом, сотрудник функционально определяет должность. Обратное, обычно, неверно. Т.е. одна должность может быть у многих сотрудников. В бизнесе принято говорить, что должность - это атрибут/реквизит/группировка для справочника сотрудников. Но суть дела не меняется.

В функциональной зависимости есть детерминант (определитель) и цель. Детерминант -> Цель. Сотрудники - детерминант, должности - цель.


А теперь представим, что нам для анализа дали хорошую плоскую таблицу. Как нам можно составить общее представление о ней (понять архитектуру таблицы). Ниже четыре вопроса, которые я задаю чаще всего:

1) Что является первичным ключом таблицы?

Плоская таблица - это рассказ про первичный ключ.

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

2) Какие функциональные зависимости есть в таблице?

В каком-то смысле, функциональная зависимость - основа информации. Т.е. именно отношение между двумя множествами - это и есть информация. Например, фраза "каждый сотрудник имеет ровно одну должность" означает два множества и функциональную зависимость между ними. Сотрудник -> Должность.

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

3) Как соотносятся два столбца в таблице?

Этот вопрос задается реже, обычно он нужен, чтобы детально изучить отношение между двумя столбцами (либо двумя наборами столбцов).

Столбцы могут соотноситься как: 1-1, 1-N, N-1, N-N (логика как в ER-диаграммах). Сотрудники и должности соотносятся как N-1.

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

4) Чем функционально определяется данный столбец?

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

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

Несколько слов про нормальные формы "по-простому"

Теперь мы готовы к пониманию первых трех нормальных форм (NF) в которых может находиться таблица.

1NF - это однострочная шапка, атомарные значения, без дубликатов строк, без пустых строк и столбцов. Ну т.е. минимальные требования к формату. Это и есть плоская таблица.

2NF и 3NF требуют соблюдения более тонкого принципа: "Таблица - рассказ про первичный ключ". Если какая-то колонка рассказывает про часть первичного ключа - нарушается нормальная форма. Если одна неключевая колонка рассказывает про другую неключевую колонку - опять же принцип нарушается.

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

Инструменты помогающие понять архитектуру плоской таблицы

Технически можно применять следующие инструменты:

MS Excel

Если нет ничего другого, используется чистый Excel, либо его аналог. Его преимущество в том, что он простой и позволяет быстро проверять простые гипотезы. Например, является ли этот набор столбцов уникальным. Но, в целом, это неэффективный инструмент для такого класса задач.

Pivot table (MS Excel)

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

Python

В сложных случаях требуется python. Интересно, но я смог найти только одну библиотеку, которая выполняет подобный архитектурный анализ из коробки (git, article). FDtool написан на python2. Видно, что библиотека не поддерживается её создателями. Что расстраивает(

Я решил написать велосипед свою версию, которая бы сделала архитектурный анализ удобнее. Код выложен на github. Ниже в кейсе я покажу применение инструмента на реальной таблице.

# базовое применение такое
!git clone https://github.com/Grigory-T/FlatTableAnalysis.git
from FlatTableAnalysis.FlatTableAnalysis import FlatTableAnalysis
fta = FlatTableAnalysis(df) # предполагается, что уже есть плоская таблица в виде df
fta.show_header_info()
fta.get_candidate_keys()
fta.show_fd_graph()
fta.show_set_relation('колонка 1', 'колонка 2')
fta...  # ряд других методов, которые применяются реже

Кейс анализа таблицы из huggingface

В качестве таблицы я взял датасет музыкальных треков из huggingface (ссылка). Он уже в хорошем плоском виде, поэтому мы сразу можем приступить к анализу (минуя скучную стадию ETL). Колаб с кодом тут. Он содержит анализ таблицы с помощью моего питоновского класса FlatTableAnalysis. Ниже я опишу основные выводы.

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

Шапка таблицы maharshipandya/spotify-tracks-dataset
архитектура таблицы не очевидна
архитектура таблицы не очевидна

Первичный ключ

что выдает нам инструмент?
fta - экземпляр нашего класса FlatTableAnalysis. Применен метод, который возвращает потенциальные ключи длиной 2 и 3. Первичные ключ выбирает человек
fta - экземпляр нашего класса FlatTableAnalysis. Применен метод, который возвращает потенциальные ключи длиной 2 и 3. Первичные ключ выбирает человек

Первый вопрос - что является первичным ключом. Никакая одна колонка не обеспечивает уникальность всех строк (113 550 строк). А сочетание двух колонок track_id, track_genre обеспечивает уникальность. Вообще это странно, можно было бы подумать, что track_id будет первичным ключом. Но около 17 тыс. треков имеют 2 и более жанра.

Функциональные зависимости

что выдает нам инструмент?

С этим вообще интересно. Из графов видно, что track_id функционально определяет почти все колонки! (кроме track_genre, конечно). Единственное исключение - колонка populatiry. 720 треков имеют две популярности, остальные 89 тыс. имеют строго одну. Это надо исследовать глубже. Похоже на ошибку в данных. Логично было бы предположить, что трек всегда имеет одну популярность...

Еще одна интересная ситуация с колонкой explicit. Это бинарный флаг, означающий, что трек содержит "info offensive or inappropriate for children", по крайней мере так говорит perplexity.ai. Если мы понизим порог отображения функциональных зависимостей до 0.95, то на графе будет видно, что album_name, artists достаточно сильно определяют explicit. Это, в общем-то, логично. Альбом содержит близки по сути треки, да и исполнители обычно придерживаются одного стиля. Хотя зависимость не точная.

Также можно заметить, что track_name и track_id имеют связь один-ко-многим. Есть случаи, когда одно название трека связано с разными id. Но это достаточно частая ситуация, поэтому больших вопросов не вызывает.

Выводы по архитектуре таблицы

Если забыть на минутку про колонку populatiry, то архитектуру таблицы надо менять! Таблица должна иметь первичный ключ - track_id. Все остальные колонки чётко зависят только от неё. Это позволит сократить количество строк с 133к до 89к.

Видимо один и тот же трек может относится к разным жанрам. Если это так, то можно создать отдельную таблицу, которая будет содержать отношение многое-ко-многим между track_id и track_genre. Две колонки. Эти же две колонки будут первичным ключом этой второй таблицы. В ней будет 133к строк.

С колонкой populatiry сложно. Моя гипотеза - тут ошибка в данных. Populatiry должна функционально зависеть от track_id. Если это так, то она пойдет в первую таблицу, где первичный ключ - это track_id. Мы же помним, что плоская таблица - это рассказ про первичный ключ :) Но если мы убедимся, что populatiry каким-то интересным образом зависит именно от сочетания двух колонок track_id и genre, то можно добавить её во вторую таблицу.

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

Итоги

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

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

Еще некоторые полезные видео по теме есть в моем блоге на youtube.

Комментарии (8)


  1. Akina
    15.03.2024 09:39

    Потенциальные ключи таблицы (candidate key) - это наборы столбцов, которые обеспечивают уникальность каждой строчки таблицы. ...

    Первичный ключ таблицы (primary key) - это то, про что рассказывает плоская таблица. ... Первичный ключ обеспечивает уникальность каждой строчки. Т.е. первичный ключ - это один из потенциальных ключей. ...

    Это не совсем правильно. Вот простейший пример, который противоречит написанному:

    CREATE TABLE test (id TEXT, PRIMARY KEY (id(10)));

    Потенциальным ключом может быть не только набор столбцов, но и включающее их выражение. Да, не каждое выражение может быть первичным ключом, есть ограничения... но кандидатом - запросто.

    Функциональные зависимости (functional dependencies).

    Пардон, а какое отношение это имеет к понятию "Архитектура плоской таблицы"? То, что вы описываете - это внешние ключи, связи между таблицами, понятие уровня схемы данных... да элементарно требует минимум двух таблиц (даже в случае self-reference, когда это две копии одной таблицы).


    1. Grigory_T Автор
      15.03.2024 09:39

      Потенциальным ключом может быть не только набор столбцов, но и включающее их выражение. Да, не каждое выражение может быть первичным ключом, есть ограничения... но кандидатом - запросто.

      Определение потенциальных ключей я беру классическое, т.е. не привязанное к базам данных. Определение из википедии: "A candidate key, or simply a key, of a relational database is any set of columns that have a unique combination of values in each row, with the additional constraint that removing any column could produce duplicate combinations of values. ".
      Т.е. в части прикладных инструментов (реляционных БД) вы, думаю, правы. Но классика все-таки предполагает, что ключи - это подмножества столбцов. В моей практике классика удобнее. Если бы я работа много с SQL думаю статья была бы совершенно о другом :)

      Пардон, а какое отношение это имеет к понятию "Архитектура плоской таблицы"? То, что вы описываете - это внешние ключи, связи между таблицами, понятие уровня схемы данных... да элементарно требует минимум двух таблиц (даже в случае self-reference, когда это две копии одной таблицы).

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

      Внешние ключи, схемы данных - это другая область. Я это не рассматривал. Опять же, потому, редко встречаю именно базы данных (нормализованные взаимосвязанные таблицы). Чаще - отдельные сложные таблицы.


      1. Akina
        15.03.2024 09:39

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

        Тем более. Именно привязка к базам данных и порождает ограничения, которых в случае классического определения быть и не должно. Просто классическое определение, как я понимаю, совсем не против переопределения данных и статических вычисляемых столбцов, а потому полагает, что любое выражение-кандидат нашло воплощение в виде отдельного столбца. То есть рассматривает вопрос с точки зрения "провёл глазом, и сразу заметил".


        1. Grigory_T Автор
          15.03.2024 09:39

          SQL у меня на начальном уровне, в работе активно использую python и excel.

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


          1. Grigory_T Автор
            15.03.2024 09:39

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


          1. Akina
            15.03.2024 09:39

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

            Реляционные БД предназначены для того, чтобы принимать данные, хранить их, обрабатывать и возвращать. И делать это эффективно. Так что указанный вами функционал никакого отношения к СУБД не имеет.

            И да, такой инструмент на поверх СУБД вполне может быть создан. Как в составе ПО, которое включается в состав всего программного комплекса (возможности встроенного клиента, например), так и в виде создаваемого пользователем SQL-приложения либо независимого приложения, использующего СУБД в соответствии с его назначением.


  1. Rion333
    15.03.2024 09:39

    Я работаю над разработкой BI системы, которая выполняет анализ данных по запросу на естественном языке.

    Сейчас тестируем агентов на базе langchain. Вы не рассматривали данные решения? Там уже есть готовый агент, который может собрать всю необходимую информацию по схеме и данным в БД.


    1. Grigory_T Автор
      15.03.2024 09:39

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

      Еще раз уточню, что мой инструмент фокусируется на одной большой сложной таблице (такие часто возникают в бизнес среде). Интересно, но такого инструмента я ни разу не видел (только FDtool).
      А вот анализ связанных таблиц (база данных, схемы данных) - думаю другая история.