Работа с переменными типа Record в Navision

Надпись на аквариуме в зоомагазине:
«Не стучите – они не откроют».

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

Это сподвигло меня на написание статьи, где бы на пальцах (с примерами) объяснялось, как и когда (не) использовать различные команды.

Большинство советов пишется с оглядкой на версию Navision 4.0 и выше в связке с SQL. Тем не менее, часть их справедлива и для родной базы Navision.

CLEAR, INIT и RESET – начнем с различия между этими тремя

TheTable.RESET: используется для отмены всех ФИЛЬТРОВ (работает по определенной FILTERGROUP). Кроме того, она сбрасывает SETCURRENTKEY на первичный. ВАЖНО – не используйте эту команду для очистки всех полей записи. Она их НЕ ОЧИСТИТ.

НЕПРАВИЛЬНО — Если в записи заполнены какие-то поля до вызова команды RESET, они там и останутся:

TheTable.RESET;
TheTable.”Primary Key” := Значение;
TheTable.”Field N” := Значение;
TheTable.INSERT(TRUE);

ПРАВИЛЬНО:

CLEAR(TheTable); // Очищает поля первичного ключа
TheTable.”Primary Key” := Значение;
TheTable.”Field N” := Значение;
TheTable.INSERT(FALSE);

TheTable.INIT: эта команда очищает все поля записи КРОМЕ ПОЛЕЙ ПЕРВИЧНОГО КЛЮЧА! Она не затрагивает ФИЛЬТРЫ или CURRENTKEY! Так что, при желании добавить новую запись, имея заполненные поля первичного ключа – это оно самое.

CLEAR(TheTable): эта команда последовательно выполняет TheTable.RESET и TheTable.INIT, а также очищает поля первичного ключа. Я практически всегда использую эту команду для ДОБАВЛЕНИЯ НОВОЙ записи. Это также единственная команда, которая сбрасывает оператор CHANGECOMPANY.

TheTable.GET: идеален для поиска записи по первичному ключу. Альтернативный вариант – можно отфильтроваться по первичному ключу и использовать FINDFIRST. Оператор SELECT, который запрашивает сервер, будет один и тот же. Но – GET проще для программирования и легче для чтения кода. Не нужно сложных конструкций типа RESET-SETCURRENTKEY–SETRANGE. GET даже не предполагает такие вещи.

Внимание. GET не учитывает наложенных на таблицу фильтров!

НЕПРАВИЛЬНО (т.к. создает бардак):

TheTable.RESET;
TheTable.SETCURRENTKEY(...)
TheTable.SETRANGE/SETFILTER
TheTable.GET(...);

ПРАВИЛЬНО:

TheTable.GET(...);

В случае использования GET, даже если запись не найдена и вы хотите продолжить выполнение кода, можно использовать следующую конструкцию:

// Версия 1.
CLEAR(TheTable);
IF TheTable.GET(....) THEN
// Версия 2.
IF NOT TheTable.GET(....) THEN
CLEAR(TheTable);

Обе версии по итогам работы предоставляют чистую запись в случае, если нужная запись не найдена. Грязных данных в этом коде не будет. Лично я предпочитаю первый вариант – сначала очистить запись, а УЖЕ потом получить данные (если это возможно).

Все остальные команды используются совместно с наложением фильтров, поэтому, прежде чем я начну о них рассказывать, преподам урок хорошего тона в программировании, который пригодится в любом случае. Для того, чтобы код было легче читать/исправлять/просмотривать и прочая — лучше расположить команды фильтрации вместе, и начинать их с RESET. Это даст уверенность в том, что вы не начали поиск чего-то нового в уже отфильтрованной переменной. Я делаю так всегда. Также я всегда добавляю в этот блок SETCURRENTKEY:

TheTable.RESET;
TheTable.SETCURRENTKEY(...); // Даже если это первичный ключ
TheTable.SETRANGE(...) или TheTable.SETFILTER(....)

Команды, использующие фильтры

Несколько примечаний по использованию команд.

  1. FIND, FINDSET ведут к открытию SQL-курсора. Открытие курсора — это тяжелая достаточно тяжелая для сервера операция, так что по возможности их лучше избегать.
  2. Команды ниже — пример того, как можно получить данные, когда вы НЕ ХОТИТЕ блокировать записи. Чуть ниже я расскажу о том, что делать, если вы ХОТИТЕ блокировать записи.
  • FINDSET получает первые N записи за один вызов. N is определяется через меню File =>Database=>Alter=>Tab advanced=>Caching=>Record Set.
  • FIND(‘-‘): эту команду использовать больше не надо (за исключением нескольких случаев, о которых я расскажу позже), т.к. есть гораздо лучшие команды — FINDFIRST и FINDSET.
  • FIND(‘+’): эту команду также не надо больше использовать (кроме ряда исключений, о которых ниже), потому что есть команды лучше — например, FINDLAST.
  • FIND(‘=’): эта команда немного странная сама по себе. Она работает практически так же, как GET, если поиск записи ведется по первичному ключу.  Для того, чтобы запустить поиск, необходимо заполнить значения полей первичного ключа. При этом она учитывает фильтры записи! Полезна такая команда может быть в случае, когда переменная уже содержит нужную записи, а вам надо просто обновить ее.
  • FIND(‘>’) / FIND(‘<‘): эти команды работают на основании текущего значения первичного ключа записи для получения предыдущей или следующей записи, и также УЧИТЫВАЮТ фильтры. Лучше пользуйтесь NEXT или NEXT(-1).
  • FIND(‘=<>’): Это худшая команда из всех. Сначала она пытается выполнить FIND(‘=’), и если не находит запись, то выполняет FIND(‘<‘), если и в этом случае ничего не находит, то вызывает FIND(‘>’). НЕ ИСПОЛЬЗОВАТЬ НИ В КОЕМ СЛУЧАЕ!
  • FINDFIRST: Обязательна к применению :) если вам нужно получить первую запись. В циклах не использовать НИКОГДА.
  • FINDLAST: Также обязательна к использованию, если вам нужно получить послденюю запись. В циклах также — не использовать НИКОГДА.
  • FINDSET: Эту команду НЕОБХОДИМО использовать для получения набора записей. Она считывает N записей за один вызов. Если реальное количество записей меньше, то все они сразу передаются на клиента. Параметр N определяется через меню File=>Database=>Alter=>Tab Advanced=>Caching: Record set
  • ISEMPTY: НЕОБХОДИМО использовать для проверки, если ли записи в таблице (возможно, с наложенными фильтрами).
  • COUNT: Оператор возвращает количество записей в таблице (в  т.ч. С фильтрами). Нагрузка на сервер при этом выше, чем при использовании COUNTAPPROX.
  • COUNTAPPROX: эту команду можно использовать, когда вам нужно примерное количество записей в таблице (с фильтрами или без). Использовать можно для ProgressBar’а. Нагрузка на сервер в этом случае будет меньше.

Чтение данных БЕЗ блокировок записей

Вводные следующие:

Вам необходимо проверить наличие записей в таблице

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

  1. ОЧЕНЬ ПЛОХО — происходит подсчет ВСЕХ записей
    IF TheTable.COUNT &gt; 0 THEN ... // или COUNTAPPROX
  2. ПЛОХО — возвращает НАБОР записей на клиента
    IF TheTable.FINDSET THEN ...
  3. НЕ ТАК ПЛОХО — до какой-то версии (не помню, до какой) вам приходилось делать это, потому что не было выбора. Происходит открытие курсора на SQL Server.
    IF TheTable.FIND('-') THEN ... // или FIND('+')
  4. ВСЕ ЕЩЕ ПЛОХО — курсор не открывается, но по-прежнему возвращается одна запись (если она есть).
    IF TheTable.FINDFIRST THEN ... // или FINDLAST
  5. ПРАВИЛЬНО — записей не возвращается НИКОГДА. Результат — булево значение с информацией о наличии/отсутствии записей
    IF NOT TheTable.ISEMPTY THEN

Вам нужно считать из базы ТОЛЬКО первую или последнюю запись (если они вообще есть), но НИКОГДА не больше одной.

  1. ОЧЕНЬ ПЛОХО — клиент получает НАБОР записей
    If TheTable.FINDSET THEN ... // для чтения последней записи, необходимо вызвать ASCENDING(FALSE)
    // до вызова FINDSET (объяснение позже - это даже ХУЖЕ, чем ОЧЕНЬ ПЛОХО)
  2. ПЛОХО — открывается курсор на SQL Server
    If TheTable.FIND('-') THEN ... // или FIND('+')
  3. ПРАВИЛЬНО — не открывает курсор и возвращает одну запись
    If TheTable.FINDFIRST THEN ... // или FINDLAST

Вам необходимо считать ВСЕ записи в порядке ВОЗРАСТАНИЯ

  • ОЧЕНЬ ПЛОХО. FINDFIRST не открывает курсора и возвращает первую запись. При первом вызове NEXT, Navision видит, что курсора нет, и в этот момент создает его.
    IF TheTable.FINDFIRST THEN
    REPEAT
    ...
    UNTIL TheTable.NEXT = 0;
  • НЕПРАВИЛЬНО — так вы будете читать по одной записи за раз
     IF TheTable.FIND('-') THEN
    REPEAT
    ...
    UNTIL TheTable.NEXT = 0;
  • ПРАВИЛЬНО — вы получаете N записей за один запрос, а далее одну за одной.
    IF TheTable.FINDSET THEN
    REPEAT
    ...
    UNTIL TheTable.NEXT = 0;

Вам необходимо считать ВСЕ записи в порядке УБЫВАНИЯ

  • ХУЖЕ, ЧЕМ ОЧЕНЬ ПЛОХО — выскочит с ошибкой runtime (попробуйте, чтобы знать, о чем напишет Navision)
    TheTable.ASCENDING(FALSE);
    IF TheTable.FINDSET THEN
    REPEAT
    ...</li>
    </ul>
    <ul>
        <li>UNTIL TheTable.NEXT = 0;
  • ПРАВИЛЬНО. Версия 1. Используйте во всех возможных случаях. Метод использует FINDSET чтобы получить данные быстрее и сохранить их во временную таблицу. Затем по временной таблице запускается цикл в порядке УБЫВАНИЯ. Это полезно, когда количество записей <= N (из настроек FINDSET). Не надо использовать этот метод при большом количестве записей, т.к. все эти записи должны быть сначала сохранены во временную таблицу.
    IF TheTable.FINDSET THEN
    REPEAT
    // сохраняем записи во временной таблице
    tmpTheTable := TheTable;tmpTheTable.INSERT(FALSE);UNTIL TheTable.NEXT = 0;// запускаем цикл по временной таблице
    tmpTheTable.RESET;
    tmpTheTable.ASCENDING(FALSE);
    IF tmpTheTable.FIND('-') THEN
    REPEAT
    ...
    UNTIL tmpTheTable.NEXT = 0;
  • ПРАВИЛЬНО — второй вариант:
    TheTable.ASCENDING(FALSE);
    IF TheTable.FIND('-') THEN
    REPEAT
    ...
    UNTIL TheTable.NEXT = 0;

Продолжение серии здесь — «Часть вторая. Modify».

Данная статья — вольный перевод заметки «How to work with record-variables (version 2)?»

Исправления и дополнения приветствуются :) Пишите на mailbox@naviart.ru

Автор:

В области Navision - с 2003 года. Профессиональные интересы: NAV, MS SQL, .NET, BPMN, IT-менеджмент. Предметная область: логистика, финансы, склады, 3PL.

Количество статей, опубликованных автором: 86.

Комментарии (13 комментариев)

  1. спасибо! еще бы добавил одну неочевидность:
    GET не обращает внимания на фильтры. например, отфильтровали набор записей, проверили искомую Гетом — опаньки! нашлась!

    FINDSETы и FINDFIRSTы заработали в версии 4 и выше, а в трешках, простите, только FIND(‘-‘)…
    а еще есть конструкция просто FIND (TheTable.FIND;), которая вообще непонятно что делает и как работает :)

    • Андрей Стрельников

      Добавил в статью красным цветом :)
      Про четверку — в курсе, да заметка ведь в категории Navision 4.0

      Добавил для верности и в саму статью

  2. Дмитрий

    1 Дополнение по использованию функции Get

    This function always uses the primary key for the table and ignores any filters. The system does not change the current key and filters after you call this function.

    То есть если на таблицу наложены любые фильтры , функция Get их игнорирует и не меняет текущий ключ.

  3. Bibo

    FINDFIRST, FINDLAST, FINDSET, ISEMPTY поивилис в 4.0 версии

  4. Storkich

    Опиши в какой момент срабатывает LOCKTABLE и для чего он нужен, с примером(кейсом) использования.
    Напиши про FINDSET(TRUE), и чем он полезен.
    Напиши, чем опасна сортировка на SQL-ном сервере при использовании FINDSET.
    И ещё напиши, что если GET/FIND/FINDSET/NEXT не сработал, то в Реке останутся предыдущие значения.

    • Андрей Стрельников

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

  5. kydraxa

    Отличная статья, ман.

  6. Дмитрий

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

  7. Navisioner

    Утверждение «heTable.INIT: эта команда очищает все поля записи КРОМЕ ПОЛЕЙ ПЕРВИЧНОГО КЛЮЧА!» не верно — смотри хелп в NAVе.

    RESET убирает все фильтры во всех FILTERGROUP и снимает маркировки с записей.

    • Андрей Стрельников

      Наверно, было бы более конструктивно, если бы вы здесь указали, в чем именно ошибка, а не отсылали к документации :)

      • Navisioner

        В общем случае поля очищает оператор CLEAR. INIT кроме того, что не «обнуляет» значания первичного ключа, инициализирует поля «дефолтными » значениями. Эти значения указываются в свойстве InitValue для поля таблицы.
        RESET, как я уже писал выше, сбрасывате все фильтры и маркировки, а не только в пределах текущей FILTERGROUP.

Добавить комментарий