Привет, Хабр!

Думаю, вы знаете, что поиск эффективных решений – это половина успеха. Я сам прошел через все эти тернии, когда работа с данными казалась слишком сложной и запутанной. И именно тогда я открыл для себя некоторые возможности PostgreSQL, которые значительно упростили мою жизнь.

Сегодня я хочу поговорить о трех фичах PostgreSQL, которые помогут сделать работу более продуктивной и вдохновить меня на создание более сложных и интересных проектов.

Эти фичи уже не раз выручали меня в сложных проектах, и я уверен, что они станут надежными помощниками и в вашей разработке.

Фича №1: Массивы и работа с JSON

PostgreSQL выделяется среди реляционных баз данных благодаря поддержке массивов и JSON-форматов. Эта функциональность позволяет хранить и манипулировать сложными структурами данных без необходимости использования дополнительных таблиц.

Массивы в PostgreSQL позволяют хранить несколько значений одного типа данных в одной ячейке таблицы.

Создание таблицы с массивами:

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    tags TEXT[] -- массив текстовых значений для тегов
);

Вставка данных в таблицу с массивами:

INSERT INTO products (name, tags)
VALUES ('Продукт 1', ARRAY['новинка', 'распродажа']),
       ('Продукт 2', ARRAY['популярное', 'скидка']);

Извлечение данных из массива:

-- найти все продукты, содержащие тег 'новинка'
SELECT * FROM products
WHERE 'новинка' = ANY(tags);

JSON предоставляет возможность хранения и манипуляции полуструктурированными данными.

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

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    profile JSONB -- бинарное представление JSON
);

Вставка JSON-данных:

INSERT INTO users (name, profile)
VALUES ('Иван Иванов', '{"age": 30, "city": "Москва", "interests": ["футбол", "чтение"]}'),
       ('Мария Смирнова', '{"age": 25, "city": "Санкт-Петербург", "interests": ["музыка", "путешествия"]}');

Извлечение данных из JSON:

-- извлечь возраст и город пользователя
SELECT 
    name,
    profile->>'age' AS age,
    profile->>'city' AS city
FROM users;

-- Найти пользователей с интересом "музыка"
SELECT * FROM users
WHERE 'музыка' = ANY(profile->'interests');

Где использовать?

  • Хранение списков предпочтений, например избранные продукты или метки.

  • Хранение ответов API.

  • Хранение агрегированных данных, таких как статистика и аналитика, в формате JSON для простоты обработки.

Допустим, есть приложение для соц. сети. Каждый пользователь имеет профиль, который может включать различные атрибуты: имя, возраст, город и интересы. Используя массивы и JSON в PostgreSQL, можно хранить и извлекать эти данные:

-- создание таблицы
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    username VARCHAR(100),
    attributes JSONB,
    tags TEXT[]
);

-- вставка данных
INSERT INTO user_profiles (username, attributes, tags)
VALUES ('user1', '{"age": 28, "location": "NY", "preferences": {"newsletter": true}}', ARRAY['active', 'premium']),
       ('user2', '{"age": 34, "location": "LA", "preferences": {"newsletter": false}}', ARRAY['inactive']);

-- запрос для извлечения данных
SELECT 
    username,
    attributes->>'age' AS age,
    attributes->>'location' AS location
FROM user_profiles
WHERE 'active' = ANY(tags);

Фича №2: Расширения

Расширения в PostgreSQL – это способ добавить дополнительные возможности и функции в базу данных. С помощью их можно расширить функциональность без необходимости вносить изменения в ядро самой БД. П

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

Усановка и использование расширения pg_trgm:

-- установка расширения
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- создание индекса для полнотекстового поиска
CREATE INDEX trgm_idx ON articles USING gin (content gin_trgm_ops);

-- поиск похожих записей
SELECT * FROM articles
WHERE content % 'поиск';

Предположим, что есть база данных статей или блога, и хочется добавить возможность поиска по содержимому:

-- установка расширения
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- создание таблицы статей
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255),
    content TEXT
);

-- вставка данных
INSERT INTO articles (title, content)
VALUES ('Статья 1', 'Это пример текста для полнотекстового поиска.'),
       ('Статья 2', 'Поиск похожих текстов в базе данных.');

-- создание индекса
CREATE INDEX content_trgm_idx ON articles USING gin (content gin_trgm_ops);

-- поиск статьи с использованием триграммного поиска
SELECT * FROM articles
WHERE content % 'поиск';

Установка расширения PostGIS:

-- установка PostGIS
CREATE EXTENSION IF NOT EXISTS postgis;

-- создание таблицы с географическими данными
CREATE TABLE locations (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    coordinates GEOGRAPHY(POINT)
);

-- вставка данных
INSERT INTO locations (name, coordinates)
VALUES ('Place 1', ST_GeographyFromText('SRID=4326;POINT(-122.4194 37.7749)')),
       ('Place 2', ST_GeographyFromText('SRID=4326;POINT(-118.2437 34.0522)'));

Со списком расширений можно ознакомиться здесь.

Фича №3: CTE и рекурсивные запросы

Общие табличные выражения и рекурсивные запросы в PostgreSQL дают возможность упрощать и организовывать сложные SQL-запросы.

Преимущества:

  • CTE позволяет разбить сложные запросы на более простые и понятные части.

  • Возможность создавать временные результирующие наборы данных, которые могут использоваться в основном запросе.

  • Позволяет сократить повторяющийся код и улучшить производительность за счет разбивки операций на подзапросы.

Пример использования CTE для разбиения сложных запросов:

WITH top_products AS (
    SELECT id, name, sales
    FROM products
    WHERE sales > 1000
),
top_customers AS (
    SELECT id, name, purchases
    FROM customers
    WHERE purchases > 500
)
SELECT tp.name AS product_name, tc.name AS customer_name
FROM top_products tp
JOIN top_customers tc ON tp.id = tc.id;

Рекурсивные запросы позволяют работать с иерархическими структурами, например такими, как категории продуктов или организационная структура.

Рекурсивный запрос для создания иерархии категорий:

WITH RECURSIVE category_hierarchy AS (
    SELECT id, name, parent_id
    FROM categories
    WHERE parent_id IS NULL
    UNION ALL


    SELECT c.id, c.name, c.parent_id
    FROM categories c
    INNER JOIN category_hierarchy ch ON c.parent_id = ch.id
)
SELECT * FROM category_hierarchy;

Рассмотрим пример создания иерархической структуры для компании, где каждый сотрудник может иметь подчиненных:

-- создание таблицы сотрудников
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    manager_id INT REFERENCES employees(id)
);

-- вставка данных
INSERT INTO employees (name, manager_id)
VALUES ('CEO', NULL),
       ('Manager 1', 1),
       ('Manager 2', 1),
       ('Employee 1', 2),
       ('Employee 2', 2),
       ('Employee 3', 3);

-- рекурсивный запрос для иерархии сотрудников
WITH RECURSIVE employee_hierarchy AS (
    SELECT id, name, manager_id
    FROM employees
    WHERE manager_id IS NULL
    UNION ALL
    SELECT e.id, e.name, e.manager_id
    FROM employees e
    INNER JOIN employee_hierarchy eh ON e.manager_id = eh.id
)
SELECT * FROM employee_hierarchy;

Заключение

Надеюсь, данная статья поможет вам углубить свои знания PostgreSQL и вдохновит на использование его возможностей в своих проектах.

А какие фичи знаете вы?


Приходите на ближайшие открытые уроки, посвященные базам данных:

  • 15 августа: «Отказоустойчивый кластер PostgreSQL». Поговорим про доступность, чем она достигается и как связана с построением архитектуры. Рассмотрим реализацию failover в PostgreSQL и настроим кластер, чтобы падение одной из нод нашей системы не приводило к сбоям всей системы в целом. Запись по ссылке

  • 21 августа: «Кластерные возможности MongoDB». Разберем варианты репликации MongoDB, как шардировать кластер MongoDB и как выбрать ключ шардирования. Запись по ссылке

Комментарии (12)


  1. longclaps
    14.08.2024 11:35
    +7

    И именно тогда я открыл для себя потрясающие возможности PostgreSQL, которые значительно мою жизнь.

    Куда спешим? Притормозите, ведь жизнь уже значительно!


  1. noker81
    14.08.2024 11:35

    Для меня большим мучением до сих пор является группировка по полю в выводом всех полей. Как это работает не совсем понятно.


    1. Akuma
      14.08.2024 11:35

      Поддерживаю. Максимально дебильное решение из-за которого группировку по возможности не использую.


      1. bzq
        14.08.2024 11:35

        В постресе можно писать:

        ... group by 1, 2, 3

        где числами заданы номера выводимых столбцов. Удобно.


        1. vkrasikov
          14.08.2024 11:35

          И следом:

          order by 1, 2, 3


    1. kylemaclohlan
      14.08.2024 11:35

      Вы немного не до конца описали задачу, поэтому не может быть однозначного ответа. Если пытаться обобщить, то когда вы группируете по какому то полю, то дальше сами решаете что именно сделать с остальными полями. Функции которые этим занимаются, так и называются групповые. Они могут делать разные вещи суммировать/конкатенировать/выбирать максимальное/выбирать первое попавшееся/ собирать массив и тд и тп и др


  1. Sipaha
    14.08.2024 11:35
    +1

    Я бы с радостью посмотрел на компанию, которая дает новичкам добавлять расширения в базу и разрешает им писать рекурсивные запросы на чистом SQL.


  1. ermadmi78
    14.08.2024 11:35

    Рекурсивные запросы позволяют работать с иерархическими структурами, например такими, как категории продуктов или организационная структура.

    Если говорить о работе с иерархическими структурами, то мне кажется, что стоит упомянуть тип данных ltree. На мой вкус это не такой громоздкий способ, как рекурсивные запросы, но он требует предварительной подготовки данных.


  1. VladimirFarshatov
    14.08.2024 11:35

    Положим третий пункт есиь и в мариидб, а с массивами покажите выборку набора юзверей у которых данное предпочтение на третьем месте массива..


  1. DmitryKuzmenko
    14.08.2024 11:35

    Если полнотекстовый поиск и CTE - вполне нормальные фичи, то вот за хранение атрибутов как указано в примерах первого пункта (JSON), я бы просто порол.
    Эти атрибуты должны быть в таблице пользователей или связанных таблицах. Т.е. нужно нормально проектировать структуру БД, а не втыкать туда JSON в невообразимом виде, а потом мучиться с тормозными запросами.
    В общем, крайне плохой пример в отношении JSON, особенно "для начинающих".


  1. qss53770
    14.08.2024 11:35

    Хахаах ну и фичи, стандартный синтаксис выдаем за фичи, знаете фичу на питоне как выводит текст? print('Hello world')

    - фича?

    - фича.


  1. xaphoon777
    14.08.2024 11:35

    Никого не смутил запрос про джойн продуктов и покупателей по айдишнику?