Vlad Karpov homepage


Sorry for bad English Простите за плохой Русский

FAQ

ЧаВо

 

VK DBF

VK NLO

VK REPORTS

VK MAPI

 

VK DBF

			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.

		

VK NLO

		

VK REPORTS

--------------------------------------
     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 понятно
		

VK MAPI

		
Сайт создан в системе uCoz