LINUX.ORG.RU

Пара вопросов по PostgreSQL

 , ,


1

1

Имеется следущая структура базы данных:

CREATE TABLE cats (
  id BIGSERIAL PRIMARY KEY,
  parent_id BIGINT REFERENCES cats(id) ON DELETE CASCADE,
  name TEXT NOT NULL UNIQUE
);

CREATE TABLE products (
  id BIGSERIAL PRIMARY KEY,
  category BIGINT REFERENCES cats(id) ON DELETE CASCADE
);

CREATE TABLE fields (
  id BIGSERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE properties (
  product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
  field_id BIGINT NOT NULL REFERENCES fields(id) ON DELETE CASCADE,
  value TEXT NOT NULL DEFAULT '',
  
  UNIQUE (product_id, field_id)
);

Т.е. существует таблица содержащая id продукта и относящая его в определённую категорию (либо без категории если category = NULL). Категории в таблице cats имеют иерархическую структуру. Т.е. например:

- Computers (id = 1, parent_id = null)
---- Motherboards (id = 2, parent_id = 1)
---- Processors (id = 3, parent_id = 1)
-------- Intel Processors (id = 4, parent_id = 3)
-------- AMD Processors (id = 5, parent_id = 3)
-------- Other Processors (id = 6, parent_id = 3)
---- Hard Drives (id = 7, parent_id = 1)
---- Network Cards (id = 8, parent_id = 1)
- Instruments (id = 9, parent_id = null)
---- Hammers (id = 10, parent_id = 9)
---- Screwdrivers (id = 11, parent_id = 9)

Предположим некий продукт относится к категории AMD Processors (id = 5). Соответственно products.category = 5.

Вопрос 1: как подсчитать количество продуктов относящихся к категории Computers (id = 1). Т.е. количество продуктов у которых products.category может быть: 1, 2, 3, 4, 5, 6, 7, 8. Т.е. требуется подсчитать количество продуктов которые относятся либо напрямую к категории «Computers» либо к одной из подкатегорий этой категории? Понятно что «SELECT COUNT(*) FROM products WHERE products.category = 1» здесь не сработает т.к. products.category = 5 (относится к конкретной подкатегории).

Кроме того с каждым продуктом может быть ассоциировано различное количество свойств с помощью таблицы properties.

Например если имеются в таблице fields следущие записи:

(id = 1, name = 'Наименование')
(id = 2, name = 'Описание')
(id = 3, name = 'Вес')

Вопрос 2: как подсчитать количество продуктов для которых например Наименование = 'XXX' И Вес = 'YYY'? Плюс объёдинить это в один запрос с запросом из Вопроса 1?


Тебе нужны рекурсивные запросы. А лучше переработать принцип хранения дерева. Например category это строка со всеми родителями, вида '1_3_5_' для AMD. Тогда твой первый вопрос решается запросом select count(*) from products where category like '1_%', ну и второй запрос по тому же принципу.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

А лучше переработать принцип хранения дерева. Например category это строка со всеми родителями, вида ‘1_3_5_’ для AMD.

А называется это “materialized path” (чтобы было ТСу, что гуглить).

theNamelessOne ★★★★★
()

Кто-то точно тупит, либо я, либо вы. Тут же просто «или» условие, и в первом и во втором случае.

SELECT COUNT(*) FROM products WHERE products.category = 1 or products.parent_id = 1

А объединить можно через подзапросы/union - select (select count(*) from table conditions) as t1, (аналогично по второму);

Про объединение, конечно, нужно проверять.

kryonn
()
Ответ на: комментарий от Legioner

А вам не кажется что это как то нарушает принципы нормализации и всё такое. Я помнится уже как то наколхозил что то в этом роде и в итоге запутался в конец - в базе данных происходило неизвестно что, данные принимали абсурдные значения по непонятным никому причинам. Вот например что делать если администратор переносит одну подкатегорию в другую родительскую категорию. Т.е. например Processors из Computers в Instruments. Пример конечно неудачный но стоит учитывать что категории не статически определены а могут редактироваться администратором. И что в этом случае во всех записях таблицы products менять это всё. Это очень плохая идея мне кажется.

iluha16
() автор топика

Есть несколько изящных способов хранить деревья в бд. Как уже сказали выше, materialized path, еще есть nested sets, closure tree (table). Литературу могу сообщить попозже, как до компа доберусь.

aol ★★★★★
()
Последнее исправление: aol (всего исправлений: 1)
Ответ на: комментарий от iluha16

А вам не кажется что это как то нарушает принципы нормализации и всё такое.

нарушает, но деревянные (рекурсивные) запросы то ещё удовольствие. то есть либо ты гемороишься со структурой, либо гемороишься с запросами. на халяву тут не отползешь.

vtVitus ★★★★★
()
Ответ на: комментарий от iluha16

Да, не обратил внимание. Тогда нужно дописать inner join на cats

kryonn
()
Ответ на: комментарий от iluha16

А вам не кажется что это как то нарушает принципы нормализации и всё такое.

Конечно нарушает. Любая реальная БД нарушает их много раз. Например любое NULL поле нарушает принципы нормализации :)

Я помнится уже как то наколхозил что то в этом роде и в итоге запутался в конец - в базе данных происходило неизвестно что, данные принимали абсурдные значения по непонятным никому причинам.

Это и с нормализацией может произойти.

Вот например что делать если администратор переносит одну подкатегорию в другую родительскую категорию. Т.е. например Processors из Computers в Instruments.

Нужно написать код, который будет обновлять все эти строки. Это несложно. При желании можно сделать даже триггером.

Пример конечно неудачный но стоит учитывать что категории не статически определены а могут редактироваться администратором. И что в этом случае во всех записях таблицы products менять это всё.

Категории меняются администратором раз в год, а пользователи их запрашивают 10 раз в секунду.

Это очень плохая идея мне кажется.

Нормальная идея, со своими плюсами и минусами.

Legioner ★★★★★
()

В ruby on rails есть счетчики, их можно реализовать и через триггеры. То есть у категорий добавить поле products_count и при добавлении товара его итерировать, а потом по цепочке наверх у категории вычислять его как сумму products_count у подкатегорий. Вполне несложный и продуктивный вариант.

Romaboy
()
Ответ на: комментарий от Legioner

Тебе нужны рекурсивные запросы. А лучше переработать принцип хранения дерева.

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

exception13 ★★★★★
()
Ответ на: комментарий от Romaboy

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

Romaboy
()

Это же постгря, что ты как мускуль-лошок. Категогии хранить в массиве с gin индексом, опции в jsonb. И нахрен реляционную чистоту. Инженеры решают задачи, а не трахаются с предикатами.

A1
()
Ответ на: комментарий от A1

Рассмотрю все варианты. Однако надо всё в меру, у меня просто уже был горький опыт когда «дорешался» дальше некуда забив на нормализацию.

iluha16
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.