Club Delphi  
    FTP   CCD     Buscar   Trucos   Trabajo   Foros

Retroceder   Foros Club Delphi > Principal > Conexión con bases de datos
Registrarse FAQ Miembros Calendario Guía de estilo Buscar Temas de Hoy Marcar Foros Como Leídos

Conexión con bases de datos

Respuesta
 
Herramientas Buscar en Tema Desplegado
  #1  
Antiguo 03-07-2016
juniorSoft juniorSoft is offline
Miembro
 
Registrado: Apr 2005
Posts: 111
Poder: 14
juniorSoft Va por buen camino
Generar Hash a partir de Registro Firedac

Hola de nuevo amigos del foro,

he buscado en diversos hilos del foro sobre sincronizar una base de datos sqlite con otra base de datos y encontré lo siguiente:

http://www.clubdelphi.com/foros/showthread.php?t=84484

Estoy utilizando firedac para conectarme a sqlite y quisiera hacer una funcion MD5 que pasandole un registro del dataset me devuelva un hash
único que se almacene en cada tabla que inserte registros para luego utilizar en la sincronización.


Desde ya muchas gracias.

Última edición por juniorSoft fecha: 03-07-2016 a las 14:42:12.
Responder Con Cita
  #2  
Antiguo 05-07-2016
bucanero bucanero is offline
Miembro
 
Registrado: Nov 2013
Ubicación: Almería, España
Posts: 111
Poder: 5
bucanero Va por buen camino
Hola juniorSoft,

Referente a mi respuesta anterior del hilo que comentas, lo que hago es convertir todos los campos implicados en la sincronización (no tienen porque ser todos los campos que contiene la tabla, si no solamente aquellos campos comunes a ambas tablas, la de origen y la de destino) de cada registro a una cadena de texto, y aplicar la función MD5 a esa cadena generada.

Esto que en principio parece una operación relativamente sencilla, puede llegar a dar muchos problemas, sobre todo cuando los motores de bases de datos son distintos, en mi caso la sincronización se hace para los motores MySQL y MsSQL y los datos que en principio deberían de ser iguales y generara un mismo HASH en ambos sitios, termina por no ser así.

Por poner un ejemplo a esto,
en MySQL una cadena almacenada en un campo de tipo varchar solo tiene la longitud de la propia cadena,
pero en MsSQL esa misma cadena almacenada también en un varchar de la misma logitud que el caso anterior, en realidad los datos van con espacios a la derecha hasta completar la logintud del varchar, por lo que al aplicar la función MD5 en ambos casos y siendo los mismos datos, el resultado sera distinto.


En cuanto a generar el HASH de un registro, yo tengo creada una clase a la que se le pasa una lista de campos implicados (campos comunes) en el hash con el nombre y tipo de campo, y a partir de la cual monto una instrucción SQL que se encarga de decirle al motor de BBDD como generar el hash de esos campos, quedando una consulta similar a esto:

Código SQL [-]
SELECT campoKey1, campoData1, ..., campoDataN, 
MD5(concat(
  campoKey1,
  if(not campoData1 is null, campoData1, 'NULL'),
  ...
  if(not campoDataN is null, campoDataN, 'NULL')  
)) as HASHVALUE
FROM tabla


Algunas de dichas funciones para MySQL son estas:


Código Delphi [-]
function SQLHASH(const value:String):string; overload;
begin
  result:='MD5('+VALUE+')';
end;

function SQLHASH(list: TStrings): String; overload;
var
  i:longint;
begin
  result:='';
  for i := 0 to list.count-1 do begin
    if result<>'' then result:=result+','+#13+#10;
    result:=result+list[i];
  end;
  if result<>'' then
    result:='UPPER('+SQLHASH('(concat('+result+')')+') as HASHVALUE';
end;

function SQLConvertirCampoAString(Field: TField): String;
//funcion para mysql
begin
   result:='CASE WHEN '+field.FieldName+' IS NULL THEN '''' ';
   if (Field is TbooleanField) then
      result:=result+'WHEN '+field.FieldName+'=1 THEN ''1'' ELSE ''0'' '
   else if (Field is TDateTimeField) then
      result:=result+'ELSE DATE_FORMAT('+field.FieldName+', "%Y-%m-%d %H:%i:%S") '
   else if (field is TStringField) or
           (field is TWideStringField) then
      result:=result+'ELSE RTRIM('+field.FieldName+')'
   else if (field is TMemoField) or (field is TWideMemoField) then begin
      result:=result+
                 'WHEN LENGTH('+field.FieldName+')<=0 THEN '''' '+
                 'ELSE '+SQLHASH('CONCAT(LENGTH('+field.FieldName+'), SUBSTRING('+field.FieldName+', 1, 500))')+' '
   end else
      result:=result+'ELSE CONVERT('+field.FieldName+'  USING utf8)';
   result:=result+' END';
end;

function SQLConvertirListaCamposAString(Dataset: TDataSet):string;
//list: devuelve en cada linea la lista de conversiones para cada uno de los campos de la tabla
var
  i:integer;
  fieldName, SQLconversion:String;
  Field:TField;
  list:TStringList;
begin
  try
    list := TStringList.create;
    List.clear;
    //Se añaden todos los campos de la consulta
    with dataset do
      for i := 0 to FieldDefs.count - 1 do begin
        fieldName := FieldDefs.items[i].Name;
        if (FieldName <> 'md5') then begin
          field := fieldByName(fieldName);
          SQLconversion := SQLConvertirCampoAString(field);
          list.add(SQLconversion);
        end;
      end;
    if (List.Count > 0) then
      result := AsSQLMD5(list);
  finally
    list.free;
  end;
end;


Todas estas funciones las tengo definidas en una clase generica de la que luego derivo para cada uno de los motores de BBDD,
puesto que hay diferencias bastante significativas en cuanto a la sentencias SQL para cada uno de los motores de BBDD, aquí dos de las funciones declaradas arriba para MySQL y aqui abajo las mismas funciones pero ahora para MSSQL
Código Delphi [-]
function SQLHASH(const value: String): String;
begin
  //Genera el valor MD5 de una cadena 
  Result:='CONVERT(char(32), HASHBYTES(''MD5'','+value+'), 2)';
end;

function SQLConvertirCampoAString(Field: TField): String;
//funcion para mssql
var
  FieldName:string;
Begin
  FieldName:='"'+field.FieldName+'"';
  result:='CASE WHEN "'+field.FieldName+'" IS NULL THEN '''' ';
  if (Field is TbooleanField) then
    result:=result+'WHEN "'+field.FieldName+'"=1 THEN ''1'' ELSE ''0'' '
  else if (Field is TDateTimeField) then
    result:=result+'ELSE CONVERT(VARCHAR(24), "'+field.FieldName+'",120)'
  else if (field is TStringField) or
           (field is TWideStringField) then
    result:=result+'ELSE RTRIM("'+field.FieldName+'")'
  else if (field is TMemoField) or (field is TWideMemoField) then begin
    result:=result+'ELSE '+
        'CASE WHEN DATALENGTH("'+field.FieldName+'")<=0 THEN '''' '+
        'ELSE '+SQLHASH('convert(varchar, DATALENGTH("'+field.FieldName+'"))+SUBSTRING("'+field.FieldName+'", 1, 500)')+' END'
  end else
    result:=result+'ELSE CONVERT(VARCHAR(MAX), "'+field.FieldName+'")';
  result:=result+' END';
end;


Algunos problemas que yo me he encontrado y que pueden complicar bastante las cosas:
- A la hora de ordenar por campos indice de tipo texto, dependiendo del COLLATE de las tablas es posible que no tengan el mismo ORDEN.
- En alguno de los motores de BBDD el hash lo devuelve en mayúsculas y en otro en minúsculas, por lo que es necesario convertirlo todo a mayúsculas o todo a minúsculas
- Los componentes ZEOS en general van muy bien, pero tienen algunos BUGS que excepcionalmente pueden dar muchos quebraderos de cabeza, uno de ellos en particular es los campos de tipo TBLOB donde no es capaz de diferenciar cuando un campo es NULL o solo esta vacío.


Un Saludo
Responder Con Cita
  #3  
Antiguo 05-07-2016
juniorSoft juniorSoft is offline
Miembro
 
Registrado: Apr 2005
Posts: 111
Poder: 14
juniorSoft Va por buen camino
Hola bucanero,

Gracias por la guía que me has ofrecido.

Lo que busco es tratar de reducir el tiempo lo mayor que se pueda en la sincronización ya que la aplicación que estoy realizando es para tablets y smartphnes los cuales tendrán una BD sqlite; puede ser que no se realice el proceso con pocos datos porque no es necesario mantenerlo sincronizados en cortos periodos de tiempo, el tope creo serían unos 10 a 15 mil registros.

Quizás tendré que realizar un backup de la base de datos sqlite para luego pasarla al equipo que va a sincronizar y realizarlo desde ahí.


Saludos,
Responder Con Cita
  #4  
Antiguo 06-07-2016
bucanero bucanero is offline
Miembro
 
Registrado: Nov 2013
Ubicación: Almería, España
Posts: 111
Poder: 5
bucanero Va por buen camino
Hola juniorSoft,

En mi sistema, la BBDD principal esta en MSSQL en local, y la BBDD de destino esta en un servidor MySQL en internet, y llego a realizar una sincronizacion completa con algo mas de 50 tablas, algunas de ellas con varios millones de registros en poco mas de 7 u 8 minutos.
Para tablas no muy grandes, varios miles de registros, apenas tardas unos segundos.
Todo esto, teniendo en cuenta que la mayoria de los datos no suelen cambiar, otra historia distinta es hacer una sincronizacion desde cero donde se van a crear todos los datos.

Yo el sistema lo he optimizado para que las cargas de trabajo al servidor de BBDD sean las minimas posibles.
Como pongo anteriormente me descargo solo los indices de cada registro con sus hash correspondientes ordenados por el/los campos indices y
recorro ambas tablas (la de origen y la de destino) buscando cambios en los hash o registros que no existan, y genero dos listas de indices, una con los indices que no existen o se han modificado y otra con los indices que hay que borrar en la tabla de destino

y una vez terminada esta comparación inicial:
- Se ejecuta la consulta de borrado con los indices de la lista a eliminar.
- Y se realiza una consulta de todos los campos que se van a actualizar para los indices que contiene la lista a modificar,
una vez que ya tengo esos datos monto una consulta de insercion masiva con modificacion de los datos, (Para MySQL seria una sentencia INSERT INTO.. ON DUPLICATE KEY UPDATE... y para MsSQL es un sentencia MERGE )


No tengo experiencia en cuanto a la programación con dispositivos moviles por lo que no se la velocidad que pueden llegar a tener este proceso.

Un saludo
Responder Con Cita
Respuesta


Herramientas Buscar en Tema
Buscar en Tema:

Búsqueda Avanzada
Desplegado

Normas de Publicación
no Puedes crear nuevos temas
no Puedes responder a temas
no Puedes adjuntar archivos
no Puedes editar tus mensajes

El código vB está habilitado
Las caritas están habilitado
Código [IMG] está habilitado
Código HTML está deshabilitado
Saltar a Foro

Temas Similares
Tema Autor Foro Respuestas Último mensaje
Generar Metadatos a partir de una consulta SQL. Neeruu MS SQL Server 10 09-04-2014 01:02:00
Generar XML a Partir de XSD Avellas Internet 1 26-12-2008 15:36:48
generar un string a partir de dos celdas de un DbGrid pablopessoa Conexión con bases de datos 3 20-10-2008 18:18:44
Generar fichero x.tlb a partir de x_TLB.pas albion Varios 1 16-06-2006 13:20:51
generar html (tablas) a partir de query jymy788 Varios 2 28-09-2004 10:29:36


La franja horaria es GMT +2. Ahora son las 22:34:10.


Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi
Copyright 1996-2007 Club Delphi