Xpoint
   [напомнить пароль]

Иерархия записей в таблице

Метки: [без меток]
2008-08-13 10:25:58 [обр] xxx+++(4/10)[досье]

Приветствую!

Тут у меня возникла задача — необходимо реализовать кнопки "поднять наверх" и "опустить наверх" для списка записей (статьи). Статьи хранятся классически — хранят ссылку на предка, т.е. на какой-то раздел. Дерева никакого нет.

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

Как можно грамотно реализовать данный функционал?

спустя 17 минут [обр] Lynn «Кофеман»(98/571)[досье]
Завести специальное поле для сортировки
спустя 31 минуту [обр] xxx+++(4/10)[досье]
Lynn «Кофеман»[досье] Это я понимаю. А поконкретнее, как сортировка должна происходить, как поднимать записи и опускать. Алгоритм нужен.
спустя 27 минут [обр] Ярослав Сюзёв (yara)(40/305)[досье]
В Drupal'е, по-моему, это называется "вес". Записи с меньшим весом "всплывают" наверх, записи с большим весом опускаются вниз. Записи с одинаковым весом сортируются по времени или еще как-то.
спустя 11 минут [обр] Thirteensmay(17/157)[досье]
Заводите поле номер по порядку, сортировка должна происходить при выборе списка из хранилища, если хранилище поддерживает SQL то используем предложение order by (сортировка будет происходить автоматически), поднять запись - уменьшить значение порядкового номера + уменьшить все значения до нее, опустить - наоборот. Если ваше хранилище не поддерживает SQL применяйте те методы которые для него доступны, вплоть до использования/написания процедуры сортировки с соответствующим алгоритмом. Алгоритмов таких мильйон в сети, здесь приводить смысла нет, смотрите сами и выбирайте наиболее подходящий. Если у вас тупо список строк в файле то вводить специальное поле не обязательно, просто меняйте строки местами, алгоритмы работы с файлами также имеются в сети в широком изобилии ;)
спустя 12 часов [обр] Сергей Сирик(180/737)[досье]
Ну, если кнопки - то это движение на одну позицию, не более. Т.е. поменять вес в специальном поле нужно только для двух записей - текущей и следующей (предыдущей). Какие проблемы-то?
спустя 2 часа 25 минут [обр] xxx+++(4/10)[досье]

Thirteensmay[досье] да, СУБД MySQl. Как раз алгоритм и нужен, в сети таких алгоритмов нет.

Сергей Сирик[досье] проблемы в том, что я не знаю, какой тип для сортировки использовать. Очевидно timestamp или datetime придется брать, т.к. при использовании некого автоинкрементного подобия будет слишком мала разница в полях сортировки.

спустя 6 часов [обр] Сергей Сирик(180/737)[досье]
xxx[досье]
Еще раз - подня/опустить на одну позицию или если поднять - то на самый верх например???
спустя 1 час 20 минут [обр] Thirteensmay(17/157)[досье]

xxx[досье] Алгоритм ? ну вот например, хотя это конечно и не PHP + MySQL, но суть демонстрирует вполне доходчиво, и свободно может быть переложен на PHP:

-- Определение порядкового номера поля
procedure set_field_order(ifield number,
                          iorder number,
                          iis_result number) is

srcidx number;
queryid number;
maxidx number;
target number;
tmode number;

begin
  select field_order, query_id
  into srcidx, queryid
  from t_report_field
  where report_field_id = ifield;

  -- Проверка выхода целевого индекса за границы возможного диапазона
  select decode(max(field_order), null, 0, max(field_order)) as max_forder
  into maxidx
  from t_report_field
  where query_id = queryid;
  
  target := iorder;
  if iorder < 1 then target := 1; end if;
  if iorder > maxidx then target := maxidx; end if;

  -- Определение режима перестановки (вверх/вниз)
  case 
    when target = srcidx then tmode := 0;
    when target > srcidx then tmode := 1;
    when target < srcidx then tmode := 2;
  end case;
  
    if tmode = 1 then
      for frec in (select report_field_id,
                     field_order
                   from t_report_field
                   where query_id = queryid
                     and field_order > srcidx
                     and field_order <= target
                   order by field_order)
      loop
        update t_report_field
        set field_order = frec.field_order - 1
        where report_field_id = frec.report_field_id;
      end loop;
    else
      for frec in (select report_field_id,
                     field_order
                   from t_report_field
                   where query_id = queryid
                     and field_order < srcidx
                     and field_order >= target
                   order by field_order desc)
      loop
        update t_report_field
        set field_order = frec.field_order + 1
        where report_field_id = frec.report_field_id;
      end loop;
    end if;

    update t_report_field
    set field_order = target,
      is_result = iis_result
    where report_field_id = ifield;

    commit;
end;

Данная функция устанавливает порядковый номер для любого поля в запросе, у меня есть запросы и поля в них, порядком следования полей надо рулить. У вас вместо моего запроса будет раздел, а вместо поля - статья. Итак, функция имеет 2 аргумента: ifield - идентификатор поля для которого необходимо установить порядковый номер, и iorder - собственно значение порядкового номера которое надо установить. Самым первым селектом мы выбираем исходный порядковый номер поля и идентификатор запроса которому оно принадлежит, в соответствующие локальные переменные для последующей работы с ними. В следующих 2 абзацах кода идет проверка выхода целевого (target) индекса (устанавливаемого порядкового номера) за границы возможного диапазона, в конечном случае если пытаемся установить порядковый номер < 1 то он установится как 1, если говорим что хотим установить порядковый номер больше чем последний возможный то он установится как последний возможный. Далее в кейсе определяем режим перестановки, вверх или вниз, тупо путем сравнения исходного порядкового номера поля и того который мы хотим установить, затем в зависимости от режима выбираем те поля которые надо подвинуть, и в цикле по одному сдвигаем их, т.е. меняем их порядковый номер, отнимаем или прибавляем единичку для каждого. Теперь у нас место для вновь устанавливаемого порядкового номера расчищено, т.е. то поле которое стояло на его месте сдвинуто, и соответственно если мы для нашего поля просто установим требуемый номер то повторов такого номера не будет. Что мы и делаем в конце.

спустя 8 часов [обр] xxx+++(4/10)[досье]

Сергей Сирик[досье] да, на одну.

Как я попытался сделать: добавил поле article_weight типа datetime. Поле заполняется при создании статьи.
Поднять/опустить реализуется путем подмены значения article_weight между записями, которые необходимо поднять или опустить. Т.е. они просто обмениваются друг с другом значениями article_weight.

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

// сделали SQL на выборку, получили массив
$data = array
(
    array('id'=>23, 'text'=>'Раз',
    array('id'=>27, 'text'=>'Два',
    array('id'=>32, 'text'=>'Три'
);

$id_article_prev = 0; // ид_предыдущей записи

foreach ($data as $val)
{
   echo $val['text'].' <a href="?id_prew='.$id_article_prev.'">поднять наверх на позицию '.$id_article_prev.'</a>';
   $id_article_prev = $val['id'];   
}

Т.е. первый элемент списка не будет "знать" какой ему нужен $id_article_prev для этой манипуляции.

Я вообще не уверен, что иду в том направлении.

Thirteensmay[досье] Если честно, я ничего не понял в этом чуждом для меня коду. Вы, я так понимаю, всю базу пересчитываете?

спустя 3 часа 11 минут [обр] ViRus[досье]

У меня прекрасно работает такой вариант. После добавлении статьи берется значение поля id (autoincrement) добавленной статьи и дублируется в поле order_id. При выводе статей - ORDER BY order_id.
При нажатии кнопки "вверх" на текущей статье -

  1. беру максимальное предыдущее значение order_id не равное текущему (обменная статья)
  2. меняю order_id обменной статьи на временное (0)
  3. меняю order_id текущей статьи на order_id обменной статьи
  4. меняю order_id обменной статьи на order_id текущей статьи

Если вниз, то тоже самое, только в п.1 вместо максимального предыдущего - минимальное следующее.
Если перемещается вверх первый элемент, то меняем местами с последним, и наоборот.
Тип поля order_id такой же как и id - "int"

спустя 1 час 47 минут [обр] Филипп Ткачев(20/112)[досье]
 У вас предки - в этой же таблице? Расплывчато написали про них. Если нет, то оперировать можно с Id статьи и сортировать по нему же.
Делать обмен можно также как Вирус написал. По сути у вас будет простой обмен idишников. Это то, что обычно называют swap. Только будьте внимательны с порядком при обмене.
спустя 9 часов [обр] Сергей Сирик(180/737)[досье]
xxx[досье]
Ну, целых два неправильных направления :) Делать вес типа дата и выбирать базу в массив и потом его обрабатывать. А ну как база большая будет? Да и поля типа дата - изначально не уникальные. Т.е. работать с вероятностью 99% будет правильно, но если вдруг баг - искать и искать его потом.
Вариант ViRus[досье] очень хорош, вообще и в частности :) Потому как универсален для любой базы.
А вот ИДшники менять, Филипп Ткачев[досье], я бы не советовал. ИД - это уникальный идентификатор, к которому может быть привязано много чего, начиная от постоянной ссылки на статью и заканчивая внешними ключами.
спустя 33 минуты [обр] Thirteensmay(17/157)[досье]
Сергей Сирик[досье] Для любой базы универсален, но для любой задачи ли ?
xxx[досье] Ненадо с datetime, это нелогично, со всеми вытекающими. Порядковый номер это не дата/время, это число. Если вам надо только на шаг вверх/вниз то можно сделать типа того что предложил ViRus[досье], но учтите, перемещение на шаг имеет смысл только для списков состоящих не более чем из 5..7 пунктов, ибо за пошаговое перемещение с 360 места на 12 пользователь может вам и руки отгрызть, особенно если каждый шаг подтормаживает потому что выполняется на сервере. Обычно такие вещи делают драгэндропом, т.е. схватил нужный пункт и потащил куда надо, а вот когда притащил выполняется процедура перестановки, с текущей позиции в нную, те пункты которые находятся между этими позициями смещаются, что и делает тот алгоритм который я вам предложил. В крайнем случае если перетаскивание не осилить, то можно сделать поле ввода - в какую позицию переставить текущее поле и кнопку. Вы говорите что "я ничего не понял", давайте откровенно, вы врете, вы не разбирались, и хотите чтобы вам дали все на блюдечке, нормальное желание вобщем то, я сам такой ;) Если бы вы попытались разобраться то 80% этого алгоритма вы бы поняли, там элементарные SQL и ифы, единственное что моглобы вас смутить это курсорный цикл, так вот и сказали бы что именно вам непонятно, а не я ничего не понял.
спустя 54 минуты [обр] ViRus[досье]
Сортировать по основному id действительно неудобно. У меня на него завязаны фотки, ссылки... Поэтому ввел order_id. Насчет пошагового перемещения согласен. Все руки не дойдут доделать. Кстати, если с 360 на 12, то наверно дешевле будет вырезать текст статьи тут и вставить там =), чем пошагово.
спустя 35 минут [обр] Thirteensmay(17/157)[досье]

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

Дан список (id, order, value):
1 - 1 - A
3 - 2 - B
4 - 3 - С
5 - 4 - D
7 - 5 - E
9 - 6 - F
Необходимо запись id = 7 сделать 2 по порядковому номеру.
Вычисляем ее текущий порядковый номер (srcidx = 5), целевой порядковый номер target = 2
Обнаруживаем что target < srcidx значит режим перестановки = вверх
Выбираем записи order < srcidx and order >= target order by order desc т.е:
3 - 2 - B
4 - 3 - С
5 - 4 - D
В цикле каждой делаем order = order + 1 (вообще можно сделать одним запросом без цикла) получаем:
3 - 3 - B
4 - 4 - С
5 - 5 - D
В заключении присваиваем нашей переставляемой записи требуемый номер
7 - 5 - E станет 7 - 2 - E
в результате получаем:
1 - 1 - A
7 - 2 - E
3 - 3 - B
4 - 4 - С
5 - 5 - D
9 - 6 - F

спустя 4 минуты [обр] xxx+++(4/10)[досье]

ViRus[досье] Спасибо за идею с автоинкрементом, она меня как-то неожиданно просветлила, и в целом за расписанный алгоритм. Все гораздо проще оказалось, чем я думал. Только вот с этим

Если перемещается вверх первый элемент, то меняем местами с последним, и наоборот.

я не согласен, поэтому реализовывать подобное не стал :-)

Сергей Сирик[досье] да, я уже осознал, что использование datetime тут не верное решение.

Thirteensmay[досье] Я обязательно изучу Ваш алгоритм. Но реализовать пришлось пока по алгоритму ViRus[досье], ибо так надо клиенту. За драг-и-дроп и инициативу не платят.

спустя 17 минут [обр] Thirteensmay(17/157)[досье]
Клиенту надо работать, слово нормально всегда подразумевается. Значит будете иметь двойной половой акт, ваше право ;)
спустя 6 часов [обр] Сергей Сирик(180/737)[досье]
Thirteensmay[досье] Абсолютно согласен, что задача первична :) Поэтому механизм, обеспечивающий оптимальный переброс с 12й на 360ю позицию будет далеко не самым оптимальным для +-1 перемещения :)
спустя 2 дня 15 часов [обр] Thirteensmay(17/157)[досье]
А каких перемещений будет больше ? какова оптимальность в результате ? И неужели вы хотите сказать что предпочитаете совершать десятки кликов вместо одного ? Короче, позиции ясны, перспективы тоже, человек уже сделал - ну и дай бог.
Powered by POEM™ Engine Copyright © 2002-2005