Неоднократно занимался подобной штукой в одиночестве на тестовой базе (Zabr абсолютно прав!
), причем, последний раз не так давно - 16.12.2009, как следует из условия в while select (см. в джобе). После постинга пробегался по всем таблицам Аксапты, имеющим записи и запоминал из них несколько (например, 200) самых последних по RecId. Дальше выгружал в Excel, сортировал по RecId и смотрел записи в районе тех RecId, в которых я был уверен, что они относятся к исследуемой операции (обычно известны RecId ранесенных записей из LedgerTrans). Пару раз обнаруживал таблицы, о которых откровенно не знал, что они задействованы при данной операции!
Вот мой джоб:
X++:
#CCADO
#define.rowLimit( 200 )
static void Job235_whereRecId(Args _args)
{
Dictionary dictionary = new Dictionary();
TableId tableId;
DictTable dictTable;
Common common;
COM rst, flds, fld;
int row, i, timeStart;
;
timeStart = timenow();
rst = AdoRst::openRecordsetInMemory([
['TableId' , #adInteger ],
['TableName' , #adVarChar ],
['RecId' , #adInteger ]]);
flds = rst.Fields();
// цикл по таблицам
for (i=1; i<= dictionary.tableCnt(); i++)
{
tableId = dictionary.tableCnt2Id(i);
dictTable = new DictTable(tableId);
print strFmt('%1 -- %2', tableId, dictTable.name());
// если в очередной таблице нет записей
// то переходим к следующей
if (! new SysDictTable(tableId).recordCount())
continue;
common = dictTable.makeRecord();
row = 0;
// цикл по последним записям таблицы
while select common order by RecId desc
where common.createdDate == 16\12\2009
&& common.createdBy == 'MNY'
{
row++;
if (row > #rowLimit) break;
rst.AddNew();
fld = flds.Item(0); fld.Value(tableId);
fld = flds.Item(1); fld.Value(dictTable.name());
fld = flds.Item(2); fld.Value(common.RecId);
rst.Update();
}
}
AdoRst::sendRecordsetToExcel(rst);
info(strfmt('Время выполнения: %1 сек', timenow()-timeStart));
}
А это к нему используемая статика (класс у меня, как видно выше, называется AdoRst):
* Метод openRecordsetInMemory можно взять в моем блоге:
http://axforum.info/forums/blog.php?b=60
* Метод sendRecordsetToExcel имеет следующий вид (и к нему еще внутри - deleteFilteredRecords):
X++:
// Created on 06 Ноя 2008 at 16:26:03 by KKU
// выводит в Excel переданный ADODB.Recordset (обычно это disconnected recordset в памяти)
// если записи выводятся на несколько листов, то рекордсет в процессе вывода "ломается"
// за счёт удаления уже выведенных в Excel записей
// возвращает false, если удалений записей не было (т.е. всё вывелось на один лист и рекордсет остался нетронутым)
// или true - если были удаления (rstWasChanged)
static boolean sendRecordsetToExcel( COM _rst,
int _maxRows = 65000,
int _maxColumns = 0)
{
COM xlApp = new COM('Excel.Application');
COM wbks = xlApp.Workbooks();
COM wbk = wbks.Add();
COM wkss = wbk.Worksheets();
COM wks;
COM rng;
COM cell;
COM column;
COM rngCols;
COMVariant columnWidth;
COM flds = _rst.Fields();
int i;
// разобраться здесь _maxColumns- то считается с единицы
int iMax = (_maxColumns) ? (min(_maxColumns,flds.Count())) : flds.Count();
int sheet = 1;
#CCADO
;
// раз уж CopyFromRecordset, как выясняется, выводит без фильтра,
// то на всякий случай еще и принудительно их удалим (а то вдруг починятся когда-нибудь у Microsoft)
_rst.Filter('');
_rst.Sort('');
while (true)
{
// добавляем лист, если нужно
if (sheet > wkss.Count())
wkss.Add(COMArgument::NoValue, wkss.Item(wkss.Count()));
wks = wkss.Item(sheet);
wks.Activate(); // для отображения ДАТЫ как ДАТЫ на 2-м и 3-м существующих листах
// выводим строку имен полей (1-я строка листа Excel)
rng = wks.Range('A1');
for (i = 1; i <= iMax; i++)
{
cell = rng.Offset(0, i-1);
cell.Value2( COM::createFromObject( flds.Item(i-1) ).Name() );
}
rng = rng.CurrentRegion();
COM::createFromObject( rng.Font() ).Bold(true); // делаем выведенные заголовки жирным шрифтом
// выводим данные, начиная со 2-й строки листа Excel
rng = wks.Range('A2');
if ( !(_rst.BOF() && _rst.EOF()) ) // если есть записи
{
_rst.MoveFirst();
if (_maxColumns)
rng.CopyFromRecordset( _rst, _maxRows, _maxColumns );
else
rng.CopyFromRecordset( _rst, _maxRows);
}
// подгонка ширины колонок (с ограничением не более 35)
rng = rng.CurrentRegion();
rngCols = rng.Columns();
COM::createFromObject( rngCols.EntireColumn() ).AutoFit();
for (i = 1; i <= iMax; i++)
{
column = COM::createFromVariant( rngCols.Item(i) );
columnWidth = column.ColumnWidth();
if (columnWidth.double() > 35) column.ColumnWidth(35);
}
// здесь накручено, чтобы не пользоваться RecordCount
// --------------------------------------------------
// попытка достичь конца файла, продвинувшись от начала на количество выведенных записей
// у нас это кол-во всегда одно - _maxRows, так как мы удаляем записи на каждом шаге
// точнее ПОСЛЕ каждого (поэтому, если лист один, то рекордсет сохраняется в первозданном виде)
if (!_rst.EOF())
_rst.Move(_maxRows, #adBookmarkFirst);
// если вдруг конец файла уже был достигнут ранее, то его обработает следующий оператор
if (!_rst.EOF())
{
AdoRst::deleteFilteredRecords(_rst, _maxRows);
sheet++;
}
else
{
break; // иначе завершаем цикл вывода на листы
}
}
xlApp.Visible(true);
if (sheet > 1)
return true; // rstWasChanged - если листов более одного, то исходный рекордсет не сохранен
else
return false;
}
/*
Удаляет "видимые" записи из текущего рекордсета.
Если рекордсет был отфильтрован, то будут удалены только записи, соответствующие фильтру.
Можно задать удаление не всех отфильтрованных записей,
а только несколько первых (_numRecords > 0).
В этом случае имеет смысл перед удалением применить сортировку,
чтобы гарантировать определенный порядок удаляемых записей.
*/
static int deleteFilteredRecords(COM _rst, int _numRecords = 0)
{
int affectedRecords = 0;
int i;
void deleteOneRecord()
{
affectedRecords++;
_rst.Delete();
_rst.MoveNext();
}
;
if (_rst.BOF() && _rst.EOF()) return 0; // если набор пуст - выходим
_rst.MoveFirst();
if (_numRecords)
{
for (i=1;i<=_numRecords;i++)
{
if (!_rst.EOF())
deleteOneRecord();
else
break; // если _numRecords больше реального числа записей
}
}
else
{
while (!_rst.EOF())
deleteOneRecord();
}
_rst.MovePrevious(); /* так надо, так как после последнего удаления
указатель находится за последней записью (EOF=true),
но при этом BOF=false - так вот для этой коррекции и надо,
чтобы стало и BOF=true, и EOF=true
если же в наборе остались записи,
то это действие поставит указатель на последнюю запись
*/
return affectedRecords; // кол-во удаленных записей
}