Оглавление
Введение
В бизнес-сценариях часто появляются новые предприятия, которые, в свою очередь, генерируют новые бизнес-данные. Это неизбежно приведет к изоляции некоторых данных, поэтому данные необходимо синхронизировать и интегрировать. В процессе очистки данных неизбежно приходится реализовывать одну и ту же логику 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
столбец записывает информацию об уровне пользователя соответствующая категория. Структура следующая:
Создайте 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;
Конечный эффект обработки заключается в следующем:
извлечь информацию о ключевом значении внутри 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 есть две строки записей.
Следующим шагом является обработка пар ключ-значение фиксированной длины из приведенных выше результатов в два новых столбца и использование ключа в качестве имени поля и значения в качестве значения 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;
Результат следующий:
В возвращаемом результате 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;
Результат следующий:
С помощью ключа очень удобно получить значение, ->
просто получив значение. Эталонный 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;
Результат следующий:
В возвращаемом результате 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
;
Окончательный результат выглядит следующим образом:
Обработка через 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;
Результат вывода:
Следующим шагом является расхождениеcnt
возвращаемых результатов по полям.Здесь возникает проблема критической точки, то есть в данный момент это означает, что есть только один элемент, затем есть два элемента и так далее. Индекс массива начинается с 0, поэтому при расхождении по созданному представлению нужно обратить внимание на обработку начальной точки и критического значения. После расхождения проиндексируйте порядковый номер расхождения, чтобы получить значение каждого элемента. Давайте посмотрим непосредственно на SQL, чтобы объяснить:cnt=0
cnt=1
lateral 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
он и делает.
На данный момент завершены только предыдущие результаты обработки 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
;
Результат возврата следующий:
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
тип карты. После обхода этого круга он фактически преобразует тип данных. Может ли это быть конвертировать напрямую?По крайней мере, на данный момент из В официальной документации такой функции не видно.
После преобразования в тип карты используйте 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
Результаты выполнения следующие:
Примечание. Возвращаемые здесь результаты также необходимо преобразовать 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;
Результат возврата следующий:
На данный момент это сделано?
Нет! Пока нет, есть еще два нерешенных вопроса.
Вопрос 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
Окончательный результат выглядит следующим образом:
Вопрос 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 view
MySQL не имеет функции суммы, такой как 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. Изменение имени поля формы производственной среды.