Página 7 de 7

Tutorial de ADO

Enviado: 03 Mai 2022 13:54
por alxsts
Olá!

Primeiro, troque esta macro por um code block:

Código: Selecionar todos

LOCAL bWhile := { || ! EOF() }

While Eval( bWhile )
   ...
End
Isto aqui consome bastante tempo e recursos:

Código: Selecionar todos

03	      @ 06,16 say str(WQTD)
04	      @ 07,19 say CARRO
SQL foi desenhado para funcionar com grupos de registros. Na base do "um por um" fica lento.

Monte blocos, por exemplo de 200 registros e insira de uma vez...

Código: Selecionar todos

#include "ado.ch"

PROCEDURE DbfImport()

   LOCAL bWhile, nCount, cString, cSQL, nQtd, nTotal
   
   bWhile := { || <condição while> }
   
   cString := "INSERT INTO QTRACS (CARRO, MCT, LATITUDE, LONGITUDE, DATA, HORA, VELOC, LOCAL) VALUES "
   
   nCount := 0
   
   nQtd := 200
   
   While Eval( bWhile )
   
      If nCount == 0
         cSql := cString
      Endif
   
      cSql += "( '" + alltrim(CARRO) + "', "
      cSql += "'" + alltrim(MCT) + "', "
      cSql += "'" + alltrim(LATITUDE) + "', "
      cSql += "'" + alltrim(LONGITUDE) + "', "
      cSql += "'" + Transform( ( dtos( DATA ), "@R 9999-99-99" ) + "', "
      cSql += "'" + alltrim(HORA) + "', "
      cSql += "'" + alltrim(alltrim(str(VELOC))) + "', "
      cSql += "'" + alltrim(LOCAL) + "' ) "

      nCount += 1

      If nCount == nQtd
         cSql += ";"
         oConexao:Execute( cSql )

         nTotal += nCount
         nCount := 0
         cSql := "" 

         DispOutAt( 06, 16, Str( nTotal ) )  // Evita carregar o Get System (@... SAY)
         DispOutAt( 07, 19, CARRO )
      Else
         cSql += ", "
      Endif
   
      DbSkip()
   End

   If ! Empty( cSql )
      oConexao:Execute( cSql )
   Endif

   DispOutAt( 06, 16, Str( nTotal ) + " Registros incluidos" )

   DbCloseAll()

   If oConexao:state == adStateOpen
      oConexao:close()
   Endif

   oConexao := NIL

RETURN
Não testei...

Tutorial de ADO

Enviado: 03 Mai 2022 14:17
por sasquast
Muito obrigado pelas dicas.

Irei modificar o fonte e fazer alguns testes.

Tutorial de ADO

Enviado: 03 Mai 2022 14:31
por JoséQuintas
Isto pode ser alterado, mas o default do MySQL é comando até 4MB, caso contrário dá erro.

É como já foi descrito, mas, apenas pra ficar mais didático:

Código: Selecionar todos

cn:Execute( "INSERT INTO tabela ( lista de campos ) VALUES ( lista de valores ), ( lista de valores ), ( lista de valores )" )
cn:Execute( "INSERT INTO tabela ( lista de campos ) VALUES ( lista de valores ), ( lista de valores ), ( lista de valores )" )
Também já foi dito, a diferença é brutal, reduz de horas pra minutos.
Quanto mais inserir por vez, mais rápido será.

Tutorial de ADO

Enviado: 03 Mai 2022 14:50
por JoséQuintas
Sobre as conversões:

Eu preferi criar funções StringSQL(), NumberSQL(), DateSQL(), etc., pra padronizar a formatação, e ValueSQL() quando o tipo é conhecido.
E em casos desse tipo, criar array com a lista de campos.
Apenas pra dar uma idéia

Código: Selecionar todos

aList := { ;
   { "CODIGO", dbf->codigo }, ;
  { "NOME", dbf->nome }, ;
   { "VALOR", dbf->valor } }

cSQL := "INSERT INTO tabela ("
FOR EACH aCampo IN aList
   cSQL += aCampo[ 1 ]
   IF ! aCampo:__EnumIsLast()
      cSQL += ","
   NEXT
NEXT
cSQL += ") VALUES ("
FOR EACH aCampo IN aList
   cSQL += ValueSQL( aCampo[ 2 ] )
   IF aCampo:__EnumIsLast()
      cSQL += ","
   ENDIF
NEXT
cSQL += ")"
cn:Execute( cSQL )
...
FUNCTION ValueSQL( xValue )

LOCAL cType 

cType := ValType( xValue )

DO CASE
CASE cType == "D"
   IF Empty( xValue )
      xValue := "NULL"
   ELSE
      xValue := [']  + Transform( Dtos( xValue ), "@R 9999-99-99" ) + ["]
   ENDIF
CASE cType == "N"
   xValue := Ltrim( Str( xValue ) )
CASE cType == "C"
   xValue := ['] + AllTrim( xValue ) + [']
ENDCASE

RETURN xValue
Com o tempo, pode ir acrescentando outras opções, como por exemplo na string trocar ' por \' , Chr(13) por "\" + Chr(13), e outras mais.
Melhor alterar uma função do que fonte por fonte.

Tutorial de ADO

Enviado: 03 Mai 2022 15:33
por sasquast
Os 4 minutos se tornaram 40 segundos.

Muito obrigado. Ainda há melhoria a ser feita mas vocês me deram o caminho.

Tutorial de ADO

Enviado: 03 Mai 2022 16:23
por JoséQuintas
sasquast escreveu:Os 4 minutos se tornaram 40 segundos.
Muito obrigado. Ainda há melhoria a ser feita mas vocês me deram o caminho.
Aproveitando...

1) É por isso que aproveitar fonte nem sempre é a melhor opção, porque pode estar perdendo velocidade

2) Fazer uma comparação entre DBF e SQL usando a mesma programação não dá. Dependendo como comparar o resultado é diferente

3) Só pra chamar a atenção: o ADO está sendo apenas um intermediário pra enviar o comando para o servidor, o servidor recebe a string e faz o serviço.
O que sempre chamei a atenção para o ADO é sobre isso, porque é usar sem depender de outras LIBs.
E se quiser trocar o ADO depois, é só usar qualquer coisa que repasse o comando para o servidor, não vai ter perdido o que aprendeu com ADO.
Na verdade ele tem muito mais recursos que esse, mas acho que nem vale a pena se aprofundar, pra não ficar dependente dele.

Comentário adicional:
Sempre usei assim sem problemas.
Mas.... dá pra indicar um parâmetro no Execute() pra indicar que nesse caso não vai ter retorno.
Segundo a documentação do ADO, isso economiza recursos, porque o ADO não precisa criar nada extra pra receber o retorno.

https://docs.microsoft.com/pt-br/sql/ad ... rver-ver15
ado.png

Tutorial de ADO

Enviado: 03 Mai 2022 16:26
por JoséQuintas
Faltou:

Código: Selecionar todos

#define AD_EXECUTE_NORECORDS           128
exemplo:

Código: Selecionar todos

         ::Cn:Execute( cSQL, , AD_EXECUTE_NORECORDS )

Tutorial de ADO

Enviado: 03 Mai 2022 18:31
por alxsts
Olá!

Postando o código novamente pois havia alguns erros:

Código: Selecionar todos

#include "ado.ch"

PROCEDURE DbfImport()

   LOCAL bWhile, nCount, cString, cSQL, nQtd, nTotal
   
   bWhile := { || <condição while> }
   
   cString := "INSERT INTO QTRACS (CARRO, MCT, LATITUDE, LONGITUDE, DATA, HORA, VELOC, LOCAL) VALUES "
   nCount  := 0
   nQtd    := 200
   nTotal  := 0

   While Eval( bWhile )
   
      If nCount == 0
         cSQL := cString
      Endif
   
      cSQL += "( " + cs(CARRO) + ", "
      cSQL += cs( MCT) + ", "
      cSQL += cs( LATITUDE) + ", "
      cSQL += cs( LONGITUDE) + ", "
      cSQL += cs( DATA ) + ", "
      cSQL += cs( HORA) + ", "
      cSQL += cs( VELOC) + ", "
      cSQL += cs( LOCAL) + " ) "

      nCount += 1

      If nCount == nQtd
         oConexao:Execute( cSQL + ";",  , AD_EXECUTE_NORECORDS )

         nTotal += nCount
         nCount := 0
         cSQL := "" 

         DispOutAt( 06, 16, Str( nTotal ) )
         DispOutAt( 07, 19, CARRO )
      Else
         cSQL += ", "
      Endif
   
      DbSkip()
   End

   If ! Empty( cSQL )
      // remover a ultima virgula
      cSQL := Alltrim( cSQL )
      cSQL := Substr( cSQL, 1, ( Len( cSQL ) - 1 ) )
      
      oConexao:Execute( cSQL + ";", ,  AD_EXECUTE_NORECORDS )
   Endif

   DispOutAt( 06, 16, Str( nTotal ) + " Registros incluidos" )

   DbCloseAll()

   If oConexao:state == adStateOpen
      oConexao:close()
   Endif

   oConexao := NIL

RETURN
//------------------------------------------------------------------------------

FUNCTION CS( xArg )
RETURN ConvertToSQL( xArg )
//------------------------------------------------------------------------------
FUNCTION ConvertToSQL( xArg )

   LOCAL cRet := "NULL"

   If xArg != NIL
      Switch Valtype( xArg )
         Case "N"  // Numeric
         Case "+"  // Auto increment
         Case "^"  // RowVers 8 Row version number; modification count of this record
         Case "Y"  // Currency 8 64 bit integer with implied 4 decimal
         Case "I"  // Integer 1, 2, 3, 4 or 8 Signed Integer ( Width : )" }
            cRet := LTrim( Str( xArg ) )
            Exit
         Case "C"  // Character
         Case "M"  // Memo
            cRet := "'" + StrTran( AllTrim( xArg ), "'", "''" ) + "'"
            Exit
         Case "D"  // Date
            cRet := "'" + hb_DtoC( xArg, "yyyy-mm-dd" ) + "'"
            Exit
         Case "L"  // Logical  -  MySQL boolean is an alias for tinyint(1)
            cRet := If( xArg, "1", "0" ) 
            Exit
         Case "B"  // B Double 8 Floating point / 64 bit binary
         Case "T"  // Time 4 or 8 Only time (if width is 4 ) or Date & Time (if width is 8 ) (?)
         Case "="  // ModTime 8 Last modified date & time of this record
         Case "@"  // DayTime 8 Date & Time
            Exit
         Otherwise
            cRet := "NULL"
      End Switch
   Endif

RETURN cRet
//------------------------------------------------------------------------------
- Inicialização da variável nTotal
- Eliminar última vírgula da string
- Incorporado o AD_EXECUTE_NORECORDS
- incorporada função CS()
sasquast escreveu:Os 4 minutos se tornaram 40 segundos.
Que bom! Quantos registros? Se aumentar o tamanho do bloco, pode diminuir este tempo, observando o tamanho máximo da string (4000 baytes, segundo postado acima).
JoséQuintas escreveu:Eu preferi criar funções StringSQL(), NumberSQL(), DateSQL(), etc., pra padronizar a formatação, e ValueSQL() quando o tipo é conhecido.
É o ideal. Eu criei uma função única, postada acima. Falta completar todos os tipos de dados.