Spec-Zone.ru › RethinkDB python

Команда 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
}

Связанные команды

  • ungroup
  • map
  • reduce
  • count
  • sum
  • avg
  • min
  • max

© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/api/python/group/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API