30 de abril de 2004

Reducir los espacios entre palabras a solo un espacio

Esta función fue solicitada en el Grupo de Noticias y este es un resumen de los mensajes y las respuestas.

Utilizando la libreria FOXTOOLS: (Mensajes de Fernando Bozzo y Hugo Ranea)
SET LIBRARY TO (HOME() + "FoxTools.fll")
? Reduce("  Reducir   los    espacios entre   palabras    a  solo un espacio")
Función en VFP (Mensaje de Ricardo Passians)
? ReduceAUnEspacio("  Reducir   los    espacios entre   palabras    a  solo un espacio")
FUNCTION ReduceAUnEspacio(tcCadena)
  DO WHILE AT(SPACE(2), tcCadena) > 0
    tcCadena = STRTRAN(tcCadena, SPACE(2), SPACE(1) )
  ENDDO
  RETURN ALLTRIM(tcCadena)
ENDFUNC
Función recursiva en VFP (Mensajes de Ricardo Passians y Luis María Guayán)
? ReduceAUnEspacio("  Reducir   los    espacios entre   palabras    a  solo un espacio")
FUNCTION ReduceAUnEspacio(tcCadena)
  RETURN IIF(AT(SPACE(2), tcCadena)=0, ;
    ALLTRIM(tcCadena), ;
    ReduceAUnEspacio(STRTRAN(tcCadena, SPACE(2), SPACE(1))))
ENDFUNC

27 de abril de 2004

Obterner el valor de identity al hacer un insert en SQL

Este truquito permite poner el valor de la columna identity de SQL Server en un cursor al momento de hacer el INSER en la tabla sin tener que hacer una nueva consulta, ni usar Store Procedure.
qsql = "SET NOCOUNT ON INSERT INTO documentos (TipoDocumento, NroDocumento, Fecha)"
qsql = qsql + "values(1,555,'20/04/2004') SELECT @@IDENTITY as iddocumento SET NOCOUNT OFF"
qhandle = SQLCONNECT(MiConexion)
IF qhandle > 0
  grabaok = SQLEXEC(qhandle, qsql, "MiCursor")
ENDIF
Si fue exitoso en MiCursor obtendré un campo con el valor del identity de la tabla documentos con nombre idDocumento.

Saludos.

Hernan Perez Tonini

14 de abril de 2004

Exportar a MS-Excel más de 65,000 registros (via Automation)

Una de las limitaciones de MS-Excel (no así de VFP con su comando COPY TO .. TYPE XL5), es que no se pueden pasar más de 65,000 registros a la vez, el MVP VFP Cetin Basoz, nos ofrece una forma de resolver tal problema....

La técnica usada es crear un libro de MS-Excel, a partir de ahí, copiar desde el cursor a exportar, los registros necesarios para que puedan caber en una hoja, agregar a dicha hoja los registros copiados a un DBF.

Clear All
* Create a test cursor
Create Cursor testcursor (Rcno i,cType c(10), nType i, dType d)
Rand(-1)
For ix = 1 To 200000 && Create 200000 recs cursor
  Insert Into testcursor Values ;
    (Recco()+1,Sys(2015), Int(Rand()*1000), Date()-Int(Rand()*100))
Endfor

Set Safety Off

Wait Window Nowait "Por favor espere, enviando datos a Excel..."
lnTotal = Reccount()
oExcel = Createobject("Excel.application")
With oExcel
  .Visible = .T.
  oMasterWorkBook = .workbooks.Add && Add a new workbook
  lnMaxRows = .ActiveWorkBook.ActiveSheet.Rows.Count && Get max row count
  lnNeededSheets = Ceiling( lnTotal / (lnMaxRows - 1) ) && 1 row header

  lnCurrentSheetCount = .sheets.Count
  If lnNeededSheets > lnCurrentSheetCount
    .sheets.Add(,.sheets(lnCurrentSheetCount),;
      lnNeededSheets - lnCurrentSheetCount) && Add new sheets after old ones
  Endif
Endwith
With oMasterWorkBook
  For ix = 1 To lnNeededSheets
    .sheets.Item(ix).Name = "Page "+Padl(ix,3,"0")
  Endfor

  lcExportName = Sys(5)+Curdir()+Sys(2015)+".dbf"
  For ix = 1 To lnNeededSheets
    lnStart = ( ix - 1 ) * (lnMaxRows-1) + 1

    Copy To (lcExportName) ;
      for Between(Recno(),lnStart,lnStart+lnMaxRows-2) ;
      type Fox2x

    oSourceWorkBook = oExcel.workbooks.Open(lcExportName)
    .WorkSheets(ix).Activate
    oSourceWorkBook.WorkSheets(1).UsedRange.Copy(;
      .WorkSheets(ix).Range('A1'))
    oSourceWorkBook.Close(.F.) && Close w/o save
    Erase (lcExportName)

  Endfor
  .WorkSheets(1).Activate
Endwith
Wait Clear

PS:FoxyClasses dbf2Excel también usa otro método de transferencia via ADO, el cuál, puede completar el proceso aún si los datos tienen campos de tipo Memo.

Çetin Basöz
MS Foxpro MVP, MCP
http://www.foxyclasses.com

Traducido por: Esparta Palma

13 de abril de 2004

Visual FoxPro contra Visual Basic...

¿Es posible establecer quien es mejor?

Me parece que es absurdo intentar confrontar un lenguaje con otro.

Toma muchísimo tiempo llegar a dominar realmente un entorno de desarrollo. Por realmente dominar me refiero a manejarlo "al derecho y al revés". Como los programadores nos debemos a nuestros clientes (o empleadores) ya que nos pagan por "solucionar problemas" no por andar con "fancy modas" en la programación. Muchos programadores que conozco de VB son unos reales mediocres porque no han profundizado en su entorno de desarrollo. He visto clientes "estafados" por programadores que les diseñaron un sistema en VB y SQL-Server para una empresa donde solo hay 2 o 3 computadoras corriendo una simple aplicación de facturación e inventarios. Digo "estafado" porque imaginate lo que el pobre cliente habrá tenido que pagar en la licencia SQL-SERVER para solo utilizar un programa en 2 o 3 computadoras. Por otro lado, he visto gente que se ha "sumergido" de fondo en el humilde ACCESS y ha hecho realmente programas admirables y funcionales; incluso, conozco programas hechos con fósiles como COBOL pero que funcionan muy bien. De hecho, uno de los mejores programas que he visto está hecho en el rupestre PARADOX, pero te puedo asegurar que "me quito" el sombrero ante la calidad, estabilidad y poder de la aplicación que ví.

Los "noveleros", aquellos programadores que no están afianzados a ningún entorno de desarrollo específico y que "saben de todo un poco"; regularmente entregan programas mediocres, inflexibles e incluso muy inestables. Conozco un programador de VB 6 que hace verdaderas "obras de arte" y la razón es muy simple: "Es Especialista en VB 6". Si hace tan buen software en VB 6, ¿porqué habría yo de criticar que su software no lo hizo en Visual FoxPro? Sus clientes están satisfechos, él cobra muy buenos honorarios, sus aplicaciones son "coquetas", rápidas y estables... ¿Porque moverlo al mundo de Visual FoxPro o .NET o lo que sea...? También hay gente muy profesional haciendo estupendas aplicaciones con DELPHI; ¿Porque criticarlos?

Visual Visual FoxProPro puede ser una belleza, pero en "manos inexpertas" ¿Servirá de algo todo su poder?

VB, .NET, etc. también son bellezas, pero en "manos inexpertas" son totalmente inútiles.

Visual FoxPro, VB, .NET, DELPHI, MySQL, Postgress son solo herramientas inhertes (se podría decir, inútiles). Es la imaginación, el ingenio, "el compromiso" del programador con su entorno de desarrollo, la responsabilidad con el cliente (o jefe) lo que le da "vida" a la herramienta que estas utilizando.

Definitivamente no debo generalizar, pero te puedo decir que todos los programadores que conozco que no son leales a su "herramienta de desarrollo", sea cual fuere, siempre entregan productos mediocres, inestables y te diría (aunque suene cursi) "sin alma". Claro, hay verdaderos "genios" por allí muy capaces de dominar en poco tiempo lo que les pongan enfrente, pero estos son la excepción.

Creo que los programadores son mejores unos que otros, no necesariamente las herramientas. Hay quienes por sus "mecanismos de pensamiento" son mas hábiles con VB, y habemos otros que somos igualmente hábiles en Visual FoxPro, precisamente por la misma razón: nuestros mecanismos de pensamiento.

Por circunstancias que no vienen al caso mencionar ahora, me ha tocado en la vida ver a alguien empuñar un fusil de asalto AK47 (gran poder de fuego), con alguien que tiene simplemente un revolver 38 Especial; y he podido ver como el individuo con el revolver (muy hábilmente manejado) ha podido vencer al portador del mortífero AK47. Es algo así como David y Goliat. ¡Conozco un programa que hace verdaderos portentos y está hecho con algún tipo arcaico de COBOL!

Puede que la herramienta de desarrollo que usas sea bastante modesta, pero si tu conocimiento de la misma es completo y profundo, si eres ingenioso y creativo; seguramente harás muy buenas aplicaciones, incluso mejores que las de aquellos que usan los poderosos ORACLE, MYSQL, etc.

Todas las herramientas de desarrollo tienen maravillas y limitantes, pero ¿Tiene acaso limitantes el poder de imaginación e ingenio la mente humana?

La raqueta de tenis mas fina y cara del mundo en mis manos no sirve casi de nada, pero una raqueta modesta y barata en manos de Serena Williams hace maravillas.

Los que trabajamos en Visual FoxPro no somos unos "Dinosaurios" ya que éste ha evolucionado con el tiempo (y así otras herramientas tipo XBASE como DBASE, ALASKA o VISUAL FLAGSHIP). Y si fuera cierto que los foxeros somos unos dinosaurios, recuerda que hasta la fecha los "dinosaurios" siempre se han salido con la suya en las 3 películas de JURASIC PARK (jaja, es medio en broma y medio en serio).

En una ocasión nos tocó hacer un software para una empresa que vendía ganado. Era importante incluir la foto de cada animal en el inventario, así como en los catálogos de precios. Había que presentar gráficas con los resultados de ventas comparativos con los últimos meses. El cliente tenía un programa ya hecho en VISUAL BASIC, y lo tenía harto y decepcionado. Era terrible: se colgaba continuamente, era difícil de manejar y muy lento ya que utilizaba como motor de base de datos a ACCESS. En aquellos tiempos ya estaba en el mercado Visual FoxPro 5, pero nosotros nos resistíamos a utilizarlo ya que habíamos tenido muy malas experiencias con Visual FoxPro 3. Entregamos nuestro programa hecho en FoxPro para DOS versión 2.6. Para incorporar imágenes al ambiente tipo texto utilizamos un “add-on” para Visual FoxPro llamado “Pinnacle Graphics Studio” y para generar las gráficas estadísticas utilizamos otro llamado “FoxGraph”. Era una red Novell Netware 4.11 con 14 estaciones con Windows 95. El producto terminado quedó sobrepasando las expectativas del cliente.

De esta experiencia se desprendieron muy malos conceptos: PRIMERO: el cliente y los usuarios del sistema comenzaron a pensar y divulgar la idea de que VISUAL BASIC no sirve; cosa totalmente errónea ya que es una plataforma de desarrollo muy poderosa. SEGUNDO: Otros programadores pensaron que era ACCESS el malo y que debieron utilizar SQL-Server; también era un concepto errado ya que el cliente tenía un servidor NETWARE perfectamente funcional, por lo que utilizar SQL-Server hubiera significado gastar mucho mas dinero en cambiar el software del servidor y adquirir SQL-Server. Con el humilde FoxPro para DOS y un par de herramientas “third-party” se completó un proyecto que inicialmente fracasó con VISUAL BASIC y ACCESS; pero el fracaso fue culpa de programadores “noveleros”, no comprometidos con su plataforma de desarrollo y no de sus herramientas de desarrollo.

Me parece que comparar herramientas de desarrollo al nivel que se suele ver en los debates donde se trata establecer quien es mejor es bastante "infantil". Es como oír a un niño decir: "mi papá es mas grande y le pega al tuyo...".

Creo que como profesionales debemos poder ofrecer la “aplicación apropiada para el tamaño justo del problema” y la capacidad económica del cliente. No es ético sacarle al cliente que compre un SQL-Server si su aplicación no lo amerita ni tampoco es ético utilizar a nuestros clientes como “conejillos de indias” solo para probar nosotros, los desarrolladores, la última versión de MySQL. Habrá cosas para las que solo Visual FoxPro no será suficiente y deberás recurrir a un motor como SQL-Server, MySQL, Firebird u otros, y ya que dominas perfectamente Visual FoxPro (eres un mago en este entorno) ¿Para qué aventurarte y arriesgarte a utilizar un lenguaje del que conoces muy poco y que no te permitirá dejar al cliente satisfecho, tu bolsa satisfecha y ante todo tu autosatisfacción de haber hecho un excelente trabajo (algo que cuando mires en el futuro no te avergüence decir que tu lo hiciste)?

Los programadores que están muy comprometidos con una plataforma (sea C++, Visual FoxPro, VB, DELPHI, ALASKA, etc.) buscan la perfección en su entorno favorito para poder trabajar mas, con mejor calidad, con mejor estabilidad y con menor esfuerzo. El cliente espera resultados y soporte para mantenimiento y futuras expansiones, salvo raras ocasiones, por lo regular no le importa como se logren estos objetivos. El cliente espera gastar lo “menos posible” no lo “máximo permisible”, por esta razón, los foxeros desarrollamos en Visual FoxPro, un entorno que incluye poderosas herramientas de programación y un poderoso manejador de bases de datos. Si alguien puede ofrecerle al cliente la misma aplicación hecha en otro entorno que no le haga gastar “de más” en un motor de base de datos o en “horas-programador”, bienvenido sea, no importa (regularmente) de que entorno de desarrollo estemos hablando siempre que cumplamos con prontitud, calidad, estabilidad, velocidad de proceso y compromiso de soporte a largo tiempo.

De momento, Microsoft ha demostrado que sigue pensando en Visual FoxPro (aunque no como quisiéramos). En mi caso particular, si Microsoft comete el grave error de “aniquilar” Visual FoxPro, bueno, es Microsoft quien se lo pierde, ya que por mi parte continuaría utilizando el entorno XBASE, específicamente DBASE (www.dbase.com) o ALASKA (www.alaska-software.com) para Windows y VISUAL FLAGSHIP (www.fship.com) para Linux. Ya he platicado de esto con muchos de mis colegas foxeros y todos (el 100%) opinan exactamente lo mismo. Habrán quienes preferirán moverse a otro entorno, bien por ellos, siempre y cuando se comprometan con ese entorno para poder desarrollar aplicaciones de alta calidad y desempeño.

PD: Perdón por el ejemplo de las armas, pero dado lo visto y vivido, me pareció una muy buena comparación.

Edgar Acevedo

12 de abril de 2004

Clase para calcular la edad con Años, Meses y Días

El MVP Turco, Cetin Bazos nos ofrece una excelente clase para calcular la edad, separando además, los años, meses y días, en propiedades.
Local birthDate,loCalculator
birthDate = DATE(1990,06,28)

loCalculator = Createobject("AgeCalculator")
loCalculator.CalcAge(birthDate,DATE(2010,01,31)) && Age in Jan 31,2010
? PrintAge(loCalculator,birthDate,DATE(2010,01,31))
loCalculator.CalcAge(birthDate) && Age today
? PrintAge(loCalculator,birthDate,Date())

Function PrintAge
  Lparameters loAge, ldBirthDate, ldTarget
  Local lcText
TEXT to m.lcText textmerge noshow
Birthday: [<<Transform(m.ldBirthDate,\@YL')>>]  Target date: [<<Transform(m.ldTarget,\@YL')>>]
<<m.loAge.Years>> Years,<<m.loAge.Months>> Months,<<m.loAge.Days>> Days old
ENDTEXT
  Return m.lcText
Endfunc

Function TestCalcBDateAccuracy
  Create Cursor datedata ;
    (dod D, dobS D Null, nyears i, nmonths i, ndays i, ;
    ncyears i, ncmonths i, ncdays i, DiffDays i)
  =Rand(-1)
  loCalculator = Createobject("AgeCalculator")
  For i = 1 To 1000000
    dod = Date() - Int(Iif(i <= 500000, 1000, 10000) * Rand())
    nyears = Int(99 * Rand() + 1)
    nmonths = Int(11 * Rand() + 1)
    ndays = Int(364 * Rand() + 1)
    dobS = loCalculator.CalcBDate(m.dod, m.nyears, m.nmonths, m.ndays)
    loCalculator.CalcAge(m.dod,m.dobS)
    ncyears = loCalculator.Years
    ncmonths = loCalculator.Months
    ncdays = loCalculator.Days
    DiffDays = loCalculator.GetDifference(m.dobS,;
      m.nyears, m.nmonths, m.ndays, ;
      m.ncyears, m.ncmonths, m.ncdays)
    Insert Into datedata From Memvar
  Endfor
  Select * From datedata Where DiffDays <> 0 Order By DiffDays Desc
Endfunc

Define Class AgeCalculator As Custom
  Years = 0
  Months = 0
  Days = 0

  * Calculate age with given birthDate and target calculation date
  * Target calculation date defaults to 'Today' if omitted
  * CalcAge(birthDate[, targetDate])
  Procedure CalcAge
    Lparameters tdBirth, tdTarget
    Local ldTemp, ldBirth, lnDrop
    tdTarget = Iif(Empty(m.tdTarget), Date(), m.tdTarget)
    If m.tdBirth > m.tdTarget
      ldTemp   = m.tdTarget
      tdTarget = m.tdBirth
      tdBirth  = m.ldTemp
    Endif

    ldBirth = Date(Year(m.tdTarget),Month(m.tdBirth),Day(m.tdBirth))
    lnDrop = 0
    If Empty(m.ldBirth) && leap case
      ldBirth = Date(Year(m.tdTarget),3,1)
      lnDrop = Iif(Month(m.tdTarget)<=2,0,1)
    Endif
    With This
      .Years = Year(m.tdTarget) - Year(m.tdBirth) - ;
        (Iif(m.ldBirth > m.tdTarget,1,0))
      .Months = (Month(m.tdTarget) - Month(m.tdBirth) + 12 - ;
        (Iif(Day(m.tdBirth)>Day(m.tdTarget),1,0)))%12
      ldTemp = Date( Year(m.tdBirth) + .Years, Month(m.tdBirth), Day(m.tdBirth) )
      If Empty(ldTemp)
        ldTemp = Date( Year(m.tdBirth) + .Years, Month(m.tdBirth), Day(m.tdBirth-1) )
      Endif
      .Days = m.tdTarget - Gomonth(m.ldTemp,.Months) - m.lnDrop
    Endwith
  Endproc

  * Calculate birthDate from given date, years, months, days
  * Question was raised originally by Jim Livermore on UT
  * If grandma died on:
  * Jan 1st, 1908
  *     and she was 83 years, 2 months, 5 days
  * when she died what is her BirthDate
  * CalcBDate(targetDate, years, months, days)

  * Depending on passed values accuracy is => 98.6%
  * With randomly generated 1 million values
  * Less than 14000 calculated birthdates'
  * difference from CalcAge() result is in the range 1 to -3 days
  * Check test routine at top
  Procedure CalcBDate
    Lparameters tdTarget,tnYears,tnMonths,tnDays
    Local ldDate,ldBirth
    ldDate = m.tdTarget-m.tnDays
    If Month(m.ldDate)=2 And Day(m.ldDate)=29
      ldDate = Gomonth(m.ldDate,-m.tnMonths)
      ldBirth = Date(Year(m.ldDate)-m.tnYears,Month(m.ldDate),Day(m.ldDate))
    Else
      ldBirth = Gomonth(;
        Date(Year(m.ldDate)-m.tnYears,;
        Month(m.ldDate),Day(m.ldDate)), -m.tnMonths)
    Endif
    Return m.ldBirth
  Endproc

  * Verification routines
  * Used to check the accuracy of CalcBDate
  Procedure GetDifference
    Lparameters tdBirth, tnYears1,tnMonths1,tnDays1, tnYears2,tnMonths2,tnDays2
    With This
      Return ;
        .DatesAdd(m.tdBirth, m.tnYears1,m.tnMonths1,m.tnDays1) - ;
        .DatesAdd(m.tdBirth, m.tnYears2,m.tnMonths2,m.tnDays2)
    Endwith
  Endproc

  Procedure DatesAdd
    Lparameters tdDate, tnYears,tnMonths,tnDays
    Local ldDate
    ldDate = Date(Year(m.tdDate)+m.tnYears,Month(m.tdDate),Day(m.tdDate))
    If Empty(m.ldDate)
      ldDate = Date(Year(m.tdDate)+m.tnYears,2,28)
    Endif
    Return (Gomonth(m.ldDate,m.tnMonths)+m.tnDays)
  Endproc
Enddefine
Autor: Çetin Basöz
Editor: Esparta Palma