Команда ReQL: group
Синтаксис команды
sequence.group(field | function..., [index=<indexname>, multi=False]) → grouped_stream r.group(sequence, field | function..., [index=<indexname>, multi=False]) → grouped_stream
Описание
Принимает поток и разбивает его на несколько групп на основе предоставленных полей или функций.
С флагом multi отдельные документы могут быть назначены нескольким группам, подобно поведению мультииндексов. Когда multi является True, а значение группировки — массив, документы будут размещены в каждой группе, соответствующей элементам массива. Если массив пуст, строка будет пропущена.
Предположим, что таблица games имеет следующие данные:
[
{"id": 2, "player": "Bob", "points": 15, "type": "ranked"},
{"id": 5, "player": "Alice", "points": 7, "type": "free"},
{"id": 11, "player": "Bob", "points": 10, "type": "free"},
{"id": 12, "player": "Alice", "points": 2, "type": "free"}
]
Пример: Группировка игр по игроку.
> r.table('games').group('player').run(conn)
{
"Alice": [
{"id": 5, "player": "Alice", "points": 7, "type": "free"},
{"id": 12, "player": "Alice", "points": 2, "type": "free"}
],
"Bob": [
{"id": 2, "player": "Bob", "points": 15, "type": "ranked"},
{"id": 11, "player": "Bob", "points": 10, "type": "free"}
]
}
Команды, присоединённые к group, будут вызваны для каждого из этих сгруппированных подпотоков, генерируя сгруппированные данные.
Пример: Какая игра лучшая для каждого игрока?
> r.table('games').group('player').max('points').run(conn)
{
"Alice": {"id": 5, "player": "Alice", "points": 7, "type": "free"},
"Bob": {"id": 2, "player": "Bob", "points": 15, "type": "ranked"}
}
Команды, присоединённые к сгруппированным данным, будут работать с каждым сгруппированным элементом, создавая ещё более сгруппированные данные.
Пример: Какое максимальное количество очков набрал каждый игрок?
> r.table('games').group('player').max('points')['points'].run(conn)
{
"Alice": 7,
"Bob": 15
}
Вы также можете группировать по нескольким полям.
Пример: Какое максимальное количество очков набрал каждый игрок для каждого типа игры?
> r.table('games').group('player', 'type').max('points')['points'].run(conn)
{
("Alice", "free"): 7,
("Bob", "free"): 10,
("Bob", "ranked"): 15
}
Вы также можете группировать по функции.
Пример: Какое максимальное количество очков набрал каждый игрок для каждого типа игры?
> r.table('games')
.group(lambda game:
game.pluck('player', 'type')
).max('points')['points'].run(conn)
{
frozenset([('player', 'Alice'), ('type', 'free')]): 7,
frozenset([('player', 'Bob'), ('type', 'free')]): 10,
frozenset([('player', 'Bob'), ('type', 'ranked')]): 15,
}
Используя функцию, вы также можете группировать по дате в поле типа ReQL дата.
Пример: Сколько матчей было сыграно в этом году по месяцам?
> r.table('matches').group(
lambda match: [match['date'].year(), match['date'].month()]
).count().run(conn)
{
(2014, 2): 2,
(2014, 3): 2,
(2014, 4): 1,
(2014, 5): 3
}
Вы также можете группировать по индексу (первичному или вторичному).
Пример: Какое максимальное количество очков набрано по типу игры?
> r.table('games').group(index='type').max('points')['points'].run(conn)
{
"free": 10,
"ranked": 15
}
Группировка по значению с multi
Предположим, что таблица games2 имеет следующие данные:
[
{ 'id': 1, 'matches': {'a': [1, 2, 3], 'b': [4, 5, 6]} },
{ 'id': 2, 'matches': {'b': [100], 'c': [7, 8, 9]} },
{ 'id': 3, 'matches': {'a': [10, 20], 'c': [70, 80]} }
]
Используя опцию multi мы можем сгруппировать данные по матчам A, B или C.
> r.table('games2').group(r.row['matches'].keys(), multi=True).run(conn)
[
{
'group': 'a',
'reduction': [ <id 1>, <id 3> ]
},
{
'group': 'b',
'reduction': [ <id 1>, <id 2> ]
},
{
'group': 'c',
'reduction': [ <id 2>, <id 3> ]
}
]
(Полный набор результатов сокращён на рисунке; <id 1>, <id 2> и <id 3> будут представлять собой все документы, соответствующие этим ключам.)
Пример: Используйте map и sum для получения общей суммы очков для каждого матча.
r.table('games2').group(r.row['matches'].keys(), multi=True).ungroup().map(
lambda doc: { 'match': doc['group'], 'total': doc['reduction'].sum(
lambda set: set['matches'][doc['group']].sum()
)}).run(conn)
[
{ 'match': 'a', 'total': 36 },
{ 'match': 'b', 'total': 115 },
{ 'match': 'c', 'total': 174 }
]
Внутренняя sum складывает баллы по матчу в каждом документе; внешняя sum складывает эти результаты вместе для общего итога по всем документам.
Рассортировка
Если вы хотите работать со всеми группами, а не с каждой группой (например, если вы хотите отсортировать группы по их результатам), вы можете использовать ungroup, чтобы преобразовать сгруппированный поток или сгруппированные данные в массив объектов, представляющих группы.
Пример: Рассортировка сгруппированных данных.
> r.table('games').group('player').max('points')['points'].ungroup().run(conn)
[
{
"group": "Alice",
"reduction": 7
},
{
"group": "Bob",
"reduction": 15
}
]
Рассортировка полезна, например, для сортировки сгруппированных данных или для вставки сгруппированных данных в таблицу.
Пример: Какое максимальное количество очков набрал каждый игрок, начиная с самых результативных?
> r.table('games').group('player').max('points')['points'].ungroup().order_by(
r.desc('reduction')).run(conn)
[
{
"group": "Bob",
"reduction": 15
},
{
"group": "Alice",
"reduction": 7
}
]
Детали реализации
При возвращении сгруппированных данных клиенту они преобразуются в специфический для клиента базовый тип. (Аналогичное преобразование выполняется с временными метками.) В Python сгруппированные данные преобразуются в dictionary. Если значение группы является array, ключ преобразуется в tuple. Если значение группы является dictionary, оно будет преобразовано в frozenset.
Если вы хотите получить исходный псевдотип с сервера (например, если вы планируете сериализовать результат как JSON), вы можете указать group_format: 'raw' в качестве необязательного аргумента к run:
Пример: Получение исходного псевдотипа GROUPED_DATA.
> r.table('games').group('player').avg('points').run(conn, group_format='raw')
{
"$reql_type$": "GROUPED_DATA",
"data": [
["Alice", 4.5],
["Bob", 12.5]
]
}
Если флаг group_format не передан, будет возвращено:
{
"Alice": 4.5,
"Bob": 12.5
}
Вы также можете использовать команду ungroup (см. выше), которая преобразует сгруппированные данные в массив объектов на сервере.
Детали производительности
Если вы выполните запрос, возвращающий сгруппированный поток, он будет автоматически преобразован в сгруппированные данные перед отправкой обратно (в настоящее время нет эффективного способа передавать группы из RethinkDB). Эти сгруппированные данные ограничены размером массива (см. run).
В целом, операции со сгруппированными потоками будут распределяться эффективно, а операции со сгруппированными данными — нет. Вы можете понять, с чем вы работаете, добавив type_of в конец вашего запроса. Ниже приведены эффективные и неэффективные примеры.
Пример: Эффективная операция.
# r.table('games').group('player').type_of().run(conn)
# Returns "GROUPED_STREAM"
r.table('games').group('player').min('points').run(conn) # EFFICIENT
Пример: Неэффективная операция.
# r.table('games').group('player').order_by('score').type_of().run(conn)
# Returns "GROUPED_DATA"
r.table('games').group('player').order_by('score').nth(0).run(conn) # INEFFICIENT
Что здесь означает неэффективность? При работе со сгруппированными данными, а не сгруппированным потоком, все данные должны быть доступны на узле, обрабатывающем запрос. Это означает, что операция будет использовать ресурсы только одного сервера и потребует памяти, пропорциональной размеру сгруппированных данных, с которыми она работает. (В случае с order_by в неэффективном примере это означает память, пропорциональную размеру таблицы.) Предел массива также применяется к сгруппированным данным, поэтому пример order_by завершится неудачей для таблиц более чем 100 000 строк, если не использовать опцию array_limit с run.
Дополнительные примеры
Пример: Какое максимальное количество очков набрал каждый игрок в бесплатных играх?
> r.table('games').filter(lambda game:
game['type'] = 'free'
).group('player').max('points')['points'].run(conn)
{
"Alice": 7,
"Bob": 10
}
Пример: Каковы наивысшие чётные и нечётные баллы каждого игрока?
> r.table('games')
.group('name', lambda game:
game['points'] % 2
).max('points')['points'].run(conn)
{
("Alice", 1): 7,
("Bob", 0): 10,
("Bob", 1): 15
}
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/api/python/group/