21 de septiembre de 2019

Almacenar objetos grandes en la base de datos

Ejemplo de como subir archivos en una base de datos, en este caso PostgreSQL.

Muchas veces nos encontramos con la necesidad de guardar archivos en el servidor para disponibilidad de los usuarios de nuestra aplicación.

La acostumbrada ruta aleatoria del archivo en una base de datos y su posterior copiado en el mismo muchas veces no sera aplicable mas aun cuando nuestra base de datos se encuentra en un servidor remoto donde no disponemos de accesos a sus recursos

Aquí es cuando surge la necesidad de cambiar la estrategia de almacenamiento de estos objetos grandes en campos de la base de datos el ejemplo utiliza un campo tipo text equivalente a un campo memo de una dbf.

Lo primero es convertir nuestro archivo a base 64 para poder almacenarlo y lo hacemos con las siguientes lineas :

nfile = GETFILE()
** convertimos a base 64
wbase64 = strconv(filetostr(nfile),13)
** almacenamos en la base de datos
sqlexec(1,"insert into sistema.multimedia (objeto,nombre) values (? wbase64,?justfname(nfile))","rta")
** para descargar el archivo usaramos la siguiente sintaxis
sqlexec(1,"select * from sistema.multimedia where ide=1","rta")
_rutarchivo = SYS(5) + "\" + ALLTRIM(rta.nombre)
sele rta
strtofile(STRCONV(rta.objeto,14),_rutarchivo)
** y lo podemos abrir con el programa asociado
oShell = CreateObject("WScript.Shell")
oShell.Run(_rutarchivo,2,.f.)

mgx

13 de agosto de 2019

Solucionar el problema del relleno de VarChar

Solucionar el problema del relleno de VarChar

Autor: Mike Lewis
Texto original: Solving the "padded VarChar" problem (http://www.ml-consult.co.uk/foxst-36.htm)
Traducido por: Ana María Bisbé York


¿Cómo evitar los espacios no deseados en las tablas en Bases de datos?

Si utiliza vistas remotas para actualizar datos en bases de datos remotas, probablemente haya sufrido el problema del relleno en campos VarChar.

Cuando Visual FoxPro lee datos remotos (como puede ser SQL Server u Oracle) a una vista remota, convierte cualquier columna VarChar en un campo caracteres. Cada campo se rellena con espacios para llenar el mayor ancho. Si luego utiliza esa vista remota para actualizar el servidor, los espacios agregados se envían de regreso. El servidor almacena espacios en blanco extras en las tablas, lo que impide aprovecharse de los beneficios de datos VarChar.

En VFP 7.0 y antes, no había mucho que se pudiera hacer. De hecho, esta era una de las razones por las que los desarrolladores evitaran el uso de vistas remotas al actualizar datos, prefiriendo en su lugar utilizar comandos UPDATE y DELETE directamente vía SQL pass-through. VFP 8.0 proporciona un tratamiento alternativo con el CursorAdapter. Al agregar la función Trim en la propiedad ConversionFunc del CursorAdapter, puede eliminar los espacios extras. Pero el paso de vistas remotas a CursorAdapters podría involucrar bastante trabajo.

Una mejor solución

VFP 9.0 ofrece una solución mucho mejor. A diferencia de versiones anteriores, la versión 9.0 soporta el tipo de dato VarChar en vistas remotas. Si ambos campos, los de Vistas remotas y los del servidor son VarChar, los datos no se van a rellenar con espacios, entonces, no surgirá el problema.

Sin embargo, esto no ocurre automáticamente. De forma predeterminada, cualquier columna VarChar en el servidor se va a corresponder con un campo de caracteres en las vistas, como en versiones anteriores. Para poder aprovechar las ventajas del nuevo tipo de dato VarChar, hay que cambiar explícitamente el tipo de dato en la vista.
Si está comenzando una aplicación nueva y no tiene creadas sus vistas remotas, está de suerte. Todo lo que necesita hacer es ejecutar el siguiente comando antes de crear las vistas:

CURSORSETPROP("MapVarChar", .T., 0) 

Esto dice a VFP que haga corresponder columnas VarChar con campos VarChar del servidor. Al pasar 0 como tercer parámetro, se estipula que esta configuración se aplica a todas las vistas creadas en la sesión actual. Esto sólo afecta a las nuevas vistas que se creen, cualquier vista creada antes, no se verá afectada. Esta configuración no es persistente, asegúrese de ejecutar el comando anterior antes de crear vistas en la sesión actual.

Hacerlo retrospectivamente

Si ha creado sus vistas remotas, será necesario alterar el tipo de dato cada uno de los campos relevantes. Una vía de hacerlo es desde dentro del diseñador de vistas. Desde el menú Consultas, abra la ventana View SQL (Ver SQL). Verá la sentencia SQL que define la vista, seguida de una serie de llamadas DBSETPROP(). Este va a incluir, por cada uno de los campos de la vista, una línea de código que establece el tipo de dato del campo. Por ejemplo:

DBSetProp(ThisView+".company","Field","DataType","C(40)")

El C(40) en este ejemplo indica un campo de caracteres con ancho fijo, igual a 40. Para convertirlo en un campo VarChar, simplemente cambie la C por una V. Repita este proceso para todos los campos que quiere que se correspondan con VarChar, luego guarde la vista y cierre el diseñador. Los cambios serán efectivos la próxima vez que se abra la vista.

Alternativamente, puede utilizar la ventana comandos para hacer este trabajo. En este caso escriba un comando similar al del ejemplo anterior; pero con el nombre de la vista precediendo al nombre de la columna, en lugar de ThisView. Nuevamente cambie el tipo de datos de C a V:

DBSetProp("Customer.company","Field","DataType","V(40)")

Repita este proceso para cada campo que desee corresponder en cada uno de las vistas remotas. Como antes, los cambios van a tener efecto, la próxima vez que se abra la vista.

Mike Lewis Consultants Ltd. Febrero 2005

9 de julio de 2019

Transacciones de usuarios en base de datos

Tuve la necesidad de crear una solución para ver las transacciones que podrían hacer los usuarios, debido a que trabajo con bases e datos de foxpro(DBC) , y se trataba de no meter mas código o funciones en el mismo sistema realizado, si no que el proceso fuera transparente, osea se trata de escribir un código en los procedimientos almacenados de la DBC y esto si crear programas.

1 - Abrir la base de datos a usar

Crear una tabla con con la siguiente información en la base de datos abierta

NOMBRE: HISTORIAL.DBF

Campo  Campo Nombre  Tipo       Ancho  
1      USUARIO       Caracter   20     
2      TIPO          Caracter   20     
3      FECHA         DateTime   8      
4      TABLA         Caracter   30     
5      EQUIPO        Caracter   50     
6      OBSERVA       Memo       4      

Los indices de la tabla pueden ser creados a su consideración para generar reportes o métodos de consulta propios

2 - Ir a las propiedades de la base de datos y activar la casilla de verificación "SET EVENTS ON " y despues dar click a el botón "EDIT CODE", después insertar el siguiente código:

PROCEDURE Hsts(clTipo)
  LOCAL clobser
  STORE SPACE(0) TO clObser,clObservaciones,clValor,clDatos
  IF !TYPE("cp_login")="C"
    cp_login="DESCONOCIDO"
  ENDIF
  clAlias=Alias()
  If Empty("clAlias")
    Return
  Endif
  Select (clAlias)
  clRutaHistorico=ADDBS(justpath(CURSORGETPROP("Database")))+"HISTORIAL.DBF"
  USE IN (SELECT("Historial_cfg"))
  nl_error=0
  ON ERROR nl_error=1
  USE (clRutaHistorico) IN 0 SHARED AGAIN Alias Historial_cfg
  ON ERROR
  IF nl_error=0
    clObser="Campos Modificados"+CHR(13)
    FOR Ind=1 TO FCOUNT(clAlias)
      clObser=clObser+"  "+FIELD(Ind)+"=  "
      clValor=ALLTRIM(clAlias)+"."+FIELD(Ind)
      DO Case
        CASE VARTYPE(&clValor) = "N"
          clDatos=STR(&clValor,16,2)
        CASE VARTYPE(&clValor) = "C"
          clDatos=&clValor
        CASE VARTYPE(&clValor) = "D"
          clDatos=DTOC(&clValor)
        CASE VARTYPE(&clValor) = "T"
          clDatos=TTOC(&clValor)
        OTHERWISE
          clDatos=""
      ENDCASE
      clObser=clObser+clDatos+CHR(13)
    NEXT
    clObservaciones=clObser
    SELECT Historial_cfg
    APPEND BLANK
    Replace Historial_cfg.TIPO WITH clTipo,;
      Historial_cfg.FECHA WITH DATETIME(),;
      Historial_cfg.USUARIO WITH Cp_LOGIN,;
      Historial_cfg.TABLA WITH clAlias,;
      Historial_cfg.EQUIPO WITH LEFT(SYS(0),AT("#",SYS(0))-1),;
      Historial_cfg.OBSERVA WITH clObservaciones
    USE IN (SELECT("Historial_cfg"))
  ENDIF
  IF !EMPTY(clalias)
    SELECT &clAlias
  ENDIF
  ON ERROR
  RETURN
ENDPROC

3 - En la tablas importantes en donde se requiera el registro de transacciones se realizar lo siguiente modificar datos de la tabla, y ir a la pestaña "Table" y en cada Triggers insertar el siguiente código:

Insert Trigger = Hsts("AGREGAR")
update Trigger = Hsts("MODIFICAR")
delete Trigger = Hsts("ELIMINAR")

4 - Listo después de esto entonces cada transacción se estará grabando el la tabla de Historial, solo faltaría hacer un reporte para visualizar información del historial.

10 de junio de 2019

Código genérico corregido para ActiveX

Artículo original: ActiveX generic fix
http://www.foxpert.com/knowlbits_200804_1.htm
Autor: Christof Wollenhaupt
Traducido por: Ana María Bisbé York


Después de leer mi artículo ActiveFiX que trataba sobre la no respuesta de controles ActiveX, Carlos Alloatti llegó a una solución genérica del problema:

*!* Habilitar ventanas de controles ActiveX 

#Define GW_CHILD 5
#Define GW_HWNDNEXT 2

Declare Integer GetWindow In win32api As apiGetWindow ;
  Integer nhWnd, ;
  Integer uCmd

Declare Integer RealGetWindowClass In win32api ;
  As apiRealGetWindowClass ;
  Integer nhWnd, ;
  String @pszType, ;
  Integer cchType

Declare Integer EnableWindow In win32api As apiEnableWindow ;
  Integer nhWnd, ;
  Integer bEnable

Local ;
  m.lnChildHWnd As Integer, ;
  m.lnCmd As Integer, ;
  m.lnEnable As Integer, ;
  m.lcClassName As String, ;
  m.lnBufferLen As Integer

*!* Para probar, cambie  m.lnEnable a 0 
*!* para inhabilitar las ventanas OleControl 
m.lnEnable = 1

If Thisform.ShowWindow = 2 Or Thisform.ScrollBars > 0 Then
  m.lnChildHWnd = apiGetWindow(Thisform.HWnd, GW_CHILD)
Else
  m.lnChildHWnd = Thisform.HWnd
Endif

m.lnCmd = GW_CHILD

Do While .T.
  m.lnChildHWnd = apiGetWindow(m.lnChildHWnd, m.lnCmd)

  If m.lnChildHWnd = 0 Then
    Exit
  Endif

  m.lcClassName = Space(254)
  m.lnBufferLen = apiRealGetWindowClass(m.lnChildHWnd, ;
    @m.lcClassName, Len(m.lcClassName))
    m.lcClassName = Left(m.lcClassName , m.lnBufferLen)

  If m.lcClassName == "CtlFrameWork_ReflectWindow" Then
    apiEnableWindow(m.lnChildHWnd, m.lnEnable)
  Endif

  m.lnCmd = GW_HWNDNEXT
Enddo

25 de mayo de 2019

No filtre datos en una cuadrícula

Artículo original: Don't FILTER Data in a Grid
http://www.craigberntson.com/blog/2008/08/dont-filter-data-in-grid.asp
Autor: Craig Berntson
Traducido por: Luis María Guayán


He visto varios mensajes en los foros donde últimamente la gente pregunta acerca de cómo filtrar los datos mostrados en una cuadrícula. Esta es una muy mala idea, ya que el deslizador de la barra de desplazamiento no funciona correctamente. Mire este formulario, que utiliza un filtro.


La tabla tiene más de 200.000 registros. Yo apliqué:

SET FILTER TO ContactName = "Alejandra Camino".

El resultado no es bueno. Vea la posición del deslizador. Cuando se mueve el deslizador hacia la parte superior de la barra de desplazamiento, éste salta de nuevo a la posición que se muestra en la imagen capturada, porque es ahí donde está el registro actual en la tabla entera. Esto es muy confuso para el usuario.

Ahora mire lo que sucede cuando se utiliza una consulta o una vista.


En este formulario creo una vista local y configuro la cuadrícula para usar la vista como fuente de datos. Luego consulto los datos utilizando una vista parametrizada. Mire en este caso, que el deslizador se encuentra en la parte superior de la barra de desplazamiento cuando el puntero de registro se encuentra en el primer registro. Esto se debe a que los datos de la cuadrícula solo contienen los registros:

WHERE ContactName = "Alejandra Camino"

en lugar de la tabla entera.

Escribir aplicaciones de esta forma podrá requerir un nuevo entrenamiento de los usuarios, ya que no tienen todos los datos al mismo tiempo, pero siempre pueden consultar los datos que deseen. En mi experiencia veo que los usuarios prefieren este tipo de aplicaciones. Su opinión puede ser distinta.

Una ventaja adicional es que estamos preparándonos para trabajar con SQL Server, ya sólo utiliza conjuntos de datos. Siempre debemos consultar los datos que deseamos y traer solamente estos, en lugar de todos.

Copyright 2008, Craig Berntson. Used by permission