Запросы, синтаксис, UDF
- Как сделать запрос по логам за последний день/неделю/месяц или какой-либо другой относительный период времени?
- Как сделать фильтрацию по большому списку значений?
- Чем отличаются MIN, MIN_BY и MIN_OF? (или MAX, MAX_BY и MAX_OF)
- Как обратиться к колонке, в имени которой есть знак минус или другой спецсимвол?
- Что такое колонка _other?
- Как создать пустую таблицу со схемой из другой таблицы?
- Как не распаковывать protobuf, а работать со строкой?
- Как превратить строку вида "a=b,c=d" в словарь?
- Как удалить из таблицы дубликаты строк, не перечисляя все колонки?
- Как скопировать пользовательские атрибуты на выходную таблицу
- JSON и YSON
- Как передать данные одной таблицы (справочника) в виде списка для обработки другой таблицы
Как сделать запрос по логам за последний день/неделю/месяц или какой-либо другой относительный период времени?
Если подставлять даты в текст запроса перед запуском неудобно, то можно сделать следующим образом:
- Пишем UDF на Python или C++ с сигнатурой
(String)->Bool
. По имени таблицы она должна вернуть true только для попадающих в нужный диапазон, ориентируясь на текущее время или дату, полученные средствами выбранного языка программирования. - Передаём эту UDF в функцию FILTER вместе с префиксом до директории с логами.
- Используем результат работы FILTER в любом месте, где ожидается путь к входным данным (FROM, PROCESS, REDUCE)
Так как форматы даты и времени в названиях таблиц могут быть произвольными, а также логика выбора подходящих таблиц может быть неопределённо сложной, более специализированного решения для этой задачи не предоставляется.
Как сделать фильтрацию по большому списку значений?
Можно, конечно, сгенерировать гигантское условие в WHERE
, но лучше:
- Сгенерировать текстовый файл со списком значений по одному на строку, а затем приложить его к запросу и воспользоваться IN в сочетании с функцией ParseFile.
- Сформировать таблицу из значений, по которым нужно сделать фильтрацию, и выполнить
LEFT SEMI JOIN
с этой таблицей. - Для строк можно сгенерировать регулярное выражение, проверяющее их через «или», и воспользоваться ключевым словом REGEXP, либо одной из предустановленных функций для работы с регулярными выражениями (Hyperscan, Pire или Re2).
Чем отличаются MIN, MIN_BY и MIN_OF? (или MAX, MAX_BY и MAX_OF)
MIN(column)
— агрегатная функция из SQL-стандарта, возвращающая минимальное значение, найденное в указанной колонке.MIN_BY(result, by)
— нестандартная агрегатная функция, которая, в отличие от предыдущей, возвращает не само минимальное значение, а какое-то другое значение из строк(-и), где нашелся минимум.MIN_OF(a, b, c, d)
— встроенная функция, которая возвращает среди N значений-аргументов минимальное.
Как обратиться к колонке, в имени которой есть знак минус или другой спецсимвол?
Обернуть имя колонки в backticks по аналогии с именами таблиц:
SELECT `field-with-minus` FROM `table`;
Что такое колонка _other?
Это виртуальная колонка. Она создаётся только YQL-движком (система YTsaurus про неё ничего не знает) и только для таблиц с нестрогой схемой. В этой колонке доступны столбцы таблицы, которые изначально в схеме не были заданы. Колонка имеет тип Dict<String,String>
.
Есть несколько распространённых способов получить таблицу, в которой есть такая колонка:
- Например, создать таблицу без схемы и затем отсортировать её. Система YTsaurus выставит отсортированной таблице схему, в которой будут явно указаны только имена столбцов, по которым производилась сортировка. А если прочитать сортированную таблицу в YQL, то остальные столбцы будут доступны в поле
_other
. - Другой способ — создать таблицу с нестрогой схемой и добавить в эту таблицу колонки, которые изначально не были явно указаны в схеме. Ниже приведён пример этого кейса.
Пример:
# Создадим таблицу с нестрогой схемой. В схеме явно заданы две колонки: 'id' и 'text'.
$ yt create table //tmp/table_not_strict --attributes '{schema = <strict=%false>[{name = id; type = int64}; {name = text; type = string}]}'
# Теперь добавим в таблицу данные, указав дополнительную колонку 'newColumn', которой нет в схеме.
$ echo '{ "id": 0, "text": "Hello, World!", "newColumn": "This column is not in schema" } ' | yt write-table //tmp/table_not_strict --format json
Примечание
Создавать таблицы с нестрогой схемой можно только через YTsaurus SDK. В веб-интерфейсе это сделать не получится.
Если прочитать эту таблицу в YQL, поле newColumn
будет доступно в колонке _other
:
SELECT * FROM `//tmp/table_not_strict` AS t;
Получить значение колонки newColumn
можно несколькими способами:
SELECT
WeakField(t.newColumn, "String")
FROM `//tmp/table_not_strict` AS t;
или
SELECT
t._other["newColumn"]
FROM `//tmp/table_not_strict` AS t;
Первый способ позволяет работать единообразно с таблицей, если вдруг колонка newColumn
попадет в строгую схему. Также WeakField позволяет сразу получить значение в нужном типе, а при работе с _other
полученные значения пришлось бы обрабатывать как Yson. Для случая строгой схемы проверяется, что тип, переданный в WeakField, совпадает с типом в схеме.
Важно
Если результат запроса SELECT * FROM <таблица с виртуальной колонкой _other>
записать в другую таблицу, то в новой таблице колонка _other
превратится уже в реальную колонку. Но схема этой таблицы тогда будет строгая.
Если в YQL пытаться прочитать нестрогую таблицу, в которой уже есть реальная колонка _other
, то возникнет ошибка: "Non-strict schema contains '_other' column, which conflicts with YQL virtual column".
Как создать пустую таблицу со схемой из другой таблицы?
-- Определяем тип записи исходных данных
$s = select * from `path/to/source_table`;
$s = process $s;
-- Создаём пустую таблицу с нужной схемой
INSERT INTO `path/to/my_destination_table` WITH TRUNCATE
SELECT * FROM as_table(ListCreate(ListItemType(TypeOf($s))))
Как не распаковывать protobuf, а работать со строкой?
YQL умеет распаковывать protobuf сообщения. Рекомендуется настраивать автоматическую распаковку через метаданные таблиц. В некоторых случаях вместо распакованного сообщения необходимо работать с исходной строкой. Для этого стоит воспользоваться специальным представлением raw:
SELECT *
FROM `path/to/my_table` VIEW raw;
Примечание
Стоит учитывать, что мета-атрибут автораспаковки таблиц, созданных таким запросом, скопирован не будет, его необходимо устанавливать заново.
Как превратить строку вида "a=b,c=d" в словарь?
Для этого достаточно применить предустановленную функцию из модуля Dsv UDF:
$str = "a=b,c=d";
SELECT Dsv::Parse($str, ",");
/*
Результат:
{
"a": "b",
"c": "d"
}
*/
Как удалить из таблицы дубликаты строк, не перечисляя все колонки?
SELECT * WITHOUT g FROM (
SELECT Some(TableRow()) AS s
FROM `path/to/table` AS t
GROUP BY Digest::Sha256(StablePickle(TableRow())) AS g
)
FLATTEN COLUMNS;
Как скопировать пользовательские атрибуты на выходную таблицу
$input = "some/input/path";
$output = "destination/path";
-- Фильтруем пользовательские атрибуы, начинающиеся с подчеркивания, и исключаем некоторые системные YQL атрибуты
$user_attr = ($attr) -> {
RETURN ListFilter(
Yson::ConvertToStringList($attr.user_attribute_keys),
($name) -> {
RETURN StartsWith($name, "_") AND $name NOT IN ["_yql_op_id", "_yql_row_spec", "_yql_runner"];
}
);
};
-- Добавляем префикс // к пути
$yt_path = ($path) -> {
RETURN IF(StartsWith($path, "//"), $path, "//" || $path);
};
-- Создаём список атрибутов для копирования
$attribute_keys = (
SELECT String::JoinFromList($user_attr(Attributes), ";")
FROM Folder(Substring($input, NULL, RFind($input, "/")), "user_attribute_keys")
WHERE $yt_path(Path) == $yt_path($input)
);
-- Читаем пользовательские атрибуты
$attributes = (
SELECT CAST(Yson::SerializePretty(Attributes) AS String) as attrs
FROM Folder(Substring($input, NULL, RFind($input, "/")), $attribute_keys)
WHERE $yt_path(Path) == $yt_path($input)
);
-- Форсируем скалярный контекст
$attributes = EvaluateExpr($attributes);
INSERT INTO $output WITH (TRUNCATE, USER_ATTRS=$attributes)
SELECT *
FROM (
SELECT *
FROM $input
LIMIT 5
)
JSON и YSON
Как превратить строку таблицы в JSON?
Для этого потребуются предустановленные функции сериализации и стандартная функция TableRow() для обращения ко всей строке:
USE <cluster-name>;
SELECT Yson::SerializeJson(Yson::From(TableRow()))
FROM `home/yql/tutorial/users`;
Как получить значение по ключу из JSON'а / YSON'а?
При работе с JSON'ом / YSON'ом стоит помнить:
- Несериализованное поле этих форматов нельзя вывести в браузере или сохранить в таблицу — возникает ошибка вида
Expected persistable data, but got: List<Struct<'column0':Resource<'Yson.Node'>>>
; - Не стоит преобразовывать в словарь, если нужно получить значение только по одному ключу.
-- Тип my_column — Yson
SELECT Yson::Serialize(my_json_column.myKey) AS my_column
FROM my_table;
Если требуется конкретный тип данных, то можно воспользоваться функцией Yson::ConvertTo...:
-- Тип my_column — String
SELECT Yson::ConvertToString(my_json_column.myKey) AS my_column
FROM my_table;
Если исходный тип колонки строка, то можно воспользоваться функцией Yson::Parse...:
SELECT Yson::Serialize(Yson::Parse(my_yson_column)) AS my_yson;
-- В результате получается Yson, при работе с которым Yson::Parse подставляется автоматически
SELECT Yson::Serialize(my_yson.myKey) AS my_key_value;
Если требуется список из всех значений по ключу, то можно воспользоваться функцией ListMap:
SELECT
ListMap(
Yson::ConvertToList(my_yson_column),
($x) -> { RETURN Yson::Serialize($x.foo) }
);
-- Результат выполнения — список строк
Пример получения элемента списка:
SELECT Yson::ConvertToString(Yson::ConvertToList(my_yson_column)[0].foo);
-- Результат выполнения — поле конкретного элемента списка
Как вертикально развернуть словарь/список из таблицы?
В случае, когда контейнер, который необходимо развернуть — список, стоит использовать FLATTEN LIST BY
:
SELECT
parents,
name,
geo_parents_list
FROM <cluster-name>.`home/yql/tutorial/users` VIEW complex
FLATTEN LIST BY geo_parents_list AS parents;
Если же список находится в колонке с типом Yson, то сначала необходимо преобразовать его в тип List. Например, так:
SELECT *
FROM (
SELECT
ListMap(
Yson::ConvertToList(my_yson_column),
($x) -> { RETURN Yson::Serialize($x.foo) }
) AS my_list
FROM my_table)
FLATTEN LIST BY my_list;
FLATTEN DICT BY
следует использовать, когда развернуть нужно словарь:
SELECT
dsv.dict.0 AS key,
dsv.dict.1 AS value
FROM <cluster-name>.`home/yql/tutorial/users_dsv` AS dsv
FLATTEN DICT BY dict;
Подробнее про конструкцию FLATTEN BY
можно прочитать в соответствующем разделе.
Как передать данные одной таблицы (справочника) в виде списка для обработки другой таблицы
Если размер таблицы справочника не превышает лимита на размер одной строки таблицы, то можно воспользоваться скалярным контекстом и функцией AGGREGATE_LIST
.
$data = SELECT AGGREGATE_LIST(TableRow()) FROM small_table; -- скалярный контекст при использовании $data в выражении, тип Optional<List>
SELECT MyModule::MyUdf($data ?? [])(x) FROM big_table;
Если размер таблицы справочника побольше, но она все еще может быть загружена в каждую джобу целиком, то можно воспользоваться табличным контекстом.
$data = SELECT * FROM medium_table;
$data = PROCESS $data; -- включаем табличный контекст, $data будет далее иметь тип List<Struct>
SELECT MyModule::MyUdf($data)(x) FROM big_table;
У такой схемы выставлен атрибут <strict=%false>
. Подробнее про схемы таблицы