Olá pessoal…
Hoje vou mostrar um relatório que desenvolvi para comparar os campos do Protheus com os campos do SQL Server (para identificar divergências de campos não criados na base ou de tamanhos).
Basicamente ao abrir a rotina é perguntado o Alias De/Até e qual tipo de impressão (somente campos iguais, diferentes ou ambos).
A partir disso, é percorrido todos os campos do SQL Server para a base, e ao percorrer é procurado o campo também na SX2 e SX3, caso a tabela não seja encontrada, já mostra uma divergência, se o campo não existir no Protheus é outra divergência, se o tipo for diferente e o tamanho também são outras.
Abaixo um print do relatório impresso.
Abaixo o fonte desenvolvido.
//Bibliotecas
#Include "Protheus.ch"
#Include "TopConn.ch"
//Constantes
#Define STR_PULA Chr(13)+Chr(10)
/*/{Protheus.doc} zCompara
Comparação de campos do Dicionário x SQL Server
@author Atilio
@since 02/08/2016
@version 1.0
@example
u_zCompara()
/*/
User Function zCompara()
Local aArea := GetArea()
Local aAreaX2 := SX2->(GetArea())
Local aAreaX3 := SX3->(GetArea())
Local oReport
Private cPerg := "X_ZCOMPARA"
Private cAliasDe := ""
Private cAliasAt := ""
Private nTipo := 0
//Enquanto a pergunta for confirmada, define as propriedades do Report e imprime
fVldPerg(cPerg)
While Pergunte(cPerg,.T.)
cAliasDe := MV_PAR01
cAliasAt := MV_PAR02
nTipo := MV_PAR03
//Definindo atributos e propriedades e gerando relatório TReport
oReport := fReportDef()
oReport:PrintDialog()
EndDo
RestArea(aAreaX3)
RestArea(aAreaX2)
RestArea(aArea)
Return
/*-------------------------------------------------------------------------------*
| Func: fReportDef |
| Autor: Daniel Atilio |
| Data: 02/08/2016 |
| Desc: Função que monta a definição do relatório |
*-------------------------------------------------------------------------------*/
Static Function fReportDef()
Local oReport
Local oSectPar := nil
Local oSectCom := nil
//Criação do componente de impressão
oReport := TReport():New( "zCompara",; //Nome do Relatório
"Comparação Dicionário x SQL",; //Título
,; //Pergunte ... Se eu defino a pergunta aqui, será impresso uma página com os parâmetros, conforme privilégio 101
{|oReport| fRepPrint(oReport)},; //Bloco de código que será executado na confirmação da impressão
) //Descrição
oReport:SetLandscape(.T.) //Define a orientação de página do relatório como paisagem ou retrato. .F.=Retrato; .T.=Paisagem
oReport:SetTotalInLine(.F.) //Define se os totalizadores serão impressos em linha ou coluna
If !Empty(oReport:uParam)
Pergunte(oReport:uParam,.F.)
EndIf
//*******************
// PARÂMETROS
//*******************
//Criando a seção de parâmetros e as células
oSectPar := TRSection():New( oReport,; //Objeto TReport que a seção pertence
"Parâmetros",; //Descrição da seção
{""}) //Tabelas utilizadas, a primeira será considerada como principal da seção
oSectPar:SetTotalInLine(.F.) //Define se os totalizadores serão impressos em linha ou coluna. .F.=Coluna; .T.=Linha
//Células da seção parâmetros
TRCell():New( oSectPar,"PARAM" ," ","Parâmetro", "@!" , 30,/*lPixel*/,/*{|| code-block de impressao }*/)
TRCell():New( oSectPar,"CONTEUDO" ," ","Conteúdo", "@!" , 30,/*lPixel*/,/*{|| code-block de impressao }*/)
//*******************
// Comparacao
//*******************
//Criando a seção de dados e as células
oSectCom := TRSection():New( oReport,; //Objeto TReport que a seção pertence
"Comparacao",; //Descrição da seção
{""}) //Tabelas utilizadas, a primeira será considerada como principal da seção
oSectCom:SetTotalInLine(.F.) //Define se os totalizadores serão impressos em linha ou coluna. .F.=Coluna; .T.=Linha
//Colunas do relatório
TRCell():New( oSectCom,"XX_ALISQL" ,"","Alias SQL" ,/*Picture*/, 06,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_ALIPRO" ,"","Alias Protheus" ,/*Picture*/, 03,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_CAMSQL" ,"","Campo" ,/*Picture*/, 10,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_TIPSQL" ,"","Tip.SQL" ,/*Picture*/, 15,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_TIPPRO" ,"","Tip.Protheus" ,/*Picture*/, 15,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_TAMSQL" ,"","Tam.SQL" ,/*Picture*/, 03,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_TAMPRO" ,"","Tam.Protheus" ,/*Picture*/, 03,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_STATUS" ,"","Status" ,/*Picture*/, 10,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,RGB(255,000,000)/*nClrFore*/,/*lBold*/)
TRCell():New( oSectCom,"XX_OBSERV" ,"","Observacao" ,/*Picture*/, 99,/*lPixel*/,/*{|| code-block de impressao }*/,/*cAlign*/,/*lLineBreak*/,/*cHeaderAlign */,/*lCellBreak*/,/*nColSpace*/,/*lAutoSize*/,/*nClrBack*/,/*nClrFore*/,/*lBold*/)
//Acrescentando totalizador nos dados
oFunTot := TRFunction():New(oSectCom:Cell("XX_ALISQL"),,"COUNT",,,"@E 99999999999")
oFunTot:SetEndReport(.F.) //Define se será impresso o total também ao finalizar o relatório (Total Geral)
Return oReport
/*-------------------------------------------------------------------------------*
| Func: fRepPrint |
| Autor: Daniel Atilio |
| Data: 02/08/2016 |
| Desc: Função que imprime o relatório |
*-------------------------------------------------------------------------------*/
Static Function fRepPrint(oReport)
Local cQryCom := ""
Local oSecPar := Nil
Local oSecCom := Nil
Local cAliasSQL := ""
Local cAliasPro := ""
Local cCampoSQL := ""
Local cTipoSQL := ""
Local cTipoPro := ""
Local xTamanSQL := ""
Local xTamanPro := ""
Local cStatus := ""
Local cObserv := ""
DbSelectArea("SX2")
SX2->(DbSetOrder(1)) //X2_CHAVE
SX2->(DbGoTop())
DbSelectArea("SX3")
SX3->(DbSetOrder(2)) //X3_CAMPO
SX3->(DbGoTop())
//Pegando as seções do relatório
oSecPar := oReport:Section(1)
oSecCom := oReport:Section(2)
//Setando os conteúdos da seção de parâmetros
oSecPar:Init()
oSecPar:Cell("PARAM"):SetValue("Alias De?")
oSecPar:Cell("CONTEUDO"):SetValue(cAliasDe)
oSecPar:PrintLine()
oSecPar:Cell("PARAM"):SetValue("Alias Até?")
oSecPar:Cell("CONTEUDO"):SetValue(cAliasAt)
oSecPar:PrintLine()
oSecPar:Cell("PARAM"):SetValue("Tipo?")
oSecPar:Cell("CONTEUDO"):SetValue(Iif(nTipo==1, "Ambos", Iif(nTipo==2, "Diferentes", "Iguais")))
oSecPar:PrintLine()
oSecPar:Finish()
//Montando consulta de dados dos Comparacao
cQryCom := " SELECT DISTINCT " +STR_PULA
cQryCom += " tab.name AS TABELA, " +STR_PULA
cQryCom += " colunas.name AS COLUNA, " +STR_PULA
cQryCom += " tipos.Name AS TIPO, " +STR_PULA
cQryCom += " colunas.max_length AS TAM_MAX " +STR_PULA
cQryCom += " FROM " +STR_PULA
cQryCom += " sys.tables tab " +STR_PULA
cQryCom += " INNER JOIN sys.columns colunas ON ( " +STR_PULA
cQryCom += " tab.object_id = colunas.object_id " +STR_PULA
cQryCom += " AND colunas.name NOT IN ('D_E_L_E_T_', 'R_E_C_N_O_', 'R_E_C_D_E_L_') " +STR_PULA
cQryCom += " ) " +STR_PULA
cQryCom += " INNER JOIN sys.types tipos ON ( " +STR_PULA
cQryCom += " colunas.user_type_id = tipos.user_type_id " +STR_PULA
cQryCom += " ) " +STR_PULA
cQryCom += " LEFT OUTER JOIN sys.index_columns indices_colunas ON ( " +STR_PULA
cQryCom += " indices_colunas.object_id = colunas.object_id " +STR_PULA
cQryCom += " AND indices_colunas.column_id = colunas.column_id " +STR_PULA
cQryCom += " ) " +STR_PULA
cQryCom += " LEFT OUTER JOIN sys.indexes indices ON ( " +STR_PULA
cQryCom += " indices_colunas.object_id = indices.object_id " +STR_PULA
cQryCom += " AND indices_colunas.index_id = indices.index_id " +STR_PULA
cQryCom += " ) " +STR_PULA
cQryCom += " WHERE " +STR_PULA
cQryCom += " tab.name >= '"+cAliasDe+cEmpAnt+"0' " +STR_PULA
cQryCom += " AND tab.name <= '"+cAliasAt+cEmpAnt+"0' " +STR_PULA
cQryCom += " AND tab.name NOT IN ('SCHDTSK', 'SX2"+cEmpAnt+"0') " +STR_PULA
cQryCom += " AND tab.name NOT LIKE 'XX%' " +STR_PULA
cQryCom += " AND tab.name NOT LIKE '%_SP%' " +STR_PULA
cQryCom += " AND tab.name NOT LIKE 'TOP%' " +STR_PULA
cQryCom += " ORDER BY " +STR_PULA
cQryCom += " TABELA, COLUNA " +STR_PULA
//Executando consulta e setando o total da régua
TCQuery cQryCom New Alias "QRY_COM"
Count to nTotal
oReport:SetMeter(nTotal)
//Enquanto houver dados
oSecCom:Init()
QRY_COM->(DbGoTop())
While ! QRY_COM->(Eof())
cAliasSQL := QRY_COM->TABELA
cAliasPro := ""
cCampoSQL := QRY_COM->COLUNA
cTipoSQL := QRY_COM->TIPO
cTipoPro := ""
xTamanSQL := QRY_COM->TAM_MAX
xTamanPro := 0
cStatus := "IGUAL"
cObserv := ""
//Se conseguir posicionar na tabela
If SX2->(DbSeek(SubStr(QRY_COM->TABELA, 1, 3)))
cAliasPro := SX2->X2_CHAVE
Else
cStatus := "DIFERENTE"
cObserv += "Tabela inexistente no Protheus; "
EndIf
//Se conseguir posicionar na coluna
If SX3->(DbSeek(QRY_COM->COLUNA))
cTipoPro := SX3->X3_TIPO
xTamanPro := SX3->X3_TAMANHO
//Comparação de Tamanho
If xTamanPro != xTamanSQL
If !(Alltrim(Upper(cTipoSQL)) == 'FLOAT' .And. xTamanSQL == 8) .And.;
!(Alltrim(Upper(cTipoSQL)) == 'IMAGE' .And. xTamanSQL == 16 .And. xTamanPro == 10)
cStatus := "DIFERENTE"
cObserv += "Tamanho diferente Protheus x SQL; "
EndIf
EndIf
//Comparação de Tipo
If (cTipoPro $ ('C;D') .And. Alltrim(Upper(cTipoSQL)) != 'VARCHAR') .Or.;
(cTipoPro $ ('N') .And. Alltrim(Upper(cTipoSQL)) != 'FLOAT') .Or.;
(cTipoPro $ ('M') .And. Alltrim(Upper(cTipoSQL)) != 'IMAGE')
cStatus := "DIFERENTE"
cObserv += "Tipo diferente Protheus x SQL; "
EndIf
Else
cStatus := "DIFERENTE"
cObserv += "Campo inexistente no Protheus; "
EndIf
//Transformando os números
xTamanSQL := Transform(xTamanSQL, "@E 999")
xTamanPro := Transform(xTamanPro, "@E 999")
//Se não tiver na filtragem, pula a impressão
If (nTipo == 2 .And. cStatus == "IGUAL") .Or. (nTipo == 3 .And. cStatus == "DIFERENTE")
oReport:IncMeter()
QRY_COM->(DbSkip())
Loop
EndIf
//Imprimindo
oSecCom:Cell("XX_ALISQL"):SetValue(cAliasSQL)
oSecCom:Cell("XX_ALIPRO"):SetValue(cAliasPro)
oSecCom:Cell("XX_CAMSQL"):SetValue(cCampoSQL)
oSecCom:Cell("XX_TIPSQL"):SetValue(cTipoSQL)
oSecCom:Cell("XX_TIPPRO"):SetValue(cTipoPro)
oSecCom:Cell("XX_TAMSQL"):SetValue(xTamanSQL)
oSecCom:Cell("XX_TAMPRO"):SetValue(xTamanPro)
oSecCom:Cell("XX_STATUS"):SetValue(cStatus)
oSecCom:Cell("XX_OBSERV"):SetValue(cObserv)
oSecCom:PrintLine()
//Incrementando contador
oReport:IncMeter()
QRY_COM->(DbSkip())
EndDo
//Finalizando a seção de dados
oSecCom:Finish()
QRY_COM->(DbCloseArea())
Return
/*---------------------------------------------------------------------*
| Func: fVldPerg |
| Autor: Daniel Atilio |
| Data: 02/08/2016 |
| Desc: Função para criar o grupo de perguntas |
*---------------------------------------------------------------------*/
Static Function fVldPerg(cPerg)
//( cGrupo, cOrdem, cPergunt, cPergSpa, cPergEng, cVar, cTipo, nTamanho, nDecimal, nPreSel, cGSC, cValid, cF3, cGrpSXG, cPyme, cVar01, cDef01, cDefSpa1, cDefEng1, cCnt01, cDef02, cDefSpa2, cDefEng2, cDef03, cDefSpa3, cDefEng3, cDef04, cDefSpa4, cDefEng4, cDef05, cDefSpa5, cDefEng5, aHelpPor, aHelpEng, aHelpSpa, cHelp)
PutSx1(cPerg, "01", "Alias De?", "", "", "mv_ch0", "C", 003, 0, 0, "G", "", "", "", "", "mv_par01", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", {}, {}, {}, "")
PutSx1(cPerg, "02", "Alias Até?", "", "", "mv_ch1", "C", 003, 0, 0, "G", "", "", "", "", "mv_par02", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", {}, {}, {}, "")
PutSx1(cPerg, "03", "Tipo?", "", "", "mv_ch2", "N", 001, 0, 0, "C", "", "", "", "", "mv_par03", "Ambos", "", "", "", "Diferentes", "", "", "Iguais", "", "", "", "", "", "", "", "", {}, {}, {}, "")
Return
Obs.: Alguns campos MEMO no Protheus não tem tamanho definido, e podem dar como divergentes, mas para isso pode se alterar o fonte para adaptar.
Bom pessoal, por hoje é só.
Abraços e até a próxima.

Sensacional, Dan! Obrigado por compartilhar conhecimento!
Grande Jonas.
Valeu jovem.
Aquele abraço.
posso rodar esse relatorio, sem ter q compilar o programa?
Boa noite Carlos, tudo bem?
No caso, esse relatório é uma customização, não é do produto padrão.
Então para rodar, você tem que compilar sim o código.