Команда ReQL: group
Синтаксис команды
sequence.group(field | function..., [:index => <indexname>, :multi => true]) → grouped_stream r.group(sequence, field | function..., [:index => <indexname>, :multi => true]) → 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{|game| game.pluck('player', 'type')}
.max('points')['points'].run(conn)
{
{"player" => "Alice", "type" => "free"} => 7,
{"player" => "Bob", "type" => "free"} => 10,
{"player" => "Bob", "type" => "ranked"} => 15
}
Используя функцию, вы также можете сгруппировать по дате в поле ReQL даты.
Пример: Сколько матчей было сыграно в этом году по месяцам?
> r.table('matches').group { |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(:multi => true){ |row| row['matches'].keys() }.run
[
{
: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('games3').group(:multi => true){ |row| row['matches'].keys()
}.ungroup().map{ |doc|
{
:match => doc['group'], :total => doc['reduction'].sum{ |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
}
]
Детали реализации
Когда сгруппированные данные возвращаются клиенту, они преобразуются в специфичный для клиента родной тип. (Аналогичное преобразование выполняется с временными значениями.) В Ruby сгруппированные данные преобразуются в Hash. Если вы хотите получить сырой псевдотип с сервера (например, если вы планируете сериализовать результат как 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). Эти сгруппированные данные ограничены размером массива, по умолчанию 100 000 элементов (см. run для получения подробностей о том, как использовать опцию array_limit для изменения этого значения).
В общем, операции над сгруппированными потоками будут эффективно распределены, а операции над сгруппированными данными — нет. Вы можете выяснить, с чем вы работаете, добавив 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{|game| game['type'].eq('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/ruby/group/