Vlad Karpov homepage
| Sorry for bad English | Простите за плохой Русский |
Range |
E>>>>>> Здравствуйте, Влад.
E>>>>>> Маленький вопрос...
E>>>>>> Как отобрать записи с пустым полем типа дата
E>>>>>> (в DBU'шке это поле такое - ' / / ')?
E>>>>>> Я пытаюсь делать setrange, но не могу понять как обозначить пустое
E>>>>>> поле даты: NTX3.Indexes[5].SetRangeFields('DATE_G',[???]) ?
E>>>>>> Да, кстати, как делать setrange по двум и более полям?
E>>>>>> Это зависит от того, как эти поля входят в индекс? - преобразуются в
E>>>>>> другой тип, порядок полей или как?
K>>>>> Сколько бы полей не входило в выражение ключа индекса, после расчета
K>>>>> этого ключа в NTX ВСЕГДА происходит преобразование значения ключа в
K>>>>> строку. NULL значения (твоя дата) преобразуются в строку пробелов
K>>>>> длинной, зависимой от типа данных. Для стандартной даты это строка из 8
K>>>>> пробелов. Именно эти текстовые строки и хранит файл NTX в качестве
K>>>>> ключей.
K>>>>> Теперь про Range. Range в конечном итоге накладывается на эти ключи,
K>>>>> т.е. на строки. Все функции, которые устанавливают Range сводятся к
K>>>>> NTXRange.HiKey := Key;
K>>>>> NTXRange.LoKey := Key;
K>>>>> NTXRange.ReOpen;
K>>>>> где HiKey и LoKey - строки. Range выбирает из индекса ключи которые
K>>>>> попадают в интервал путем обычного сравнения строк, причем длинна
K>>>>> HiKey и LoKey может и отличаться от длинны ключа в индексе.
K>>>>> Тебе надо либо вручную устанавливать строковые границы Range, либо
K>>>>> использовать NTX3.Indexes[5].SetRangeFields в варианте с массивом
K>>>>> вариантов, и там в качестве значений полей указывать NULL.
K>>>>> Да, в этом массиве вариантов поля должны быть перечислены в том
K>>>>> порядке, в каком к ним обращается парсер (чаще всего это порядок
K>>>>> следования полей в выражении ключа, но необязательно).
E>>>> С пустой датой получилось, спасибо: установил верхнюю и нижнюю границы
E>>>> в ' '.
E>>>> А если я хочу отобрать по двум полям из индекса (например, ключ
E>>>> такой - 'dtos(DATE_G) + ID_EMIT', первое поле - дата, второе -
E>>>> символьный идентификатор) с диапазоном дат 27.08.2003-28.08.2003 и
E>>>> постоянным (или с диапазоном) идентификатором:
E>>>> NTX3.Indexes[5].SetRangeFields('DATE_G;ID_EMIT',[StrToDate('27.08.2003'),'00000',StrToDate('28.08.2003'),'00000'])
E>>>> В результате получается отбор только по первой дате.
E>>>> Посмотрел парсер - там, что верхнему, что нижнему ключу, присваиваетс
E>>>> одно значение '2003082700000', что и соответствует двум первым значениям
E>>>> массива.
E>>>> ???
K>>> Естественно.
K>>> Ты смотрел реализацию NTX3.Indexes[5].SetRangeFields в исходниках?
K>>> Вот она:
K>>> Key := TrimRight(FKeyParser.EvaluteKey(FieldList, FieldValues));
K>>> NTXRange.HiKey := Key;
K>>> NTXRange.LoKey := Key;
K>>> NTXRange.ReOpen;
K>>> И верхней и нижней границе присваивается одно значение ключа,
K>>> посчитанное по значениям StrToDate('27.08.2003'),'00000'.
K>>> И это не случайно, а для совместимости с SetRange в
K>>> VCL.
K>>> Тебе надо делать так:
K>>> NTXRange.HiKey := '2003082700000';
K>>> NTXRange.LoKey := '2003082800000';
K>>> NTXRange.ReOpen;
K>>> С пустой датой так:
K>>> NTXRange.HiKey := ' 00000';
K>>> NTXRange.LoKey := '2003082800000';
K>>> NTXRange.ReOpen;
E>> Так то оно так, но не катит.
E>> Пробовал я уже.
E>> Для пустых дат сделал так:
E>> (Возможно, здесь где напортачил?)
E>> NTXRange := TVKNTXRange.Create;
E>> NTXRange.NTX := NTX3.Orders[5];
E>> //устанавливаем верхнюю и нижнюю границы
E>> NTXRange.HiKey := ' ';
E>> NTXRange.LoKey := ' ';
E>> NTXRange.Active := true;
E>> и для дипазона дат как у Вас пробовал,
E>> а отбираются !все! записи
K> Какой еще NTXRange := TVKNTXRange.Create; !!!!
K> Ты чо!
K> with NTX3.Orders[5].NTXRange do begin
K> HiKey := ' 00000';
K> LoKey := '2003082800000';
K> ReOpen;
K> end;
8)))
Чайник)
Понял
Спасибо...
|
Влад, а теперь маленький вопрос. Меня очень волнует скорость доступа к данным. Если можно хотя бы в двух словах, твои рекомендации. Я так понимаю, этот компонент ты делаешь не просто так и он имеет какое-то практическое применение. Заранее Спасибо. Игорь Самусь |
1)
1. Если есть индексы, обязательно сделай Flock - это кэширует
индексные страницы, но имей в виду, что они все будут копиться в
памяти, до тех пор, пока не сделаешь Unlock. (Для сброса
индексных страниц на диск можно использовать
for i := 0 to Indexes.Count - 1 do Indexes[i].Flush;
после изменения, скажем каждой 1000 ключей). Или используй режим
Exclusive.
2. Есть проперт
property FastPostRecord: Boolean
Она включает/выключает перечитку DataSet-а в операции Post,
тебе надо выключить (FastPostRecord := true)
3. Лучше использовать следующую конструкцию (с точки зрени
быстродействия):
for i:=1 to Table1.RecordCount do
begin
Table1.SetTmpRecord(i);
Table1.Edit;
что-то делаем
Table1.Post;
end;
Table1.CloseTmpRecord;
2)
CashedUpdates нету.
Есть только буферизация добавленных записей.
procedure BeginAddBuffered(RecInBuffer: Integer);
procedure FlushAddBuffer;
procedure EndAddBuffered;
Добавлю, еще пару слов.
Indexes[i].Flush; сделать не получиться, она protected :)
буферизация добавленных записей сильно увеличивает скорость массового
добавления записей (в 4 - 6 раз).
Поиграйся BufferSize. Если нет индексов операции чтения идут челиком в
буфер. (Если есть, то только по записям.)
Для быстрого прохождения по таблице есть метод DBEval с событием
OnDBEval.
Для быстрого прохождения по индексу есть метод SubIndex с событием OnSubIndex
АД> Често говоря property FastPostRecord: Boolean не очень то и ускоряет.
Да, это не очень сильное свойство, оно просто отключает перерисовку
DataSet-а после Post, хотя слегка помогает.
У тебя вообще, в принципе, какая версия работает?
АД> // Я понимаю что я сделал что-то не-то но LookUp работает в любых вариантах
АД> // Locate не проверял
А какие такие варианты у lookup?
Про FindKey:
Я не нашел куска программы в FindKey
АД> Было
АД> // if ( item.page <> 0 ) then Pass(item.page);
АД> // Result := false;
там только в конце процедурины Pass:
item := pNTX_ITEM(pChar(page) + page.ref[page.count]);
if ( item.page <> 0 ) then Pass(item.page);
Result := false;
похоже вроде....
АД> Ты забыл про
АД> BeginAddRecord;
АД> EndAddRecord;
АД> тоже ускоряет,
Я не забыл, я не сказал, но это гораздо менее сильный ход чем
BeginAddBuffered.
АД> Но я не понял одного эта вещь не работает для визуальных компонентов или надо делать Refresh?.
Ситуация такая: TDataSet сам по себе очень тормозная вешь. Поэтому
любые примочки, которые не используют его виртуальные функции, а
обращаются непосредственно к хранилищу данных по определению работают
быстрее.
К ним и относятс
procedure BeginAddRecord;
procedure EndAddRecord;
procedure BeginAddBuffered(RecInBuffer: Integer);
procedure FlushAddBuffer;
procedure EndAddBuffered;
это сильно ускаряет в 3 - 6 раз, если
необходимо добавить сразу и много.
procedure SetTmpRecord(nRec: DWORD);
procedure CloseTmpRecord;
Независимая от DataSet установка текущей записи, например:
SetTmpRecord(5);
Edit;
Fields[0].AsInteger := ...
Post;
CloseTmpRecord;
Будет работать гораздо быстрее чем
Recno := 5;
Edit;
Fields[0].AsInteger := ...
Post;
Независимый от DataSet проход по таблице DBEval с возбуждением событи
OnDBEval. Событию передается номер записи, но внутри события не надо
делать SetTmpRecord/CloseTmpRecord. Это уже сделано автоматически
внутри DBEval.
Независимый от DataSet проход по индексу SubIndex с возбуждением событи
OnSubIndex. Осуществляет проход по индексу в некоторых границах. В
отличае от DBEval внутри события OnSubIndex необходимо делать
SetTmpRecord/CloseTmpRecord, ну если Вас вообще интересует запись.
но за удавольствие надо платить, все DB-Aware контролы не замечают
того, что Вы делаете с DataSet-ом.
Да, и еще, если нет индексов (или SetOrder(0)), операции чтения таблицы с диска проходят
блоками, размером BlockSize
АД> Indexes[i].Flush; сделать не получиться, она protected :)
АД> Так можно делать или нет?
Нет, нельзя, оно protected. Оно делается автоматом из UnLock.
С индексами следующая тонкость:
Всегда включина буферизация индексных страниц. Компонент загружает в
память все страницы, к которым было когда либо хоть одно обращение (
это вскорости буду переделывать на ограниченное число страниц в памяти).
В заголовке индекса храниться номер версии индекса. При любом
обращении к индексу всегда сравниваются версии хранящегося в памяти
индекса и текущего из только что считанного заголовка. Если они совпадают,
то ничего не происходит, страницы продолжают копиться в памяти, если не
совпадают, то компонент "забыват" о накопленном буфере и начинает копить
его заново.
Теперь: компонент менят таблицу и вместе с ней меняется индекс.
Сдесь 2 варианта:
RLock и FLock, с соответсвующим UnLock.
Ну дык вот, на UnLock компонент сбрасывает все измененные страницы на
диск (Flush) и увеличивает на 1 номер версии индекса и тоже записывает его на
диск вместе с корневой страницей.
А вывод очевиден:
Делать FLock, вместо RLock, где это только возможно. Unlock после
модификаций. И в промежутке между FLock/Unlock делать как можно больше
этих модификаций.
Edit автоматом делает Rlock
Post снимает RLock (но не Flock!!), если Rlock - единственна
блокировка, то делает Indexes.Flush
Unlock снимает все блокировки (и FLock тоже) и делает Indexes.Flush
всегда.
Rlock и FLock реентерабельны, их можно вызывать хоть милион раз одно в
другом на одни и те-же записи, если они уже заблокированны ничего не
произойдет (в одном экземпляре компонента).
Ну, и последнее есть массив заблокированных записей:
property LockRecords: TList read FLockRecords;
|
LOCKING SCHEMA IN VKDBF |
Clipper share mode
AccessMode = 66
Clipper exclusive mode
AccessMode = 18
All locks realised by call OS (Windows, DOS, ...) functions FileLock(handler, offset,
NumBytes) and FileUnLock(handler, offset, NumBytes)
There are 3 lock functions in VKDBF
protected LockHeader() lock 1 byte for offset 1000000000
public RLock(nRec) lock 1 byte for offset 1000000000 + nRec
public Flock() lock 1000000000 bytes for offset 1000000000
And 3 unlock
protected UnlockHeader() unlock 1 byte for offset 1000000000
public RUnLock(nRec) unlock 1 byte for offset 1000000000 + nRec
public UnLock() unlock 1000000000 bytes for offset 1000000000
This VKDBF lock and unlock functions invoke from the next VKDBF
functions and procedures:
PROTECTED
protected InternalPost (invoke from public VKDBF.Post())
RLock(nRec)
physical writing on disk
RUnlock(nRec)
protected InternalAddRecord (invoke from public VKDBF.Post() !!!!!!!!
not from DataSet.Append or
DataSet.Insert;
public DataSet.AddRecord;
public VKDBF.AddRecord(...);
public VKDBF.EndAddRecord)
LockHeader()
RLock(LastRec + 1)
physical writing on disk
RUnlock(LastRec + 1)
UnLockHeader()
protected DeleteRecallRecord (invoke from public DataSet.Delete;
public VKDBF.DeleteRecord;
public VKDBF.RecallRecord;
in DBF record only marked as "DELETED",
the physical remove deleted records
happens when Pack() method call)
RLock(nRec)
physical writing on disk
RUnlock(nRec)
protected InternalLast (invoke from public DataSet.Last;)
LockHeader()
physical read from disk
UnLockHeader()
protected GetRecordCount (invoke from public DataSet.recordCount
property)
LockHeader()
physical read from disk
UnLockHeader()
PUBLIC
public FlushAddBuffer;
LockHeader()
physical writing on disk the block of records
UnLockHeader()
public SetAutoInc, GetCurrentAutoInc, GetNextAutoInc
LockHeader()
physical read/write from/on disk
UnLockHeader()
public Pack
LockHeader()
physical write on disk
UnLockHeader()
public Truncate
LockHeader()
physical write on disk
UnLockHeader()
public ReindexAll
LockHeader()
physical read from disk
UnLockHeader()
public DBEval
LockHeader()
loop by your scope
RLock(nRec)
fire event OnDBEval
RUnlock(nRec)
UnLockHeader()
Any read/write operation for index file do with
LockHeader() (for index file)
read/write operation in index file
UnLockHeader() (for index file)
WaitBusyRes:
when VKDBF determinate that resource is Busy (record, file, header)
VKDBF are waiting WaitBusyRes time (in milisec !!!!!) and raise
exception.
All aforesaid means:
you add record(s) to the table:
vkdbf.append;
fkdbf.field1.value := ..
...
vkdbf.post; //all locks and physical write do here !!!
//see InternalAddRecord
or you can add many recors
vkdbf.BeginAddBuffered(100)
i := 0
loop i 0..250
vkdbf.append;
fkdbf.field1.value := ..
...
vkdbf.post; //The recors accumulate in inner buffer.
//When i = 100 invike FlushAddBuffer
//(see FlushAddBuffer lock scema)
end loop
vkdbf.EndAddBuffered; //invike FlushAddBuffer for rest 50 recors
vkdbf.edit;
fkdbf.field1.value := ..
...
vkdbf.post; //all locks and physical write do here !!!
//see InternalPost lock scema
in addition you can lock record explicitly:
if vkdbf.RLock then begin
vkdbf.edit;
fkdbf.field1.value := ..
...
vkdbf.post; //Then RUnLock in post method release the record
end;
you can lock file explicitly:
if FLock() then begin
...
UnLock() //ATTANTION release all lock records
end;
And finally
there is a
property LockRecords: TList read FLockRecords;
this is a list all locked records by current instance of VKDBF.
|
|
|
|
--------------------------------------
ICQ History Log For:
98903427 Pavel
Started on Tue Aug 12 11:17:44 2003
--------------------------------------
Pavel 12.08.20 10:12 Здравствуте Влад!
Не сильно заняты?
VKar 12.08.20 10:12 Здравствуй
Мы знакомы?
Pavel 12.08.20 10:14 Да я одно время у вас просил разрешения на
публикацию вашей статьи по Интергации MS Office с
CAVO
VKar 12.08.20 10:14 Я давно не пишу на CAVO
Pavel 12.08.20 10:15 :) да я не пока КАВО
Pavel 12.08.20 10:15 я по VKReportу хотел задать вопрос
Pavel 12.08.20 10:15 если конечно можно
VKar 12.08.20 10:15 Задавай
Pavel 12.08.20 10:16 Можно ли узнать в VKReporte для Excel таблицы с
какой строки начинается секция и на какой
заканчиваетс
Pavel 12.08.20 10:17 мне например нужно сделать итоговую сумму по
столбцам.
VKar 12.08.20 10:19 Можно.
Для Excel-я надо использовать событие
OnDataRequest, там в параметрах есть абсолютные и
относительные координаты.
Но сумму надо считать не через формулу Excel
(формулу в ячейку ты не вделыешь, только через
строку с последующим пересчетом)
Pavel 12.08.20 10:22 т.е. будет только последний конечный результат
VKar 12.08.20 10:22 Суммы считаются вручную в событиях
OnSectionComplet (или OnSectionEnd) на BODY,
вручную обнуляются, если это суммы по группам и
подгруппам,
и вручную же выводятся в эти самые группы,
подгруппы и футоры как обычно по меткам
VKar 12.08.20 10:26 в Excele метки лучше делать числовые, но форматы
ставить какие надо для ячееек. типа нужна дата,
ставишь в ячейку 1(2, 3, ...) а формат делаешь
даты
В OnDataRequest тебе приходит 1(2, 3, ...) ты в
ответе в вариант пихаешь Excel дату (обычный
флоат- количество дней с какого-то 70 года
(посмотри сам конкретно), дробная часть - часть
суток) получаеш то что надо
VKar 12.08.20 10:27 Это же ты на моей странице спрашивал как в Excel
воткнуть что-то кроме строки
Pavel 12.08.20 10:28 в форуме?
VKar 12.08.20 10:28 да
Pavel 12.08.20 10:28 последнаяя тема мо
Pavel 12.08.20 10:29 жалко что нельзя вставить формулу Excel :(
VKar 12.08.20 10:30 Там сложный парсер надо писать для формул и нет
горантии, что мелкософт не изменит логику его
работы в будующем
Pavel 12.08.20 10:32 это понятно, ну нельзя так нельз
Pavel 12.08.20 10:36 а если в ячейку вставить строку,что-то типа
такого '=СУММА(E6:E100)', сработает или нет?
Pavel 12.08.20 10:37 еще один вопрос, а обязательно использовать
контейнер и вообще для чего он нужен?
VKar 12.08.20 10:39 Сработает, но в конце на OnEndReport надо сделать
нечто, чтобы втолковать Excel-ю что это не строка
а формула, я не помню чего конкретно, типа
пересчитать, али чего-то еще, у меня студент
занимался этим, но я конкретно не знаю что он
делал
Pavel 12.08.20 10:40 тогда попробую
VKar 12.08.20 10:46 Контейнер использовать не обязательно
Нужен он если одни и теже отчеты нужно делать в
разные форматы, подпиховаешь ему разные бланки
(разные обекты отчетов) а он их по одному коду
заполняет
Но там свои тонкости, типа в RTF/HTML/TXT отчете
нет события OnDataRequest (точнее есть-то оно
есть, но никогда не вызывается), поэтому если
хочешь чтоб один код работал и на RTF и на Excel
надо использовать только OnRequestByNumber(Name)
Pavel 12.08.20 10:46 Спасибо что раъяснили, буду переваривать.
А репорт хороший получился, работает быстрее чем
то как писал я.
Pavel 12.08.20 10:47 и еще появился вопрос :)
VKar 12.08.20 10:48 Задавай
Pavel 12.08.20 10:49 У вас в примере вы используете
ReportContainer1.OnOldSectionComplit для чего он
нужен?
if Assigned(ReportContainer1.OnOldSectionComplit)
then
ReportContainer1.OnOldSectionComplit(self,
SectionName, SectionNum)
else if SectionName = 'BODY' then
begin
Inc(eee);
Caption := IntToStr(eee);
end;
end;
VKar 12.08.20 10:51 Это он в Caption выводин количество выведенных
секций BODY
Pavel 12.08.20 10:52 понятно
|
|
|
|
|