Relatório que compara campos do Protheus com campos do SQL

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.

Relatório executado

Relatório executado

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.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

4 Responses

  1. Jonas disse:

    Sensacional, Dan! Obrigado por compartilhar conhecimento!

  2. carlos disse:

    posso rodar esse relatorio, sem ter q compilar o programa?

Deixe uma resposta para JonasCancelar resposta

Terminal de Informação