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;
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;
var
i:integer;
fieldName, SQLconversion:String;
Field:TField;
list:TStringList;
begin
try
list := TStringList.create;
List.clear;
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
Result:='CONVERT(char(32), HASHBYTES(''MD5'','+value+'), 2)';
end;
function SQLConvertirCampoAString(Field: TField): String;
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