Поваренная книга по JavaScript
Не нашли нужный рецепт? Запросите или добавьте рецепт, открыв запрос на GitHub.
<img class="api_command_illustration" src="data:image/png;base64,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
Основные команды
Создание базы данных
Вы можете использовать команду dbCreate следующим образом:
r.dbCreate("blog").run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Другой способ создания базы данных — через веб-интерфейс. Вы можете перейти к веб-интерфейсу по адресу http://HOST:8080. Нажмите на вкладку Таблицы вверху и затем кнопку Добавить базу данных.
Переименование базы данных
Самый простой способ переименовать базу данных — использовать команду config для доступа к db_config системе таблицы, а затем просто использовать команду update.
r.db("old_db_name").config().update({name: "new_db_name"}).run(conn,
function(err, result) {
if (err) throw err;
console.log(result);
}
);
Создание таблицы
Вы можете выбрать базу данных, в которой вы хотите создать таблицу, с помощью команды db и использовать команду tableCreate следующим образом:
r.db("blog").tableCreate("posts").run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Обратите внимание, что вы можете опустить команду db, если вы создаёте таблицу в базе данных по умолчанию на вашем подключении (установлено на test, если не указано в connect).
Ещё один способ создать новую таблицу — использовать веб-интерфейс. Вы можете перейти к веб-интерфейсу по адресу http://HOST:8080. Нажмите на вкладку Таблицы в верхней части страницы, а затем используйте кнопку Добавить таблицу.
Вставка документов
Вы можете вставлять документы, вызвав команду insert в соответствующей таблице:
r.table("user").insert({
name: "Michel",
age: 26
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Вы можете вставлять сразу несколько документов, передав массив документов в insert следующим образом:
r.table("user").insert([
{
name: "Michel",
age: 26
},
{
name: "Slava",
age: 30
}
]).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Удаление документов
Для удаления документов выберите документы, которые нужно удалить, и используйте команду delete. Например, удалим все сообщения с автором «Michel»:
r.table("posts").filter(r.row("author").eq("Michel")).delete().run(conn,
function(err, result) {
if (err) throw err;
console.log(result);
}
);
Или, давайте попробуем удалить одного пользователя:
r.table("posts").get("7644aaf2-9928-4231-aa68-4e65e31bf219").delete().run(conn,
function(err, result) {
if (err) throw err;
console.log(result);
}
);
Вот как мы удалим все документы в таблице:
r.table("posts").delete().run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Получение документов
Чтобы получить все документы в таблице, просто используйте команду table:
r.table("posts").run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Команда table возвращает курсор; используйте команду next или each для перебора набора результатов или toArray для получения набора в виде массива.
Чтобы получить конкретный документ по ID, используйте get:
r.table("posts").get(1).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Чтобы получить документы по значению определённого поля, используйте filter:
r.table("posts").filter({author: "Michel"}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Чтобы получить документы по значению определённого индекса, используйте getAll:
r.table("posts").getAll("review", {index: "category"}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
(Для более сложных рецептов фильтрации читайте дальше.)
Фильтрование
Фильтрование по нескольким полям
Предположим, вам нужно выбрать все записи, где имя автора «Michel», а категория «Geek». Вы можете сделать это следующим образом:
r.table("posts").filter({
author: "Michel",
category: "Geek",
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
В качестве альтернативы вы можете создать предикат с помощью команды and и передать его в filter:
r.table("posts").filter(
r.row("author").eq("Michel").and(r.row("category").eq("Geek"))
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Вы также можете использовать префиксную нотацию (передавая все аргументы в r.and), если предпочитаете:
r.table("posts").filter(
r.and(r.row("author").eq("Michel"), r.row("category").eq("Geek"))
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Аналогично, вы можете использовать команду r.or для фильтрации на основе одного из нескольких условий.
Фильтрование по наличию значения в массиве
Предположим, у нас есть таблица users с документами следующего формата:
{
name: "William Adama"
emails: ["bill@bsg.com", "william@bsg.com"],
ship: "Galactica"
}
Если мы хотим получить всех пользователей с адресом электронной почты user@email.com, мы можем написать:
r.table("user").filter(r.row("emails").contains("user@email.com"))
.run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Если мы хотим получить всех пользователей на Galactica и Pegasus, мы можем написать:
r.table("user").filter(function (user) {
return r.expr(["Galactica", "Pegasus"]).contains(user("ship"))
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Фильтрование по вложенным полям
В JavaScript вы можете использовать оператор () для получения значения поля. Этот оператор может быть скопирован для получения значений из вложенных полей.
Предположим, у нас есть таблица users с документами следующего формата:
{
name: "William Adama"
contact: {
phone: "555-5555"
email: "bill@bsg.com"
}
}
Давайте отфильтруем по вложенному полю email:
r.table("user").filter(
r.row("contact")("email").eq("user@email.com")
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Для многих команд ReQL вы также можете использовать синтаксис в стиле JSON, который обеспечивает значительно большую гибкость. Подробнее см. в разделе «Доступ к вложенным полям».
Эффективное получение нескольких документов по первичному ключу
Если вы хотите получить все сообщения с первичными ключами 1, 2, или 3, вы можете использовать команду getAll:
r.table("posts").getAll(1, 2, 3).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Эффективное получение нескольких документов по вторичному индексу
Предположим, у нас есть таблица posts, которая связывает сообщения с авторами через поле author_id. Если мы создали вторичный индекс на author_id, и хотим получить все сообщения, где author_id равно 1, 2, или 3, мы можем использовать команду getAll следующим образом:
r.table("posts").getAll(1, 2, 3, {index: 'author_id'})
.run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Прочитайте о создании вторичных индексов в RethinkDB.
Получение всех объектов из потока (курсора) в виде массива
Если вы используете команду, возвращающую поток, и хотите получить все её результаты сразу в виде массива, а не перебирая их с помощью объекта курсора, вы можете преобразовать его в массив, используя команду toArray.
r.table('posts').run(conn, function(err, cursor) {
if (err) throw err;
cursor.toArray(function(result) {
console.log(result);
});
});
См. документацию по типам данных для получения дополнительной информации о потоках.
Возвращение определённых полей документа
Если вам нужно вернуть только несколько определённых полей из ваших документов, вы можете использовать команду pluck. Например, вот как вы вернёте только поля name и age из каждой строки в таблице users:
r.table("users").pluck("name", "age").run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Это эквивалентно вызову SELECT name, age FROM users в SQL.
Команда pluck также поддерживает выбор вложенных полей в документе. Например, предположим, что мы хотим выбрать поля phone и email из следующего документа:
{
name: "William Adama"
contact: {
phone: "555-5555"
email: "bill@bsg.com"
}
}
Мы можем использовать следующий синтаксис:
r.table("users").pluck(
{contact: {phone: true, email: true}}
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Фильтрование по диапазону дат
Предположим, вы хотите получить все записи, дата которых находится между 1 января 2012 года (включительно) и 1 января 2013 года (исключительно). Вы можете сделать это так:
r.table("posts").filter(function(post) {
return post("date").during(r.time(2012, 1, 1, 'Z'), r.time(2013, 1, 1, 'Z'));
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Вы также можете вручную сравнить даты:
r.table("posts").filter(function(post) {
return post("date").ge(r.time(2012, 1, 1, 'Z')).and(
post("date").lt(r.time(2013, 1, 1, 'Z')));
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Фильтрование с использованием регулярных выражений
Если вы хотите получить всех пользователей, фамилия которых начинается с «Ma», вы можете использовать r.match следующим образом:
// Will return Martin, Martinez, Marshall etc.
r.table("users").filter(function(user) {
return user("lastName").match("^Ma");
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Если вы хотите получить всех пользователей, фамилия которых заканчивается на «s», вы можете использовать r.match следующим образом:
// Will return Williams, Jones, Davis etc.
r.table("users").filter(function(user) {
return user("lastName").match("s$");
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Если вы хотите получить всех пользователей, фамилия которых содержит «ll», вы можете использовать r.match следующим образом:
// Will return Williams, Miller, Allen etc.
r.table("users").filter(function(user) {
return user("lastName").match("ll");
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Регистронезависимый фильтр
Получить всех пользователей, имя которых «William» (регистронезависимо).
// Will return william, William, WILLIAM, wiLLiam etc.
r.table("users").filter(function(user) {
return user("name").match("(?i)^william$");
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Выполнение нескольких агрегаций одновременно
Если вам нужно выполнить запрос, возвращающий агрегации по различным полям вместе, это типичный случай использования map-reduce.
Предположим набор данных, в котором перечислены лучшие фильмы, ранжированные по голосам пользователей. Вам нужно получить общее количество голосов и средний возраст 25 лучших фильмов: avg() столбца year и sum() столбца votes, отсортированных по столбцу rank для получения диапазона 1–25.
Для этого, выполните map первых 25 фильмов в новый набор результатов, добавив столбец count, затем reduce каждую строку сопоставленного набора результатов в сумму для каждого поля (votes, year и column). Затем используйте do для возврата набора результатов с общим количеством голосов и средним годом, вычисленным путем деления суммы лет на их количество.
r.table('movies').orderBy('rank').limit(25).map(function (doc) {
return { totalVotes: doc('votes'), totalYear: doc('year'), count: 1 };
}).reduce(function (left, right) {
return {
totalVotes: left('totalVotes').add(right('totalVotes')),
totalYear: left('totalYear').add(right('totalYear')),
count: left('count').add(right('count'))
};
}).do(function (res) {
return {
totalVotes: res('totalVotes'),
averageYear: res('totalYear').div(res('count'))
};
}).run(conn, callback);
Мы работаем над более простым синтаксисом для выполнения нескольких агрегаций после команд group. Следите за прогрессом по этому вопросу в вопросе 1725.
Изменение документов
Добавление/перезапись поля в документе
Для добавления или перезаписи поля можно использовать команду update. Например, если вы хотите добавить поле author со значением «Michel» для всех документов в таблице posts, используйте:
r.table("posts").update({ author: "Michel" }).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Удаление поля из документа
Команда update позволяет перезаписывать поля, но не удалять их. Если вы хотите удалить поле, используйте команду replace. Команда replace заменяет весь ваш документ новым документом, который вы передаёте в качестве аргумента. Например, чтобы удалить поле author из записи блога с идентификатором 1, используйте:
r.table("posts").get("1").replace(r.row.without('author'))
.run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Атомарное обновление документа на основе условия
Все изменения, внесённые с помощью команд update и replace, всегда атомарны относительно одного документа. Например, предположим, что мы хотим атомарно обновить счётчик просмотров страницы, если поле countable имеет значение true, и получить старые и новые результаты в одном запросе. Мы можем выполнить эту операцию следующим образом:
r.table("pages").update(function(page) {
return r.branch(page("countable").eq(true), // if the page is countable
{ views: page("views").add(1) }, // increment the view count
{} // else do nothing
);
}, {returnChanges: true}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Условное добавление или замена
Используя аналогичную технику, что в предыдущем примере, мы можем использовать branch и replace для поддержания полей updated_at и created_at документа, либо добавляя новый документ, либо обновляя существующий в зависимости от того, существует ли документ с указанным идентификатором.
r.table('users').get(id).replace(function (doc) {
return r.branch(
doc.eq(null),
r.expr(userObject).merge({id: id, created_at: r.now()}),
doc.merge(userObject).merge({updated_at: r.now()})
)
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Хранение временных меток и строк даты JSON как типов данных Time
Вы можете использовать команды epochTime и ISO8601 для преобразования временных меток Unix (в секундах) и строк JSON даты и времени (в формате ISO 8601) в тип времени ReQL. Драйвер ReQL также преобразует объекты JavaScript Date в время ReQL.
var theDate = new Date();
var timestamp = theDate.getTime();
var JSONDate = theDate.toJSON();
r.table("dates").insert({
from_object: theDate,
from_epoch: r.epochTime(timestamp/1000.0),
from_iso: r.ISO8601(JSONDate)
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Используйте команды toEpochTime и toISO8601 для обратного преобразования.
Инкремент значения поля
Можно инкрементировать значение поля в документе (например, счётчик) за один шаг на сервере.
r.table('aggregated').get(id).update(
{ count: r.row('count').default(0).add(1) }
).run(conn, callback);
Используйте default, чтобы убедиться, что если поле count ещё не существует в документе, оно добавится правильно, а не add вызывало ошибку.
Пейджирование
Ограничение количества возвращаемых документов
Вы можете ограничить количество документов, возвращаемых вашими запросами, с помощью команды limit. Давайте получим только первые 10 записей блога:
r.table("posts").orderBy("date").limit(10).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Реализация пейджирования
Существует несколько способов реализации пейджирования результатов в RethinkDB. Самый простой способ — использовать skip и limit (подобно работе OFFSET и LIMIT в SQL), но это наименее эффективный способ. Более эффективно использовать slice, а ещё более эффективно — between со вторичным индексом.
Команда slice возвращает диапазон от заданного начального значения до, но не включая, заданного конечного значения. Это позволяет легко использовать её как замену skip/limit: начальное значение — это первый элемент для извлечения, а конечное значение — это первый элемент плюс предел. Чтобы получить записи с 11 по 20 из базы данных, используя slice,:
r.table("posts").orderBy("date").slice(11,21).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Если у вас есть вторичный индекс, вы можете использовать команду between в сочетании с orderBy и limit. Это наиболее эффективный способ реализации пейджирования, но требует поиска значений в поле вторичного индекса, чтобы найти первую запись каждой страницы.
Предположим, вы хотите просмотреть набор пользователей по 25 на странице. Вы можете эффективно получить первые 25 записей с помощью limit.
r.table("users").orderBy({index: "name"}).limit(25).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Для каждой последующей страницы начните с последнего имени в предыдущей странице.
r.table("users").between(lastName, r.maxval, {leftBound: "open", index: "name"})
.orderBy({index: "name"}).limit(25).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Мы передаём значение lastName, сохранённое из предыдущего набора, в between в качестве начального индекса. Для конечного индекса мы передаём null, чтобы вернуть документы от начального индекса до конца таблицы. Параметр leftBound сообщает between не включать первую запись, поскольку она уже была возвращена в предыдущей странице.
Преобразования
Подсчёт количества документов в таблице
Вы можете подсчитать количество документов с помощью команды count.
r.table("posts").count().run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Вычисление среднего значения поля
Вы можете вычислить среднее значение поля с помощью команды avg.
r.table("posts").avg("num_comments").run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Использование подзапросов для возвращения дополнительных полей
Предположим, мы хотим получить все записи в таблице post и также вернуть дополнительное поле comments, которое представляет собой массив всех комментариев к соответствующей записи, полученный из таблицы comments. Мы можем сделать это с помощью подзапроса:
r.table("posts").merge(function(post) {
return {
comments: r.table("comments").filter(function(comment) {
return comment("id_post").eq(post("id"))
}).coerceTo("ARRAY")
}
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Выполнение операции сводки
Предположим, таблица marks хранит оценки каждого студента по каждому курсу:
[
{
"name": "William Adama",
"mark": 90,
"id": 1,
"course": "English"
},
{
"name": "William Adama",
"mark": 70,
"id": 2,
"course": "Mathematics"
},
{
"name": "Laura Roslin",
"mark": 80,
"id": 3,
"course": "English"
},
{
"name": "Laura Roslin",
"mark": 80,
"id": 4,
"course": "Mathematics"
}
]
Возможно, вас интересуют результаты в таком формате:
[
{
"name": "Laura Roslin",
"Mathematics": 80,
"English": 80
},
{
"name": "William Adama",
"Mathematics": 70,
"English": 90
}
]
В этом случае вы можете выполнить операцию сводки с помощью команд group и coerceTo.
r.db('test').table('marks').group('name').map(function (row) {
return [row('course'), row('mark')];
}).ungroup().map(function (res) {
return r.expr({name: res('group')}).merge(res('reduction').coerceTo('object'));
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Примечание: В будущем будет добавлен более удобный синтаксис. Следите за прогрессом в вопросе 838 на GitHub.
Выполнение операции не-сводки
Выполнение операции не-сводки для «отмены» операции сводки можно выполнить с помощью команд concatMap, map и keys.
r.table("pivotedMarks").concatMap(function (doc) {
return doc.without("id", "name").keys().map(function (course) {
return {
name: doc("name"),
course: course,
mark: doc(course)
};
});
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Примечание: В будущем будет добавлен более удобный синтаксис. Следите за прогрессом в вопросе 838 на GitHub.
Переименование поля при получении документов
Предположим, мы хотим переименовать поле id в idUser при получении документов из таблицы users. В подзапросе мы можем использовать merge для добавления нового поля со значением существующего поля, а затем without для удаления старого поля:
r.table("users").map(
r.row.merge({ idUser: r.row("id") }).without("id")
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Группировка результатов запроса по периодам даты/времени
ReQL имеет команды для извлечения частей дат и времени, включая год, месяц, день, день недели и многое другое. Вы можете использовать их с group для группировки по различным интервалам. Предположим, у вас есть таблица сведений об инвойсах, и вы хотите получить их в группах, отсортированных по году и месяцу:
r.table("invoices")
.group([r.row("date").year(), r.row("date").month()])
.ungroup()
.merge({invoices: r.row('reduction'), month: r.row('group')})
.without('reduction', 'group')
.orderBy('month')
.run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
(Мы также используем технику переименования поля, описанную выше, чтобы дать именам «reduction» и «group» более полезные имена «invoices» и «month».) Вы можете использовать любое сочетание команд ReQL для интервалов даты/времени в группировке или работать с датой/временем как с исходным объектом.
В настоящее время ReQL имеет ограничение по умолчанию в 100 000 элементов в массиве, и реализация group требует, чтобы общее количество сгруппированных документов входило в этот предел, поэтому вы ограничены 100 000 инвойсами. Однако это можно изменить, передав параметр arrayLimit к run. (Также обратите внимание, что ungroup всегда возвращает массив, хотя это может измениться в будущих версиях. Следите за прогрессом по этому вопросу в #2719 на GitHub.)
Вы также можете использовать этот подход со составным индексом по интервалам, которые вы хотите сгруппировать:
r.table('invoices').indexCreate(
'byDay', [r.row('date').year(), r.row('date').month(), r.row('date').day()]
).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Затем вы можете использовать этот индекс в функции group. Этот запрос вернёт инвойс с наибольшим значением для каждого дня.
r.table("invoices")
.group({index: 'byDay'})
.max('price')
.run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Разное
Генерация монотонно возрастающих значений первичного ключа
Эффективная генерация монотонно возрастающих идентификаторов в распределённой системе — удивительно сложная задача. Если вставляемому документу не указан первичный ключ, RethinkDB в настоящее время генерирует случайный UUID. В будущем мы будем поддерживать дополнительные схемы автогенерации (см. https://github.com/rethinkdb/rethinkdb/issues/117), но пока вы можете использовать одну из доступных открытых библиотек для генерации распределённых идентификаторов (например, twitter snowflake).
Парсинг ответа RethinkDB на запрос записи
При выполнении запроса записи (insert, delete, update, или replace), RethinkDB возвращает объект резюме, который выглядит так:
{deleted:0, replaced:0, unchanged:0, errors:0, skipped:0, inserted:1}
Самое важное поле этого объекта — errors. В общем случае, если не произошло никаких исключений и значение errors равно 0, то запись выполнена успешно. (RethinkDB выбрасывает исключение, когда не может получить доступ к таблице; устанавливает поле errors, если доступ к таблице возможен, но при записи произошла ошибка. Эта согласованность используется для того, чтобы пакетные записи не прерывались на полпути при встрече с ошибкой.)
Следующие поля всегда присутствуют в этом объекте:
-
inserted– Количество новых документов, добавленных в базу данных. -
deleted– Количество документов, удалённых из базы данных. -
replaced– Количество документов, которые были изменены. -
unchanged– Количество документов, которые должны были быть изменены, но новое значение совпадало со старым. -
skipped– Количество документов, которые остались неизменными при операции записи, потому что документ недоступен для удаления или обновления. Возможно, документ был удалён другой операцией, выполнявшейся параллельно, или, в случае операцииget, ключ может отсутствовать. -
errors– Количество документов, оставшихся неизменными из-за ошибки.
Кроме того, следующие два поля устанавливаются в зависимости от обстоятельств:
-
generated_keys– Если вы выполняете запрос на вставку, где некоторые или все строки не имеют первичных ключей, сервер сгенерирует для вас первичные ключи и вернёт массив этих ключей в этом поле. (Порядок этого массива будет соответствовать порядку строк в вашем запросе вставки.) -
first_error– Если значениеerrorsположительно, текст первого сообщения об ошибке, с которым столкнулись, будет находится в этом поле. Это очень полезный инструмент отладки. (Мы не возвращаем все ошибки, потому что одна опечатка может привести к миллионам ошибок при работе с большой базой данных.)
Использование динамических ключей в командах ReQL
Иногда вам может потребоваться записать документ ReQL с динамическим ключом — имя поля хранится в переменной. Вы можете сделать это с помощью команды object, которая принимает список ключей и значений ((key, value, key, value ...)) и возвращает объект из них.
r.table('users').get(1).update(r.object(propertyName, value)).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Имя поля также может быть определено полностью на сервере. Например, для обновления поля, чьё имя взято из значения другого поля:
r.table('users').forEach(function (doc) {
return r.table('users').get(doc('id')).update(r.object(doc('field'), newValue));
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Для примера, представьте набор данных, похожий на пример из примера поворота, где каждый документ представляет запись о курсе студента.
[
{
"name": "John",
"mark": 70,
"id": 1,
"course": "Mathematics"
},
{
"name": "John",
"mark": 90,
"id": 2,
"course": "English"
}
]
Но вы хотите получить документ, более похожий на «ведомость оценок»:
{
"Mathematics": 70,
"English": 90
}
Вы можете добиться этого с помощью object и поворота.
r.table("marks").filter({student: "John"}).map(function(mark) {
return r.object(mark("course"), mark("mark"));
}).reduce(function(left, right) {
return left.merge(right);
}).run(conn, function(err, result) {
if (err) throw err;
console.log(result);
});
Возвращение запроса ReQL в виде строки
В целях тестирования или ведения журнала вам может потребоваться сохранить созданный запрос ReQL в виде строки. (Вы можете увидеть пример этого в сообщениях об ошибках ReQL.) Несмотря на отсутствие команды ReQL для этого, вы можете просто использовать метод toString() в конце цепочки запросов, а не run().
r.table('users').filter(r.row('groups').contains('operators')).toString()
Создание запросов ReQL на нескольких строках
В некоторых интерфейсах запросов часто используется шаблон «создания» запросов программно, путём создания объекта запроса, вызова его несколько раз подряд для добавления команд запроса, а затем вызова команды выполнения. Это позволяет динамически изменять запрос в зависимости от условий во время выполнения. Вы, возможно, ожидаете, что можно сделать это в ReQL так:
var query = r.table('posts');
if (request.filter !== undefined) {
query.filter(request.filter);
}
query.orderBy('date');
query.run(conn, callback);
Однако, это не сработает! Причина в том, что объект запроса не хранит состояние. Каждая из команд после первой просто выполняется над исходным значением query (в данном случае, таблица posts). Вы можете решить эту проблему, явно присваивая результат каждой новой команды переменной query.
var query = r.table('posts');
if (request.filter !== undefined) {
query = query.filter(request.filter);
}
query = query.orderBy('date');
query.run(conn, callback);
Объединение нескольких потоков изменений в один
Вам может понадобиться создать «объединённый» поток изменений для наблюдения за несколькими таблицами или запросами в одном потоке. Поскольку команда union работает с changes, ReQL делает это довольно просто. Для мониторинга двух таблиц одновременно:
r.table('table1').union(r.table('table2')).changes().run(conn, callback);
Вы можете «пометить» таблицы, чтобы было ясно, какие изменения относятся к какой таблице.
r.table('table1').merge({table: 'table1'})
.union(r.table('table2').merge({table: 'table2'})
.changes().run(conn, callback);
Также, вы можете использовать changes с каждым запросом, а не после всего.
r.table('table1').filter({flag: 'blue'}).changes()
.union(r.table('table2').filter({flag: 'red'}).changes())
.run(conn, callback);
© RethinkDB contributors
Licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
https://rethinkdb.com/docs/cookbook/javascript/