Drill — это фантастический инструмент для запроса данных JSON. Но Drill не волшебный, и иногда он сталкивается с некоторыми данными, которые он не может обработать (пока). Этот пост посвящен примеру такого сценария и тому, как вы можете решить эту проблему, используя немного кода Python.
сценарий
У вас есть данные, где схема меняется. В этом примере мы рассмотрим случай, когда у вас есть поле, которое изменяется от одного значения к списку или наоборот.
Образец данных
Эти данные разбиты на два файла.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
{ "name": "Vince", "favorite_foods": [ { "name": "ice-cream", "flavors": "vanilla" }, { "name": "cheeseburgers", "flavors": [ "animal style", "black label" ] } ]}{ "name": "Nikki", "favorite_foods": [ { "name": "ice-cream", "flavors": [ "chocolate", "dulce de leche" ] }, { "name": "cheeseburgers", "flavors": [ "black label" ] } ]} |
запрос
Оба файла являются действительными JSON, и это хорошо, но оказывается, что схема меняется. Винсу нравится только ванильное мороженое, и это хранится как единая ценность. Никки любит шоколад и Дульсе де Лече (Винс не отказывается от них, просто не его любимый), поэтому они хранятся в виде списка.
Drill предпочел бы, чтобы вы не организовывали свои данные таким образом, и не стесняется сказать вам следующее:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;java.lang.RuntimeException: java.sql.SQLException: DATA_READ ERROR: You tried to start when you are using a ValueWriter of type NullableVarCharWriterImpl.File /Users/vince/src/drill-data-prep-example/schema-change/data/vince.jsonRecord 1Line 1Column 127Field flavorsFragment 0:0[Error Id: 2c9020c1-cfcf-42e2-926c-d00962ceb7f9 on 192.168.56.1:31010] at sqlline.IncrementalRows.hasNext(IncrementalRows.java:73) at sqlline.TableOutputFormat$ResizingRowsProvider.next(TableOutputFormat.java:87) at sqlline.TableOutputFormat.print(TableOutputFormat.java:118) at sqlline.SqlLine.print(SqlLine.java:1583) at sqlline.Commands.execute(Commands.java:852) at sqlline.Commands.sql(Commands.java:751) at sqlline.SqlLine.dispatch(SqlLine.java:738) at sqlline.SqlLine.begin(SqlLine.java:612) at sqlline.SqlLine.start(SqlLine.java:366) at sqlline.SqlLine.main(SqlLine.java:259) |
Что если мы изменим порядок этих объектов на входе? Нравится ли Drill лучше, когда мы начинаем со списка, а потом меняем одно значение?
|
01
02
03
04
05
06
07
08
09
10
11
|
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;Error: DATA_READ ERROR: You tried to write a VarChar type when you are using a ValueWriter of type SingleListWriter.File /Users/vince/src/mapr/drill-data-prep/schema-change/sample_data.jsonRecord 2Line 3Column 84Field flavorsFragment 0:0[Error Id: 93ceda84-8710-46de-8d5f-6b2290914703 on 192.168.56.1:31010] (state=,code=0) |
Нет. Дрель все еще недовольна.
В Drill 1.6 может быть включен тип объединения (экспериментальный) . Это позволит хранить несколько типов данных в одном поле, что может помочь нам обойти эту проблему. Итак, давайте включим это и попробуем.
|
1
2
3
4
5
6
7
8
|
ALTER SESSION SET `exec.enable_union_type` = true;0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;Error: Unexpected RuntimeException: java.lang.IllegalArgumentException: The field $offsets$(UINT4:REQUIRED) doesn't match the provided metadata major_type { minor_type: MAP mode: REQUIRED}. (state=,code=0) |
К сожалению, это тоже не работает. Итак, давайте отключим тип объединения и продолжим.
В чем проблема?
Drill не нравится, когда вы даете ему данные, в которых тип меняется с одного значения в список или наоборот.
Как нам обойти это?
Мы массируем данные таким образом, чтобы поля, в которых иногда, но не всегда, использовались списки, становились полями, где списки используются всегда. В примере данных проблема была flavors одним из полей flavors . Дрель услужливо определила строку и столбец, с которыми возникла проблема, так что вы можете просмотреть ее, прежде чем приступить к написанию кода для ее устранения.
Один из способов исправить данные в Python. Вы можете просто прочитать в объектах JSON и переписать их, преобразовав отдельные значения в списки с одним значением:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
import jsonimport typesfor filename in ("data/vince.json", "data/nikki.json"): # Read the file into memory. with file(filename) as infile: data = json.loads(infile.read()) # Open and truncate the input file. with file(filename, "w") as outfile: # enumerate returns an incrementing integer with each iteration. # convenient way to keep track of the index into the list # we need to modify. for i,f in enumerate(data["favorite_foods"]): # Check if the value pointed to by "flavors" is of type # ListType. if type(f["flavors"]) != types.ListType: # If it's not, then overwrite the value as a list. data["favorite_foods"][i]["flavors"] = [ f["flavors"] ] outfile.write(json.dumps(data)) |
Теперь, запрашивая обработанные данные, Drill более счастлив:
|
1
2
3
4
5
6
7
8
|
0: jdbc:drill:zk=local> select * from `/Users/vince/src/drill-data-prep-example/schema-change/data`;+---------------------------------------------------------------------------------------------------------------------+--------+| favorite_foods | name |+---------------------------------------------------------------------------------------------------------------------+--------+| [{"flavors":["chocolate","dulce de leche"],"name":"ice-cream"},{"flavors":["black label"],"name":"cheeseburgers"}] | Nikki || [{"flavors":["vanilla"],"name":"ice-cream"},{"flavors":["animal style","black label"],"name":"cheeseburgers"}] | Vince |+---------------------------------------------------------------------------------------------------------------------+--------+2 rows selected (0.119 seconds) |
Более счастливые запросы:
|
01
02
03
04
05
06
07
08
09
10
11
12
|
0: jdbc:drill:zk=local> select t.name, t.yum.name as fave_food, flatten(t.yum.flavors) as fave_flave from (select t.name, flatten(t.favorite_foods) as yum from (select name,favorite_foods from `/Users/vince/src/drill-data-prep-example/schema-change/data`) t) t;+--------+----------------+-----------------+| name | fave_food | fave_flave |+--------+----------------+-----------------+| Nikki | ice-cream | chocolate || Nikki | ice-cream | dulce de leche || Nikki | cheeseburgers | black label || Vince | ice-cream | vanilla || Vince | cheeseburgers | animal style || Vince | cheeseburgers | black label |+--------+----------------+-----------------+6 rows selected (0.153 seconds) |
Интересная заметка
При запросе каталога файлов JSON вы получаете другой стиль вывода ошибок, чем при запросе файла.
| Ссылка: | Устранение изменений схемы JSON с помощью Drill и Python от нашего партнера по JCG Винса Гонсалеса в блоге Mapr . |