Resposta rápida
- O que é
- uma classe do framework que cria uma tabela temporária no banco, com alias próprio.
- Sequência
New(cAlias),SetFields(aStru),AddIndex(...),Create().- Em SQL
GetRealName()devolve o nome físico, paraINSERT... SELECTeJOIN.- Em AdvPL
- o alias funciona como qualquer tabela —
RecLock,DbSeek,DbSkip. - Obrigatório
Delete()ao final. Sem isso, a tabela fica ocupando o banco.
Nesta página6 seções
Guardar um resultado intermediário em array funciona com cem linhas. Com cem
mil, você reescreve à mão ordenação e busca que o banco faz de graça — veja o
limite em arrays em AdvPL. A saída é uma tabela
temporária, e no Protheus atual ela se chama FWTemporaryTable.
Por que não CriaTrab
Em fonte antigo você ainda vai encontrar CriaTrab — o próprio padrão chama,
dentro de SelDados():
cIndex := CriaTrab(nil,.f.)Ele gerava um arquivo de trabalho fora do SGBD: não participa de query, e a
limpeza dependia de quem escreveu lembrar de apagá-lo — um hábito fácil de
esquecer no meio de uma rotina grande, com vários pontos de saída. A orientação da TOTVS
para código novo é FWTemporaryTable, que cria a tabela dentro do banco
— e abre a porta que importa: cruzar a temporária com SE1, SA1 ou qualquer
outra tabela num único comando SQL.
Em legado, não troque CriaTrab por atacado: muitas dessas chamadas só geram
um nome de arquivo de índice.
A sequência, lida na rotina padrão
A classe mora na biblioteca do framework, não em código-fonte visível
diretamente. O jeito de provar como ela se usa é ler quem a usa. Dentro de
Atf040Item(), a sequência completa aparece em ordem:
cTrbTmp := GetNextAlias()
_oATFA040 := FWTemporaryTable():New( cTrbTmp )
_oATFA040:SetFields(aStruct)
_oATFA040:AddIndex("1", {"ATFITEM"})
_oATFA040:Create()
cTmpReal := _oATFA040:GetRealName()O que cada linha diz:
| Método | O que faz |
|---|---|
New(cAlias) | Instancia a tabela e define o alias pelo qual o AdvPL vai acessá-la |
SetFields(aStru) | Recebe a estrutura: um array com {nome, tipo, tamanho, decimal} por campo |
AddIndex(cOrdem, aCampos) | Declara um índice; o primeiro argumento é o identificador, o segundo, os campos |
Create() | Cria de fato a tabela no banco — antes dele, nada existe |
GetRealName() | Devolve o nome físico, que é o que vai dentro do SQL |
Delete() | Apaga a tabela e libera o alias |
Repare no GetNextAlias() — mas não generalize a partir dele. Em boa parte
das chamadas de FWTemporaryTable():New(...) pelo padrão, o alias já chega
literal — inclusive na rotina ATFA012, adiante, com New( "ATFCOE" ).
Alias fixo colide quando a rotina roda duas vezes na mesma thread, ou quando
outra customização teve a mesma ideia; o sintoma é um erro de tabela já
aberta, no meio de uma rotina que funcionava até ontem. É por isso que
aquela rotina, mais abaixo, testa se o objeto já existe antes de recriá-lo.
GetNextAlias() evita o problema na raiz, sem precisar dessa defesa extra.
Montando a estrutura sem digitar tamanho
Digitar {"SALDO","N",16,2} funciona até o dia em que alguém aumenta o campo
no dicionário. O padrão resolve lendo o tamanho do SX3: o setUp() da
classe F811TmpFWP percorre a lista de campos e faz
AAdd(::aStruct, FWSX3Util():getFieldStruct( allTrim(::aFieldList[nX]) )) em
cada um — o retorno já vem no formato que o SetFields espera, com nome,
tipo, tamanho e decimal nas posições 1 a 4.
Repare no que ele faz depois do laço: acrescenta {'FWP_OK','C',1,0} à
mão. Campo de controle que não existe no dicionário não tem de onde ser lido,
e é essa mistura — estrutura derivada do SX3 para os campos reais, literal
para os de controle — que aparece no padrão.
A alternativa mais antiga é TamSX3(cCampo). Atenção à ordem: ela devolve
{tamanho, decimal, tipo} — o tipo vem por último, ao contrário do que o
SetFields espera. Misturar as duas ordens é um erro silencioso.
Exemplo: saldo em aberto por cliente
O padrão que aparece em código de produção: carregar a temporária com um
INSERT... SELECT e depois percorrê-la em AdvPL, marcando registros.
#Include "Protheus.ch"
/*/{Protheus.doc} PGV010
Monta, numa tabela temporária, o saldo em aberto por cliente e marca
os que têm título vencido.
@type user function
@author Pragiva
/*/
User Function PGV010()
Local aStru := {}
Local cAlias := GetNextAlias()
Local oTmp := Nil
Local cTabela := ""
Local cSql := ""
aAdd(aStru, CampoDe("CLIENTE", "E1_CLIENTE"))
aAdd(aStru, CampoDe("LOJA", "E1_LOJA"))
aAdd(aStru, CampoDe("NOME", "A1_NOME"))
aAdd(aStru, CampoDe("SALDO", "E1_SALDO"))
aAdd(aStru, CampoDe("VENCTO", "E1_VENCREA"))
aAdd(aStru, {"VENCIDO", "C", 1, 0})
oTmp := FWTemporaryTable():New(cAlias)
oTmp:SetFields(aStru)
oTmp:AddIndex("1", {"CLIENTE", "LOJA"})
oTmp:Create()
cTabela := oTmp:GetRealName()
cSql := "INSERT INTO " + cTabela + " (CLIENTE, LOJA, NOME, SALDO, VENCTO, VENCIDO) "
cSql += "SELECT E1_CLIENTE, E1_LOJA, A1_NOME, SUM(E1_SALDO), MIN(E1_VENCREA), 'N' "
cSql += "FROM " + RetSqlName("SE1") + " SE1 "
cSql += "INNER JOIN " + RetSqlName("SA1") + " SA1 "
cSql += " ON A1_FILIAL = '" + xFilial("SA1") + "' "
cSql += " AND A1_COD = E1_CLIENTE AND A1_LOJA = E1_LOJA "
cSql += " AND SA1.D_E_L_E_T_ = ' ' "
cSql += "WHERE E1_FILIAL = '" + xFilial("SE1") + "' "
cSql += " AND E1_SALDO > 0 AND SE1.D_E_L_E_T_ = ' ' "
cSql += "GROUP BY E1_CLIENTE, E1_LOJA, A1_NOME"
If TcSqlExec(cSql) < 0
ConOut("[PGV010] Falha ao carregar temporaria: " + TcSqlError())
Else
(cAlias)->(DbSetOrder(1))
(cAlias)->(DbGoTop())
While (cAlias)->(!Eof())
If (cAlias)->VENCTO < dDataBase
RecLock(cAlias, .F.)
(cAlias)->VENCIDO := "S"
(cAlias)->(MsUnlock())
EndIf
(cAlias)->(DbSkip())
EndDo
EndIf
// Consumo do resultado. Depois, sempre:
oTmp:Delete()
Return Nil
Static Function CampoDe(cNome, cCampoSX3)
Local aInfo := FWSX3Util():GetFieldStruct(cCampoSX3)
Return {cNome, aInfo[2], aInfo[3], aInfo[4]}Três leituras.
O INSERT lista só as suas colunas. É o que o padrão faz dentro de
F090MTTMP(), com GetRealName() e a lista de campos, seguido de
TcSQLExec e do teste de retorno negativo. Carregar pelo banco evita
trazer linha por linha para o application server.
Depois do INSERT, o alias enxerga os dados. DbSeek, DbSkip e
RecLock funcionam no alias da temporária como em qualquer tabela — para ir
direto a um cliente, (cAlias)->(DbSeek(cCliente + cLoja)) usa o índice “1”
do AddIndex. RecLock é a escolha óbvia aqui, tabela sua e sem regra do
padrão; o critério geral está em
RecLock ou ExecAuto.
A data vai como tipo D. No banco ela é texto AAAAMMDD, por isso o
MIN(E1_VENCREA) funciona e o AdvPL lê de volta como data.
Para a montagem da query em si — RetSqlName, xFilial, D_E_L_E_T_ e o
cuidado com concatenação — veja
consultas SQL em AdvPL.
O Delete não é opcional
Na rotina ATFA012, dentro de AF012aCalc() — o mesmo trecho do alias fixo
"ATFCOE", lá em cima —, o padrão testa se o objeto já existe e chama
Delete() antes de recriá-lo: defesa certa para rotina executada mais de uma
vez na sessão. Com vários pontos de saída, centralize a limpeza no fim. É a
mesma disciplina de
GetArea e RestArea: o que você abriu,
você fecha.
Onde ela compensa
- Base de relatório ou browse de várias tabelas, entregue a um componente que espera alias.
- Cruzamento em SQL entre uma lista sua — códigos vindos de planilha,
por exemplo — e as tabelas do sistema, num
JOINsó.
Para dez linhas, array continua sendo a ferramenta certa.
Se uma rotina sua está lenta por carregar tudo em array e você quer uma segunda leitura, fale com a gente.
Referências
TDNtdn.totvs.com
TDN — FWTemporaryTable
Continue por aqui
- Vídeo
DesenvolvimentoTutorial
Consultas SQL em AdvPL: query em string, Embedded SQL e FWExecStatement
Consultas SQL em AdvPL: três formas de ler com SELECT e o que nenhuma dispensa — filtro de filial, D_E_L_E_T_, laço até o EoF e fechar o alias.
- Vídeo
DesenvolvimentoTutorial
GetArea e RestArea em AdvPL: área de trabalho e posicionamento
GetArea e RestArea em AdvPL: o que é a área de trabalho, por que qualificar todo acesso com ALIAS-> e por que o PE tem que devolver a área como recebeu.