Следующие записи

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

Это упрощенная версия таблицы, с которой я работаю:

Record Name  |  Val1  | Val2  | Link |  Prev Link |
Rec1         |   5    |  3    | Rec2 |            |
Rec2         |   2    |  4    | Rec6 |  Rec1      |
Rec3         |   1    |  8    | Rec4 |            |
Rec4         |   1    |  1    |      |  Rec3      |
Rec5         |   8    |  3    |      |            |
Rec6         |   9    |  3    |      |  Rec2      |

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

Record Name  |  Val1  | Val2  | Link |  Prev Link |
Rec1         |   0    |  0    | Rec2 |            |
Rec2         |   0    |  0    | Rec6 |  Rec1      |
Rec3         |   0    |  0    | Rec4 |            |
Rec4         |   2    |  9    |      |  Rec3      |
Rec5         |   8    |  3    |      |            |
Rec6         |   16   |  10   |      |  Rec2      |

Текущую процедуру, которую я использую, можно найти по следующему адресу: http://pastebin.com/A10hW0C6.

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

Любая помощь будет оценена по достоинству :)


person Community    schedule 24.07.2012    source источник
comment
Пожалуйста, разместите свой код здесь. Необходимость покинуть этот сайт, чтобы перейти в другое место, чтобы прочитать его, раздражает, и он недоступен, если внешний сайт по какой-то причине перемещен или отключен от сети. Он также не доступен для поиска будущими читателями. Разместите наименьшую часть, которая относится к вашему вопросу здесь, пожалуйста. Спасибо.   -  person Ken White    schedule 25.07.2012
comment
Я не вижу, где вы на самом деле перебираете таблицу. Вам нужно иметь tblParts.first перед циклом while и tblParts.next в нижней части цикла while.   -  person John Easley    schedule 25.07.2012
comment
lvBookmark := tblParts.RecNo — почему? Стандартным способом, который лучше поддерживается библиотекой, будет lvBookmark := tblParts.Bookmark   -  person Arioch 'The    schedule 25.07.2012


Ответы (3)


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

qry1.sql := 'select * from table where prev_link is null;';

Это дает

Record Name  |  Val1  | Val2  | Link |  Prev Link |       
Rec1         |   5    |  3    | Rec2 |            |       
Rec3         |   1    |  8    | Rec4 |            |       
Rec5         |   8    |  3    |      |            |       

Затем вы можете следить за результирующим набором данных, искать/находить другой запрос (query2) и применять там свою логику обработки.

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

Некоторое улучшение. Вы можете добавить столбец под названием «Статус», чтобы отразить статус записи. Например, статус = 0 означает «Необработано», «1» означает «обработано», «2» означает неработающие ссылки, «3» означает циклическую ссылку и т. д. Вы можете начать с заполнения всего столбца статуса 0 (необработано).

Если вы не можете найти запись, ища/обнаружив ее в столбце «Ссылка» (возможно, каким-то образом она была удалена), вы можете отметить статус «2».

Каждый раз, когда вы переходите по ссылке, следите за отслеживаемой записью. Например, вы можете использовать список. Прежде чем перейти к записи в столбце «Ссылка», проверьте список. Если запись есть в списке, то у вас циклическая ссылка. Прекратите подписку, установите статус «3» (циклическая ссылка), очистите список и начните со следующей записи в query1. Работа с кольцевой ссылкой важна, иначе ваша программа может застрять на ней (никогда не завершиться).

Когда вы закончите обработку цепочки ссылок, пометьте статус «1» для всех записей в списке.

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

После того, как вы закончите, вы можете проверить столбец состояния. Если все «1» означает, что все хорошо (обработано). Если нет, то вы можете решить, что делать дальше.

Статус столбца можно использовать для фильтрации уже обработанных записей в других последующих операциях, поэтому приведенный выше запрос можно изменить:

qry1.sql := 'select * from table where prev_link is null and status = 0;';

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

person Hendra    schedule 25.07.2012

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

/* metadata */
set sql dialect 3;

create table test (
    id       integer not null,
    val1     integer,
    val2     integer,
    next_id  integer,
    prev_id  integer
);

alter table test add constraint pk_test primary key (id);
alter table test add constraint fk_test_next_id foreign key (next_id) references test (id) on update cascade;
alter table test add constraint fk_test_prev_id foreign key (prev_id) references test (id) on update cascade;

/* data */
insert into test (id, val1, val2, next_id, prev_id) values (1, 5, 3, 2, null);
insert into test (id, val1, val2, next_id, prev_id) values (2, 2, 4, 6, 1);
insert into test (id, val1, val2, next_id, prev_id) values (3, 1, 8, 4, null);
insert into test (id, val1, val2, next_id, prev_id) values (4, 1, 1, null, 3);
insert into test (id, val1, val2, next_id, prev_id) values (5, 8, 3, null, null);
insert into test (id, val1, val2, next_id, prev_id) values (6, 9, 3, null, 2);

/* update statement (could also be a stored procedure) */
execute block
as
  declare variable id integer;
  declare variable val1 integer;
  declare variable val2 integer;
begin
  for with recursive test_list (
    id,
    val1,
    val2,
    next_id
  )
  as (
    select
      t.id,
      t.val1,
      t.val2,
      t.next_id
    from test t
    where (t.prev_id is null)
    union all
    select
      t.id,
      t.val1 + tl.val1,
      t.val2 + tl.val2,
      t.next_id
    from test t
    join test_list tl on (tl.id = t.prev_id)
  )
  select
    tl.id,
    iif(tl.next_id is null, tl.val1, 0) val1,
    iif(tl.next_id is null,  tl.val2, 0) val2
  from test_list tl
  order by tl.id
  into
    :id, 
    :val1,
    :val2
  do
    update test set
      val1 = :val1,
      val2 = :val2
    where (id = :id);
end
person Ondrej Kelle    schedule 25.07.2012

TOndrej дал вам очень хороший ответ, но для этого вам нужно хорошо знать SQL. Это окупается - создание правильного SQL сделает вашу базу данных надежной, а ошибки в вашей программе не повредят данные базы данных. Но у вас будет время научиться. Также его SQL использует функции относительно недавних версий Firebird, слово вряд ли применимо к NexusDB или другим серверам.

Вот более тупой подход. Вы тоже можете попробовать.

Итак, если я понимаю вашу задачу, таблица разбита на набор цепочек? Здесь у вас есть три цепочки: Rec1->Rec2->Rec6 и Rec3->Rec4 и Rec5 отдельно.

Когда вы добавляете новый элемент, он всегда переходит в хвост, поэтому это будет Rec1->Rec2->Rec6->NewRec7, но никогда может быть NewRec7->Rec1->Rec2->Rec6 или Rec1->Rec2->NewRec7->Rec6.

Все ли так?

Затем вы можете добавить расстояние столбца от корня

Record Name  |  Val1  | Val2  | Link |  Prev Link | Dist |
Rec1         |   5    |  3    | Rec2 |            |  0   |
Rec2         |   2    |  4    | Rec6 |  Rec1      |  1   |  
Rec3         |   1    |  8    | Rec4 |            |  0   |  
Rec4         |   1    |  1    |      |  Rec3      |  1   |  
Rec5         |   8    |  3    |      |            |      |  
Rec6         |   9    |  3    |      |  Rec2      |  2   |  

Вы можете вычислить и обновить его в триггерах SQL или в своих программах. Триггеры SQL лучше — надежнее. Но вам нужно будет их понять. В любое время, когда вы добавляете запись, удаляете запись или меняете ссылки на записи, вам придется пересчитывать расстояния для всех затронутых записей. Например, если я возьму Rec6, обрежу и пересоединю его в Rec1->Rec2, Rec6->Rec3->Rec4, то расстояния для Rec6 и Rec3 и Rec4 придется пересчитывать

1) получить максимальное (расстояние) из базы данных.

ВЫБЕРИТЕ МАКСИМАЛЬНОЕ (РАССТОЯНИЕ) ИЗ THE_TABLE

2) для каждого selected_dist от 0 до max(dist)-1

2.1) для каждой записи с таким расстоянием

ВЫБЕРИТЕ RecordName, Link, Val1, Val2 ИЗ THE_TABLE, ГДЕ dist = :choosen_dist AND Link IS NOT NULL

2.1.1) обновить следующую связанную, увеличивая значения

ОБНОВИТЕ НАБОР ТАБЛИЦ Val1 = Val1 + :PrevLinked_Val1; Val2 = Val2 + :PrevLinked_Val2 ГДЕ RecordName = :NextLink

2.1.2) обновить выбранное, очистив значения

ОБНОВИТЬ НАБОР ТАБЛИЦ Val1 = 0; Val2 = 0 ГДЕ RecordName = :CurrentRecordName

Этот подход хуже:

  1. не так быстро - повторяется множество отдельных операторов, а не объединенных в одну процедуру и вызываемой один раз;
  2. не так быстро и менее надежно - если бы вы вызывали его из программы одну строку за другой, то данные летели бы по сети от сервера к программе и обратно, а не все расчеты внутри сервера
  3. вам нужно быть очень осторожным при расчете расстояний. Вплоть до удаления их ВСЕ и пересчета, если что-то сломалось.

Также лучше:

  1. легче понять новичкам SQL

  2. использует только очень простой SQL, поэтому может работать с любым сервером: NexusDB, Firebird, SQLite, что бы вы ни выбрали.

person Arioch 'The    schedule 25.07.2012