Форум » Clipper » фильтрование базы с большим количеством записей » Ответить

фильтрование базы с большим количеством записей

SergeyKorotun: clipper 5.01 есть база следующей структуры: id n 7 name character 42 В этой базе более миллиона записей, name не повторяется. В get поле вводится несколько символов, потом после нажатия какой то комбинации клавиш выводится только та часть записей базы, в которых первые символы поля name совпадают с введенными в поле get. Так вот если применить set filter to. то фильтрация продолжается очень продолжительное время и поэтому для базы с большим количеством записей не подходит. Даже если дождаться и начать двигаться по записям, то переход от записи к записи не замедляется, так как база отсортирована по name. но когда с первой отображенной записи шагнуть выше или после последней отображенной шагнуть вниз - снова большая задержка. Причина задержек мне понятна. Вопрос: чем заменить set filter to? Переписывать нужные записи в массив тоже не подойдет, так как при небольшом количестве введенных символов в get поле количество записей, подходящих под критерий выборки может превысить максимальный размер массива.

Ответов - 53, стр: 1 2 3 All

Pasha: Вместо set filter to Name=cName надо использовать scope: команду set scope to cName или функцию OrdScope(0, cName) OrdScope(1, cName) Если используется clipper 5.2, то эти средства есть в rdd comix Если clipper 5.3 - то они есть и в обычных rdd dbfcdx/dbfntx Вместо scope можно просто использовать выборку: dbSeek(cName) while ! eof() .and. Name = cName ... skip enddo

Haz: Вместо фильтра в данном случае можно использовать условную индексацию, тормоза при перемещении по записям точно не будет

SergeyKorotun: Pasha пишет: Вместо set filter to Name=cName надо использовать scope: команду set scope to cName или функцию OrdScope(0, cName) OrdScope(1, cName) У меня Clipper 5.01 В NG не нашел ни SET SCOPE ни OrdScope Программу хотя и объемную надо написать, но одну, не хотелось бы разбираться с новыми версиями клиппера Если используется clipper 5.2, то эти средства есть в rdd comix Если clipper 5.3 - то они есть и в обычных rdd dbfcdx/dbfntx Вместо scope можно просто использовать выборку: dbSeek(cName) while ! eof() .and. Name = cName ... skip enddo А что внутри цикла? Создать временную базу? Неподходящие под фильтр записи должны быть скрытыми


SergeyKorotun: Haz пишет: Вместо фильтра в данном случае можно использовать условную индексацию, тормоза при перемещении по записям точно не будет А можно поподробнее и с примером. И если возможно, то для клиппер 5.01

Haz: Озадачил поподробнее это наверное так: вместо SET FILTER TO (cExp) используй INDEX ON Name FOR (cExp) , где cExp - условие фильтра. Синтаксис команды INDEX есть в той же NG. Индекс по базе c двумя полями построиться достаточно быстренько, в него попадут только записи удовлетволяющие условию. Кстати что значит "так как база отсортирована по name" ? командой SORT ? Если так то это лишний тормоз, т.к. индекс уже выстроит тебе порядок записей

Pasha: Haz пишет: Индекс по базе c двумя полями построиться достаточно быстренько эээ.. по миллиону записей ? Да и сколько же таких индексов понадобится, для каждой комбинации ввода в Get ? При каждом нажатии клавиши молотить миллион записей для создания индекса по условию, который не нужен, ведь индекс по name и так есть Это не наш метод (c) Если scope не поддерживаются в 5.01, тогда уже лучше определить навигационные блоки кода для tbrowse. Их есть у меня, только надо адаптитовать компиляцию под 5.01, давно я с ним дело не имел

PSP: Проще Клиппер 5.3 скопировать.

СевДон: Сделать свой Browse либо создать TBrowse-объект с блоками для навигации: bRange := {|| cFind == имя_поля } // это пример (укажите Ваше условие) bGoTop := {|| dbseek( cFind ) } bGoBottom := {|| FindBottom( cFind , bRange)} // поиск последней записи по условию при наличии индекса по символьному полю func FindBottom(Key,bRange) local nR := recno(), xKey := substr(Key,1,len(Key)-1)+chr(asc(substr(Key,-1))+1) dbseek(xKey) if !eof() ; dbskip(-1) ; endif if eof() .or. ! eval(bRange) // не попали ! dbgoto(nR) do while eval(bRange) // мелкими прыжками dbskip() enddo dbskip(-1) endif retu nil ЗЫ Индекс по симв. полю надо приводить к верхнему/нижнему регистру и, следовательно, переменную-ключ после Get`a тож надо привести к выбранному регистру при передаче в коды поиска и сравнения

Pasha: СевДон пишет: Сделать свой Browse либо создать TBrowse-объект с блоками для навигации: Я это и имел в виду, только надо еще определить skipblock Вот мои процедуры, попробуйте их собрать с 5.01 Параметры: bIndExp: индексое выражение, либо блок кода, который его вычисляет, либо массив {xTop, xBottom} bFilter - дополнительный фильтр (необязательно) nOrder - номер индекса (необязательно) Пример использования: oB:GoTopBlock := {|| mGoTop(cName)} oB:GoBottomBlock := {|| mGoBottom(cName)} oB:SkipBlock := {|n, x| imSkipper(n, cName)} #translate ISARRAY( <v1> ) => ( valtype( <v1> ) == "A" ) #translate ISBLOCK( <v1> ) => ( valtype( <v1> ) == "B" ) #translate ISCHARACTER( <v1> ) => ( valtype( <v1> ) == "C" ) #translate ISDATE( <v1> ) => ( valtype( <v1> ) == "D" ) #translate ISLOGICAL( <v1> ) => ( valtype( <v1> ) == "L" ) #translate ISMEMO( <v1> ) => ( valtype( <v1> ) == "M" ) #translate ISNUMBER( <v1> ) => ( valtype( <v1> ) == "N" ) oB:GoTopBlock := {|| mGoTop(bIndExp, bFilter, nOrder)} oB:GoBottomBlock := {|| mGoBottom(bIndExp, bFilter, nOrder)} oB:SkipBlock := {|n, x| imSkipper(n, bIndExp, bFilter, nOrder)} Function mGoTop(bIndExp, bFilter, nOrder) // ------------------------------------------------------------- // Перемещение на первую запись по фильтру // ------------------------------------------------------------- Local uIndExp, xIndExp, Released := .f., nOrdSave, lC if bIndExp == nil dbGoTop() else if nOrder # nil nOrdSave = IndexOrd() dbSetOrder(nOrder) endif uIndExp = if(IsBlock(bIndExp), EVAL(bIndExp), bIndExp) if ! IsArray(uIndExp) xIndExp = uIndExp if xIndExp # nil dbSeek(xIndExp) else dbGoTop() endif else xIndExp = uIndExp[2] dbSeek(uIndExp[1], .t.) lC = .f. endif endif while ! Eof() .and. CheckIndex(xIndExp, lC) IF bFilter == nil .or. EVAL(bFilter) Released = .t. EXIT ENDIF dbSkip() enddo if ! Released dbGoto(0) endif if nOrdSave # nil dbSetOrder(nOrdSave) endif Return nil Function mGoBottom(bIndExp, bFilter, nOrder) // ------------------------------------------------------------- // Перемещение на последнюю запись по фильтру // ------------------------------------------------------------- Local uIndExp, xIndExp, Released := .f., nOrdSave, lC if bIndExp == nil dbGoBottom() else if nOrder # nil nOrdSave = IndexOrd() dbSetOrder(nOrder) endif uIndExp = if(IsBlock(bIndExp), EVAL(bIndExp), bIndExp) if ! IsArray(uIndExp) xIndExp = uIndExp if xIndExp # nil dbSeekLast(xIndExp) else dbGoBottom() endif else xIndExp = uIndExp[1] dbSeekLast(uIndExp[2], .t.) lC = .t. endif endif while ! Bof() .and. ! Eof() .and. CheckIndex(xIndExp, lC) IF bFilter == nil .or. EVAL(bFilter) Released = .t. EXIT ENDIF dbSkip(-1) enddo if ! Released dbGoto(0) endif if nOrdSave # nil dbSetOrder(nOrdSave) endif Return nil Function mSkipper(n, bIndExp, bFilter, nOrder) // ------------------------------------------------------------- // Перемещения по файлу на количество записей n // ------------------------------------------------------------- Local uIndExp, nRet := 0, nRec := RecNo(), xIndExp1, xIndExp2, nOrdSave Local lC if bIndExp # nil if nOrder # nil nOrdSave = IndexOrd() dbSetOrder(nOrder) endif uIndExp := if(IsBlock(bIndExp), EVAL(bIndExp), bIndExp) if ! IsArray(uIndExp) xIndExp1 := xIndExp2 := uIndExp else xIndExp1 = uIndExp[1] xIndExp2 = uIndExp[2] lC := (n < 0) endif endif if n < 0 while nRet > n if Eof() .and. LastRec() > 0 mGoBottom(uIndExp, bFilter,,) else dbSkip(-1) endif if Eof() .or. Bof() .or. ! CheckIndex(xIndExp1, lC) dbGoto(nRec) if bFilter # nil Eval(bFilter) endif Exit endif if bFilter == nil .or. Eval(bFilter) nRec = RecNo() nRet -- endif enddo elseif n > 0 while nRet < n dbSkip() if Eof() .or. ! CheckIndex(xIndExp2, lC) dbGoto(nRec) if bFilter # nil Eval(bFilter) endif Exit endif if bFilter == nil .or. Eval(bFilter) nRec = RecNo() nRet ++ endif enddo elseif bFilter # nil Eval(bFilter) endif if nOrdSave # nil dbSetOrder(nOrdSave) endif Return nRet Function CheckIndex(xIndExp, lC) // ------------------------------------------------------------- // Проверка фильтра по индексному выражению // ------------------------------------------------------------- Local l, xKey if xIndExp == nil l = .t. else xKey := &(IndexKey()) if lC == nil if IsDate(xKey) .and. IsCharacter(xIndExp) l := (DTOS(xKey) = xIndExp) else l := (xKey = xIndExp) endif else l := if(lC, (xKey >= xIndExp), (xKey <= xIndExp)) endif endif Return l Function dbSeekLast(xExpr, lC, nOrd) // ------------------------------------------------------------- // Находит последнюю запись по ключевому выражению xExpr // Возвращает: // Логическое значение (найдена или нет). // ------------------------------------------------------------- Local xSeek, x, cc, nc, nOldOrd Local cIndex := IndexKey() if nOrd # nil .and. (nOldOrd:=IndexOrd()) # nOrd dbSetOrder(nOrd) endif if ValType(xExpr) == 'C' if len(xExpr) == 0 dbGoBottom() Return ! Eof() endif xSeek := incStr(xExpr) else //IF ValType(xExpr) == 'N' xSeek := xExpr + 1 endif dbSeek(xSeek, .t.) dbSkip(-1) x := &cIndex if lC == nil if ! (if(ValType(x) == 'D' .and. ValType(xExpr) == 'C', DTOS(x), x) = xExpr) dbGoto(0) endif else if x > xExpr dbGoto(0) endif endif if nOrd # nil .and. nOrd # nOldOrd dbSetOrder(nOldOrd) endif Return ! Eof() Function IncStr(cStr) Local ser, c for ser := len(cStr) to 1 step -1 c := Chr(Asc(Substr(cStr, ser, 1)) + 1) cStr := Stuff(cStr, ser, 1, c) if c # Chr(0) exit endif next Return cStr

Haz: Согласен с СевДон и Pasha, для отображения в бровсе предложенный вариант оптимален. Я же предложил самый простой и возможно - единственно приемлемый для SergeyKorotun исходя из его комментариев к задаче. Pasha пишет: эээ.. по миллиону записей ? Да и сколько же таких индексов понадобится, для каждой комбинации ввода в Get ? При каждом нажатии клавиши молотить миллион записей для создания индекса по условию, который не нужен, ведь индекс по name и так есть Это не наш метод (c) Проверил, на миллион записей индекс строится 4 секунды. Приемлемое время для ожидания, да и не на каждое нажатие, а по "какой -то" комбинации, и про индекс по Name не уточнялось ( есть только "отсортировано" ), и как потом выборку эту обрабатывать будут тоже не известно. Индекс потребуется один, после нажатия заветной комбинации , старый индекс рушим, новый строим . Да и навыки программирования ( в том числе и броуз объектов ) у топикстартера - неизвестны. В любом случае, решение предложенное СевДон и Pasha многим на форуме будет полезно. PS. Только прошу "меня за советскую власть не агитировать" , на клиппере подобные проблемы когда-то решал через Sx_WildSeek() - SIXRDD , на харбуре - через SQL запросы.

Pasha: Haz пишет: Проверил, на миллион записей индекс строится 4 секунды Так тачка наверное шустрая, да и локально. Если так быстро индекс строится, то и обычный фильтр так же быстро должен работать. Ведь построение индекса - это та же выборка всех записей файла, которая используется и для фильтра

Pasha: К тому же 5.01 не поддерживает условную индексацию, так что это в любом случае не вариант

Haz: Pasha пишет: К тому же 5.01 не поддерживает условную индексацию, так что это в любом случае не вариант а вот это аргумент, однако

Vlad04: Есть у меня библиотека SIXrdd к Clipper 5.01. Программу хотя и объемную надо написать, но одну, не хотелось бы разбираться с новыми версиями клиппера Но если уж переделывать программу , то лучше сразу на Харбор.Много другого хорошего получишь.

СевДон: Судя по условиям осмелюсь предположить что эта база содержит список, например, матценностей с идентификаторами и задача состоит в отсеве похожих записей по условию для последующей ВИЗУАЛЬНОЙ выборки оператором (с пометкой) и передаче этих записей в др.документ. Если это так предлагаю не фильтровать базу а делать контекстный поиск первой записи по условию и дальше пусть оператор выполняет свою работу. 1. на нижней части рамки броуза сделать строку ввода и по нажатию любой цифро-буквенной клавишы формировать строку поиска и делать seek, и сразу обновлять брауз 2. если искомая комбинация не найдена можно бибикнуть т.е. если надо найти "Пиво светлое Опупенное" то оператор уже после ввода 4 букв попадет в раздел ПИВО и, например, светлого почемуто нет то можно переходить к поиску следующей позиции накладной, т.е. вводить новую строку поиска, а если есть то оператор будет шагать вниз пока не найдет шо надо или пока ПИВО не закончится. Таким образом не тратиться время на выборку, переход на первую запись произойдет АРХИБЫСТРО, а выход за пределы "фильтра" оператор и сам увидит ЗЫ чёто пример с ПИВОМ не совсем удачен -- организм стал быстро пересыхать

SergeyKorotun: СевДон пишет: Судя по условиям осмелюсь предположить что эта база содержит список, например, матценностей с идентификаторами и задача состоит в отсеве похожих записей по условию для последующей ВИЗУАЛЬНОЙ выборки оператором (с пометкой) и передаче этих записей в др.документ. Матценностей наверно столько не существует. Это база с списком всех фамилий Украины. Передать в get поле надо только одну запись, визуально выбранную оператором из каким-то образом отфильтрованной базы. В базе должны отображаться только те записи, у которых первые символы фамилий совпадают с теми, что введены в get поле. Фамилии, не удовлетворяющие условиям фильтрации, не должны быть видимыми. INDEX ON есть в NG для клиппер 5.01, а INDEX ON ... FOR нет Я не переделываю программу, а пишу новую. Существует официальная программа на Фокспро, но очень неудобная в использовании. Хотя отфильтрованный справочник фамилий высвечивается практически мгновенно. Справочники обновляются централизовано. Но я могу делать с ними что угодно, так как работаю с их копиями. То есть после копирования могу например отсортировать. Последней проге, написанной мной на клиппере лет десять. TBrowse тогда не освоил. И сейчас использую dbedit() Переход на клиппер версии выше 5.01 нежелателен в связи с англоязычным NG. Но если реализовать выборку на 5.01 не удастся, то лучше перейду на 5.х, чем на 5.01 осваивать tbrowse Индексация базы каждый раз перед вызовом из get поля не подходит, так как продолжительность индексации сделает работу с программой некомфортной.

PSP: SergeyKorotun пишет: Я не переделываю программу, а пишу новую. Существует официальная программа на Фокспро, но очень неудобная в использовании. Ё-маё! Дык, делайте на Harbour. Там все есть, что вам нужно. Сразу сделаете с OrdScope и всё...

SergeyKorotun: PSP пишет: Ё-маё! Дык, делайте на Harbour. Там все есть, что вам нужно. Сразу сделаете с OrdScope и всё... процентов 90 уже написано. Если б вы предложили делать, ну например, на оракле, возможно б и начал изучать с нуля. Но заставлять изучать Harbour, это все равно что заставлять фирму по производству телевизоров усовершенствовать черно-белый телевизор. Или я заблуждаюсь? Тогда дайте ссылку на нормальную доку по Harbour

PSP: SergeyKorotun пишет: процентов 90 уже написано. Ничего переписывать не надо. Но заставлять изучать Harbour, это все равно что заставлять фирму по производству телевизоров усовершенствовать черно-белый телевизор. Это - тот же Клиппер. http://harbour-project.org/ В этом форуме: http://clipper.b.qip.ru/?0-4 Все, что есть в Клиппер 5.3 (а уж тем более 5.01), есть в Harbour. И даже больше.

Andrey: SergeyKorotun пишет: Или я заблуждаюсь? Тогда дайте ссылку на нормальную доку по Harbour Заблуждаешься... Бери и пользуйся.... http://clipper.b.qip.ru/?1-4-0-00000515-000-0-0-1267900828



полная версия страницы