DNATech :: Processos set-based em TOPCONN no Protheus

**DNATech :: Processos set-based em TOPCONN no Protheus**

Na postagem anterior falei sobre um ponto importante:

Muitas rotinas Protheus não precisam de mais threads.

Precisam de menos loops.

O problema clássico aparece quando uma rotina começa a crescer e vira uma sequência de:

```advpl
While !EOF()
    DbSeek(...)
    RecLock(...)
    Campo := Valor
    MsUnlock()
    DbSkip()
EndDo
```

Ou:

```advpl
While !EOF()
    cQuery := MontaSQL(...)
    DbUseArea(.T., "TOPCONN", TCGenQry(,,cQuery), cAlias)
    DbSkip()
EndDo
```

Esse padrão aumenta roundtrips, pressão no DBAccess, locks, tempo de execução e complexidade.

A refatoração mais eficiente normalmente não é paralelizar o problema.

É mudar o paradigma.

Entram aqui os processos **set-based em TOPCONN**.

Em vez de processar registro por registro, o Protheus passa a orquestrar operações em lote:

```tlpp
cQuery := "INSERT INTO " + cTmp + " (R_E_C_N_O_, CODIGO, TOTAL) "
cQuery += "SELECT "
cQuery += DNATechSQLProcessTools():RecNoSql(cTmp, "BASE.CODIGO") + ", "
cQuery += "BASE.CODIGO, "
cQuery += "SUM(BASE.VALOR) "
cQuery += "FROM " + RetSQLName("SD2") + " BASE "
cQuery += "WHERE BASE.D_E_L_E_T_ = ' ' "
cQuery += "GROUP BY BASE.CODIGO"

DNATechSQLProcessTools():Exec(cQuery, "CargaResumo", cTmp)
```

A ideia é centralizar um conjunto de helpers para rotinas massivas:

```tlpp
DNATechSQLProcessTools():TmpTable()
DNATechSQLProcessTools():DropTable(cTmp)
DNATechSQLProcessTools():Refresh(cTmp)
DNATechSQLProcessTools():Exec(cQuery, cEtapa, cTmp)
DNATechSQLProcessTools():RecNoSql(cTmp, cOrder)
DNATechSQLProcessTools():SqlStr(cValor)
DNATechSQLProcessTools():NumSql(nValor)
DNATechSQLProcessTools():DateSql(dData)
DNATechSQLProcessTools():ArrayIn(aDados, 1, .T.)
```

Isso reduz repetição e evita erros comuns em SQL dinâmico.

Um exemplo importante é o `R_E_C_N_O_`.

Ao fazer `INSERT INTO ... SELECT` direto em uma temporária TOPCONN, não dá para simplesmente ignorar o `R_E_C_N_O_`.

Uma forma segura é gerar em lote:

```sql
ISNULL((SELECT MAX(R_E_C_N_O_) FROM TMP),0)
+ ROW_NUMBER() OVER (ORDER BY CAMPO1, CAMPO2)
```

Encapsulado:

```tlpp
static method RecNoSql(cTable, cOrder) class DNATechSQLProcessTools
return "ISNULL((SELECT MAX(R_E_C_N_O_) FROM " + cTable + "),0) " + ;
       "+ ROW_NUMBER() OVER (ORDER BY " + cOrder + ")"
```

Outro ponto útil é transformar arrays pequenos em filtros SQL.

Para poucos registros:

```tlpp
aVendedores := { {"000001"}, {"000002"}, {"000003"} }

cVendIn := DNATechSQLProcessTools():ArrayIn(aVendedores, 1, .T.)

cQuery += " AND SA3.A3_COD IN (" + cVendIn + ") "
```

Para caches pequenos, o array funciona muito bem.

Para volume maior, o melhor caminho é criar uma tabela real ou temporária TOPCONN, carregar os dados uma vez e fazer `JOIN`.

Regra prática:

```text
Poucos registros  -> array/cache em memória
Muitos registros  -> tabela real ou temporária TOPCONN
Processamento     -> SQL set-based
```

Também dá para padronizar conversões numéricas, datas e strings:

```tlpp
cQuery += "WHERE D2_EMISSAO BETWEEN "
cQuery += DNATechSQLProcessTools():DateSql(dDataDe)
cQuery += " AND "
cQuery += DNATechSQLProcessTools():DateSql(dDataAte)
```

E evitar conversão insegura de campos numéricos armazenados como caractere:

```tlpp
cDesc := DNATechSQLProcessTools():NumericSql("SZ3.Z3_FX01")
```

Por baixo:

```sql
ISNULL(
    TRY_CONVERT(FLOAT,
        NULLIF(REPLACE(RTRIM(CONVERT(VARCHAR(40), CAMPO)), ',', '.'), '')
    ),
0)
```

Esse tipo de helper parece pequeno, mas em rotinas grandes faz muita diferença.

Ele evita que cada rotina tenha sua própria versão de:

- montagem de `IN`;
- tratamento de aspas;
- conversão de data;
- `ISNULL`;
- geração de `R_E_C_N_O_`;
- criação e limpeza de temporárias;
- execução SQL com log;
- `TCRefresh`.

No final, o ganho não vem apenas de SQL.

Vem de arquitetura.

O Protheus continua fazendo o que ele faz bem: contexto, regra, tela, orquestração.

O banco passa a fazer o que ele faz melhor: processar conjuntos.

Antes:

```text
Loop
  Seek
  Calcula
  Grava
  Próximo
```

Depois:

```text
Cria temporária
Insere em lote
Calcula em lote
Atualiza em lote
Consolida resultado
Limpa temporárias
```

Menos thread.

Menos roundtrip.

Menos lock.

Menos timeout.

Mais previsibilidade.

A pergunta deixa de ser:

“Quantas threads preciso para essa rotina aguentar?”

E passa a ser:

“Quantos loops consigo eliminar dessa rotina?”

Essa mudança de mentalidade é onde a performance real começa.

---

`#DNATech` `#Protheus` `#TOTVS` `#AdvPL` `#TLPP` `#TOPCONN` `#DBAccess` `#SQLServer` `#Performance` `#ERP` `#Refactoring` `#ArquiteturaDeSoftware`

---

Referência de estilo: [Quando múltiplas threads mascaram problemas de arquitetura: uma refatoração real no TOTVS Protheus]( https://www.blacktdn.com.br/2026/05/dnatech-quando-multiplas-threads.html)

Comentários

Postagens mais visitadas