Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Построение дерева, повысить скорость 
:(
    Опции темы
jsa
Дата 16.8.2006, 16:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 704
Регистрация: 19.1.2006
Где: Новосибирск

Репутация: нет
Всего: 20



Hi All!

вот какая задача:
есть 2 таблицы, в первой таблицы содержиться описание(просто элементы с определенной инфой), во второй таблице - указание, как из описания построить дерево (какие узлы после каких), кроме всего прочего помимо указания как строить дерево из описания, на конкретный момент времени из описания можно построить только определенное (но не окончательное) количестов узлов, т.е.  по ходу времени узлы будут достраиваться как только появиться описание в первое таблице, так вот сейчас сделаны две процедуры: 1-я - обход рут-узлов, 2-я (рекурсивная) - обход child-узлов, фактически в рекурсии открывается новый курсор по данным, делается обход по элементам ну и т.д. (каждый элемент может стать узлом), ес-но что данных в первой таблице очень много, и что рекурсивное открытие курсоров очень требовательно как к ресурсам так и ко времени, в связи с этим хочу просто выкачать плоские данные из двух таблиц, и делать обход по данным в пямати, кто что скажет?


--------------------
Все мы, на перине с песней, строим небо на земле © Ю. Шевчук
PM MAIL ICQ   Вверх
KostenkoSergey
Дата 16.8.2006, 16:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 290
Регистрация: 31.10.2005
Где: Kiev

Репутация: 3
Всего: 8



ну если я правильно допонял вопрос то первым в голову пришло следующее:

я так понимаю достаточно просто сделать выборки из этих таблиц -  по резултсету на каждую, соответственно курсоров буит только 2.
Далее двойной цикл идёшь по элементам от главного до дочерних и строишь дерево...

а вообще можно поробовать написать вид :
element_code|parent_code|element_name  - order by по паренту - и за один проход можно попробовать сделать.

зы а покаж таблички ?
PM ICQ   Вверх
chief39
Дата 16.8.2006, 16:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


карманная тигра
***


Профиль
Группа: Участник Клуба
Сообщений: 1631
Регистрация: 20.5.2005
Где: Киев

Репутация: 15
Всего: 77



То есть одна табличка - вершины графа,
вторая - переходы?

Тогда как обычно - вытянул рут и пошёл в рекурсии.
Таблички, конечно, лучше сразу подтянуть. Хотя, если у тебя миллиарды записей... smile)


--------------------
Люди - это свечи. Они либо горят, либо их - в жопу!(с)

PM MAIL   Вверх
jsa
Дата 16.8.2006, 17:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 704
Регистрация: 19.1.2006
Где: Новосибирск

Репутация: нет
Всего: 20



Цитата(KostenkoSergey @ 16.8.2006,  16:22)
я так понимаю достаточно просто сделать выборки из этих таблиц -  по резултсету на каждую, соответственно курсоров буит только 2.
Далее двойной цикл идёшь по элементам от главного до дочерних и строишь дерево...

а вообще можно поробовать написать вид :
element_code|parent_code|element_name  - order by по паренту - и за один проход можно попробовать сделать.

зы а покаж таблички ?

все именно так:
table1
  id number not null pk
  ... other fields

table2
  id number not null pk
  pid number nullable 
  tab1_id number not null fk

chief39, не миллиарды, в первой таблице содержиться актуальная информация только за прошедьшую ночь с 0 часов + текущий день, по ней все и строиться
вся проблема в том, что в рекурсивной функции каждый раз открывается курсор, это долго, страничка генериться от 60 до 100 сек, я хочу в идеале иметь 2 набора данных из двух таблиц, и по ним ходить, может уже есть готовое средство для этого, сам я пока склоняюсь к xml + xpath (получить xml и делать по нему обход), но на сколько это будет быстрее, я не знаю


--------------------
Все мы, на перине с песней, строим небо на земле © Ю. Шевчук
PM MAIL ICQ   Вверх
KostenkoSergey
Дата 16.8.2006, 17:55 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 290
Регистрация: 31.10.2005
Где: Kiev

Репутация: 3
Всего: 8



Цитата(jsa @  16.8.2006,  17:22 Найти цитируемый пост)
вся проблема в том, что в рекурсивной функции каждый раз открывается курсор, это долго, страничка генериться от 60 до 100 сек


не открывай ... можно бегать и  по резултсету - 
result.previous();
resul.next();... и пр. - для этого нужно 

Код

conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);


Но мне кажется тебе нужно сделать HashMap в который загнать key = id, value = name из первой таблички
А далее  resultSet = "select * from table2 order by parent_id"

Код

while(resultSet.next()){
long elementId = resultSet.getLong(tabl1_id);
String nodeName = map.get(Long.parse(elementId));
// и строишь чё там надо appendChildaми
}



PM ICQ   Вверх
w1nd
Дата 16.8.2006, 22:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вертилятор
***


Профиль
Группа: Завсегдатай
Сообщений: 1077
Регистрация: 22.3.2006
Где: Москва

Репутация: 20
Всего: 54



Загнать в map интереснее, так как несмотря на толькочитабельность выход за границы fetchsize (с помощью next() или previons()) практически эквивалентен очередному запросу. А еще рекомендую ознакомится с такой вот организацией дерева - во многих случаях она позволит избавиться от рекурсии вообще.



--------------------
user posted imageuser posted image
PM MAIL ICQ   Вверх
Stampede
Дата 17.8.2006, 00:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Гносеолог
**


Профиль
Группа: Участник Клуба
Сообщений: 963
Регистрация: 25.4.2005
Где: Calgary, Alberta, Canada

Репутация: 24
Всего: 144



Цитата(w1nd @  16.8.2006,  13:40 Найти цитируемый пост)
Загнать в map интереснее


Загнать в Мап безусловно интереснее, но тут есть ряд маленьких нюансов:
  • что если данные со временем разрастутся настолько, что перестанут влезать в память?
  • кэширование данных в памяти содержит риск возможной рассинхронизации с содержимым базы;
  • если со временем встанет вопрос о разнесении нагрузки на несколько серверами, автора системы, завязанной на внутрипамятную обработку данных, будет подстерегать большой сюрприз.
  • Хранение дерева в памяти делает затруднительным формирование прямых SQL запросов, связанных с позицией в иерархии.

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

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

Так вот, коль скоро речь идет о преагрегации "деревянных" данных, то способов такой преагрегации существует сильно больше одного. Я сейчас опишу один из них, который мне особенно нравится, и возможно для jsa это окажется то что нужно.

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

Код

Инструменты
    Дрели
        Коловороты
        Ручные дрели
        Электродрели
    Пилы
        Ножовки
            Ножовки по дереву
            Ножовки по металлу
        Лобзики
        Лучковые пилы
        Бензопилы
    Топоры
        Колуны
        Столярные топоры
        Лесорубные топоры
        Туристские топорики


Традиционно для хранения таких структур используют самоссылающиеся (self-referencing) таблицы:

Код

create table NODES (
  NODE_ID integer not null,
  PARENT_ID integer null,
  NODE_NAME varchar(100) not null,
  primary key NODES_PK(NODE_ID),
  foreign key NODES_FK(PARENT_ID) references N0DES
)



Код

NODE_ID        PARENT_ID       NODE_NAME
1000           null      Инструменты
1001           1000      Дрели
1002           1001      Коловороты
1003           1001      Ручные дрели
1004           1001      Электродрели
...


А теперь делаем финт ушами: добавляем поле для пути и зполняем его значениями:

Код

alter table NODES add column PATH varchar(255);


Код

NODE_ID        PARENT_ID       NODE_NAME
1000           null      Инструменты        /
1001           1000      Дрели        /1000
1002           1001      Коловороты        /1000/1001
1003           1001      Ручные дрели        /1000/1001
1004           1001      Электродрели        /1000/1001
...


И тогда в такой таблице очень легко делать поиски в подузлах, и при случае легко создать соответствующую структуру в памяти буквально за один проход. Затраты на создание нового узла при этом минимальны, и единственная операция, при которой может потребоваться массовое изменение пути, это перенос ветки из одного узла в другой.

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



--------------------
"If you want something done right, do it yourself"
По секрету: выучить английский - реально!
PM WWW   Вверх
jsa
Дата 17.8.2006, 04:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 704
Регистрация: 19.1.2006
Где: Новосибирск

Репутация: нет
Всего: 20



1. По поводу map: я думал об это, но map ведь по сути таблица с двумя столбцами, только мне нужно столбцов побольше
2. 2Stampede, в целом идея толковая, но есть нюансы: сейчас слишком большое дерево,и состовлять пути будет проблематично, кроме всего, дерево непостоянно, ветки могут менятся
3. По поводу статьи: мне кажется, что для меня это будет введение дополнительной таблицы (либо добавление дополнительных столбцов), я хочу получить максимальную простоту и скорость


--------------------
Все мы, на перине с песней, строим небо на земле © Ю. Шевчук
PM MAIL ICQ   Вверх
Stampede
Дата 17.8.2006, 05:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Гносеолог
**


Профиль
Группа: Участник Клуба
Сообщений: 963
Регистрация: 25.4.2005
Где: Calgary, Alberta, Canada

Репутация: 24
Всего: 144



Цитата(jsa @  16.8.2006,  19:57 Найти цитируемый пост)
2Stampede, в целом идея толковая, но есть нюансы: сейчас слишком большое дерево,и состовлять пути будет проблематично


Что значит проблематично? Написать одну рекрсивную процедуру - и пускай себе мослает.

Цитата(jsa @  16.8.2006,  19:57 Найти цитируемый пост)
кроме всего, дерево непостоянно, ветки могут менятся


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

Код

// узел N перемещается A из B; по изменении поля PARENT_ID
// для N срабатывает триггер; старые значения доступны через
// встроенную переменную old, новые - через new
var from_path = select PATH from NODES where NODE_ID = old.PARENT_ID
var to_path = select PATH from NODES where NODE_ID = new.PARENT_ID
cursor cur = select * from NODES where PATH like ':from_path + "%"'
var len = length(from_path)
for each rec in cur
  var new_path = to_path + substr(rec.PATH, len)
  update NODES set PATH = :new_path where NODE_ID = :rec.NODE_ID
end


Можно аналогичным образом оформить в теле Java программы, если неохота заморачиваться с триггерами.


Это сообщение отредактировал(а) Stampede - 17.8.2006, 05:24
PM WWW   Вверх
w1nd
Дата 17.8.2006, 08:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Вертилятор
***


Профиль
Группа: Завсегдатай
Сообщений: 1077
Регистрация: 22.3.2006
Где: Москва

Репутация: 20
Всего: 54



Цитата(Stampede @ 17.8.2006,  00:02)
Что касается варианта представления деревьев, описанного в статье. Мне, если честно, не понравилось. 
<...>
Так вот, коль скоро речь идет о преагрегации "деревянных" данных, то способов такой преагрегации существует сильно больше одного. Я сейчас опишу один из них

В общем-то, вы описали тот же способ, разница только в представлении и месте хранения путей. А какие еще есть способы хранения деревьев, позволяющие избежать рекурсии при построении?


--------------------
user posted imageuser posted image
PM MAIL ICQ   Вверх
pompei
Дата 18.10.2007, 15:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бывалый
*


Профиль
Группа: Участник
Сообщений: 155
Регистрация: 7.9.2007

Репутация: 1
Всего: 6



Эту задачу решить можно, и без всякой рекурсии.

Давайте сведём эту задачу к задаче ЗА - перебор всех дочерних элементов данного корневого элемента без всякой рекурсии. Если задачу ЗА мы решим, то думаю наша задача будет решена тоже.

Теперь давайте определим ещё две задачи:

З1 - поиск первого элемента во множестве нашего корневого элемента
З2 - поиск следующего элемента для данного элемента

Если мы эти задачи решим, задача ЗА будет решаться так:
1) решаем задачу З1 и запоминаем найденный элемент как текущий;
2) если текущий элемент = нулл, то переход к п. 6;
3) исполняем полезную работу с текущим элементом;
4) для текущего элемента решаем задачу З2 и запоминаем новый элемент как текущий;
5) переход к п. 2;
6) утверждение: задача ЗА решена.

Для того чтобы можно было решить задачи З1 и З2, необходимо упорядочить всех братьев, а проще упорядочить вообще все элементы, и тогда все братья будут упорядочены тоже. Т.к. в table2 есть уникальный целочисленный идентификатор (это первичный ключ), то он вполне может подойти как средство упорядочивания.

Замечание: я предполягаю что table2.pid и table2.tab1_id ссылаются на table1.id

Рассмотрим ещё две задачи:

Z1 - поиск минимального сына у текущего элемента (current_id - ид текущего элемента)
Решение: 
Код

    select res_id=tab1_id from table2 where pid = current_id order by id limit 1

где: limit 1 - это значит, что запрос возвращает не больше одной строчки (из MySQL)
Если получиться что res_id == null, то у текущего элемента нет детей

Z2 - поиск следующего брата (current_id - текущий брат - у него должен быть родитель)
Решение: 
Код

    select parend_id = pid from table2 where tab1_id = current_id;
    select res_id = tab1_id from table2 where pid = parent_id and tab1_id > current_id limit 1

Если res_id == null, то это значит что текущий брат самый старший.

Деалее будем писать: res_id = Z1( current_id ) - решение задачи Z1 для current_id с результатом в res_id
аналогично и с Z2.

Решение задачи З1:
Код

Let x = root_id (корневой элемента)
While True //Безконечный цикл
    Let y = Z1(x)
    If y == null Then Return x EndIf
    Let x = y
EndWhile

Задача решена

Ну и последняя задача З2 - самая сложная:
Обозначим: parent_id = P(current_id) - получение родителя, если parent_id == null, то current_id - корневой
Код

Let current_id - текущий элемент
Let x = Z2(current_id)
If x == null Then // current_id - Самый старший брат у своего родителя
    Return P(current_id) // Следующий значит его родитель
Else // x - следующий брат для current_id
    // Спускаемся к самому младшему отпрыску полученного брата
    // а если у него нет детей то он сам следующий
    While True
        Let y = Z1(x)
        If y == null Then Return x EndIf // И возвращаем его
        Let x = y
    EndWhile
EndIf

Задача решена

При решении задачи ЗА последним элементом мы обработаем корневой

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

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

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


Это сообщение отредактировал(а) pompei - 18.10.2007, 15:42
--------------------
А всё оказывается гораздо проще: пассивные наноструктуры - активные наноструктуры - системы наносистем - молекулярные наносистемы - сингулярность! По пять лет на каждый этап.
PM MAIL   Вверх
fixxer
Дата 18.10.2007, 15:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 672
Регистрация: 14.9.2006
Где: Саратов, Россия

Репутация: 6
Всего: 27



Цитата(w1nd @ 17.8.2006,  08:04)
А какие еще есть способы хранения деревьев, позволяющие избежать рекурсии при построении?

Мне нравится такой способ


--------------------
user posted image
PM MAIL ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0762 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.