Jump to content
Fivewin Brasil

Exportação para SQL - MUITO RÁPIDA


emotta

Recommended Posts

Pessoal, fiz uma rotina que pode ajudar a muitos aqui. Acredito que muitos passam pelo problema de ter uma tabela em DBF com muitos dados e ter que exporta-la a um banco de dados SQL.Pela tabela ter muitos dados o processo da forma convencional é extremamente lento e demora muito.

Eu consegui desenvolver uma solução para deixar muito rapida, na verdade absurdamente mais rapida. No exemplo que estou disponibilizando uma tabela com 10mil registros demora 34 segundos no modo convencional e na forma rapida ele faz o mesmo trabalho de exportação em apenas 1 segundo.

Para executar este exemplo você precisa usar o SQLRDD e já estar conectado ao banco de dados. Nos meus testes eu fiz com o SQL SERVER porem acredito que dê facilmente para executar em outros banco de dados apenas compatibilizando a linha onde tem "SET NOCOUNT ON" deve ser encontrado o comando equivalente para outros bancos. Para saber o que essa funcao faz basta pesquisar no google que tem ótimas explicações.

A rotina que estou colocando já cria o arquivo DBF, popula ele e exportar ao SQL. Os comandos para conexão ao banco você deve implementar. Lembrando que o exemplo funciona para SQLRDD, se alguem usa outro RDD é só convertar, o fonte está muito simples.

Bom divertimento, abraços

/*
TEMPO DE IMPORTAÇÃO PARA O SQL
MODO CONVENCIONAL (lFast = .f.) : 34 segundos
MODO OTIMIZADO (lFast = .T.) : 1 segundo
*/

Function u_Teste()
Local cFile_Dbf := "TAB_CLI.DBF"
Local cFile_Sql := "TAB_CLI"
Local aStru := {}
Local lFast := .t.  // mude aqui para .T. se quiser testar o modo otimizado (rapido) ou .f para utilizar o modo convencional (Mais lento)
Local nSec

fErase(cFile_Dbf)

aadd(aStru,{"TAB_CODIGO","C",6,0})
aadd(aStru,{"TAB_NOME","C",30,0})

DbCreate(cFile_Dbf,aStru,"DBFCDX")
DbUseArea(.t.,"DBFCDX",cFile_Dbf,"DBF",.T.,.F.)
Popula_Dbf()

nSec := Seconds()
If lFast
	FastExp("DBF",cFile_Sql)  // funcao para exportação rapida para SQL
Else
   SlowExp("DBF",cFile_Sql)  // funcao para exportação convencional (mais lenta)para SQL
EndIf

MsgStop("Tempo de exportação de dados: "+Str(Seconds()-nSec,6))

DbUseArea(.t.,"SQLRDD",cFile_Sql,"SQL",.T.,.F.)
SQL->(Browse())
SQL->(DbCloseArea())
DBF->(DbCloseArea())

Return

// funcao que faz a exportação do modo convencional (mais lento)
Static Function SlowExp(cAlias,cFileSql)
If Sr_File(cFileSql)
	SR_DropTable(cFileSql)
EndIf

COPY TO &cFileSql VIA "SQLRDD"

Return

// funcao que faz a exportação do modo otimizado (mais rapido)
Static Function FastExp(cAlias,cFileSql)
Local aStru := (cAlias)->(DbStruct())
Local aInsert := {}
Local cInsert

If Sr_File(cFileSql)
	SR_DropTable(cFileSql)
EndIf

DbCreate(cFileSql,aStru,"SQLRDD")

DbSelectArea(cAlias)
DbGotop()
While !Eof()
   cInsert := SqlMontaInsert(cFileSql)
   aadd(aInsert,cInsert)
   DbSkip()
EndDo

FlushData(aInsert)

Return

// funcao que monta string para comando insert do banco de dados
Static Function SqlMontaInsert(cTable)
Local aStru  := DbStruct()
Local nI
Local aCampos := {}
Local cRet := ""
Local uVal

For nI := 1 to Len(aStru)
   uVal := FieldGet(nI)
   If Empty(uVal)
      uVal := "null"
   Else
      uVal := Sr_cDbValue(uVal)
   EndIf
	aadd(aCampos,{aStru[nI,1],uVal})
Next

cRet := "INSERT INTO "+cTable+" ("
For nI := 1 to Len(aCampos)
   If nI > 1
      cRet+=","
   EndIf
   cRet+=aCampos[nI,1]
Next
cRet+=") VALUES ("
For nI := 1 to Len(aCampos)
   If nI > 1
      cRet+=","
   EndIf
   cRet+=aCampos[nI,2]
Next
cRet+=")"

Return cRet


// funcao que descarrega os comandos INSERT para o banco de dados fazendo a quebra para que não tenha perda de pacote ao enviar ao sql
Static Function FlushData(aInsert)
Local cSql := ""
Local aSql := {}
Local nPacote
Local nMaxPacote  := 120 * 1024  // Limita o tamanho do pacote para envio ao SQL em 60kb

cSql := "SET NOCOUNT ON;"+Chr(13)+Chr(10)
For nI := 1 to Len(aInsert)
   cSql+=aInsert[nI]+Chr(13)+Chr(10)
	If Len(cSql) > nMaxPacote // o limite é 65,536 * Network Packet Size - coloquei menos pra garantir que não estoure
		SR_GetConnection():Execute( cSql )
		cSql := "SET NOCOUNT ON;"+Chr(13)+Chr(10)
	EndIf
Next

If !Empty(cSql)
	SR_GetConnection():Execute( cSql )
EndIf
SR_GetConnection():Commit()

Return




// funcao para população inicial do DBF
Static Function Popula_Dbf()
Local nI

For nI := 1 to 10000
   Append Blank
   DBF->TAB_CODIGO := StrZero(nI,6)
   DBF->TAB_NOME   := "NOME "+StrZero(nI,6)
Next
DBF->(DbCommitAll())
Return

 

Link to comment
Share on other sites

O segredo da rotina é que no comando tradicional, vc da insert por insert.

E pela sua rotina vc monta um insert múltiplo.

Isso faz toda diferença.

Em 2006, eu fiz um backup personalizado que gerava mais rápido que o MySQLDUMP e também restaurava mais rápido que o próprio MySQL. E a técnica usada foi justamente esta, de aumentar a quantidade de registro em um insert, também compactei strings e explodia na restauração. Assim o arquivo ficava bem menor que o atual SQL do dump. Além de certa forma criptografar o backup.

Parabéns emotta.


A propósito ele pode ficar ainda mais rápido (e menor) se você usar apenas um insert com várias inserções:

insert into tabela (campoa, campob) values ( valora1, valorb1), (valora2, valorb2), (valora3, valorc3)....

Dê uma olhada.

 

Link to comment
Share on other sites

Static Procedure FastTurbo(cAlias,cFileSql)
   Local aStru := (cAlias)->(DbStruct())
   Local cSql,nTupla:=2
   Local nMaxTupla  := 1000

   If Sr_File(cFileSql)
      SR_DropTable(cFileSql)
   EndIf
   DbCreate(cFileSql,aStru,"SQLRDD")

   DbSelectArea(cAlias)
   DbGotop()
   cSql := SqlInsert(cFileSql)
   DBSkip()
   While !Eof()
      If nTupla > nMaxTupla // Limita a qtde maxima de linha no sqlserver
         SR_GetConnection():Execute( cSql )
         cSql := SqlInsert(cFileSql) //fields e values
         nTupla:=2
      Else
         cSql += SqlValue()  //values
      Endif
      DbSkip()
      nTupla++
   EndDo
   SR_GetConnection():Execute( cSql )
   SR_GetConnection():Commit()
Return

// funcao que monta string para comando insert do banco de dados
Static Function SqlInsert(cTable)
   Local aStru  := DbStruct()
   Local nI
   Local aCampos := {}
   Local cRet := "SET NOCOUNT ON;"+Chr(13)+Chr(10)
   Local uVal

   For nI := 1 to Len(aStru)
      uVal := FieldGet(nI)
      If Empty(uVal)
         uVal := "null"
      Else
         uVal := Sr_cDbValue(uVal)
      EndIf
      aadd(aCampos,{aStru[nI,1],uVal})
   Next

   cRet := "INSERT INTO "+cTable+" ("
   For nI := 1 to Len(aCampos)
      If nI > 1
         cRet+=","
      EndIf
      cRet+=aCampos[nI,1]
   Next
   cRet+=") VALUES ("
   For nI := 1 to Len(aCampos)
      If nI > 1
         cRet+=","
      EndIf
      cRet+=aCampos[nI,2]
   Next
   cRet+=")"

Return cRet


// funcao que monta string para comando insert do banco de dados
Static Function SqlValue()
   Local aStru  := DbStruct()
   Local nI
   Local aCampos := {}
   Local cRet := "",cSql
   Local uVal

   For nI := 1 to Len(aStru)
      uVal := FieldGet(nI)
      If Empty(uVal)
         uVal := "null"
      Else
         uVal := Sr_cDbValue(uVal)
      EndIf
      aadd(aCampos,{aStru[nI,1],uVal})
   Next

   cRet+=",("
   For nI := 1 to Len(aCampos)
      If nI > 1
         cRet+=","
      EndIf
      cRet+=aCampos[nI,2]
   Next
   cRet+=")"

Return cRet

Eduardo com a tua implementação aplicando a sugestão do Alex, baixou em 2 segundos os testes do seu  exemplo.

Ficou muito boa sua rotina, mais uma vez parabéns!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...