Довільно упорядковуючи записи в таблиці


28

Загальна потреба при використанні бази даних - це доступ до записів по порядку. Наприклад, якщо у мене є блог, я хочу мати змогу змінити порядок своїх публікацій в довільному порядку. Ці записи часто мають багато стосунків, тому, здається, реляційна база даних має сенс.

Поширене рішення, яке я бачив, - це додати цілий стовпець order:

CREATE TABLE AS your_table (id, title, sort_order)
AS VALUES
  (0, 'Lorem ipsum',   3),
  (1, 'Dolor sit',     2),
  (2, 'Amet, consect', 0),
  (3, 'Elit fusce',    1);

Тоді, ми можемо сортувати рядки за тим, orderщоб відредагувати їх у відповідному порядку.

Однак це здається незграбним:

  • Якщо я хочу перенести запис 0 на початок, я повинен переупорядкувати кожен запис
  • Якщо я хочу вставити новий запис в середину, я повинен переупорядкувати кожен запис після нього
  • Якщо я хочу видалити запис, мені доведеться переупорядкувати кожен запис після нього

Легко уявити такі ситуації, як:

  • Два записи однакові order
  • orderМіж записами є прогалини

Це може статися досить легко з кількох причин.

Це такий підхід, який застосовують такі програми, як Joomla:

Приклад підходу Джомли до замовлення

Ви можете стверджувати, що інтерфейс тут поганий, і що замість того, щоб люди безпосередньо редагували номери, вони повинні використовувати стрілки або перетягування - і ви, мабуть, матимете рацію. Але за лаштунками відбувається те саме.

Деякі люди пропонують використовувати десятковий знак для зберігання замовлень, так що ви можете використовувати "2.5", щоб вставити запис між записами в порядку 2 та 3. І хоча це трохи допомагає, це, мабуть, ще швидше, тому що ви можете закінчити дивні десятичні знаки (де ви зупиняєтесь? 2,75? 2,875? 2,8125?)

Чи є кращий спосіб зберігати замовлення в таблиці?


5
Просто щоб ти знав . . . «Причина , такі системи називаються" реляційними "є те , що термін відношення в основному тільки математичний термін для таблиці ...» - Вступ до систем баз даних , CJ Date, 7-е видання. p 25
Майк Шеррілл 'Згадка про котів'


@ MikeSherrill'CatRecall ', що я не спіймав, я вирішив питання зі старим ordersі ddl.
Еван Керролл

Відповіді:


17

Якщо я хочу перенести запис 0 на початок, я повинен переупорядкувати кожен запис

Ні, є простіший спосіб.

update your_table
set order = -1 
where id = 0;

Якщо я хочу вставити новий запис в середину, я повинен переупорядкувати кожен запис після нього

Це вірно, якщо ви не використовуєте тип даних, який підтримує значення "між". Типи float та numeric дозволяють оновити значення, скажімо, до 2,5. Але і varchar (n) працює. (Подумайте "a", "b", "c"; тоді подумайте "ba", "bb", "bc".)

Якщо я хочу видалити запис, мені доведеться переупорядкувати кожен запис після нього

Ні, є простіший спосіб. Просто видаліть рядок. Решта рядків все одно будуть правильно сортовані.

Легко уявити такі ситуації, як:

Два записи мають однаковий порядок

Унікальне обмеження може запобігти цьому.

Між записами є пробіли в порядку

Пропуски не впливають на те, як dbms сортує значення в стовпці.

Деякі люди пропонують використовувати десятковий знак для зберігання замовлень, так що ви можете використовувати "2.5", щоб вставити запис між записами в порядку 2 та 3. І хоча це трохи допомагає, це, мабуть, ще швидше, тому що ви можете закінчити дивні десятичні знаки (де ви зупиняєтесь? 2,75? 2,875? 2,8125?)

Ти не зупинишся, поки не доведеться . Dbms не має проблем зі сортуванням значень, які мають 2, 7 або 15 знаків після десяткової крапки.

Я думаю, що ваша реальна проблема полягає в тому, що ви хочете бачити значення в упорядкованому порядку як цілі числа. Ви можете це зробити.

create table your_table (
  id int primary key, 
  title varchar(13), 
  sort_order float
);

insert into your_table values
(0, 'Lorem ipsum', 2.0),
(1, 'Dolor sit', 1.5),
(2, 'Amet, consect', 0.0),
(3, 'Elit fusce', 1.0);

-- This windowing function will "transform" the floats into sorted integers.
select id, title,
       row_number() over (order by sort_order)
from your_table

Заради акуратності ви могли закінчити роботу чимось на кшталтwith cte as (select *,row_number() over (order by sort_order desc) as row from test) update cte set sort_order=row;
Манго

Ось додатковий натяк: Якщо ви хочете, щоб він був справді ідеальним, вам слід перевірити, чи рухаєтесь ви більше рядків, а потім хочете залишатися недоторканими. Якщо це так, то оновлювати менш численний - «незайманий» - ті, D
Рубен Boeck

7

Це дуже просто. Потрібно мати структуру "діри кардинальності":

Потрібно мати 2 стовпці:

  1. pk = 32bit integer
  2. замовлення = 64 біт bigint( не double )

Вставити / оновити

  1. Під час вставки першого нового запису встановіть order = round(max_bigint / 2).
  2. Вставляючи на початку таблиці, встановіть order = round("order of first record" / 2)
  3. Під час вставлення в кінці таблиці встановіть order = round("max_bigint - order of last record" / 2) 4) Під час вставлення в середину встановітьorder = round("order of record before - order of record after" / 2)

Цей метод має дуже велику кардинальність. Якщо у вас є помилка обмеження або ви думаєте, що у вас є невелика кардинальність, ви можете відновити стовпець замовлення (нормалізувати).

У максимальній ситуації з нормалізацією (з цією структурою) ви можете мати "кардинальну дірку" в 32 біт.

Пам’ятайте, що не використовуйте типи з плаваючою комою - замовлення має бути точним значенням!


4

Як правило, замовлення виконується відповідно до певної інформації в записах, заголовку, посвідченні особи або будь-якої відповідної для конкретної ситуації.

Якщо вам потрібно спеціальне замовлення, використання цілого стовпця не так вже й погано, як може здатися. Наприклад, щоб звільнити місце для запису на 5 місце, ви можете зробити щось на кшталт:

update table_1 set place = place + 1 where place > 5.

Сподіваємось, ви можете оголосити стовпець як такий uniqueі, можливо, мати процедуру перестановки "атомної". Деталі залежать від системи, але це загальна ідея.


4

... це, мабуть, ще більш суттєво, тому що ви можете закінчитись дивними десятковими знаками (де ви зупинитесь? 2,75? 2,875? 2,8125?)

Хто дбає? Ці цифри є лише для того, щоб комп’ютер мав справу, тому не має значення, скільки у них дробових цифр або як некрасиво вони здаються нам.

Використання знаків десяткових значень означає, що для переміщення елемента F між елементами J і K все, що вам потрібно зробити, це вибрати значення порядку для J і K, потім середнє значення їх, а потім оновити F. Два оператори SELECT і один оператор UPDATE (можливо, це робиться з використанням серіалізаційної ізоляції, щоб уникнути тупики).

Якщо ви хочете бачити цілі числа, а не дроби у висновку, то або обчисліть цілі числа в клієнтській програмі, або використовуйте функції ROW_NUMBER () або RANK () (якщо ваша RDBMS включає їх).


1

У своєму власному проекті я планую спробувати рішення, подібне до рішення десяткового числа, але натомість використовую байтові масиви:

def pad(x, x_len, length):
    if x_len >= length:
        return x
    else:
        for _ in range(length - x_len):
            x += b"\x00"
        return x

def order_index(_from, _to, count, length=None):
    assert _from != _to
    assert _from < _to

    if not length:
        from_len = len(_from)
        to_len = len(_to)
        length = max(from_len, to_len)

        _from = pad(_from, from_len, length)
        _to = pad(_to, to_len, length)

    from_int = int.from_bytes(_from, "big")
    to_int = int.from_bytes(_to, "big")
    inc = (to_int - from_int)//(count + 1)
    if not inc:
        length += 1
        _from += b"\x00"
        _to += b"\x00"
        return order_index(_from, _to, count, length)

    return (int.to_bytes(from_int + ((x+1)*inc), length, "big") for x in range(count))
>>> index = order_index(b"A", b"Z", 24)
>>> [x for x in index]
[b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y']
>>> 
>>> index = order_index(b"A", b"Z", 25)
>>> [x for x in index]
[b'A\xf6', b'B\xec', b'C\xe2', b'D\xd8', b'E\xce', b'F\xc4', b'G\xba', b'H\xb0', b'I\xa6', b'J\x9c', b'K\x92', b'L\x88', b'M~', b'Nt', b'Oj', b'P`', b'QV', b'RL', b'SB', b'T8', b'U.', b'V$', b'W\x1a', b'X\x10', b'Y\x06']

Ідея полягає в тому, що ви ніколи не можете закінчити можливі значення між проміжками, тому що ви просто додаєте а b"\x00"до відповідних записів, якщо вам потрібно більше значень. ( intне обмежено в Python 3, інакше вам доведеться вибрати фрагмент байтів наприкінці для порівняння; припущення полягає в тому, що між двома сусідніми значеннями різниці будуть запаковані до кінця.)

Наприклад, у вас є два записи, b"\x00"і b"\x01", і ви хочете запис йти між ними. Немає доступних значень між 0x00і 0x01, тому ви додаєте b"\x00"обидва, і тепер у вас є купа значень, які ви можете використовувати для вставки нових значень.

>>> records = [b"\x00", b"\x01", b"\x02"]
>>> values = [x for x in order_index(records[0], records[1], 3)]
>>> records = records + values
>>> records.sort()
>>> records
[b'\x00', b'\x00@', b'\x00\x80', b'\x00\xc0', b'\x01', b'\x02']

База даних може легко сортувати її, оскільки все закінчується в лексикографічному порядку. Якщо ви видалите запис, він все ще в порядку. У моєму проекті я створив b"\x00"і b"\xff"як, FIRSTі LASTзаписи, однак для того, щоб використовувати ці значення як віртуальні значення "від" і "до", щоб додати / додати нові записи:

>>> records = []
>>> value = next(order_index(FIRST, LAST, 1))
>>> value
b'\x7f'
>>> records.append(value)
>>> value = next(order_index(records[0], LAST, 1))
>>> value
b'\xbf'
>>> records.append(value)
>>> records.sort()
>>> records
[b'\x7f', b'\xbf']
>>> value = next(order_index(FIRST, records[0], 1))
>>> value
b'?'
>>> records.append(value)
>>> records.sort()
>>> records
[b'?', b'\x7f', b'\xbf']

0

Цю відповідь я знайшов набагато краще. Цитую це повністю:

Бази даних оптимізовані для певних речей. Швидке оновлення багатьох рядків - одна з них. Це стає особливо актуальним, коли ви дозволяєте базі даних робити свою роботу.

Поміркуйте:

order song
1     Happy Birthday
2     Beat It
3     Never Gonna Give You Up
4     Safety Dance
5     Imperial March

І ви хочете перейти Beat Itдо кінця, у вас було б два запити:

update table 
  set order = order - 1
  where order >= 2 and order <= 5;

update table
  set order = 5
  where song = 'Beat It'

І це все. Це масштабується дуже добре з дуже великою кількістю. Спробуйте помістити кілька тисяч пісень у гіпотетичний список відтворення у вашій базі даних і подивіться, скільки часу потрібно для переміщення пісні з одного місця в інше. Оскільки вони мають дуже стандартизовані форми:

update table 
  set order = order - 1
  where order >= ? and order <= ?;

update table
  set order = ?
  where song = ?

У вас є дві підготовлені заяви, які ви можете дуже ефективно використовувати.

Це дає деякі істотні переваги - порядок таблиці - це те, на що ви можете міркувати. Третя пісня завжди має order3, завжди. Єдиний спосіб гарантувати це - використання послідовних цілих чисел як порядку. Використання псевдозв’язаних списків або десяткових чисел чи цілих чисел із пробілами не дасть вам гарантії цього властивості; у цих випадках єдиний спосіб отримати n-ту пісню - сортувати всю таблицю та отримати n-й запис.

І справді, це набагато простіше, ніж ти думаєш, що це є. Зрозуміти, що ви хочете зробити, просто зрозуміти, що згенерувати два заяви оновлення, а іншим людям подивитися на ці дві заяви оновлення та зрозуміти, що робиться.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.