AXForum  
Вернуться   AXForum > Microsoft Dynamics AX > DAX: Программирование
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 07.12.2010, 14:29   #1  
Alenka is offline
Alenka
Участник
 
58 / 25 (1) +++
Регистрация: 19.04.2006
Миф об уникальности RecId?
Axapta 3.0 SP3

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

Я решила промоделировать эту ситуацию на тестовой базе. Сдвинула NextVal в SystemSequences на значение, близкое к нулю. Как и ожидалось, RecId снова стало принимать положительные значения. Но! стали появляться записи с уже существующими RecId.

Хотелось бы услышать ваше мнение по данному вопросу. Получается, что уникальность RecId - это миф?
Старый 07.12.2010, 14:37   #2  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
Цитата:
Сообщение от Alenka Посмотреть сообщение
Получается, что уникальность RecId - это миф?
Миф - это идея о том, что в системе есть функциональность, которая будет искать свободные "дырки" в диапазонах выделенных значений RecId
А уникальность RecId в таблице при наличии уникального индекса по этому полю - не миф
см. Как выполнять дефрагментирование RecID
__________________
-ТСЯ или -ТЬСЯ ?
За это сообщение автора поблагодарили: Alenka (1).
Старый 07.12.2010, 14:41   #3  
Alenka is offline
Alenka
Участник
 
58 / 25 (1) +++
Регистрация: 19.04.2006
Дефрагментация стандартными способами на нашей базе невозможна из-за ее размера (около 500Гб) и потере целостности (стандартный метод не правит те ссылки на RecId, которые хранятся в таблицах).
Как же уникальность не миф, когда я получила по крайней мере 2 записи с одинаковыми RecId?
Старый 07.12.2010, 14:44   #4  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
Цитата:
Сообщение от Alenka Посмотреть сообщение
Дефрагментация стандартными способами на нашей базе невозможна из-за ее размера (около 500Гб) и потере целостности (стандартный метод не правит те ссылки на RecId, которые хранятся в таблицах)
Поразительно, как быстро Вы прочли всю ветку по ссылке из первого моего сообщения
Стандартный метод корректно правит ссылки в пределах одной компании там, где они оформлены корректно (с использованием правильных EDT)
Делает он это неторопливо, но даже в Вашем случае за сутки отработать должен может
__________________
-ТСЯ или -ТЬСЯ ?
Старый 07.12.2010, 14:57   #5  
Alenka is offline
Alenka
Участник
 
58 / 25 (1) +++
Регистрация: 19.04.2006
2 Vadik : Просто я ее уже читала раньше.
2 oip: Все правильно, я неверно понимала понятие уникальности. Ошибка при вставке и сообщение о существующей записи появляются при создании записи с существующим RecId в этой же таблице. При этом запись все-таки создается со следующим RecId.

Спасибо за помощь и наставления .
Старый 07.12.2010, 15:05   #6  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
Цитата:
Сообщение от Alenka Посмотреть сообщение
2 Vadik : Просто я ее уже читала раньше
Значит невнимательно читали, потому что Ваши выводы и аргументация ошибочны
Цитата:
Дефрагментация стандартными способами на нашей базе невозможна из-за ее размера (около 500Гб) и потере целостности (стандартный метод не правит те ссылки на RecId, которые хранятся в таблицах)
__________________
-ТСЯ или -ТЬСЯ ?
Старый 07.12.2010, 15:35   #7  
egorych is offline
egorych
Участник
Самостоятельные клиенты AX
Oracle
 
761 / 154 (7) ++++++
Регистрация: 09.11.2006
Адрес: Краснодарский край
Цитата:
Сообщение от Alenka Посмотреть сообщение
Дефрагментация стандартными способами на нашей базе невозможна из-за ее размера (около 500Гб) и потере целостности (стандартный метод не правит те ссылки на RecId, которые хранятся в таблицах).
Надо просто найти поля где хранятся ссылки на RecID и исправить EDT поля на RefRecId - тогда обновляться все будет корректно.
Старый 07.12.2010, 14:41   #8  
oip is offline
oip
Axapta
Лучший по профессии 2014
 
2,564 / 1416 (53) ++++++++
Регистрация: 28.11.2005
Записей в блоге: 1
Вы все неправильно понимаете. Смотрите, для 3.0 ситуация такая. Генерация Аксаптой нового RecId проста как три копейки. Аксапта грубо говоря (опустим сейчас не значащие для нас нюансы) берет соответствующее последнее значение из номерной серии и прибавляет к нему единицу. Все, данное значение и записывается в таблицу. Разумеется, Аксапта не проверяет никакую уникальность, и не пытается найти неиспользуемый номер. Представляете, с какой бы скоростью она работала, если бы каждый раз это делала?!

Поэтому никакой гарантированной уникальности RecId в пределах всей базы нет. Проблема возникает, если в той же самой таблице, в которой мы создаем запись, уже есть запись с таким RecId

Соответственно, как только у вас RecId пойдут по второму кругу, вы будете в случайные моменты времени все чаще и чаще сталкиваться с такой ошибкой.

А вообще, возникающие проблемы и возможные способы решения уже обсуждались и не один раз.
__________________
С уважением,
Олег.
За это сообщение автора поблагодарили: Alenka (1).
Старый 07.12.2010, 16:46   #9  
KiselevSA is offline
KiselevSA
Злыдни
Аватар для KiselevSA
Злыдни
Лучший по профессии 2015
 
958 / 333 (13) ++++++
Регистрация: 25.01.2002
Адрес: Москва
Скорость дефрагментации RecId очень сильно зависит от производительноти сервера и скорости работы дисковой подсистемы. Лет 5 назад проводил дефрагментацию на базе размером в 25 Гигов. Сервер с двумя старенькими Xeon-ами, 4Gb оперативки и SCSI подсстемой (RAID-10) пережевал дефрагментацию за час с небольшим. При этом не должны работать пользователи и пакетники. Я думаю, что в Вашем случае все должно уложиться в один выходной день. Можете, во избежании случайных подключений, запустить дефрагментацию в двухуровневой конфигурации.
__________________
люди...считают, что если техника не ломается, то ее не нужно ремонтировать. Инженеры считают, что если она не ломается, то нуждается в совершенствовании.
Старый 07.12.2010, 19:47   #10  
Владимир Максимов is offline
Владимир Максимов
Участник
КОРУС Консалтинг
 
1,656 / 1158 (42) ++++++++
Регистрация: 13.01.2004
Записей в блоге: 3
Не помню, был ли где уже подобный совет, поэтому, возможно, повторюсь.

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

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

Код:
select dataAreaId, round(RecId/1000000,0), count(*)
from InventTrans with (nolock)
group by dataAreaId, round(RecId/1000000,0)
order by 1,2
Если такие разрывы есть, то можно просто установить счетчик RecId на значение, с которого начинается этот "разрыв" и на некоторое время отодвинуть вопрос дефрагментации

Правда, такой совет подойдет, скорее, для виртуальных компаний, где не так уж и много таблиц и относительно легко найти "дыры" в последовательности значений. Для "обычных" компаний придется писать скрипт по перебору всех таблиц (не сложно, но "муторно"), чтобы убедиться, что найденная "дыра" действительно "дыра" RecId, а не просто большая группа удаленных записей в одной..двух таблицах.

PS: Ага, была озвучена эта идея

http://axforum.info/forums/showthrea...992#post110992

Цитата:
По результатам телефонного митинга с John McBride (менеджер команды разработки) и Mathieu Kemenovic (глобальная служба поддержки) мне подтвердили что они ничего менять в 3.0 не будут (и даже не будут делать private hot-fix) и предоставлили набор SQL-скриптов, которые ищут большие "дырки" последовательности идентификаторов записей и используют их. Данные скрипты неавтоматические и необходимо выполнять ряд шаманских танцев с бубнами...
Старый 07.12.2010, 20:22   #11  
Ivanhoe is offline
Ivanhoe
Участник
Аватар для Ivanhoe
Лучший по профессии 2017
Лучший по профессии 2015
Лучший по профессии 2014
Лучший по профессии AXAWARD 2013
Лучший по профессии 2011
 
4,143 / 2155 (80) +++++++++
Регистрация: 29.09.2005
Адрес: Санкт-Петербург
Возможно, у автора появился повод для перехода на новую версию?
__________________
Ivanhoe as is..
Старый 07.12.2010, 21:10   #12  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
В порядке эксперимента - автоматическое использование дыр RecId
Эх, готовил материал для сообщения в блоге (где-то уже с года полтора назад), все собирался нормально оформить и выложить, да, чувствую, если сейчас не сообщу, то для "трёшки" уже совсем никому не надо будет. Поэтому несколько сумбурно, схематично, но выкладываю. Ветка, вроде, подходящая

Честно скажу, в промышленной эксплуатации не пользовал (не припёрло), но на тестовой всё отрабатывало довольно прилично. Axapta - 3.0 SP4, СУБД - Oracle 10.

Общий смысл такой - заставить Аксапту автоматически использовать имеющиеся дыры RecId. Для этого нужно провести подготовительную работу: при помощи самописного скрипта пробежаться по всем таблицам системы и собрать информацию о неиспользованных интервалах RecId. Для Ax 3.0 (двухзвенка, без АОС) имеет смысл отбирать только непрерывные диапазоны размером не менее 25, т.е. не меньше размера кэша. Далее грузим полученные диапазоны в таблицу дыр (не аксаптовскую, просто созданную на уровне БД в той же схеме):
Код:
CREATE TABLE RECIDHOLES
(
  FROMRECID NUMBER(10),
  TORECID   NUMBER(10)
)
Для пущей убедительности добавим проверочку на размер диапазона не менее 25 упомянутых выше идентификаторов
Код:
ALTER TABLE RECIDHOLES ADD (
  CHECK (TORECID-FROMRECID+1>=25))
Ну и наконец на таблицу SystemSequences вешаем триггер BEFORE UPDATE:
Код:
CREATE OR REPLACE TRIGGER SystemSequences_TBU
BEFORE UPDATE
ON SYSTEMSEQUENCES REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
WHEN (
SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'ppp' 
      AND Old.Id = -1 
      AND Old.TabId = 0
      )
DECLARE
    currNextVal NUMBER(10);
    cntBetween  NUMBER(10);
    cntAbove    NUMBER(10);   
    minDelta    NUMBER(10);
    b4switching NUMBER(10);
    holesRange  RecIdHoles%ROWTYPE;
    
BEGIN
    b4switching := 1118091751; -- NextVal перед переключением (передвинуть с запасом на 100-200, чтобы не схватили)
    minDelta    := 25; -- Axapta 3.0 Cache Size
    
    currNextVal := :New.NextVal; -- значение, которое собирается вставить Аксапта
    
    -- если здесь 1, то более ничего не делаем - :New.NextVal проходит в таблицу
    SELECT COUNT(*) INTO cntBetween 
        FROM RecIdHoles h 
        WHERE currNextVal BETWEEN h.FromRecId AND h.ToRecId
          AND h.ToRecId - currNextVal + 1 >= minDelta;  

    IF cntBetween = 0 THEN    
        -- иначе попадаем сюда и устанавливаем новое значение, равное FromId следующего диапазона
        SELECT COUNT(*) INTO cntAbove 
            FROM RecIdHoles h 
            WHERE h.FromRecId > currNextVal
              AND h.ToRecId - h.FromRecId + 1 >= minDelta; -- на всякий случай

        IF cntAbove > 0 THEN -- если мы в диапазонах, охватываемых таблицей дыр RecIdHoles
    
            SELECT * INTO holesRange
                FROM (SELECT * FROM RecIdHoles h 
                      WHERE h.FromRecId > currNextVal
                        AND h.ToRecId - h.FromRecId + 1 >= minDelta -- на всякий случай                 
                      ORDER BY h.FromRecId)
                WHERE ROWNUM = 1;
            
            currNextVal := holesRange.FromRecId;
            
        ELSE -- если уже нет
         
            IF currNextVal <= b4switching THEN -- значит кончились записи в таблице дыр RecIdHoles
                currNextVal := b4switching;
            END IF;
            -- если currNextVal > b4switching, то уже всё поехало нормально
            
        END IF;
        
        :New.NextVal := currNextVal;             
    END IF;              

   EXCEPTION
     WHEN OTHERS THEN
       -- Consider logging the error and then re-raise
       RAISE;
END SystemSequences_TBU;
Всё! Аксапта начинает генерить RecId под управлением диапазонов таблицы RECIDHOLES.

P.S. ОГРАНИЧЕНИЯ:

1. В текущей редакции триггер можно использовать на монотонно возрастающем участке генерации RecId, достаточно далеком от крайних значений, т.е. перевалка через максимальное значение (2G) и продолжение с минимального (-2G) текущим алгоритмом не предусмотрена.

2. У таблицы SystemSequence в Ax 3.0 имеется метод setCacheSize, позволяющий установить размер кэша иным, нежели 25. Перед использованием триггера рекомендуется проверить код приложения Аксапты на присутствие вызовов этого метода (у меня не было ни одного). При необходимости можно увеличить minDelta в триггере до значения максимального параметра этих вызовов, либо (более муторно) в триггере предусмотреть генерирование ошибки (исключения) при попытке Аксапты сделать шаг больше, чем 25. По иронии судьбы в Ax 4.0 метод setCacheSize отсутствует, но в "четверке" уже и подобный триггер не нужен

Последний раз редактировалось Gustav; 08.12.2010 в 11:13.
За это сообщение автора поблагодарили: fed (3), Vadik (1), Владимир Максимов (10), gl00mie (10), Alenka (1), Mileyko (1).
Старый 08.12.2010, 00:17   #13  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
А код, который отыщет свободные диапазоны RecId в компании на объеме данных, сопоставимом с постановкой задачи (500Гб) - будет?
__________________
-ТСЯ или -ТЬСЯ ?
Старый 08.12.2010, 10:30   #14  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Цитата:
Сообщение от Vadik Посмотреть сообщение
А код, который отыщет свободные диапазоны RecId в компании на объеме данных, сопоставимом с постановкой задачи (500Гб) - будет?
Поделюсь всем, что имею на эту тему, без утайки. Я готовил диапазоны дыр в своем любимом MS Access и далее заполнял ими таблицу RECIDHOLES в схеме Аксапты в Оракле. В Аксесе обработка очень простая: в одну табличку собираем все существующие RecId и затем в другую складываем диапазоны НЕсуществующих RecId, т.е. дыры.

Для этих манипуляций у меня имеется джоб оббегания по таблицам Аксапты и сохранения данных в Аксесе:
X++:
static void Job350_AllRecIds(Args _args)
{
    str             strFileName = @'C:\AxForumTests\Gustav\RecId.mdb';
    str             strFolderName;

    Filename        filename;
    FilenameType    filenameType;
    FilePath        filePath;
    container       conPath;

    COM             dbe, db;
    COM             cnn, rst;
    COM             flds, fld;

    int             i, nLines;
    int             timeFullStart, timeFullFinish;

    Dictionary      dictionary = new Dictionary();
    TableId         tableId;
    DictTable       dictTable;
    Common          common;
    int             row, timeStart;
    int             recordCount;

    #CCADO
    #define.dbLangGeneral(';LANGID=0x0409;CP=1252;COUNTRY=0')

    void recreateAccessTable(str _table, str _fields)
    {
        try
        {
            nLines = infolog.line();
            db.Execute('DROP TABLE [' + _table + ']');
        }
        catch   (Exception::Error)
        {
            infolog.clear(nLines);
        }

        db.Execute( 'CREATE TABLE [' + _table + '] (' + _fields + ')' );
    }
    ;

    timeFullStart = timenow();

    // parse file name -----------------------------------------------------------------------------
    [filePath, filename, filenameType] = fileNameSplit(strFileName);

    conPath = str2con_RU(filePath, '\\'); // you can use str2con if you do not have Russian DIS-layer

    if (subStr(filePath,2,1) != ':')
    {
        info(@'For drive use only one character syntax: C:\...! Do not use: \\server\folder\...!');
        return;
    }

    // create folders if they not exist ------------------------------------------------------------
    if (conlen(conPath)>1)
    {
        strFolderName = conpeek(conPath,1); // C:

        for (i=2; i<=conlen(conPath); i++)
        {
            strFolderName += strFmt(@'\%1', conpeek(conPath,i)); // C:\AxForumTests...

            if (!WinAPI::folderExists(strFolderName))
                 WinApi::createDirectory(strFolderName);
        }
    }

    // create MDB-file if not exists ---------------------------------------------------------------
    dbe = new COM('DAO.DBEngine.36');

    if (!WinAPI::fileExists( strFileName ))
    {
        try
        {
            nLines = infolog.line();
            db = dbe.CreateDatabase(strFileName, #dbLangGeneral);
        }
        catch  (Exception::Error)
        {
            infolog.clear(nLines);
            info( 'Probably mdb-file already exists and is open at the moment!' );
        }
    }
    else
    {
        db = dbe.OpenDatabase( strFileName );
    }

    // (re)create tables in MDB-file ----------------------------------------------------------------
    recreateAccessTable(
        'UsedRecId',
            'TblId          LONG,   ' +
            'DataAreaId     TEXT(3),' +
            'RecId          LONG    ' );

    recreateAccessTable(
        'RecIdHoles',
            'FromRecId      LONG,   ' +
            'ToRecId        LONG    ' );

    db.Close();
    db = null;
    dbe = null;

    // export EmplTable (a few fields) from Axapta to similar table in Access ----------------------
    cnn = new COM('ADODB.Connection');
    cnn.connectionString('Provider=Microsoft.Jet.OLEDB.4.0;' +
                         'Data Source=' + strFileName);
    cnn.Open();

    rst = new COM('ADODB.Recordset');
    rst.LockType(#adLockOptimistic);
    rst.Open('UsedRecId', cnn);

    flds = rst.Fields();

    row = 0;
    for (i=1; i<= dictionary.tableCnt(); i++)
    {
        tableId   = dictionary.tableCnt2Id(i);

        dictTable = new DictTable(tableId);

        print strFmt('%1 -- %2 -- %3', tableId, dictTable.name(), row);

        // если в очередной таблице нет записей
        // то переходим к следующей
        try
        {
            nLines = infolog.line();
            recordCount = new SysDictTable(tableId).recordCount();
        }
        catch //может случиться, если таблица есть в репозитарии, но нет в базе
        {
            infolog.clear(nLines);
            recordCount = 0;
        }
        if (! recordCount)
            continue;

        common = dictTable.makeRecord();

        // цикл по записям таблицы (МОЖНО ОГРАНИЧИТЬ ДИАПАЗОН ПОИСКА RecId ПО ВСЕМ ТАБЛИЦАМ)
        while select common
            where common.RecId >= 800000001 && common.RecId <= 900000000
        {
            row++;
            rst.AddNew();
                fld = flds.Item('TblId'     ); fld.Value(tableId);
                fld = flds.Item('DataAreaId'); fld.Value(common.dataAreaId);
                fld = flds.Item('RecId'     ); fld.Value(common.RecId);
            rst.Update();
        }
    }

    rst.Close();
    rst = null;

    cnn.Close();
    cnn = null;

    timeFullFinish = timenow();
    box::info(strfmt('Total running time: %1 sec -- Records: %2', timeFullFinish - timeFullStart, row));
}
Обратите внимание, что можно задать диапазон поиска RecId и таким образом разобраться с базами Аксапты любых объемов, просто деля задачу на части (т.е. на диапазоны поиска RecId по всем таблицам) и даже создавая для каждой части отдельный mdb-файл. Тут необходимо помнить, что имеется ограничение на максимальный размер mdb-файла = 2 Гб (по крайней мере, раньше было такое ограничение, не знаю как сейчас).

Далее уже в Аксесе создаем VBA-модуль со следующей начинкой:
Код:
Option Compare Database
Option Explicit

Sub packHolesInIntSequenceToRanges()
'===================================================================
' Сворачивание "дырок" в последовательности целых чисел в диапазоны
'===================================================================

    Dim cnn As ADODB.Connection
    Dim rstSource As ADODB.Recordset
    Dim rstRanges As ADODB.Recordset
   
    Dim rangeNum   As Integer 'счетчик непрерывных диапазонов
    
    Dim curr      As Long  'текущее (очередное) значение из последовательности
    Dim prev      As Long  'предыдущее значение (конец непрерывного диапазона)
    
    Const minDelta As Long = 25    'минимальный размер непрерывного диапазона
                                   '(диапазоны меньшей длины игнорируются)
                                   
    Const startFrom As Long = 15082620 '15000001 'стартовое значение процессса - либо "дырка", либо нет
                                       'первый prev все равно на 1 меньше и считается не дыркой
    Const stopOn As Long = 19997895 'следующий NextVal - 25*кол-во пользователей - последний curr - считается дыркой
    
    
    Set cnn = Application.CurrentProject.AccessConnection
    
    Set rstSource = New ADODB.Recordset
    With rstSource
         Set .ActiveConnection = cnn
         .source = "SELECT * FROM UsedRecId WHERE DataAreaId=""ppp""" & _
                   " AND RecId >= " & CStr(startFrom) & _
                   " AND RecId <= " & CStr(stopOn) & " ORDER BY RecId"
                   
         .Open
    End With
    
    Set rstRanges = New ADODB.Recordset
    With rstRanges
         Set .ActiveConnection = cnn
         .source = "RecIdHoles"
         .LockType = adLockOptimistic
         .Open
    End With
       
    rangeNum = 0
    prev = startFrom - 1
    
    Do While Not rstSource.EOF
    
        curr = rstSource("RecId").Value
        
        If curr - prev > 1 Then
            'записываем диапазон, закончившийся на предыдущем
            If curr - prev - 1 >= minDelta Then 'здесь именно минус 1
                rangeNum = rangeNum + 1
                Debug.Print rangeNum
                rstRanges.AddNew
                    rstRanges("FromRecId").Value = prev + 1 'From
                    rstRanges("ToRecId").Value = curr - 1 'To
                rstRanges.Update
            End If
        End If
        
        prev = curr
        rstSource.MoveNext
    Loop
    
    'для последнего диапазона
    curr = stopOn + 1 'не дырка
    If curr - prev > 1 Then
        'записываем диапазон, закончившийся на предыдущем
        If curr - prev - 1 >= minDelta Then 'здесь именно минус 1
            rangeNum = rangeNum + 1
            Debug.Print rangeNum
            rstRanges.AddNew
                rstRanges("FromRecId").Value = prev + 1 'From
                rstRanges("ToRecId").Value = curr - 1 'To
            rstRanges.Update
        End If
    End If
    
    Set rstRanges = Nothing
    Set rstSource = Nothing
    Set cnn = Nothing
    
End Sub
Собственно, эта единственная процедура packHolesInIntSequenceToRanges и запускается. Она ищет непрерывные последовательности дырок RecId длиной не менее 25 в аксесной табличке UsedRecId и складывает их в аксесную же табличку RecIdHoles (таблички были созданы в mdb-файле упомянутым выше аксаптовским джобом, а UsedRecId еще и заполнена им же).

Последующий перенос данных из аксессной RecIdHoles в RECIDHOLES в схеме Аксапты - любым желаемым способом, вплоть до приаттачивания RECIDHOLES к файлу MDB как таблицы ODBC и элементарного ручного копипаста из одной таблицы в другую. Или через Excel - вставляем данные RecIdHoles в колонки A и B, в ячейке C1 пишем формулу ="INSERT INTO RECIDHOLES VALUES ("&A1&","&B1&");" и копируем ее на следующие строки; далее копируем содержимое колонки С в QA (для MS SQL Server) или TOAD (для Oracle) и исполняем этот набор операторов INSERT.

Последний раз редактировалось Gustav; 08.12.2010 в 11:28.
За это сообщение автора поблагодарили: mazzy (2), Logger (10), lev (5), vml (1), S.Kuskov (8).
Старый 08.12.2010, 11:37   #15  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
Цитата:
Сообщение от Gustav Посмотреть сообщение
Код:
CREATE TABLE RECIDHOLES
(
  FROMRECID NUMBER(10),
  TORECID   NUMBER(10)
)
..
Код:
CREATE OR REPLACE TRIGGER SystemSequences_TBU
BEFORE UPDATE
ON SYSTEMSEQUENCES REFERENCING NEW AS New OLD AS Old
FOR EACH ROW
WHEN (
SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'ppp' 
      AND Old.Id = -1 
      AND Old.TabId = 0
      )
А если компаний более одной?
Код:
while select common
where common.RecId >= 800000001 && common.RecId <= 900000000
{
    row++;
    rst.AddNew();
    fld = flds.Item('TblId'     ); fld.Value(tableId);
    fld = flds.Item('DataAreaId'); fld.Value(common.dataAreaId);
    fld = flds.Item('RecId'     ); fld.Value(common.RecId);
    rst.Update();
}
А сколько времени нужно чтобы отработал этот код в компании с числом записей в несколько сотен миллионов? А триггер проверялся для случая с несколькими активными "писателями" (толстыми клиентами и AOS-ами) в SystemSequences ? Я конечно предвижу ответ что и диапазоны можно в разрезе компаний хранить и триггер переписать, но что-то вся обвязка становится слишком похожей на самолет-этажерку. Не проще вправить мозг стандартному "дефрагментатору" и успокоиться еще на пару лет?
P.S. Особенно если в простых случаях (нет виртуальных компаний и правильное наследование типов для ссылок по RecId) все и так само собой работает
__________________
-ТСЯ или -ТЬСЯ ?
За это сообщение автора поблагодарили: Gustav (3).
Старый 08.12.2010, 12:45   #16  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Цитата:
Сообщение от Vadik Посмотреть сообщение
А если компаний более одной?
Ну, несколько компаний можно прописать либо через условие триггера:
Код:
WHEN (
(SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'ppp' AND Old.Id = -1 AND Old.TabId = 0)
OR
(SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'rrr' AND ...)
OR
(SUBSTR(NLS_LOWER(Old.DataAreaId),1,3) = 'sss' AND ...)
     )
либо создав для каждой компании свой отдельный триггер (их вроде может быть несколько на одно событие). Соответственно, внутри придется развить логику на эти несколько компаний. Но обычно критической по заканчивающимся RecId является какая-то одна. У нас, например, есть вторая (помимо dat, конечно), в которую транслируются данные из основной для ГААПа, так там любо-дорого посмотреть - дыр практически нет, всё последовательно и текущее максимальное значение раз в 30 меньше текущего максимального из основной (русской) компании. А в основной построишь какую-нибудь подробную оборотно-сальдовую ведомость по складу или ОС - 100 тыщ RecId улетело в трубу (Привет, GMCS!)
Цитата:
Сообщение от Vadik Посмотреть сообщение
А сколько времени нужно чтобы отработал этот код в компании с числом записей в несколько сотен миллионов?
Навскидку не помню... Не одна минута конечно, но и не какое-то невероятное кол-во времени (типа несколько суток, недель), способное удручить и заставить отказаться от эксперимента. Я обычно оставлял на ночь, утром приходил - заказанная порция была готова. Порция внушительная, в джобе же виден диапазон реального перебора в 100 миллионов (от 800 до 900 млн). Для 500 млн. пусть будет нуу... часов 10-12...Не имею возможности сейчас проверить, но тот, кто заинтересуется, может погонять на своей системе и сообщить получающееся время.

И совсем же необязательно сразу искать дыры во всем диапазоне от 0 до текущего значения NextVal. Допустим, текущий NextVal в районе 1.5 миллиардов, тогда можно, не торопясь, составить таблицу дыр для RecId = 0..500 млн, настроить триггер, запустить его в эксплуатацию. А через годик обработать уже диапазон 500 млн..1 млрд и т.д. Это ж всё разовые задачки - обработал, запустил, забыл ("украл, выпил, в тюрьму" (с))
Цитата:
Сообщение от Vadik Посмотреть сообщение
А триггер проверялся для случая с несколькими активными "писателями" (толстыми клиентами и AOS-ами) в SystemSequences ?
Мы без АОСа, на двухзвенке, клиенты у нас все толстые. Несколько одновременно активных "писателей" захватывали свои порции по 25 штук вполне благополучно.
Цитата:
Сообщение от Vadik Посмотреть сообщение
..но что-то вся обвязка становится слишком похожей на самолет-этажерку. Не проще вправить мозг стандартному "дефрагментатору" и успокоиться еще на пару лет?
Я ж только предлагаю, не навязываю же...
Старый 08.12.2010, 12:59   #17  
Владимир Максимов is offline
Владимир Максимов
Участник
КОРУС Консалтинг
 
1,656 / 1158 (42) ++++++++
Регистрация: 13.01.2004
Записей в блоге: 3
Цитата:
Сообщение от Vadik Посмотреть сообщение
А если компаний более одной?
А сколько времени (...)? А триггер проверялся для случая (...)? (...) Не проще вправить мозг стандартному "дефрагментатору" и успокоиться еще на пару лет?
Я тут за автора попробую ответить.

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

Теперь по вопросам:

1. Насчет компаний, сам же и ответил. Не вижу особых проблем добавить фильтр по компаниям

2. Время выполнения определения "дыр"

Очевидно, по крайней мере, будет не больше, чем время дефрагментации. Кроме того, очевидно, есть предмет для оптимизации алгоритма. Чего не скажешь про алгоритм дефрагментации.

Ведь поиск дыр - это чистые запросы, а дефрагментация - это запросы+модификация. Если запросы еще можно оптимизировать (да и сам алгоритм изменить), то с модификацией особых вариантов нет. Надо модифицировать ВСЕ записи. Быстро это выполнено быть не может

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

3. Преимущества, по сравнению с дефрагментацией (явно не прозвучало, но вопрос очевиден)

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

Цитата:
Сообщение от Vadik Посмотреть сообщение
P.S. Особенно если в простых случаях (нет виртуальных компаний и правильное наследование типов для ссылок по RecId) все и так само собой работает
Как мне кажется, это из области фантастики. Большинство российских внедрений кастомизировано по самое "не балуйся". Причем не один раз и самыми разным людьми с очень разным опытом. Поэтому простых случаев не может быть "по определению"
За это сообщение автора поблагодарили: Gustav (12).
Старый 08.12.2010, 13:23   #18  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
И правда, если
X++:
    EDT  ,  RecId
в собственных же модификациях, я пожалуй агитацию за советскую власть в этой ветке закончу
Цитата:
Судя по не утихающим до сих пор дискуссиям, в решении данной проблемы простота и надежность не являются приоритетными критериями
да уж, богата земля рассейская Кулибиными
__________________
-ТСЯ или -ТЬСЯ ?
Старый 08.12.2010, 13:14   #19  
glibs is offline
glibs
Member
Сотрудники компании It Box
Most Valuable Professional
Лучший по профессии 2011
Лучший по профессии 2009
 
4,942 / 911 (40) +++++++
Регистрация: 10.06.2002
Адрес: I am from Kyiv, Ukraine. Now I am in Moscow. For private contacts: glibs@hotmail.com
Цитата:
Сообщение от Vadik
...
Не проще вправить мозг стандартному "дефрагментатору" и успокоиться еще на пару лет?
...
Судя по не утихающим до сих пор дискуссиям, в решении данной проблемы простота и надежность не являются приоритетными критериями.
__________________
С уважением,
glibs®
Старый 08.12.2010, 00:22   #20  
Vadik is offline
Vadik
Модератор
Аватар для Vadik
Лучший по профессии 2017
Лучший по профессии 2015
 
3,631 / 1849 (69) ++++++++
Регистрация: 18.11.2002
Адрес: гражданин Москвы
Цитата:
Сообщение от Ivanhoe Посмотреть сообщение
Возможно, у автора появился повод для перехода на новую версию?
- неспортивно
- неуплаченная "дань за двенадцать лет" (поддержка) зело велика будет
- перевод тройки на 2009 - челлендж покруче любой дефрагментации RecId
__________________
-ТСЯ или -ТЬСЯ ?
За это сообщение автора поблагодарили: CDR (1).
Теги
ax3.0, recid, дефрагментирование recid, законченный пример, полезное

 

Похожие темы
Тема Автор Раздел Ответов Посл. сообщение
if (record) vs if (record.RecId) kashperuk DAX: Программирование 18 27.11.2008 18:53
поля, содержащие RecId somebody DAX: Программирование 15 16.05.2008 17:50
Что лучше select RecId или select TableId Logger DAX: Программирование 9 02.06.2007 15:13
aEremenko: Дефрагментация RecID Blog bot DAX Blogs 2 06.03.2007 22:25
Два RecId у одной записи таблицы sparur DAX: Программирование 33 18.12.2006 15:56

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 18:14.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.