В моей статье « SQL против NoSQL: различия» я упоминал, что грань между базами данных SQL и NoSQL становится все более размытой, поскольку каждый лагерь перенимает функции другого. MySQL 5.7 Базы данных InnoDB и PostgreSQL 9.4 обе напрямую поддерживают типы документов JSON в одном поле. В этой статье мы рассмотрим реализацию JSON MySQL более подробно.
(PostgreSQL поддерживал JSON до версии 9.4, и любая база данных будет принимать документы JSON в виде одного строкового двоичного объекта. Однако MySQL и PostgreSQL теперь напрямую поддерживают проверенные данные JSON в реальных парах ключ / значение, а не в базовой строке.)
Просто потому, что вы можете хранить JSON …
… это не следует за вами.
Нормализация — это метод, используемый для оптимизации структуры базы данных. Правило «Первая нормальная форма» (1NF) определяет, что каждый столбец должен содержать одно значение, что нарушается при хранении многозначных документов JSON.
Если у вас есть четкие требования к реляционным данным, используйте соответствующие поля с одним значением. JSON следует использовать в качестве крайней меры. Поля значений JSON не могут быть проиндексированы, поэтому избегайте его использования в столбцах, которые обновляются или регулярно ищутся. Кроме того, все меньше клиентских приложений поддерживают JSON, а технология новее и, возможно, менее стабильна, чем другие типы.
Тем не менее, существуют хорошие сценарии использования JSON для малонаселенных данных или пользовательских атрибутов.
Создать таблицу с полем JSON
Рассмотрим магазин по продаже книг. Таблица книг будет иметь идентификатор, номер ISBN , название, издателя, количество страниц и другие данные, относящиеся ко всем книгам. Предположим, мы хотим добавить любое количество тегов категории в любую книгу. Мы могли бы добиться этого в SQL, используя:
- таблица тегов, в которой каждое имя тега хранится по уникальному идентификатору
- таблица тегов с записями « многие ко многим», отображающая идентификаторы книг в идентификаторы тегов
Это будет работать, но это громоздкие и значительные усилия для незначительной функции. Поэтому мы определим поле JSON тегов в таблице книг нашей базы данных MySQL:
CREATE TABLE `book` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`tags` json DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
Обратите внимание, что столбцы JSON не могут иметь значение по умолчанию, использоваться в качестве первичного ключа, ссылаться на внешний ключ или иметь индекс. Вы можете создавать вторичные индексы для сгенерированных виртуальных столбцов , но, возможно, проще сохранить индексированное значение в отдельном поле.
Добавление данных JSON
Целые документы JSON могут быть переданы в операторах INSERT или UPDATE . Например, наши теги книги могут быть переданы в виде массива:
INSERT INTO `book` (`title`, `tags`)
VALUES (
'ECMAScript 2015: A SitePoint Anthology',
'["JavaScript", "ES2015", "JSON"]'
);
JSON также может быть создан с помощью:
- Функция JSON_ARRAY (), которая создает массивы, например
-- returns [1, 2, "abc"]: SELECT JSON_ARRAY(1, 2, 'abc');
- JSON_OBJECT () функция, которая создает объекты, например
-- returns {"a": 1, "b": 2}: SELECT JSON_OBJECT('a', 1, 'b', 2);
- JSON_MERGE () функция для объединения документов, например
-- returns ["a", 1, {"key": "value"}]: SELECT JSON_MERGE('["a", 1]', '{"key": "value"}');
- или вы можете
(CAST anyValue AS JSON)
Функция JSON_TYPE () позволяет вам проверять типы значений JSON. Должен возвращать OBJECT, ARRAY или ошибку, например
-- returns ARRAY:
SELECT JSON_TYPE('[1, 2, "abc"]');
-- returns OBJECT:
SELECT JSON_TYPE('{"a": 1, "b": 2}');
-- returns an error:
SELECT JSON_TYPE('{"a": 1, "b": 2');
Точно так же функция JSON_VALID () возвращает 1, когда JSON действителен:
-- returns 1:
SELECT JSON_TYPE('[1, 2, "abc"]');
-- returns 1:
SELECT JSON_TYPE('{"a": 1, "b": 2}');
-- returns 0:
SELECT JSON_TYPE('{"a": 1, "b": 2');
Попытка вставить недопустимый документ JSON вызовет ошибку, и вся запись не будет вставлена / обновлена.
Поиск данных JSON
Функция JSON_CONTAINS () принимает искомый документ JSON и другой для сравнения. Возвращает 1, когда совпадение найдено, например
-- all books with the 'JavaScript' tag:
SELECT * FROM `book`
WHERE JSON_CONTAINS(tags, '["JavaScript"]');
Аналогичная функция JSON_SEARCH () возвращает путь к данному совпадению или NULL, если совпадения нет. Передается искомый документ JSON, 'one'
'all'
-- all books with tags starting 'Java':
SELECT * FROM `book`
WHERE JSON_SEARCH(tags, 'one', 'Java%') IS NOT NULL;
JSON Paths
Путь JSON предназначен для значений и может использоваться для извлечения или изменения частей документа. Функция JSON_EXTRACT () демонстрирует это путем извлечения одного или нескольких значений:
-- returns "SitePoint":
SELECT JSON_EXTRACT(
'{"id": 1, "website": "SitePoint"}',
'$.website'
);
Все определения пути JSON начинаются с символа $
- период, за которым следует имя, например,
$.website
-
[N]
- a
.[*]
-
[*]
- подстановочный знак
prefix**suffix
Ссылаясь на этот документ JSON:
{
"a": 1,
"b": 2,
"c": [3, 4],
"d": {
"e": 5,
"f": 6
}
}
следующие пути:
-
$.a
1
-
$.c
[3, 4]
-
$.c[1]
4
-
$.d.e
5
-
$**.e
[5]
Извлечение путей JSON в запросах
Мы можем извлечь первый тег из нашей таблицы книг, используя запрос:
SELECT
name,
tags->"$[0]" AS `tag1`
FROM `book`;
Для более сложного примера предположим, что у нас есть пользовательская таблица с данными профиля JSON, например
Я бы | имя | профиль |
---|---|---|
1 | Craig | { «Твиттер»: «@craigbuckler», «Facebook»: «craigbuckler», «Googleplus»: «craigbuckler» } |
2 | SitePoint | { «Твиттер»: «@sitepointdotcom» } |
Мы можем извлечь имя Twitter, используя путь JSON, например
SELECT
name, profile->"$.twitter" AS `twitter`
FROM `user`;
Мы также можем ссылаться на путь JSON в предложении WHERE, чтобы возвращать только пользователей с учетной записью Twitter:
SELECT
name, profile->"$.twitter" AS `twitter`
FROM `user`
WHERE
profile->"$.twitter" IS NOT NULL;
Модификация части документа JSON
Существует несколько функций MySQL для изменения частей документа JSON с использованием обозначения пути. Это включает:
-
JSON_SET(doc, path, val[, path, val]...)
вставляет или обновляет данные в документе -
JSON_INSERT(doc, path, val[, path, val]...)
вставляет данные в документ -
JSON_REPLACE(doc, path, val[, path, val]...)
заменяет данные в документе -
JSON_MERGE(doc, doc[, doc]...)
объединяет два или более документов -
JSON_ARRAY_APPEND(doc, path, val[, path, val]...)
добавляет значения в конец массива -
JSON_ARRAY_INSERT(doc, path, val[, path, val]...)
вставляет массив в документ -
JSON_REMOVE(doc, path[, path]...)
удаляет данные из документа.
Поэтому мы можем добавить тег «программирования» в любую книгу, в которой уже есть тег «JavaScript»:
UPDATE `book`
SET tags = JSON_MERGE(tags, '["technical"]')
WHERE
JSON_SEARCH(tags, 'one', 'technical') IS NULL AND
JSON_SEARCH(tags, 'one', 'JavaScript') IS NOT NULL;
Дальнейшая информация
Руководство MySQL предоставляет дополнительную информацию о типе данных JSON и связанных функциях JSON .
Опять же, я призываю вас не использовать JSON, если это не является абсолютно необходимым. Вы можете эмулировать всю документно-ориентированную базу данных NoSQL в MySQL, но это сведет на нет многие преимущества SQL , и вы также можете переключиться на настоящую систему NoSQL. Тем не менее, типы данных JSON могут сэкономить некоторые усилия для более неясных требований к данным в приложении на основе SQL.