Практическое занятие 8 по работе с большими данными Alibaba Cloud: распакуйте каждый элемент json, по одному в строке


Введение

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

В этой статье представлена ​​одна и та же логика SQL, работающая на разных платформах.
Связанные платформы: postgresql от Alibaba Cloud и MaxCompute SQL от Alibaba Cloud (далее — ODPS SQL).

Примечания к версии:

PostgreSQL: PostgreSQL 11.3 64-разрядная версия
MySQL: MySQL 8.0.16
ODPS SQL: версия функции odps-sql: r570e07eb77a5063f8c5715b0fa0beeba (по-видимому, облако Alibaba обновляется до последней версии по умолчанию)

2. Цель введения

Во-первых, давайте представим абстрактные данные. Существует таблица, в которой записаны 2 столбца данных. Ее можно рассматривать как запись ответа. Столбец contentзаписывает содержание ответа пользователя определенной категории, а levelстолбец записывает информацию об уровне пользователя соответствующая категория. Структура следующая:

изображение.png
Создайте SQL для временного набора данных:

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select *
from t1;

Конечный эффект обработки заключается в следующем:

изображение.png

извлечь информацию о ключевом значении внутри contentи levelотдельно, а затем объединить ее в форму содержания ответа пользователя и информацию об уровне определенного типа для облегчения бизнес-анализа.

Базовый анализ: типы символов contentи levelявляются строками, но их структура данных довольно особенная. contentЭто строка со структурой массива Json, levelно строка со структурой пары ключ-значение. Во время обработки их можно преобразовать в тип символов Json для обработки.

Поскольку разные пользователи ведут себя по-разному, длина элементов Json также неодинакова, поэтому для typeрасширения типа ( ) необходимо обрабатывать два поля отдельно и, наконец, соединить их.

3. Реализация с использованием pgsql

pgsql имеет относительно мощную функцию json, которая может помочь в обработке данных структуры json с помощью связанных функций json. См. документацию по функции json postgresql в Alibaba Cloud.

3.1 Разделение поля содержимого

contentЭто строка со структурой массива Json, поэтому ее можно ::jsonпреобразовать в тип данных json с помощью функции, а затем json_array_elements()элементы можно разделить с помощью функции, по одному элементу в строке.

Справочник по SQL выглядит следующим образом:

-- 拆元素
with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select user_id,json_array_elements(content::json) as "content_kv"
from t1;

Результат разделения следующий.Вы можете видеть, что contentкаждый элемент данных был разделен, и в каждой строке сохраняется один элемент.В настоящее время у пользователя 102 есть две строки записей.

изображение.png

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

-- 拆元素并取值
with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select user_id
	,json_array_elements(content::json) 						as "content_kv"
	,json_array_elements(content::json)->>'type'    as "type"
  ,json_array_elements(content::json)->>'content' as "content"
from t1;

Результат следующий:

изображение.png

В возвращаемом результате user_idполя , typeи contentтри — это поля, которые в конечном итоге необходимы. Вот и contentвыполнена задача разделения исходных полей.

3.2 Поле разделения уровней

Далее разделите levelполя.
levelЭто строка структуры пары ключ-значение. Пара ключ-значение представляет собой стандартную структуру json, поэтому ее можно преобразовать в тип данных json, а затем можно json_object_keys()извлечь все ключи в паре ключ-значение, по одному в строке. , а также можно извлечь значения, соответствующие ключам. .

Сначала выньте ключ, SQL выглядит следующим образом:

-- 取键值对的键
with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select user_id,level,json_object_keys(level::json) as "type"
from t1;

Результат следующий:

изображение.png

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

-- 取键值对的键和值
with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select user_id
	,level
 	,json_object_keys(level::json) 								as "type"
	,level::json -> json_object_keys(level::json) as "level"
from t1;

Результат следующий:

изображение.png

В возвращаемом результате user_idполя , typeи levelтри — это поля, которые в конечном итоге необходимы. Вот и levelвыполнена задача разделения исходных полей.

3.3 Объединение двух результатов разделения

Шаг объединения относительно прост: возьмите два приведенных выше результата разделения как два подзапроса, а затем соедините их через user_idи . Эталонный SQL выглядит следующим образом:type

-- 拼接
with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
select t1_content.user_id,t1_content.type,t1_content.content,t1_level.level 
from(
    select user_id
    	,json_array_elements(content::json)->>'type'    as "type"
      ,json_array_elements(content::json)->>'content' as "content"
    from t1
)t1_content 
left join(
    select user_id
    	,json_object_keys(level::json) 									as "type"
    	,level::json -> json_object_keys(level::json) 	as "level"
    from t1
)t1_level on t1_level.user_id=t1_content.user_id and t1_level.type=t1_content.type
;

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

изображение.png

Обработка через pgsql относительно проста . По сути, ее можно решить с помощью четырех функций. Четыре функции: ::json, json_array_elements(), и . Но использовать ODPS SQL не так уж и удобно!json_object_keys()->->>


4. Реализация с использованием ODPS SQL.

ODPS построен Alibaba на основе основной идеи Hive. Разница в том, что файлы Hive хранятся в HDFS, а ODPS хранятся в Pangu от Alibaba. Кроме того, ODPS внес некоторые оптимизации для Hive, поэтому ODPS SQL относительно близок к этому. на HQL, а также похож на MySQL.Определенное сходство.

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

Постгрес SQL ОДПС SQL
::json json_parse()
json_array_elements() regexp_count(), взрыв вида сбоку()
json_object_keys() str_to_map()、map_values()、взрыв вида сбоку()
-> [] или json_extract()
->> json_extract()

Ссылка: JSON ODPS SQL и другие сложные функции .

Давайте представим это подробно ниже.

4.1 Разделение поля содержимого

Поскольку ODPS SQL не может разделить данные json за один шаг из одной строки на несколько строк, его необходимо расширить по строкам другим способом, то есть lateral viewраспределить данные по представлениям. Это определяется количеством элементов в json, поэтому сначала вам нужно вычислить количество элементов.

ODPS SQL предоставляет regexp_count()функции, которые можно определить путем подсчета количества элементов массива.
Итак, как посчитать количество элементов в массиве? Наблюдая за структурными характеристиками данных, я определил разделитель элемента },{ как идентификатор, то есть regexp_count(content,'}\\s*,\\s*{')конкретное значение следующее:

  • \s: Соответствует любым пробельным символам во избежание несовпадений из-за пробелов. Эквивалентно ** [\t\n\f\r ]**
  • \\: поскольку система использует обратную косую черту \в качестве escape-символа, все, что встречается в шаблоне регулярного выражения, должно \быть экранировано дважды. Например, регулярное выражение должно соответствовать строке a+b. Это +специальный символ в регулярных выражениях, поэтому его необходимо выражать экранированным способом.Метод выражения в регулярном движке a\\+b: Поскольку системе по-прежнему приходится интерпретировать один уровень экранирования, строка может соответствовать выражению a\\\+b. Простое понимание состоит в том, что при использовании специальных букв для экранирования в SQL вам необходимо добавить дополнительный уровень экранирования, то есть добавить еще один **\**.
  • *: Соответствует предыдущему подвыражению 0 или более раз.

Давайте посмотрим на эффект обработки
применительно к SQL:

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,content
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
    from t1
)
select *
from t1_json t1;

Результат вывода:

изображение.png

Следующим шагом является расхождениеcnt возвращаемых результатов по полям.Здесь возникает проблема критической точки, то есть в данный момент это означает, что есть только один элемент, затем есть два элемента и так далее. Индекс массива начинается с 0, поэтому при расхождении по созданному представлению нужно обратить внимание на обработку начальной точки и критического значения. После расхождения проиндексируйте порядковый номер расхождения, чтобы получить значение каждого элемента. Давайте посмотрим непосредственно на SQL, чтобы объяснить:cnt=0cnt=1lateral view

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,content
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
    from t1
)
select t1.user_id
	,t1.content_json
	,t1.cnt
	,index_tbl.index_no
  ,json_extract(t1.content_json, concat('$[',index_tbl.index_no,']')) as "content"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt
;

Приведенный выше SQL lateral viewрасширяет одну строку до четырех строк за счет расхождения, увеличивая данные в 4 раза, а затем index_tbl.index_no<=t1.cntизвлекает строки, соответствующие ожиданиям, с помощью ограничений. Вы можете посмотреть на рисунок ниже. Возвращаемый результат — это whereрезультат без добавления конечного условия. Как видно из рисунка, только одна из четырех записей, отправленных пользователем 101, является полезной, а остальные три возвращаются с contentнулевыми значениями.Эти строки с нулевыми значениями можно отфильтровать, и именно это whereон и делает.

изображение.png

На данный момент завершены только предыдущие результаты обработки json_array_elements(), и наша цель — извлечь typeсоответствующее значение и соответствующее значение пары ключ-значение в результате обработки, затем использовать соответствующий ключ для присвоения ему имени, а затем извлечь ценить. Функция, используемая для получения значения, также та же самая. Используйте для его получения, поэтому просто добавьте ключ непосредственно в приведенный выше SQL . Конкретный SQL выглядит следующим образом:content

json_extract()json_extract()

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,content
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
    from t1
)
select t1.user_id
	,t1.content_json
  ,json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].type')) 		as "type"
  ,json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].content')) as "content"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt
;

Результат возврата следующий:

изображение.png

typeКак видно из возвращаемых результатов, contentоба они заключены в кавычки. Это связано с тем, что json_extract()тип данных, возвращаемый функцией, является типом JSON, а не строкой, поэтому требуется явное преобразование типа данных.

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

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,content
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
    from t1
)
select t1.user_id
  ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].type')) as string)		as "type"
  ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].content')) as string) as "content"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt
;

4.2 Поле разделения уровней

levelПри разбиении поля вы можете использовать contentметод, аналогичный разбиению поля, чтобы levelвычислить количество элементов в поле, затем использовать lateral viewпредставление для расхождения, а затем взять непустые строки.

Поскольку levelколичество элементов в поле contentсоответствует количеству элементов в поле, вы можете использовать поля, которые были посчитаны ранее cnt, потому что оба расходятся с помощью этого метода и могут в конечном итоге быть объединены.Чтобы не путать и использовать cntполя единообразно.

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

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
    from t1
)
select *
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt

Дальше возникает трудность, то есть как узнать, levelчто это за ключ? После предыдущего contentразделения ключи все одинаковые, но ключи здесь имеют переменную длину, может быть 1, или 2, или 3, или десяток и т. д., поэтому здесь нельзя использовать логические столбцы. Нужно использовать Другие методы. «разумно» извлекать ключи.

Прочитав официальные документы (ссылка: json и другие сложные функции ODPS SQL ), я обнаружил, что его можно mapобработать путем преобразования в тип данных. Сначала str_to_map()преобразовать в mapтип, а затем использовать map_keys()ключ. Предыдущий метод заключается в том, чтобы получить значение через ключ, но в mapтипе предусмотрены и Функции map_values(), то есть значение можно получить напрямую, без использования ключа, за один прием!

Давайте реализуем это ниже:

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
    from t1
)
select t1.user_id
    ,t1.level
    ,str_to_map(regexp_extract(t1.level,'{(.*?)}'),',',':') as level_map
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt

Примечание. Приведенная выше пара t1.levelбыла обработана на одном уровне: regexp_extract(t1.level,'{(.*?)}'), цель состоит в том, чтобы удалить внешние фигурные скобки и использовать str_to_map()структуру, которая не требует фигурных скобок и имеет пары ключ-значение. str_to_map(<col_nmae>,',',':')Затем разрежьте элементы по запятым и обработайте их парами ключ-значение через двоеточия. На самом деле «вид» до и после лечения один и тот же!

Возвращаемый результат приведенного выше SQL выглядит следующим образом. Хотя он выглядит одинаково, тип данных отличается. levelЭто строковый тип и level_mapтип карты. После обхода этого круга он фактически преобразует тип данных. Может ли это быть конвертировать напрямую?По крайней мере, на данный момент из В официальной документации такой функции не видно.

изображение.png

После преобразования в тип карты используйте map_keys()и map_values()для получения массивов ключей и значений соответственно, а затем извлеките соответствующие значения по индексу. Можно использовать значение индекса []или json_extract()значение.

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

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
    from t1
)
select t1.user_id
    ,t1.level
    ,map_keys(str_to_map(regexp_extract(t1.level,'{(.*?)}'),',',':'))[index_tbl.index_no]        as "type"
    ,map_values(str_to_map(regexp_extract(t1.level,'{(.*?)}'),',',':'))[index_tbl.index_no]      as "level"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt

Результаты выполнения следующие:

изображение.png

Примечание. Возвращаемые здесь результаты также необходимо преобразовать typeв строковый тип и level2(при повторяющихся именах, автоматически помеченных серийными номерами) в тип int или bigint.

Здесь есть еще одна небольшая деталь, с которой необходимо разобраться, то есть после преобразования typeв строковый тип вы можете обнаружить, что typeполе все еще имеет двойные кавычки. Это связано с тем, что длина строки "a", т. е. заключена в двойные кавычки a, равна 3.

Как это сделать? Чтобы выполнить еще один уровень обработки, вы можете удалить двойные кавычки после обработки или удалить двойные кавычки в начале.

Ниже показан эталонный метод удаления двойных кавычек с самого начала:

сначала удалите двойные кавычки из поля и, наконец, преобразуйте тип данных replace().level

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
    from t1
)
select t1.user_id
    ,t1.level
    ,cast(map_keys(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no]   as string)    as "type"
    ,cast(map_values(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as int)       as "level"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt

На этом разделение levelполей завершено.

4.3 Объединение и разделение

Затем разделите и объедините два вышеуказанных шага. Давайте посмотрим на код непосредственно:

with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
        -- ,REGEXP_COUNT(level,':')                as level_cnt
    from t1
)
select t1.user_id
    ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].type')) as string)       as "type1"
    ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].content')) as string)    as "content"

    ,cast(map_keys(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as string)    as "type2"
    ,cast(map_values(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as int)    as "level"
from t1_json t1
-- 一行变成四行,发散4倍
lateral view explode(array(0,1,2,3)) index_tbl as index_no
-- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
where index_tbl.index_no<=t1.cnt;

Результат возврата следующий:

изображение.png

На данный момент это сделано?

Нет! Пока нет, есть еще два нерешенных вопроса.

Вопрос 1. Порядок элементов массива гарантирован, но пары ключ-значение не обязательно расположены в порядке расположения элементов массива. Между ними может быть несовпадение. Просто так получилось, что не было несовпадение в приведенном примере. Чтобы гарантировать typeсогласованность типа ( ), требуется дополнительный уровень операции для самостоятельного присоединения к временной таблице, возвращаемой приведенным выше SQL. Эталонный SQL выглядит следующим образом:


with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
        -- ,REGEXP_COUNT(level,':')                as level_cnt
    from t1
)
,temp as(
    select t1.user_id
        ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].type')) as string)       as "type1"
        ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].content')) as string)    as "content"
    
        ,cast(map_keys(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as string)    as "type2"
        ,cast(map_values(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as int)    as "level"
    from t1_json t1
    -- 一行变成四行,发散4倍
    lateral view explode(array(0,1,2,3)) index_tbl as index_no
    -- 限制发散的值 index_no 小于等于 t1.cnt 的行,把大于 t1.cnt 的行去掉
    where index_tbl.index_no<=t1.cnt
)
select t2.user_id,t2.type1 as "type",t2.content,t3.level
from temp t2, temp t3
where t2.user_id=t3.user_id and t2.type1=t3.type2

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

изображение.png

Вопрос 2. Сколько строк надо разогнать? Может быть, вы раньше задавались вопросом, почему именно оно, array(0,1,2,3)а не что-то другое? Это array(0,1,2,3)легко понять.Сначала прогоните данные.На самом деле в такой обработке есть большой риск.Как только количество элементов превысит 4, данные будут потеряны.Поэтому, если вы используете этот метод, вам может потребоваться добавляйте элементы массива достаточно долго, чтобы избежать этого риска. Однако после того, как элементы будут увеличены достаточно сильно, исходные записи строк будут увеличены на соответствующее кратное число, что потребует много ресурсов. Есть ли лучший способ?

Я обратился к GPT за помощью в решении этой проблемы и получил отзыв о том, что можно использовать sequence(start, stop)функцию для динамического создания массива, startустановить для нее значение 0 и stopзадать · количество элементов cntдля достижения динамического расхождения. Справочный SQL выглядит следующим образом:

Ссылка: Документация по функциям последовательности Alibaba Cloud.


with t1 as(
  select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
  select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select user_id
        ,level
        ,json_parse(content)                    as content_json
        ,regexp_count(content,'}\\s*,\\s*{')    as cnt
        ,json_parse(level)                      as level_json
        -- ,REGEXP_COUNT(level,':')                as level_cnt
    from t1
)
,temp as(
    select t1.user_id
        ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].type')) as string)       as "type1"
        ,cast(json_extract(t1.content_json, concat('$[',index_tbl.index_no,'].content')) as string)    as "content"
        ,cast(map_keys(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as string)    as "type2"
        ,cast(map_values(str_to_map(regexp_extract(replace(t1.level,'"',''),'{(.*?)}'),',',':'))[index_tbl.index_no] as int)     as "level"
    from t1_json t1
    -- 使用 sequence() 动态发散
    lateral view explode(sequence(0, t1.cnt)) index_tbl as index_no
)
select t2.user_id,t2.type1 as "type",t2.content,t3.level
from temp t2, temp t3
where t2.user_id=t3.user_id and t2.type1=t3.type2

На этом этапе все готово!

5. Реализация с использованием MySQL

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

Ссылка: официальное введение в функцию json MySQL8.

После практики я обнаружил, что это не так! Этот процесс аналогичен процессу реализации ODPS SQL, но есть некоторые различия в функциях. Справочник по SQL приведен ниже:

with 
-- 递归创建数字序列
RECURSIVE index_tbl AS (
    SELECT 0 AS index_no
    UNION ALL
    SELECT index_no + 1 FROM index_tbl WHERE index_no < 10
)
,t1 as(
    select 101 as user_id,'[{"type":"a","content":"abc"}]' as "content",'{"a":1}' as "level" union all 
    select 102 as user_id,'[{"type":"a","content":"ad"},{"type":"b","content":"ab"}]','{"a":1,"b":2}' 
)
,t1_json as(
    select 
        user_id
        ,cast(content as json)  						"content_json"
        ,cast(level AS json)    						"level_json"
        ,json_length(cast(content as json)) "cnt"
    from t1
)
,temp as(
    select 
        t1.user_id
        ,json_unquote(json_extract(content_json, concat('$[',index_tbl.index_no,'].type')))         as "type1" 
        ,json_unquote(json_extract(content_json, concat('$[',index_tbl.index_no,'].content')))      as "content" 
        ,json_unquote(json_extract(json_keys(level_json) , concat('$[',index_tbl.index_no,']')))    as "type2"
        ,json_unquote(json_extract(level_json, concat('$.',json_unquote(json_extract(json_keys(level_json) , concat('$[',index_tbl.index_no,']')))))) as "level"
    from t1_json t1
    -- 发散
    join index_tbl on index_tbl.index_no<cnt
)
select t2.user_id,t2.type1 as "type",t2.content,t3.level
from temp t2, temp t3
where t2.user_id=t3.user_id and t2.type1=t3.type2
;

lateral viewMySQL не имеет функции суммы, такой как ODPS SQL explode(), поэтому ее нельзя расширить напрямую. MySQL RECURSIVEсоздает числовую последовательность, реализуя рекурсию, а затем напрямую добавляет ее в поле количества элементов joinи устанавливает граничное значение для достижения того же эффекта. .

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

MySQL имеет json_length()функции, позволяющие напрямую вычислять количество элементов, что удобнее, чем ODPS SQL; он также поддерживает json_keys()прямой поиск ключей, что аналогично pgsql, за исключением того, что pgsql напрямую расходится и разбивает ключи на одну строку, а MySQL также требует для объединения index_tblчисел Список последовательностей расходится вручную, кроме того, MySQL также предоставляет функцию json_unquote(), которая может напрямую преобразовывать json_extract()возвращаемый тип json в строку.

6. Резюме

В этой статье для обработки типов json используются три синтаксиса SQL: pgsql, ODPS SQL и MySQL. Среди них метод обработки с использованием pgsql является самым простым и кратким, тогда как ODPS SQL является самым сложным, с несколькими изменениями типов данных посередине, и для облегчения обработки требуется даже более редкий тип карты типов данных; в то время как MySQL находится где-то между два между.

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

Постгрес SQL ОДПС SQL MySQL
::json json_parse() cast() или неявное преобразование
json_array_elements() regexp_count(), вид сбоку, взрыв(), последовательность(), json_extract(), cast() РЕКУРСИВНЫЙ、json_extract()、json_unquote()
json_object_keys() str_to_map()、map_values()、regexp_extract()、replace()、regexp_count()、боковой вид взрывчатки()、sequence()、json_extract()、cast() РЕКУРСИВНЫЙ、json_extract()、json_unquote()、json_keys()
-> [] или json_extract() json_extract()
->> json_extract() json_extract()





Обзор прошлых выпусков:

Практическая запись по большим данным Alibaba Cloud 7. Как бороться с дубликатами данных в формах производственной среды.
Практическая запись по большим данным Alibaba Cloud 6. Изменение типа данных поля формы производственной среды.
Практическая запись по большим данным Alibaba 5. Изменение имени поля формы производственной среды.

Supongo que te gusta

Origin blog.csdn.net/qq_45476428/article/details/132568196
Recomendado
Clasificación