2 de mayo de 2009

Como acelerar las consultas en MySQL

Aunque MySQL lleva su propio sistema de optimización cuando lanzamos un "Query SQL", nos es posible mediante programación elegir el índice que utilizará el MySQL en cada una de las tablas para optimizar nuestra consulta.

Como es mejor un ejemplo que mil palabras, vamos a ilustrar un pequeño ejemplo desde nuestro querido Fox.

Creamos en nuestra BBDD de MySQL una pequeña tabla de apuntes contables. Con varios índices (No.Apunte,SubCuenta,Fecha etc).
DROP TABLE IF EXISTS apuntes;

CREATE TABLE apuntes (
  apunte decimal(8,0) default '0.00',
  fecha date default '2009-01-01',
  texto varchar(40) collate utf8_spanish_ci default '',
  cuenta varchar(10) collate utf8_spanish_ci default '0000000000',
  debe decimal(12,2) default '0.00',
  haber decimal(12,2) default '0.00',
  PRIMARY key  (apunte),
  key K01 (fecha),
  key K02 (cscta))
ENGINE = InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish_ci ROW_FORMAT=DYNAMIC ;

Una vez visto el ejemplo de tabla, vamos a crear nuestra consulta en nuestro querido Fox. Y dependiendo de los datos que queramos extraer de la tabla, elegiremos que la consulta sea optimizada por un índice determinado.
*******************************************************************
*  Ejemplo de Consulta Optimizada para MySQL
*******************************************************************

KNDX = 'K01'
DO FORM FCONSULTA    && (FORMULARIO DE CAPTURA DE VARIABLES)
IF T1APTS = T2APTS
  KNDX = 'PRIMARY'
ENDIF

IF T1FECHA = T2FECHA
  KNDX = 'K01'
ENDIF

IF T1CUENTA = T2CUENTA
  KNDX = 'K02'
ENDIF

TEXT TO XSQL TEXTMERGE NOSHOW
  SELECT  *
    FROM  apuntes FORCE INDEX(<<KNDX>>)
    WHERE apunte >= ?T1APTS AND apunte <= ?T2APTS AND
    fecha >= ?T1FECHA AND fecha <= ?T2FECHA AND
    cuenta >= ?T1CUENTA AND cuenta <= ?T2CUENTA
    ORDER BY apunte,fecha 
ENDTEXT

SQLPREPARE(NH, '' + XSQL, 'TCURSOR')

DO WHILE SQLEXEC(NH) = 0
ENDDO

BROWSE

*******************************************************************

Espero que el código anterior sea descriptivo de lo que os quiero transmitir.
En el ejemplo anterior utilizamos la variable KNDX que es la que almacena el índice que queremos utilizar para optimizar nuestra consulta. Por defecto en este ejemplo concreto ponemos el valor del indice fecha 'K01'. Y utilizamos la orden FORCE INDEX para forzar al MySQL que utilice el índice que le indicamos en cada consulta dependiendo de los valores que queramos extraer en cada momento.

En tablas relativamente pequeñas es posible que no se aprecie rendimiento alguno. Pero en consulta a tablas con mas de 2 Millones de registros, como es mi caso. Os aseguro que el rendimiento es exponencial.

Un cordial saludo a la comunidad Fox.

Antonio L. Montagut
www.ontarioxb.es

26 de abril de 2009

Utilizando _MemberData con el objeto _Screen

Por lo general no acostumbro utilizar variables públicas en mis aplicaciones. Siempre uso el objeto _SCREEN, le adiciono propiedades con valores que estarán disponibles para toda la aplicación. No obstante, al programar y usar Intellisense la presentación de las propiedades del objeto _SCREEN en el código se muestran siempre en minúsculas, lo cual me dejaba un tanto insatisfecho, dado que esto no ocurría con mis otras clases porque les añadía la propiedad _MemberData y lograba una mejor presentación en mi código.

Así que un día pensé, ¿qué pasaría si al objeto _SCREEN le agrego la propiedad _MemberData y le asigno el valor requerido?

Pues me di con la sorpresa que también funciona, y ahora con el Intellisense puedo tener en mi código una mejor presentación.




Les adjunto un pequeño código que se puede perfeccionar. Si por ejemplo, guardamos nuestras variables en una tabla junto con sus valores, usando SCAN podemos recorrerla e ir formando la cadena requerida para la propiedad _MemberData y a la vez ir agregando nuestras propiedades al objeto _SCREEN con el método AddProperty().
* Ejemplo de _MemberData en _Screen en VFP9 SP2

* Formando la propiedad _MemberData en formato XML
LOCAL cMemberData as String 
TEXT TO cMemberData NOSHOW PRETEXT 15
  <VFPData>
  <memberdata name="cconectbdsistema" type="property" display="cConectBDSistema"/>
  <memberdata name="cconnectioncatastro" type="property" display="cConnectionCatastro"/>
  <memberdata name="cconnectionrentas" type="property" display="cConnectionRentas"/>
  <memberdata name="cconnectionstring" type="property" display="cConnectionString"/>
  <memberdata name="cconnectiontramite" type="property" display="cConnectionTramite"/>
  <memberdata name="cconnectioncuentacte" type="property" display="cConnectionCuentaCte"/>
  </VFPData>
ENDTEXT 
_Screen.AddProperty("_MemberData", cMemberData)

* Agregando mis propiedades
_Screen.AddProperty("cconectbdsistema","")
_Screen.AddProperty("cconnectioncatastro","")
_Screen.AddProperty("cconnectionrentas","")
_Screen.AddProperty("cconnectionstring","")
_Screen.AddProperty("cconnectiontramite","")
_Screen.AddProperty("cconnectioncuentacte","")
Espero que les sirva.

Miguel Herbias, Lima, Perú

15 de abril de 2009

Reemplazo para SYS(2014)

Artículo original: Replacement for SYS(2014) 
http://doughennig.blogspot.com/2009/03/replacement-for-sys2014.html
Autor: Doug Hennig
Traducido por: Luis María Guayán

SYS(2014) es una pequeña gran función que ayuda a hacer sus aplicaciones portátiles. Como retorna la ruta relativa de un archivo, a una carpeta específica, se puede utilizar en un nombre de archivo absoluto (como el que retorna GETFILE()) y almacenar la ruta relativa, en lugar de la ruta absoluta.

Sin embargo, una cosa que siempre digo sobre SYS(2014), es que retorna la ruta en mayúsculas. Si se desea mostrar la ruta de acceso para el usuario, ellos preguntarán por qué la ruta está toda en en mayusculas en el programa, y diferente en el disco.

Afortunadamente, hay una simple función API de Windows que usted ae puede llamar, que hace lo mismo que SYS(2014), pero respeta las mayúsculas y minusculas. El siguiente es un ejemplo de cómo utilizar esta función:

(ACTUALIZACION: Walt Krzystek señaló que GetRelativePath retornó una cadena en blanco si las dos rutas se encuentran en unidades diferentes. Además, yo olvidé quitar los espacios al valor devuelto.)

*- Ejemplo de ambas funciones
lcFile = "C:\VFP\Desarrollo\Programa.prg" 
lcPath = "C:\VFP\Aplicaciones\"

? SYS(2014, lcFile, lcPath)
? GetRelativePath(lcFile, lcPath)

FUNCTION GetRelativePath(tcTo, tcFrom)
  #DEFINE FILE_ATTRIBUTE_DIRECTORY 0x10
  #DEFINE FILE_ATTRIBUTE_NORMAL 0x80
  #DEFINE MAX_PATH 260
  
  DECLARE INTEGER PathRelativePathTo IN shlwapi.DLL ;
    STRING @out, STRING @from, INTEGER fromattrib, ;
    STRING @to, INTEGER toattrib

  lcPath = SPACE(MAX_PATH)
  lcFrom = IIF(VARTYPE(tcFrom) = 'C', tcFrom, ;
    SYS(5) + CURDIR()) + CHR(0)
  lnFromAttrib = IIF(DIRECTORY(lcFrom), FILE_ATTRIBUTE_DIRECTORY, ;
    FILE_ATTRIBUTE_NORMAL)
  lcTo = IIF(VARTYPE(tcTo) = 'C', tcTo, ;
    SYS(5) + CURDIR()) + CHR(0)
  lnToAttrib = IIF(DIRECTORY(lcTo), FILE_ATTRIBUTE_DIRECTORY, ;
    FILE_ATTRIBUTE_NORMAL)
    
  PathRelativePathTo(@lcPath, @lcFrom, lnFromAttrib, @lcTo, lnToAttrib)

  lcPath = ALLTRIM(STRTRAN(lcPath, CHR(0), ' '))

  IF EMPTY(lcPath)
    lcPath = tcTo
  ENDIF EMPTY(lcPath)

  RETURN lcPath
ENDFUNC

22 de marzo de 2009

Unifica dos archivos en formato MSWORD que estan separados en uno solo

Hace unos días un amigo me pidió que le ayudara con un rutina que ligara dos documentos separados en uno solo realizados en MSWORD. Aquí esta el código.

Todos los comandos utilizados en esta rutina están en la documentación que PortalFox tiene disponible para todos nosotros.

*--Verifica que existan los archivos antes de instanciar WORD
*--------------------------------------------------------------------------
*--Instancia y copia la primera hoja que contiene el texto del .RTF
*--------------------------------------------------------------------------
wFile=FULLPATH("HojaTexto.Rtf")
IF !FILE(wFile)
  =MESSAGEBOX("¡¡Archivo &wFile, no se pudo encontrar!!",16,Titulo)
  RETURN
ENDIF

*----------------------------------------------------------------------
*--Contiene la fotografia y pequeña reseña.
*----------------------------------------------------------------------
gFile=FULLPATH("hoja.Doc")
IF !FILE(gFile)
  =MESSAGEBOX("¡¡Archivo &gFile, no se pudo encontrar!!",16,Titulo)
  RETURN
ENDIF

*------------------------------------------------------------------------
*---ABRE WORD OCULTO PARA COPIAR LOS DATOS Y SE SALE
*------------------------------------------------------------------------
oWord=CREATEOBJECT("Word.Application")
WAIT WIND "Abriendo sesión de MS Word" NOWAIT
WITH oWord
  .Documents.ADD(wFile)  &&Abre el documento .RTF
  .ActiveDocument.SELECT &&Activa la hoja
  cText=.SELECTION.TEXT  &&Selecciona todo el texto
  .SELECTION.COPY        &&Copia todo el texto del documento seleccionado
  .VISIBLE=.F.           &&Abre la primera INSTANCIA de WORD OCULTO del documento .RTF
  .ActiveDocument.CLOSE  &&Cierra el documento .RTF ACTIVO

  .Documents.ADD(gFile)  &&Abre el documento .DOC que contiene las fotos
  .CAPTION="ProtalFox.com......" &&Coloca un titulo al documento

  *--Bajar las filas que desea en el documento abierto
  FOR i=1 TO 100         &&Baja 100 lineas hasta el final del archivo
    oWord.SELECTION.MoveDown
  ENDFOR
  .SELECTION.Paste       &&Pega los datos al final del documento .DOC
  *.Selection.HomeKey     &&Se supone que coloca el cursor en la primera linea
  .VISIBLE=.T.
ENDWITH
WAIT CLEAR
RETURN
*------------------------------------------------------------------------
Tonny Molina

14 de marzo de 2009

Envío de correo electrónico por el servidor SMTP de Windows Live Hotmail

Actualmente Windows Live Hotmail habilitó el acceso POP3 y SMTP en todas las cuentas de correos de Hotmail.


Está publicado el artículo Envío de correo electrónico por el servidor SMTP de Gmail del cual partiremos y solo cambiaremos las configuraciones necesarias que son:
  • Nombre de usuario: El nombre de tu Windows Live ID, (Ejemplo: usuario@hotmail.com)
  • Contraseña: La contraseña de tu cuenta Windows Live
  • Servidor SMTP: smtp.live.com
  • Puerto SMTP: 25
  • Autenticación requerida: Si
  • Cifrado TLS/SSL requerido: Si
Para los que quieren saber los datos del servidor POP3 de Windows Live Mail, no necesarios para este artículo:
  • Servidor POP3: pop3.live.com
  • Puerto POP3: 995
  • POP3 SSL requerido: Si
El siguiente es el código modificado que nos permite el envió de correo electrónico a través del servidor SMTP de Windows Live Hotmail.
TRY
  LOCAL lcSchema, loConfig, loMsg, loError, lcErr
  lcErr = ""
  lcSchema = "http://schemas.microsoft.com/cdo/configuration/"
  loConfig = CREATEOBJECT("CDO.Configuration")
  WITH loConfig.FIELDS
    .ITEM(lcSchema + "smtpserver") = "smtp.live.com"
    .ITEM(lcSchema + "smtpserverport") = 25
    .ITEM(lcSchema + "sendusing") = 2
    .ITEM(lcSchema + "smtpauthenticate") = .T. 
    .ITEM(lcSchema + "smtpusessl") = .T.
    .ITEM(lcSchema + "sendusername") = "miusuario@hotmail.com"
    .ITEM(lcSchema + "sendpassword") = "micontrasenia"
    .UPDATE
  ENDWITH
  loMsg = CREATEOBJECT ("CDO.Message")
  WITH loMsg
    .Configuration = loConfig
    .FROM = "Usuario Hotmail <miusuario@hotmail.com>"
    .TO = "usuario@gmail.com"
    .Subject = "Prueba desde Windows Live Hotmail"
    .TextBody = "Este es un mensaje de prueba con CDO con " + ;
      "autenticación y cifrado SSL desde Windows Live Hotmail."
    .Send()
  ENDWITH
CATCH TO loError
  lcErr = [Error: ] + STR(loError.ERRORNO) + CHR(13) + ;
    [Linea: ] + STR(loError.LINENO) + CHR(13) + ;
    [Mensaje: ] + loError.MESSAGE
FINALLY
  RELEASE loConfig, loMsg
  STORE .NULL. TO loConfig, loMsg
  IF EMPTY(lcErr)
    MESSAGEBOX("El mensaje se envió con éxito", 64, "Aviso")
  ELSE
    MESSAGEBOX(lcErr, 16 , "Error")
  ENDIF
ENDTRY
Para conocer muchas mas opciones que nos brinda CDO para el envío de correos electrónicos desde VFP, podemos ver el siguiente artículo: Mas sobre el envio de mensajes de correo electrónico desde Visual FoxPro

Hasta la próxima,

Luis María