Хранение JSON в Postgres с использованием Node.js

Одна из вещей, которая сделала базы данных NoSQL, такие как MongoDB, настолько популярными, заключалась в том, насколько легко просто добавить кучу JSON, когда это то, что вам нужно. Однако вы можете не осознавать, что Postgres одинаково хорошо поддерживает JSON. В дополнение к простому добавлению JSON в поле, вы можете запросить его, используя все обычные операции SQL. Вы получаете JOIN, транзакции, индексы и т. Д.

JSON против JSONB

Первым шагом при добавлении поля JSON в postgres является выбор между JSON и JSONB. Я сделаю это для вас легко:

Всегда используйте JSONB, никогда не используйте JSON

JSONB похож на JSON, за исключением того, что он не хранит фактическую строку JSON, а хранит эффективное двоичное представление. Единственная причина, по которой вы можете когда-либо захотеть сохранить JSON, - это если вы хотите отслеживать пробелы в исходной сериализации JSON. В этом никогда не должно быть необходимости. Если вы хотите получить «красивый» вид вашего JSON, вы можете просто использовать JSON.stringify для его предварительной атрибуции. Я не могу себе представить, зачем вам вообще понадобились исходные пробелы.

Создание таблицы

Теперь, когда вы выбрали JSONB в качестве формата, вы можете создать таблицу, как обычно.

CREATE TABLE my_data (
  id TEXT NOT NULL PRIMARY KEY,
  data JSONB NOT NULL
);

Это создает таблицу с первичным ключом id типа TEXT и столбцом data для хранения наших данных JSON.

Чтение и запись данных JSON

Если вы используете клиент @databases/pg, вы можете читать и записывать данные Postgres так же, как и любое другое значение:

import connect, {sql} from '@databases/pg';
const db = connect();
export async function get(id) {
  const [row] = await db.query(
    sql`
      SELECT data
      FROM my_data
      WHERE id=${id}
    `
  );
  return row ? row.data : null;
}
export async function set(id, value) {
  await db.query(sql`
    INSERT INTO my_data (id, data)
    VALUES (${id}, ${value})
    ON CONFLICT id
    DO UPDATE SET data = EXCLUDED.data;
  `);
}

Это дает нам простое хранилище значений ключей для больших двоичных объектов JSON с использованием базы данных postgres.

Запрос JSON

Представьте, что мы храним несколько сообщений блога в нашей «базе данных NoSQL Postgres»:

await set('post-a', {
  author: 'ForbesLindesay',
  title: 'Post A',
  body: 'This post is about the letter A',
});
await set('post-b', {
  author: 'ForbesLindesay',
  title: 'Post B',
  body: 'This post is about the letter B',
});
await set('post-a-rebuttal', {
  author: 'JoeBloggs',
  title: 'Post A - Rebuttal',
  body: 'Forbes was wrong about the letter A',
});

Теперь представьте, что мы хотим получить список всех сообщений в блоге от ForbesLindesay. Поле автора скрыто в поле JSONB, но это не значит, что мы не можем использовать его в нашем запросе.

export async function listByAuthor(author) {
  return await db.query(
    sql`
      SELECT data
      FROM my_data
      WHERE
        data ->> 'author'
          = ${author}
    `
  );
}

Здесь оператор ->> означает «получить значение этого свойства». Он будет работать, только если значение является строкой, числом или логическим значением. Если значением является другой объект, вы должны использовать оператор ->, что означает «получить значение этого свойства как JSON».

Надеюсь, ясно, что это означает, что здесь вы можете использовать всю мощь SQL, но просто чтобы привести еще один пример, мы могли бы получить список всех авторов:

export async function getAuthors() {
  return (await db.query(
    sql`
      SELECT DISTINCT
        data ->> 'author' as author
      FROM my_data
    `
  )).map(({author}) => author);
}

Здесь мы извлекаем автора из данных, а затем с помощью оператора SQL DISTINCT возвращаем каждого автора только один раз.

Заключение

В Postgres вы можете использовать JSON, как и любое другое значение, и было бы легко настроить хранилище, подобное NoSQL, для больших двоичных объектов JSON и просто использовать его как всю вашу базу данных. Это не обязательно означает, что вы должны. Эти данные JSON полностью не содержат схемы, поэтому очень важно, чтобы вы правильно проверили, соответствует ли они любой ожидаемой структуре, прежде чем вставлять их в базу данных. Это очень полезно, когда вам нужно хранить большие структуры JSON, и вы еще не знаете, как вы собираетесь запрашивать их, но по большей части я все же рекомендую иметь явное поле в SQL и использовать JOIN для хранения вложенных списки и т. д.