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

У меня есть таблица MySQL 5.7, в которой хранятся продукты. Столбцы узлов относятся к категориям и подкатегориям. Среди этих узлов только node_0 и node_4 гарантированно будут не NULL, тогда как остальные могут иметь или не иметь NULL. Эта таблица будет использоваться для обработки продуктов в пакетах на основе уникальных комбинаций значений их узлов (unique_cat). В каждой уникальной комбинации может быть от 1 до 50 товаров. Имена отдельных узлов не уникальны (т. е. node_i может иметь то же имя, что и node_k), но комбинации этих узлов уникальны.

Один воспроизводимый пример выглядит следующим образом:

    CREATE TABLE products (
    product_id int(11) NOT NULL AUTO_INCREMENT,
    node_0 varchar(400) DEFAULT NULL,
    node_1 varchar(400) DEFAULT NULL,
    node_2 varchar(400) DEFAULT NULL,
    node_3 varchar(400) DEFAULT NULL,
    node_4 varchar(255) DEFAULT NULL,
    PRIMARY KEY (id)
);

INSERT INTO products (node_0, node_1, node_2, node_3, node_4) VALUES
('a_0', NULL, NULL, NULL, 'a_1'),
('a_0', NULL, NULL, NULL, 'a_1'),
('a_2', 'a_1', NULL, NULL, 'a_1'),
('a_0', NULL, NULL, 'a_3', 'a_2'),
('a_3', NULL, NULL, 'a_0', 'a_2'),
('a_0', NULL, NULL, NULL, 'a_2'),
('a_2', 'a_1', NULL, NULL, 'a_1')

Мне нужно создать новый столбец unique_cat, который будет числом, представляющим различные значения комбинации node_0, node_1, node_2, node_3 и node_4. Я не уверен, как обрабатывать значения NULL в этом контексте. Ожидаемый результат:

| node_0 | node_1 | node_2 | node_3 | node_4 | unique_cat |
|--------|--------|--------|--------|--------|------------|
| a_0    | NULL   | NULL   | NULL   | a_1    | 0          |
| a_0    | NULL   | NULL   | NULL   | a_1    | 0          |
| a_2    | a_1    | NULL   | NULL   | a_1    | 1          |
| a_0    | NULL   | NULL   | a_3    | a_2    | 2          |
| a_3    | NULL   | NULL   | a_0    | a_2    | 3          |
| a_0    | NULL   | NULL   | NULL   | a_2    | 4          |
| a_2    | a_1    | NULL   | NULL   | a_1    | 1          |

При работе только с узлами node_0 и node_4 работает следующий подход:

# Create a unique combination of node_0,node_4 with unique IDs
unique_cat_df = df \
      .select("node_0", "node_4") \
      .distinct() \
      .withColumn("unique_cat", monotonically_increasing_id())

# Join the unique combinations back to the original DataFrame
df_with_cat_ids = df.join(
      unique_cat_df,
      on=["node_0", "node_4"],
      how = "left"
)

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

placeholder = "___NULL___"
df = df_0 \
    .withColumn("node_2", F.when(col("node_2").isNull(), placeholder).otherwise(col("node_2"))) \
    .withColumn("node_3", F.when(col("node_3").isNull(), placeholder).otherwise(col("node_3")))

# Select columns and create distinct combinations with a unique ID
unique_combinations_df = df \
    .select("node_0", "node_1", "node_2", "node_3", "node_4") \
    .distinct() \
    .withColumn("unique_cat", monotonically_increasing_id())

# Join the unique combinations back to the original DataFrame
df_with_ids = lastest_data_df_2.join(
    unique_combinations_df,
    on=["current_node", "node_1", "node_2", "node_3", "root_node"],
    how = "left"
)

Этот подход не работает. Есть идеи или предложения? Спасибо!

Обновление: решено!

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

🥁 барабанная дробь 🥁

Я забыл заменить значения NULL в node_1! 🚨

Как мне стыдно... 🙂

Я не понимаю, как вы рассчитываете unique_cat в своем примере. В строке 6 есть два значения: a_0 и a_2, как получить unique_cat = 4?

Barmar 12.07.2024 19:12

unique_cat — это просто число, которое классифицирует комбинации node_0,...., node_4. Создано с использованием отличающихся(node_0,....,node_4)

AlbertoM 12.07.2024 19:20

То есть это просто последовательный индекс, создаваемый для каждой новой комбинации?

Barmar 12.07.2024 19:22

Да, это именно так. Я не знаю, почему он возвращает unique_cat с NULL при попытке получить все узлы.

AlbertoM 12.07.2024 19:28

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

Barmar 12.07.2024 19:30

О, я только что понял, что забыл добавить node_1 в свой код! 😅 Кстати, планирую последовать вашему совету по нормализации. Как вы думаете, что-то подобное может сработать? Продукты: (product_id, Product_name), узлы: (node_id, Product_id, node_level, node_value) -> основной (node_id), внешний (product_id).

AlbertoM 13.07.2024 00:50

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

Vikas Sharma 13.07.2024 12:26

Есть еще несколько прямых способов сделать это без использования placeholder и distinct(), но лучший метод будет зависеть от версии Spark. Какую версию Spark вы используете?

smurphy 15.07.2024 23:52
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для разработчиков
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
8
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Шаг 1: Агрегация

Aggregate the values for each row into a unique set of values for that row.
df_array = df.withColumn("node_array", F.array(F.col("node_0"),
                                                    F.col("node_1"),
                                                    F.col("node_2"),
                                                    F.col("node_3"),
                                                    F.col("node_4"))
                             )\
                  .withColumn("node_array_clean", F.filter(F.col("node_array"), lambda x: x.isNotNull()))\
                  .withColumn("node_array_distinct", F.array_distinct(F.col("node_array_clean")))\
                  .withColumn("unique_node_values", F.array_sort(F.col("node_array_distinct")))
df_array.show(truncate=False)

Выход

Код товара узел_0 узел_1 узел_2 узел_3 узел_4 node_array node_array_clean node_array_distinct unique_node_values 1 а_0 нулевой нулевой нулевой а_1 [a_0, ноль, ноль, ноль, a_1] [а_0, а_1] [а_0, а_1] [а_0, а_1] 2 а_0 нулевой нулевой нулевой а_1 [a_0, ноль, ноль, ноль, a_1] [а_0, а_1] [а_0, а_1] [а_0, а_1] 3 а_2 а_1 нулевой нулевой а_1 [a_2, a_1, ноль, ноль, a_1] [а_2, а_1, а_1] [а_2, а_1] [а_1, а_2] 4 а_0 нулевой нулевой а_3 а_2 [a_0, ноль, ноль, a_3, a_2] [а_0, а_3, а_2] [а_0, а_3, а_2] [а_0, а_2, а_3] 5 а_3 нулевой нулевой а_0 а_2 [a_3, ноль, ноль, a_0, a_2] [а_3, а_0, а_2] [а_3, а_0, а_2] [а_0, а_2, а_3] 6 а_0 нулевой нулевой нулевой а_2 [a_0, ноль, ноль, ноль, a_2] [а_0, а_2] [а_0, а_2] [а_0, а_2] 7 а_2 а_1 нулевой нулевой а_1 [a_2, a_1, ноль, ноль, a_1] [а_2, а_1, а_1] [а_2, а_1] [а_1, а_2]

Что это делает,

Порядок действий:

node_array -> node_array_clean -> node_array_distinct -> unique_node_values

[a_2, a_1, null, null, a_1] -> [a_2, a_1, a_1] -> [a_2, a_1] -> [a_1, a_2]

  • node_array: использует F.array для сбора значений для столбцов.
  • node_array_clean: использует F.filter для удаления нулей из собранных значений.
    • Если вы используете Spark 3.4+, используйте array_compact для этого шага.
  • node_array_distinct: использует F.array_distinct для удаления дубликатов.
    • Я не был уверен, важно ли хранить эти дубликаты. Этот шаг можно удалить, если дубликаты важны для вашего варианта использования.
  • unique_node_values: использует F.array_sort для упорядочивания значений в массиве.
    • Я не был уверен, важен ли порядок, в котором сообщаются значения. Этот шаг можно удалить, если это важно для вашего варианта использования.

Почему это полезно,

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

df_array.select("product_id", "unique_node_values")\
    .filter(F.array_contains(F.col("unique_node_values"), "a_1"))\
    .show()
Код товара unique_node_values 1 [a_0, a_1] 2 [a_0, a_1] 3 [a_1, a_2] 7 [a_1, a_2]

Шаг 2. Очистка

Let's remove the columns we no longer require, to make things easier to read.
df_clean = df_array.drop("node_array")\
    .drop("node_array_clean")\
    .drop("node_array_distinct")

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

Шаг 3. Создайте столбец идентификатора unique_category.

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

window_spec = Window.partitionBy().orderBy("unique_node_values")

Выбор между rank и dense_rank будет зависеть от вашего варианта использования финального unique_category, ниже вы можете увидеть, как различаются их результаты.

Пример использования rank

df_ranked = df_clean.withColumn("unique_category", F.rank().over(window_spec))
df_ranked.show(truncate=False)
Код товара узел_0 узел_1 узел_2 узел_3 узел_4 node_array_ordered уникальная_категория 1 а_0 нулевой нулевой нулевой а_1 [а_0, а_1] 1 2 а_0 нулевой нулевой нулевой а_1 [а_0, а_1] 1 6 а_0 нулевой нулевой нулевой а_2 [а_0, а_2] 3 4 а_0 нулевой нулевой а_3 а_2 [а_0, а_2, а_3] 4 5 а_3 нулевой нулевой а_0 а_2 [а_0, а_2, а_3] 4 3 а_2 а_1 нулевой нулевой а_1 [а_1, а_2] 6 7 а_2 а_1 нулевой нулевой а_1 [а_1, а_2] 6

Пример использования dense_rank

df_ranked = df_clean.withColumn("unique_category", F.dense_rank().over(window_spec))
df_ranked.show(truncate=False)
Код товара узел_0 узел_1 узел_2 узел_3 узел_4 node_array_ordered уникальная_категория 1 а_0 нулевой нулевой нулевой а_1 [а_0, а_1] 1 2 а_0 нулевой нулевой нулевой а_1 [а_0, а_1] 1 6 а_0 нулевой нулевой нулевой а_2 [а_0, а_2] 2 4 а_0 нулевой нулевой а_3 а_2 [а_0, а_2, а_3] 3 5 а_3 нулевой нулевой а_0 а_2 [а_0, а_2, а_3] 3 3 а_2 а_1 нулевой нулевой а_1 [а_1, а_2] 4 7 а_2 а_1 нулевой нулевой а_1 [а_1, а_2] 4

Соображения

При использовании rank, dense_rank и monotonically_increasing_id следует учитывать, что эти столбцы идентификаторов могут измениться, если входные данные различаются. Значение 1 может представлять собой комбинацию узлов [a_0, a_1] с имеющимися у вас сегодня данными. Если данные изменяются, значение 1 может быть присвоено для представления другой комбинации узлов (например, [a_0]).

Спасибо за Ваш ответ. Предположим, существует массив [a1, Null, Null, Null, a2]. Будет ли этот подход считать ту же комбинацию, что и [Null, a1, Null, Null, a2]? Мне нужно, чтобы это рассматривалось как другая комбинация. Можете ли вы дать мне подсказку?

AlbertoM 17.07.2024 14:26

При таком подходе они будут считаться одинаковыми. Если вы хотите, чтобы они считались разными, вы можете использовать столбец node_array из шага 2 шага 3, чтобы создать столбец идентификатора unique_category.

smurphy 22.07.2024 21:00

Другие вопросы по теме