Página 1 de 1

SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 13:35
por Itamar M. Lins Jr.
Olá!
Comecei a fazer por aqui:
Ja fiz upload do estoque, com mudanças dos nomes dos campos, não todos.
fiz alguns ajustes para UTF8, pq dá erro se não olharmos isto. Preferi não mudar o padrão que é UTF8 do DATABASE.
Alguma telas e MSG em Hwgui, eu removi.
O importante é que podemos escrever o SQL, vejam o TEXTO, com espaços e observações, sem nenhum problema para o Harbour e o SQLMIX.
TEXT TO VAR cSQL
...
ENDTEXT


A carga é beeem rápida, pega de 100 em 100 os itens.
Detalhe que essa parte chatinha de ficar colocando as estruturas foi a IA que fez, a IA fez quase 100% dessa função.
Só fiz monitorar, pq sempre tem erros. Coaching de IA :lol:

Código: Selecionar todos

#include "dbinfo.ch"
#xcommand TEXT TO VAR <var> => #pragma __stream|<var>:=%s
#xcommand ENDTEXT => #pragma __endtext

Function Main
CriaDbPGSQL()
Return .t.
...

lRet := rddInfo( RDDI_EXECUTE, "DROP TABLE IF EXISTS estoques" )
IF lRet
   alert('Apagou a tabela estoques.')
ELSE
   alert('Falha apagando a tabela estoques')
   RDDINFO(RDDI_DISCONNECT, nCon)
   RDDSETDEFAULT( cRdd )
   RETURN .T.
ENDIF
...
Function CriaDbPGSQL()
...
Text TO VAR cSQL
CREATE TABLE IF NOT EXISTS estoques (
    -- Chave Primária Auto-Incremento (Equivalente ao I:+)
    idproduto INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   etc ...
    -- Campos Lógicos (Equivalente ao L)
    marcado BOOLEAN DEFAULT FALSE,
    m2 BOOLEAN DEFAULT FALSE,
    m3 BOOLEAN DEFAULT FALSE,
    etc ...
    -- Campos de Texto de Tamanho Fixo (Códigos e Chaves)
    cProd CHAR(20), -- Antigo cod_mercad 13 caractere
    gtin CHAR(14), -- Antigo cod_fiscal
    codcompart CHAR(20),
    idgrupo CHAR(06), -- Antigo cod_grupo
    NCM CHAR(08), -- Antigo cod_ncm
    etc ...
    -- Campos de Texto Variável (Descrições - Otimiza espaço em disco)
    xProd VARCHAR(60), -- Antigo mercadoria
    grupo VARCHAR(25),
    etc ...
    -- Campos Numéricos Decimais (Precisão Comercial Exata)
    quantidade NUMERIC(14,3) DEFAULT 0.000,
    qtdaentreg NUMERIC(14,3) DEFAULT 0.000,
    qtd_baixar NUMERIC(14,3) DEFAULT 0.000,
   etc ...
    -- Campos de Valores Monetários, Custos e Índices
    aliqu_icms NUMERIC(06,2) DEFAULT 0.00,
    desconto NUMERIC(06,2) DEFAULT 0.00,
   etc ...
   -- Campos de Data (Equivalente ao D)
    validade DATE,
    dtapromini DATE,
   etc ...
)
ENDTEXT

lRet := rddInfo( RDDI_EXECUTE, cSQL)
IF lRet
   alert('Criou a tabela estoques !')
ELSE
   alert('Falha na criação da tabela estoques !')
   RDDINFO(RDDI_DISCONNECT, nCon)
   RDDSETDEFAULT( cRdd )
   RETURN .T.
ENDIF

ImportaDbfParaPostgres()
dbUseArea( .T., "SQLMIX" , "select * from estoques",'xx'  )
Browse()
ShowEstruturaTabela( 'estoques' )

Return .t.

// --------------------------------------------------------------------------
// Função interna que processa o loop em lotes e atualiza a tela
// --------------------------------------------------------------------------
STATIC PROCEDURE ProcessaMigracao(oDlg, oGauge, oLabel, nTotalReg, nLote)
    LOCAL cSqlInsert := ""
    LOCAL cValues    := ""
    LOCAL nContador  := 0
    LOCAL nProcessados := 0
    LOCAL cErroPostgres := ""
    dbSelectArea("DBF_EST")
    dbGoTop()

    DO WHILE !EOF()
        nContador++
        nProcessados++

        // Se for o início de um novo lote, monta o cabeçalho do INSERT
        IF nContador == 1
            cSqlInsert := "INSERT INTO estoques ( "
            cSqlInsert += "marcado, cProd, gtin, codcompart, xProd, quantidade, qtdaentreg, "
              etc ...
            cSqlInsert += "aluga, status, diamod, digital, enviado ) VALUES "
        ENDIF

        // Monta a dupla de valores deste registro específico do DBF
        cValues := "( "
        cValues += SqlVal(DBF_EST->marcado)    + ", "
        cValues += SqlVal(DBF_EST->cod_mercad) + ", "
        cValues += SqlVal(DBF_EST->cod_fiscal) + ", "
        cValues += SqlVal(DBF_EST->codcompart) + ", "
        cValues += SqlVal(DBF_EST->mercadoria) + ", "
        cValues += SqlVal(DBF_EST->quantidade) + ", "
        cValues += SqlVal(DBF_EST->qtdaentreg) + ", "
        etc ...
        cValues += SqlVal(DBF_EST->aluga)      + ", "
        cValues += SqlVal(DBF_EST->status)     + ", "
        cValues += SqlVal(DBF_EST->diamod)     + ", "
        cValues += SqlVal(DBF_EST->digital)    + ", "
        cValues += SqlVal(DBF_EST->enviado)    + " )"

        cSqlInsert += cValues

        // Se atingiu o tamanho do lote ou chegou ao fim do arquivo
        IF nContador == nLote .OR. nProcessados == nTotalReg
            // Descarrega o lote inteiro de uma vez no banco
            lRet := rddInfo(RDDI_EXECUTE, cSqlInsert)

            IF lRet
            ELSE
               cErroPostgres := rddInfo(RDDI_ERROR)
               IF Empty(cErroPostgres)
                  // Se ainda sumir, tente esta outra constante comum no SQLMix:
                   // #define RDDI_ERRORMSG 28
                  cErroPostgres := rddInfo( 28 )
               ENDIF
               Alert( "Erro no banco de dados: " + cErroPostgres )
               alert('Falha na inclusão: ' + str(nProcessados))
               hb_memowrit('sql.txt',cSqlInsert)
               cancel
               exit
            ENDIF

            nContador := 0 // Reseta o acumulador do lote
        ELSE
            cSqlInsert += ", " // Adiciona a vírgula para separar a próxima linha/registro
        ENDIF
        dbSelectArea("DBF_EST")
        dbSkip()
    ENDDO

    dbCloseArea()

    alert("Sucesso! Processamento de " + AllTrim(Str(nProcessados)) + " registros concluido.")
RETURN
Captura_de_tela_20260722_134408.png
Captura_de_tela_20260722_134408.png (5.06 KiB) Exibido 65 vezes
Captura_de_tela_20260722_134459.png
Captura_de_tela_20260722_134459.png (97.78 KiB) Exibido 65 vezes
Captura_de_tela_20260722_134540.png
Captura_de_tela_20260722_134540.png (26.7 KiB) Exibido 65 vezes

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 14:09
por Itamar M. Lins Jr.
Olá!
A função que converte do DBF para o padrão do PostgreSQL. hb_StrToUTF8 Sem essa mudança ele não migra.
Podemos mudar a criação da tabela para ser igual ao padrão do DBF. PTISO -> (ISO-8859-1 ou Latin1) se não quer usar UTF8.

Código: Selecionar todos


// --------------------------------------------------------------------------
// Função Auxiliar que formata os dados do Harbour para a sintaxe aceita pelo SQL
// --------------------------------------------------------------------------
STATIC FUNCTION SqlVal( xValor )
    LOCAL cRet := ""
    LOCAL cTipo := ValType(xValor)

    IF cTipo == "C"
        // Escapa aspas simples duplicando-as para não quebrar a string SQL
        cRet := "'" + StrTran(AllTrim(hb_StrToUTF8(xValor)), "'", "''") + "'"
    ELSEIF cTipo == "N"
        cRet := AllTrim(Str(xValor, 16, 5))
    ELSEIF cTipo == "D"
        IF Empty(xValor)
            cRet := "NULL"
        ELSE
            // Formata no padrão ANSI do PostgreSQL (AAAA-MM-DD)
            cRet := "'" + DToS(xValor) + "'"
            cRet := "'" + Left(DToS(xValor),4) + "-" + SubStr(DToS(xValor),5,2) + "-" + Right(DToS(xValor),2) + "'"
        ENDIF
    ELSEIF cTipo == "L"
        cRet := If(xValor, "TRUE", "FALSE")
    ELSE
        cRet := "NULL"
    ENDIF
RETURN cRet

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 15:27
por JoséQuintas
Tem certeza que essa conversão funciona pra data vazia ?

Em todo caso, pode economizar fonte usando hb_Dtoc( dData, "YYYY-MM-DD")
E isso funciona pra qualquer formato de data.

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 16:26
por Itamar M. Lins Jr.
Olá!
JoséQuintas escreveu: 22 Jul 2026 15:27 Tem certeza que essa conversão funciona pra data vazia ?
Eu não tenho certeza. Eu sei que a função empty() pega NULL/NIL.
Em todo caso, pode economizar fonte usando hb_Dtoc( dData, "YYYY-MM-DD")
Eu uso assim, mas a IA, ainda não sabe disso :lol:

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 16:38
por Itamar M. Lins Jr.
Olá!
Na verdade tô aqui só enrolando. :mrgreen:
Eu quero é fazer a parte LEFT JOIN da NFe ou do Cliente com as DIVERSAS tabelas... E ver como eu vou paginar isso, pra ficar igual ou melhor em velocidade que o LetoDBf. Pq no LetoDBf não existe esperar, nem erro com DBF. Mas deve ficar melhor ainda, acredito.

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 17:17
por Itamar M. Lins Jr.
Olá!
Videozinho com Hwgui no Linux. O mesmo código para Windows.

Vejam a velocidade de upload da base de dados para um servidor remoto.
Observem o sinal WIRELESS só uma linha.

Re: SQLMIX e a sintaxe SQL do PostgreSQL

Enviado: 22 Jul 2026 17:29
por alxsts
Olá!

Uma ideia de como montar paginação fornecida pela IA do Google:
Aqui está como implementar o Keyset Pagination no Harbour utilizando a RDD SQLMIX com PostgreSQL.Como o Harbour trabalha de forma procedural e sequencial, nós guardamos o ID do último registro processado em uma variável para utilizá-lo no filtro (WHERE) da próxima consulta.Exemplo Prático em Harbourharbour

Código: Selecionar todos

#include "sqlmix.ch"

PROCEDURE Main()
    LOCAL cConnString, hConn
    LOCAL nUltimoId := 0
    LOCAL nItensPagina := 100

    // 1. Conexão com o PostgreSQL via SQLMIX
    cConnString := "PG:host=localhost;db=meubanco;uid=postgres;pwd=minhasenha"
    hConn := RddInfo( RDDI_CONNECT, { "SQLMIX", cConnString } )

    IF Empty( hConn )
        ? "Erro ao conectar ao banco de dados: " + RddInfo( RDDI_ERROR )
        RETURN
    ENDIF

    ? "--- CARREGANDO PÁGINA 1 ---"
    // Na primeira página, nUltimoId inicia em 0
    nUltimoId := BuscarPagina( nUltimoId, nItensPagina )

    ? ""
    ? "Pressione qualquer tecla para ir para a PÁGINA 2..."
    Inkey(0)
    Cls

    ? "--- CARREGANDO PÁGINA 2 ---"
    // Passamos o ID da última linha da Página 1 para buscar as próximas 100
    IF nUltimoId > 0
        nUltimoId := BuscarPagina( nUltimoId, nItensPagina )
    ELSE
        ? "Não há mais registros."
    ENDIF

    // Fecha a conexão
    RddInfo( RDDI_DISCONNECT, hConn )
RETURN

// Função que executa o SELECT paginado
FUNCTION BuscarPagina( nIdAnterior, nLimite )
    LOCAL cQuery, nUltimoIdEncontrado := 0
    LOCAL nContador := 0

    // Monta a query utilizando o Keyset (WHERE id > nIdAnterior)
    cQuery := "SELECT id, nome FROM clientes " + ;
              "WHERE id > " + AllTrim(Str(nIdAnterior)) + " " + ;
              "ORDER BY id ASC " + ;
              "LIMIT " + AllTrim(Str(nLimite))

    // Executa a Query abrindo uma área de trabalho temporária
    DBUseArea( .T., "SQLMIX", cQuery, "TEMP_PAG" )

    IF NetErr()
        ? "Erro ao executar a consulta."
        RETURN 0
    ENDIF

    // Percorre os 100 registros retornados
    DO WHILE ! TEMP_PAG->( Eof() )
        nContador++
        
        // Exibe os dados na tela
        ? "Reg " + AllTrim(Str(nContador)) + " - ID: " + AllTrim(Str(TEMP_PAG->id)) + " | Nome: " + TEMP_PAG->nome
        
        // Guarda o ID do registro atual (o último loop terá o maior ID)
        nUltimoIdEncontrado := TEMP_PAG->id
        
        TEMP_PAG->( DbSkip() )
    ENDDO

    TEMP_PAG->( DbCloseArea() )

    // Retorna o ID da centésima linha para ser usado na próxima chamada
    RETURN nUltimoIdEncontrado
Use o código com cuidado.

Explicação dos Pontos Chave

- Query Dinâmica: O Harbour remonta a string SQL a cada página inserindo o valor numérico de nIdAnterior diretamente no WHERE id > ....

- Desempenho no PostgreSQL: O comando DBUseArea traz da rede apenas as 100 linhas exatas que você pediu, economizando memória no servidor e no terminal Clipper/Harbour.

- Controle do Cursor: A variável nUltimoIdEncontrado captura o valor do campo id logo antes de fechar a tabela temporária (DbCloseArea), garantindo o ponteiro para a próxima página.