Список реализаций поверх SQL

Мне нужно реализовать абстракцию списка поверх базы данных SQL. Эта абстракция списка должна поддерживать операции добавления, удаления и вставки.

Я планирую реализовать таблицу «ключ-значение», в которой ключ содержит информацию о сортировке / порядке, а значение содержит значения списка. Чтобы добавить элементы вверху, мне просто нужно создать ключ, который упорядочивает / сортирует над предыдущим верхом. Чтобы вставить элемент, мне просто нужно создать ключ, который упорядочивает / сортирует между двумя записями в позиции вставки. При такой схеме мне не нужно перебалансировать таблицу после того, как я вставлю элемент посередине.

Одна из возможных наивных реализаций - использовать дробные числа для ключей. Чтобы вставить значение между двумя элементами, я просто вычисляю среднее значение ключей для двух элементов и использую его как ключ для вставленного значения. Большинство БД имеют конечную точность для чисел. Чтобы преодолеть это, мы можем преобразовать число в строку и использовать различные библиотеки произвольной точности для вычисления среднего. По этой схеме ключ увеличивается на 1 цифру pr insert. См. Пример реализации ниже

function between(prev_key:number,next_key:number):number {
  var insert_key = (prev_key + next_key) / 2.0;
  assert(insert_key > prev_key && insert_key < next_key);
  return insert_key;
}

Увеличение ключа на 1 цифру для каждой вставленной строки не очень масштабируемо. Я экспериментировал с различными схемами ключей и «промежуточными» реализациями. Но у большинства / всех есть проблемы с быстро растущей длиной ключей на вставке. Мое чутье подсказывает мне, что для этого есть оптимальная стратегия, но решение ускользает от меня.

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

Любые идеи?

Что ж, одна очевидная оптимизация длины, которую вы можете сделать, - это когда вы в среднем пытаетесь округлить ее и посмотреть, работает ли она по-прежнему. т.е. когда среднее значение 1 и 1,5, вы получаете 1,25. Округлите это до 1,3, и это все еще работает. Ключ не растет. На следующем шаге, сделав 1.5 и 1.3, вы получите 1.4. Или 1 и 1,3, вы получите 1,15, а округляя его, вы получите приемлемый 1,2. Только когда вы смотрите между 1,2 и 1,3, вам понадобится ключ, чтобы действительно вырасти в длину. Возможно, это все еще не идеально, но это, по крайней мере, лучше, чем каждый раз увеличивать ключ на 1 цифру ...

Chris 06.08.2018 20:03

Насколько я понимаю, это уменьшит проблему, но не решит ее. Сложность размера ключа по-прежнему будет O (N) относительно размера списка, то есть 1M строк будет генерировать длину ключа в диапазоне 100k. Я ищу что-то близкое к решению O (log N) для размера ключа :)

frelars 06.08.2018 20:31
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
1
2
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

Было бы проще использовать структуру данных, напоминающую список, т.е. где каждая запись имеет указатели на следующий и предыдущий узел. В SQL указатели - это внешние ключи. В списке в памяти идентификатором узла будет его адрес, который является случайным, поэтому в SQL вы можете просто использовать автоматически увеличивающийся PK:

CREATE TABLE List (
    ID   INTEGER PRIMARY KEY,
    Prev INTEGER REFERENCES List,
    Next INTEGER REFERENCES List,
    Value
);

Чтобы вставить или удалить, вы должны настроить значения Prev / Next в самом узле и его соседних узлах.

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

WITH RECURSIVE iteration AS (
  SELECT Value, Next
  FROM List
  WHERE ID = 0    -- or Prev IS NULL, or however you identify the first entry

  UNION ALL

  SELECT Value, Next
  FROM List
  JOIN iteration ON List.ID = iteration.Next
)
SELECT Value FROM iteration;

Спасибо! Я не думал о применении рекурсивного CTE к этой проблеме, но вы правы, это решит мою проблему. И производительность должна быть в порядке с индексом первичного ключа по ID.

frelars 06.08.2018 23:42

Мне все еще интересно, есть ли способ сгенерировать ключи таким образом, чтобы мой первоначальный подход работал для больших списков. Я оставлю эту тему открытой на день или два и посмотрю, получу ли я какие-нибудь интересные комментарии. Но считайте, что ваш ответ принят!

frelars 06.08.2018 23:51

После ночного сна я думаю, что нашел способ сгенерировать эффективные ключи для алгоритмической проблемы, указанной в первоначальном сообщении.

Хитрость заключается в том, чтобы зарезервировать диапазон значений, если вам нужно увеличить длину ключа при вставке. Поэтому вместо добавления одной цифры, как при усреднении, мы «резервируем» N цифр. В этом зарезервированном диапазоне мы можем рассчитывать на единицу для каждой вставки, где ключ больше предыдущего. Это дает нам длину ключа O (log n) в зарезервированном диапазоне. Если нам нужно будет спуститься, у нас не будет места, и нам нужно будет снова расширяться. Но второй трюк - изменить зарезервированный диапазон для каждого расширения. Итак, в следующем зарезервированном диапазоне мы начинаем сверху и обратный отсчет.

Предварительное тестирование показывает, что эта структура генерирует очень компактные ключи как для случайных вставок, так и для нескольких вставок в одну и ту же позицию в списке и заголовке. Однако я думаю, что можно увеличить размер ключа, если «атаковать» эту структуру только правильным шаблоном вставки. Так что рекурсивный CTE, вероятно, более безопасный подход.

Я могу уточнить позже, если потребуется

Другие вопросы по теме