LINUX.ORG.RU

Обновление порядка элементов в таблице с помощью SQL

 ,


0

1

Допустим, есть вот такая табличка (для простоты опустим индексы и т. п.):

CREATE TABLE items (
    id BIGSERIAL PRIMARY KEY,
    category_id BIGINT NOT NULL,
    name VARCHAR NOT NULL,
    position INT NOT NULL
);
Для отображения юзеру элементы из неё выбираются вот таким запросом:
SELECT name FROM items WHERE category_id = ? ORDER BY position
То есть порядок элементов внутри категории жёстко задан с помощью поля position (можно, кстати, навесить уникальный индекс на category_id + position, чтобы быть уверенным в детерменизме выборки).

Встаёт вопрос как дать пользователю возможность менять порядок элементов. Допустим, на фронте реализован интерфейс с drag'n'drop и кнопкой «Сохранить изменения», при нажатию на которую бек получает category_id и список id всех элементов этой категории в новом порядке. Теперь беку нужно обновить значения полей position в БД (элементов в каждой категории не очень много, так что мне кажется допустимым перезаписать position у всех элементов в заданной категории).

Я пока вижу тут только решение в лоб с отдельным UPDATE для каждой записи, а значения position вычислять в коде (банальный цикл for по индексу в массиве id c фронта и использовать счётчик цикла). Но мне кажется такой подход не очень эффективным, так как будет N запросов, к тому же он плохо уживается с уникальным индексом на category_id + position (либо такой индекс надо убрать, либо использовать deferred индексы и т. п.).

Есть ли какие-то решения лучше?

P. S.: СУБД PostgreSQL

★★★★★

Последнее исправление: KivApple (всего исправлений: 4)

Решение в лоб. Делай разреженные позиции (100, 200 и т.д.). Все запросы на обновление позиции — в одной транзакции.

filosofia
()
Последнее исправление: filosofia (всего исправлений: 1)

Передаёшь в запрос массив ID страниц в нужном порядке (прям как у тебя приходит из фронта) и используешь в запросе unnest ... with ordinality:

update pages
   set position = new_ordering.position
  from unnest(ARRAY[3, 2, 1]) -- массив ID в нужном порядке
       with ordinality as new_ordering(id, position)
 where pages.id = new_ordering.id;

https://www.db-fiddle.com/f/bXP4vjCyA5AqzAUHiTiWeD/3

theNamelessOne ★★★★★
()

Не выпендривайся и пиши как проще. В SQL есть куча способов решить твою проблему, но все они избыточно сложные и непонятные. Сделать 50 апдейтов это миллисекунды. Что там у тебя драгндропать целыми днями будут. Это же редкая операция. Не переусложняй.

Разумный компромисс это изначально ставить с 128 промежутком (например), тогда драгндроп станет O(1), но будет случай, когда задрагндропали до талого и надо пересчитывать все позиции, всё равно этот код придётся писать.

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

Можно ещё использовать рациональные дроби (пример можно глянуть в статье https://begriffs.com/posts/2018-03-20-user-defined-order.html, Approach 3, а также в Postgres Wiki).

Тут стоит отметить, что такая схема не очень подходит под ситуацию, когда надо «перенумеровывать» все страницы внутри категории каждый раз, когда с фронта переходит запрос (e.g., bulk reordering); она подходит для операций типа «вставить новую страницу после страницы А» или «поменять местами страницы А и Б».

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

но все они избыточно сложные и непонятные

Если всё твоё знакомство с SQL заканчивается на ORM — безусловно.

Но соглашусь, что в большинстве юз-кейсов и вариант с 50 запросами в одной транзакции будет работать нормально. А вот уже если будет тормозить, тогда надо искать другие варианты.

theNamelessOne ★★★★★
()

решение в лоб с отдельным UPDATE для каждой записи, а значения position вычислять в коде

А просто сделать position = position + 1 для всех элементов после заданного? На самом деле чуть сложнее, с учётом перемещений в большую/меньшую сторону и отдельных случаев (перемещение в начало/конец), но принцип ты понял.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

Ты пишешь код не для себя. Код надо писать максимально просто и понятно. Делать по-другому это непрофессионализм. Есть случаи, когда нужен сложный код. Но вряд ли этот случай тут.

А ORM это очень сложно. Я пока не видел человека, который разбирается в том же Hibernate хотя бы на моём уровне, а я в нём разбираюсь плохо. SQL в тыщу раз проще ORM.

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

Спасибо, воспользовался этим решением.

KivApple ★★★★★
() автор топика

Но мне кажется такой подход не очень эффективным, так как будет N запросов

Делай несколько апдейтов пакетом (batch) будет у тебя тогда 1 сетевой запрос.

же он плохо уживается с уникальным индексом на category_id + position

Зачем тебе такой индекс? Если у тебя будут появляться одинаковые значения в position - это не смертельно.

Есть ли какие-то решения лучше?

Такая схема - самое лучшее решение, т.к. содержит необходимый минимум атрибутов. Любое дублирование сущностей/аттрибутов где-то - это головняк с консистентностью.

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