Как реализовать приоритеты в SQL (postgres)
Я пишу некоторое программное обеспечение, которое требует хранения элементов в базе данных, элементы должны иметь "приоритет", поэтому мы в конечном итоге
ID | Name | Priority
--------+--------------+----------
1 | Pear | 4
2 | Apple | 2
3 | Orange | 1
4 | Banana | 3
Так что теперь, приоритетным фруктом является апельсин, потом яблоко, потом банан, потом груша.
Теперь я хочу сделать грушу приоритетом номер один, таким как груша, апельсин, яблоко, банан. Таблица будет выглядеть так:
ID | Name | Priority
--------+--------------+----------
1 | Pear | 1
2 | Apple | 3
3 | Orange | 2
4 | Banana | 4
Какой лучший способ достичь этого с PHP и Postgres. Учитывая, что в таблице не будет больше 12-13 пунктов, я подумал о ВЫБОРЕ всей таблицы и переписывании Приоритетов перед ОБНОВЛЕНИЕМ всего назад.
* Важный *
Приоритеты могут быть изменены в любом порядке, поэтому приоритет 7 можно установить на приоритет 3 (таким образом, перемещая все ниже приоритета 3 на ступеньку ниже), и нам нужно закрыть пробел, оставленный элементом с приоритетом 7, который был перемещен в приоритет 3 в списке приоритетов.
2 ответа
Хорошо, вот моя попытка сохранить приоритеты уникальными и последовательными. Реализуется с помощью триггера + функция. Сложная задача - избежать бесконечной рекурсии, которая может возникнуть в результате обновлений внутри триггера. Это решается с помощью флага грязи / цвета, который должен быть размещен внутри стола. Его значение не важно; только изменение этого.
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE fruits
( id INTEGER NOT NULL PRIMARY KEY
, zname varchar NOT NULL
, priority INTEGER NOT NULL
, flipflag boolean NOT NULL default false
, CONSTRAINT unique_priority UNIQUE (priority) DEFERRABLE INITIALLY DEFERRED
);
INSERT INTO fruits(id,zname,priority) VALUES
(1 , 'Pear' ,4)
,(2 , 'Apple' ,2)
,(3 , 'Orange' ,1)
,(4 , 'Banana' ,3)
;
CREATE function shift_priority()
RETURNS TRIGGER AS $body$
BEGIN
UPDATE fruits fr
SET priority = priority +1
, flipflag = NOT flipflag -- alternating bit protocol ;-)
WHERE NEW.priority < OLD.priority
AND OLD.flipflag = NEW.flipflag -- redundant condition
AND fr.priority >= NEW.priority
AND fr.priority < OLD.priority
AND fr.id <> NEW.id -- exlude the initiating row
;
UPDATE fruits fr
SET priority = priority -1
, flipflag = NOT flipflag
WHERE NEW.priority > OLD.priority
AND OLD.flipflag = NEW.flipflag
AND fr.priority <= NEW.priority
AND fr.priority > OLD.priority
AND fr.id <> NEW.id
;
RETURN NEW;
END;
$body$
language plpgsql;
CREATE TRIGGER shift_priority
AFTER UPDATE OF priority ON fruits
FOR EACH ROW
WHEN (OLD.flipflag = NEW.flipflag AND OLD.priority <> NEW.priority)
EXECUTE PROCEDURE shift_priority()
;
UPDATE fruits
SET priority = 1
WHERE id=1;
РЕЗУЛЬТАТЫ:
SELECT * FROM fruits ORDER BY id;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table tmp.fruits
drop cascades to function tmp.shift_priority()
DROP SCHEMA
CREATE SCHEMA
SET
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "fruits_pkey" for table "fruits"
NOTICE: CREATE TABLE / UNIQUE will create implicit index "unique_priority" for table "fruits"
CREATE TABLE
INSERT 0 4
CREATE FUNCTION
CREATE TRIGGER
UPDATE 1
id | zname | priority | flipflag
----+--------+----------+----------
1 | Pear | 1 | f
2 | Apple | 3 | t
3 | Orange | 2 | t
4 | Banana | 4 | t
(4 rows)
Два обновления в одной транзакции должны нормально работать на такой маленькой таблице.
create temp table priorities (
id integer primary key,
name varchar(15) not null,
priority integer not null check (priority > 0 and priority < 100)
);
insert into priorities values
(1,'Pear',4),
(2,'Apple',2),
(3,'Orange',1),
(4,'Banana',3);
-- Make Pear priority 1.
begin;
update priorities
set priority = priority + 1
-- The value below is the priority you're aiming for. You want
-- Pear to be #1, so you use ">= 1".
where priority >= 1;
update priorities
set priority = 1 where name = 'Pear';
commit;
Для удобства вы можете обернуть это в хранимую процедуру.