Цитата:
Сообщение от
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.