Page 1 of 2

Extending SetScope()

Posted: Fri Oct 18, 2024 10:57 pm
by TimStone
I have a datafile that I use for "parts used on invoices".

For quick filterning, I use SetScope functions. ( top and bottom ). It uses the invoice number as the value for both. Thus when the invoice is open, only those parts used for that specific one will display in the xBrowse control. It all works perfectly.

Now I have a client who wants that same ability, but he wants to have the numbers "searchable" which would be accomplished by placing them in partnumber order.

Does anyone have a suggestion on how to do this without messing up the scope ?

Tim

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 8:00 am
by paquitohm
Hello,
In my case I use TSBrowse 6.0 with dbf table search.
When the user searches for articles, the program does an ordwildseek() and compiles the records (recnos()) into an array. Then oBrowse switches to "ARRAY" mode and displays the records of the collected array. The advantage of the process is that it is very fast. In addition, OrdWildSeek() does two searches, first a search for those that "start with" and then a search for those that "contain" so that first those that "start with" appear and then those that "contain". It would be very interesting if Mr. Nages added this feature for those who may be using xBrowse, which does not eliminate the possibility that the fwh developer can implement it more or less easily

[Original in spanish]
Hola,
En mi caso uso TSBrowse 6.0 con busqueda de tabla dbf.
Cuando el usuario hace una busqueda de artículos, el programa hace un ordwildseek() y recopila los registros (recnos()) en un array. Luego el oBrowse conmuta a modo "ARRAY" y muestra los registros del array recopilado. La ventaja del proceso es que es muy rapido. Ademas hace dos busquedas OrdWildSeek(), primero una busqueda para los que "empiezan por" y luego una busqueda para los que "contienen" de manera que primero aparecen los que "empiezan por" y despues los que "contienen". Interesantísimo sería que Mr. Nages añadiera esta característica para los que puedan estar usando xBrowse, lo cual no quita la posibilidad de que el desarrollador fwh lo pueda implementar más o menos facilmente

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 12:46 pm
by karinha
Use INDEX TEMPORARY/MEMORY

https://forums.fivetechsupport.com/view ... db#p269865

Regards, saludos.

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 3:06 pm
by nageswaragunupudi
TimStone wrote:I have a datafile that I use for "parts used on invoices".

For quick filterning, I use SetScope functions. ( top and bottom ). It uses the invoice number as the value for both. Thus when the invoice is open, only those parts used for that specific one will display in the xBrowse control. It all works perfectly.

Now I have a client who wants that same ability, but he wants to have the numbers "searchable" which would be accomplished by placing them in partnumber order.

Does anyone have a suggestion on how to do this without messing up the scope ?

Tim
When we are using SetScope(), we can not change the index order. That is our constraint now.
There are quite a few alternatives, including reading into an array, etc. but they reduce overall speeds and also require major changes.
We need to look for simpler solutions without much change in our existing software.

1. Use Filter instead of Scope:

I am sure you will not like this idea at all.
Most of us are brain-washed to believe that filters are very very slow.
Not so.
Situation changed 3 decades back when FoxPro introduced Rushmore technology and our present DBFCDX uses similar technology to optimize filters.
Since we already have and index on the field "INVOICE",

Code: Select all | Expand

INCOICE="ABCDEF"
is highly optimized.
That means we can use filters instead of Scopes to limit visibility of parts table pertaining to an invoice using filters also.
Once we use filters instead of scopes, we can change the index order of the parts browse to any indexed column and do seeks.

Note: Filters are still a bit slower than Scopes though the difference in speeds may not be perceptible.

2. Continue using scopes

(a) Use compound index "INVOICE+PARTNUMBER" instead of "INVOICE"
Still SetScope on invoice number works the same way even with this compound index.
At the same time, within the browse partnumbers appear in the ascending order.
When you want to search a part number, seek "INCVOICE" + "PARTNUMBER". We can do incremental search also.

(b) Continue your same program as it is. Instead of SEEK, use LOCATE to search the part number. I can assure that this also will be fast enough

You may consider options 2(b), 2(a), 1 in that order.

Please feel free to ask if you like us to support you with a sample program in any of these alternatives.

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 3:27 pm
by Marc Venken
Several members had filter, scopes etc. questions last period, so samples of optimised filters are VERY welcome.
Allmost all actions in my program are filter based.

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 4:38 pm
by karinha
Marc, how do you make your filters? I always prefer using these commands:

Marc, ¿cómo haces tus filtros? Siempre prefiero usar estos comandos:

Code: Select all | Expand

// C:\FWH\SAMPLES\SETSCOPE.PRG

#include "FiveWin.ch"

ANNOUNCE RDDSYS
REQUEST OrdKeyNo, OrdKeyCount, OrdCreate, OrdKeyGoto
REQUEST DBFCDX, DBFFPT

FUNCTION Main()

   FIELD AGE

   LOCAL cAlias, aGeIni := 20, aGeFin := 30

   RDDSETDEFAULT("DBFCDX")
   RDDREGISTER( "DBFCDX", 1 ) // RDT_FULL

   // The following example illustrates the SET SCOPE command:
   CLOSE DATABASE

   USE CUSTOMER NEW

   INDEX ON AGE TO CUSTOMER

   // Only part numbers between 20 and 30 will be used
   SET SCOPE TO 20, 30  // AGES: MIN 20 MAX 30

   GO TOP

   XBROWSE()

   SET SCOPE TO

   cAlias := ALIAS()

   // Using INDEX ON... MEMORY / TEMPORARY
   // Only part numbers between 20 and 30 will be used
   INDEX ON AGE TAG 02 TO MARCTEMP FOR ( .NOT. EOF() ) .AND. ;
         ( cAlias )->AGE >= aGeIni                     .AND. ;
         ( cAlias )->AGE <= aGeFin MEMORY // TEMPORARY

   GO TOP

   XBROWSE()

   OrdDestroy( "MARCTEMP" )

   CLOSE DATABASE

RETURN NIL

/*
// Seealso

// ORDSCOPE(), SET SCOPEBOTTOM, SET SCOPETOP
*/

// FIN / END - kapiabafw@gmail.com
 
Regards, saludos.

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 7:44 pm
by Marc Venken
I use several.... because I saw the samples on the forum. therefore that the explenation of Mr. Rao could give me the final tip for the best.

Thinks I active use : (code like they are)

oBrw[1]:bChange := { || orderfilter("factart","factart",factinfo->document),orderfilter("detail","factart",factinfo->document),oBrwFol2[1]:gotop(),oBrwFol2[1]:refresh() }

Code: Select all | Expand

function orderfilter(cAlias,cTag,cStart)
   local cOld_alias:=Alias(), NTag:= &cOld_alias->(indexord())
   default cStart:=""
   select &cAlias
   set order to tag &cTag
   if !empty(cTag)
      set scope to alltrim(cStart)
   endif
   &cAlias->(dbgotop())
   select &cOld_alias
   &cOld_alias->(dbsetorder(nTag))
return NIL

 
or this

Code: Select all | Expand

  netopen("brwsetup","Code","TempXb")
  select TempXB
  set filter to brwnaam = cData
  tempXB->(dbgotop())
  hBrw:= FW_RecToHash()  // Start using Hash for all replaces below

 

:bBarGetAction := {|| ( oBrw:cAlias )->( MARC_SETFILTER( oBrw ) ) } // this for show the bitmap on the get and associated a action

Code: Select all | Expand

FUNCTION MARC_SETFILTER( oBrw ) // My filter for Xbrowse heather gets.

   LOCAL cFilter := ""
   LOCAL n, oCol, uVal, cType, cVal1, cVal2, nGevonden
   LOCAL nZoekKnop, cExprt
   FOR n := 1 TO Len( oBrw:aCols )
      oCol  := oBrw:aCols[ n ]
      IF ! Empty( uVal := oCol:uBarGetVal )
         IF !Empty( cFilter )
            cFilter  += " .AND. "
         ENDIF
         cType    := ValType( uVal )
         if cType = "C"
           nGevonden = at("++",uVal) //  used because I want to look for 2 items in one get
         endif
         if cType = "C"
           nZoekKnop = at("²",uVal)  // used because I set a function key to this to start the search, so no click needed
         endif

         DO CASE
          CASE cType == 'C' .and. nZoekKnop > 0  
            uVal     := Upper( AllTrim( uVal ) )
            cVal1 = substr(uVal,1,nZoekknop-2)
            cFilter += '"' + cVal1 + '" $ UPPER( ' + oCol:CExpr + " )"
          CASE cType == 'C' .and. nGevonden > 0
            uVal     := Upper( AllTrim( uVal ) )
            cVal1 = substr(uVal,1,nGevonden-1)
            cVal2 = substr(uVal,nGevonden+2)

            cFilter += '"' + cVal1 + '" $ UPPER( ' + oCol:CExpr + " )"
            cFilter += " .AND. "
            cFilter += '"' + cVal2 + '" $ UPPER( ' + oCol:CExpr + " )"
         CASE cType == 'C'
            uVal     := Upper( AllTrim( uVal ) )
            cFilter += '"' + uVal + '" $ UPPER( ' + oCol:CExpr + " )"
         CASE cType == 'D'
            cFilter  += oCol:cExpr + " = " + ( uVal )
         OTHERWISE
            cFilter  += oCol:cExpr + " == " + cValToChar( uVal )
         ENDCASE
      ENDIF

   NEXT

   IF Empty( cFilter )

      IF ! Empty( dbFilter() )
         dbClearFilter()
         oBrw:Refresh()
      ENDIF

   ELSE

      IF !( dbFilter() == cFilter )
         oClp:SetText(cFilter) //  used because I generate filters on the fly with Xbrowse, copy paste into a lookup table for later use
         if !empty(alias())
           SET FILTER TO &cFilter
           oClp:SetText(cFilter)
           GO TOP
         else
           msginfo("Error.... Geen Alias beschikbaar")
         endif

         oBrw:Refresh()
      ENDIF

   ENDIF
   oBrw:maketotals()
   oBrw:SetFocus()

RETURN NIL
 
and this

oBrw[2]:bChange := { || Scope("factinfo","document","factart","factart"),oBrw22:refresh() }

Code: Select all | Expand

function Scope(cParent,cData,cChild,cChildTag,cFoto)
  LOCAL cZoekdata
  LOCAL aScope1, aScope2
  DEFAULT cFoto:=""

  cZoekdata = cData
  if left(cZoekdata,1) = "#" //  breakpoint in order to look for 2 items in one field 
    cZoekdata:= substr(cZoekdata,2)
  else
    cZoekdata:= upper( (cParent)->&cData)
  endif


  if cZoekdata = sys_scope
     return NIL
  endif
  sys_Scope = cZoekdata

  (cChild)->(dbsetorder(cChildTag))

  (cChild)->(ORDSCOPE(0, "" ) )
  (cChild)->(ORDSCOPE(1, "" ) )

  (cChild)->( ORDSCOPE(0, cZoekData ) )
  (cChild)->(ORDSCOPE(1, cZoekData ) )
  (cChild)->(DBGOTOP())

   //aScope := FW_DbfToArray( "JAN,FEB,MAR,APR,MAY", { || FIELD->REGION = cRegion } )
   //aScope1 := (cChild)->( FW_DbfToArray() )

  do case
    case upper(cChild) = "NOFOTOART"
      nofotoart->(dbsetorder("ID"))
      nofotoart->(ORDSCOPE(0, "" ) )
      nofotoart->(ORDSCOPE(1, "" ) )
      nofotoart->( ORDSCOPE(0, cZoekData ) )
      nofotoart->(ORDSCOPE(1, cZoekData ) )
      nofotoart->(DBGOTOP())
      aScope2 := (cChild)->( FW_DbfToArray() )
      ACopy(aScope1,aScope2)
      xbrowser(aScope2)

    case cFoto = "NOFOTO"
       Sys_wnd_foto = getorgfoto(nofoto->orgfoto)
       oSys_image:refresh()
  endcase
Return NIL

 

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 7:49 pm
by Marc Venken
The magic lies in the Optimized filter with CDX, but when is it optimized ?

By setting the indexes ?
You see option 2b of Mr. Rao ? Using Locate ? didn't know it )))

I think if more members request the samples from Mr. Rao he will help us

Re: Extending SetScope()

Posted: Sat Oct 19, 2024 11:26 pm
by TimStone
Thank you.

I decided to use 2(a) because having them in partnumber order is sufficient at this time. However, I will happily explore the FILTER option.

I should mention that I'm using database objects, and though I tried the filter first, it wasn't working. I'm sure it was how I implemented it.

I, too, would appreciate some examples of these. Links to previous references on this forum would be fine.

Tim

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 11:50 am
by Marc Venken

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 12:03 pm
by karinha
Marc, SET FILTER TO &cFilter, doesn't this make the FILTER extremely SLOW? Some clients of the company I work for have giant databases, and using SET FILTER TO was driving them crazy, it took up to 30 minutes to make a simple FILTER. I changed everything to INDEX ON...MEMORY/TEMPORARY.

Marc, SET FILTER TO &cFilter, ¿esto no hace que el FILTRO sea extremadamente LENTO? Algunos clientes de la empresa para la que trabajo tienen bases de datos gigantes, y usar SET FILTER TO los estaba volviendo locos, les llevó hasta 30 minutos hacer un FILTRO simple. Cambié todo a ÍNDICE EN...MEMORIA/TEMPORAL.

Gracias, tks.

Regards, saludos.

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 1:08 pm
by Marc Venken
I have a dbf with 380.000 records, 32 fields and 7 tags. (242 Megabite)

the filter look like :

"WIC" $ UPPER( TAG ) .AND. "POLO" $ UPPER( NAAM ) .AND. "WHITE" $ UPPER( KLEUR )

This filter is build with the above function Marc_Setfilter using Xbrowse header gets.

The active tag =

upper(reflev)+upper(kleur)+upper(size)

If I try 10 filtercombinations the results are between 4-20 secs, not knowing why slower some times.

Thats why Mr. Rao's presentation will be great.

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 1:15 pm
by Marc Venken
karinha wrote:Marc, I changed everything to INDEX ON...MEMORY/TEMPORARY.
This I want to try .... Just made a tmp index once in a while but as you confirm, it is fast.

Have you acode snipped where you use Xbrowse Header gets and use temp index instead of filter.
You would make

local ...
Field Tag, Naam, Kleur

Filter =
"WIC" $ UPPER( TAG ) .AND. "POLO" $ UPPER( NAAM ) .AND. "WHITE" $ UPPER( KLEUR )
Index will be like
index on upper(TAG)+upper(NAAM)+upper(KLEUR) tmp (sample, not the correct syntax)

cData = upper(Tag)+upper(naam)+upper(kleur)
ean->(dbseek(cData))

this is what you mean ?

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 1:24 pm
by Enrico Maria Giordano
Marc Venken wrote:upper(reflev)+upper(kleur)+upper(size)
I suggest this optimization:

Code: Select all | Expand

upper(reflev+kleur+size)

Re: Extending SetScope()

Posted: Sun Oct 20, 2024 8:27 pm
by Marc Venken
Enrico Maria Giordano wrote:
Marc Venken wrote:upper(reflev)+upper(kleur)+upper(size)
I suggest this optimization:

Code: Select all | Expand

upper(reflev+kleur+size)
I see that the index will be faster since upper is from 3 to 1, but is it also nessesary to change how the filter will be created ?

Current filter :

"WIC" $ UPPER( TAG ) .AND. "POLO" $ UPPER( NAAM ) .AND. "WHITE" $ UPPER( KLEUR )