Показать сообщение отдельно
Старый 08.12.2010, 10:30   #22  
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).