Исправление ключей JSON в PostgreSQL

Я хочу исправить или проверить ключи для объекта JSON в PostgreSQL (v10.7).

Например, у меня есть объект JSON с именем service_config, который выглядит так:

{"con_type": "Foo", "capacity": 2, "capacity_unit": "gbps"}

И у меня есть таблица:

 id(serial)   service_name(char)   service_type(char)    service_config(JSON)
-----------+---------------------+---------------------+---------------------
    1      |          com        |        ethernet     | {"con_type": "ddc", "capacity": 2, "capacity_unit": "gbps"}
    2      |          res        |        gpon         | {"con_type": "ftth", "capacity": 1, "capacity_unit": "gbps"} 

Теперь всякий раз, когда я вставляю строку в таблицу, я хочу убедиться или подтвердить, что столбец service_config содержит все ключи, упомянутые выше, ни больше, ни меньше. Однако для ключей может быть нулевое значение.

Возможно ли это в Postgres и/или есть ли лучший способ сделать это?

Возможные решения:

1- Проверьте service_config в бэкэнд-API и убедитесь, что все ключи есть. (сейчас есть и работает)

2- Напишите функцию в Postgres для проверки service_config при вставке и обновлении. (выполнимо, но утомительно)

Ограничение: я не могу добавить какое-либо расширение в Postgres.


person Myz    schedule 17.06.2020    source источник


Ответы (1)


Я хочу убедиться или подтвердить, что столбец service_config содержит все ключи, упомянутые выше, ни больше, ни меньше. Однако для ключей может быть нулевое значение.

Превратите их в столбцы.

JSON удобен, когда вам нужно просто вывести некоторые данные в строку, и вы не уверены, что это будет. Теперь, когда вы уверены, что это будет, и хотите больше ограничений, это то, что столбцы делают лучше всего.

alter table whatever add column con_type text;
alter table whatever add column capacity integer;
alter table whatever add column capacity_unit text;

update whatever set
  con_type = data->'con_type',
  capacity = data->'capacity',
  capacity_unit = data->'capacity_unit';

alter table whatever drop column data

Колонки всегда будут. Их значения могут быть нулевыми. Вы можете добавить check ограничений и индексов для каждого столбца. Никаких дополнительных подтверждений не требуется.

Если вам все еще нужен json, используйте jsonb_build_object.

select
  jsonb_build_object(
    'con_type', con_type,
    'capacity', capacity,
    'capacity_unit', capacity_unit
  )
from whatever;

И, если вам это нужно для совместимости, вы можете сделать это представление.

create view whatever_as_json
select
  *,
  jsonb_build_object(
    'con_type', con_type,
    'capacity', capacity,
    'capacity_unit', capacity_unit
  ) as data
from whatever;

Обратите внимание, что я использую text, а не char, потому что в Postgres нет никаких преимуществ перед char. См. совет в 8.3. Типы символов

Между этими тремя типами нет разницы в производительности, за исключением увеличения места для хранения при использовании типа с заполнением пробелами и нескольких дополнительных циклов ЦП для проверки длины при сохранении в столбец с ограничениями по длине. В то время как в некоторых других системах баз данных у character(n) есть преимущество в производительности, в PostgreSQL такого преимущества нет; на самом деле character(n) обычно является самым медленным из трех из-за дополнительных затрат на хранение. В большинстве случаев вместо этого следует использовать текст или символы.

person Schwern    schedule 17.06.2020
comment
Причиной использования JSON и отсутствия столбцов является неструктурированный характер данных. Например, может быть несколько служб с разными значениями емкости_единицы. Есть и другой пример, когда одно и то же поле может иметь разные типы данных (или, по крайней мере, быть представлено в разных типах данных). - person Myz; 17.06.2020
comment
@Myz Например, может быть несколько сервисов с разными значениями capacity_unit. Извините, я не понимаю, что это значит. Что касается разных типов данных, если это просто разные представления, их нормализация при вставке облегчит работу с данными. Например, "capacity": 1 и "capacity": "1" должны быть нормализованы до целого числа 1. Это облегчит работу с данными, индексацию и обеспечит их правильность. Показ нам некоторых примеров поможет. - person Schwern; 17.06.2020
comment
Извините, если я был не ясен. Под разным я подразумевал то, что некоторые услуги могут иметь capacity_unit как Мбит/с или Гбит/с, а иногда даже кВт или кв.м. Это не большая проблема, так как тип данных capacity_unitchar. Немного сложнее ситуация с полем capacity, так как оно может быть либо 5, либо 5/5. Чтобы решить эту проблему, я мог бы просто использовать char, а затем разобрать его на int, где это необходимо. - person Myz; 17.06.2020
comment
@Myz Да, вы можете хранить их как текст (опять же, избегайте символов), но это имеет последствия для простоты использования и индексирования. Я не могу понять, что означает 5/5 для любой из этих единиц, если только это не означает просто 1, поэтому я бы посоветовал вам продолжить дальнейшую нормализацию. Возможно, вам нужна еще одна колонка? - person Schwern; 17.06.2020
comment
Вместо того, чтобы записывать пропускную способность загрузки и загрузки отдельно (в отдельных столбцах), она записывается как 5/5. Таким образом, 5/5 означает 5 Гбит/с пропускной способности и 5 Гбит/с пропускной способности. Кстати, он не симметричный, т.е. может быть и 8/10, или 1/8 и т. д. Что касается дальнейшей нормализации, то объединение неструктурированных данных в JSON кажется подходящим вариантом из-за требований проекта. - person Myz; 18.06.2020
comment
@Myz Я бы предложил нормализовать это для числовых столбцов емкости загрузки и выгрузки. Затем его можно индексировать и эффективно запрашивать. С 8/10 вам нужно сканировать и анализировать каждую строку, что делает запросы медленными и подверженными ошибкам. - person Schwern; 18.06.2020