Introdução
Uma classe relativamente recente e que vejo poucos programadores usarem é a FwTemporaryTable. Ela serve para criar e manipular tabelas temporárias no banco de dados. Uma classe cheia de recursos e não é muito difícil de se utilizar.
Fique ligado!
Uma observação importante é que seu uso é obrigatório para criação de tabelas temporárias caso queira migrar o dicionário do ERP para o banco de dados. Sabendo disso, o uso das funções CriaTrab, DbCreate, MsCreate, Copy To e etc. para criação de tabelas temporárias no banco de dados se tornou proibido. Caso haja alguma customização com essas funções, elas serão detectadas como bug crítico pela ferramenta TOTVS CodeAnalysis e impedirão sua migração de dicionário de dados, sendo necessária a substituição pela classe FwTemporaryTable.
A descrição do erro é: O uso de drive ISAM na linha Microsiga Protheus foi descontinuado.
Bora Codar !!
Bom, sem mais delongas, vamos analisar um exemplo de como criar uma tabela usando essa classe:
// Instancio o objeto
oTable := FwTemporaryTable():New('TRB')
aCpoData := {}
// Crio com array com os campos da tabela
aAdd(aCpoData, {'TMP_FILIAL', TamSx3('E1_FILIAL')[3] , TamSx3('E1_FILIAL')[1] , 0})
aAdd(aCpoData, {'TMP_CLIENT', TamSx3('E1_CLIENTE')[3] , TamSx3('E1_CLIENTE')[1] , 0})
aAdd(aCpoData, {'TMP_LOJA' , TamSx3('E1_LOJA')[3] , TamSx3('E1_LOJA')[1] , 0})
aAdd(aCpoData, {'TMP_PREFIX', TamSx3('E1_PREFIXO')[3] , TamSx3('E1_PREFIXO')[1] , 0})
aAdd(aCpoData, {'TMP_NUM' , TamSx3('E1_NUM')[3] , TamSx3('E1_NUM')[1] , 0})
aAdd(aCpoData, {'TMP_PARCEL', TamSx3('E1_PARCELA')[3] , TamSx3('E1_PARCELA')[1] , 0})
aAdd(aCpoData, {'TMP_VALOR' , TamSx3('E1_VALOR')[3] , TamSx3('E1_VALOR')[1] , TamSx3('E1_VALOR')[2]})
aAdd(aCpoData, {'TMP_VALLIQ', TamSx3('E1_VALLIQ')[3] , TamSx3('E1_VALLIQ')[1] , TamSx3('E1_VALLIQ')[2]})
aAdd(aCpoData, {'TMP_SALDO' , TamSx3('E1_SALDO')[3] , TamSx3('E1_SALDO')[1] , TamSx3('E1_SALDO')[2]})
// Adiciono os campos na tabela
oTable:SetFields(aCpoData)
// Adiciono os índices da tabela
oTable:AddIndex('01', {'TMP_FILIAL','TMP_PREFIX','TMP_NUM','TMP_PARCEL'})
oTable:AddIndex('02', {'TMP_FILIAL','TMP_CLIENT','TMP_LOJA'})
// Crio a tabela no banco de dados
oTable:Create()
Ao instanciar a classe, você pode ou não informar o Alias da tabela no método New. No meu caso, informei o Alias TRB. Caso não seja informado, o sistema irá preencher com o próximo Alias disponível.
Caso queira posicionar em um registro dentro do programa, você pode utilizar os seguintes comandos:
// Caso você saiba o Alias
TRB->TMP_NUM
// Caso você não saiba o Alias – primeira opção
cTable := oTable:GetAlias()
(cTable)->TMP_NUM
// Caso você não saiba o Alias – segunda opção
(oTable:GetAlias())->TMP_NUM
SetFields
O método SetFields recebe os campos para criação da tabela, ele deve receber um array onde suas posições devem ser preenchidas da seguinte maneira:
- Nome do campo
- Tipo do campo (Caractere, numérico, etc.)
- Tamanho do campo
- Decimal do campo
AddIndex
O método AddIndex é opcional, e você pode estar utilizando para criar índices em sua tabela temporária. Informe o nome do índice no primeiro parâmetro e um array com os campos daquele índice no segundo parâmetro.
Create
Por fim, o método Create efetiva a criação da tabela no banco de dados.
Ótimo! Agora que a tabela está criada, como podemos fazer para popular? Existem duas maneiras bem simples.
A primeira e mais usada é a seguinte:
// Faço uma consulta SQL dos dados que desejo popular
BeginSql Alias _cAlias
%NoParser%
SELECT
SE1.E1_FILIAL,
SE1.E1_CLIENTE,
SE1.E1_LOJA,
SE1.E1_PREFIXO,
SE1.E1_NUM,
SE1.E1_PARCELA,
SE1.E1_VALOR,
SE1.E1_VALLIQ,
SE1.E1_SALDO
FROM %TABLE:SE1% (NOLOCK) SE1
WHERE
SE1.E1_SALDO > 0 AND
DATEDIFF(DAY,SE1.E1_VENCREA,CONVERT(DATE,GETDATE())) > 15 AND
SE1.%NOTDEL%
EndSQL
(_cAlias)->(DbGoTop())
DbSelectArea('TRB')
// Insiro todos os dados na minha tabela
While(!(_cAlias)->(EoF()))
RecLock('TRB', .T.)
TRB->TMP_FILIAL := (_cAlias)->E1_FILIAL
TRB->TMP_CLIENT := (_cAlias)->E1_CLIENTE
TRB->TMP_LOJA := (_cAlias)->E1_LOJA
TRB->TMP_PREFIX := (_cAlias)->E1_PREFIXO
TRB->TMP_NUM := (_cAlias)->E1_NUM
TRB->TMP_PARCEL := (_cAlias)->E1_PARCELA
TRB->TMP_VALOR := Round((_cAlias)->E1_VALOR,2)
TRB->TMP_VALLIQ := Round((_cAlias)->E1_VALLIQ,2)
TRB->TMP_SALDO := Round((_cAlias)->E1_SALDO,2)
TRB->(MsUnlock())
(_cAlias)->(DbSkip())
EndDo
TRB->(DbGoTop())
(_cAlias)->(DbCloseArea())
Importante! Caso utilize essa forma de preenchimento e não tenha informado um alias para a tabela, lembre-se de substituir o alias TRB pelo método GetAlias.
Existe uma outra forma de popular esses dados e que é mais performático que o anterior, inserindo diretamente via comando SQL:
cFields := ''
// Busco todos os campos da tabela temporária e preencho numa variável
For nI := 1 To Len(aCpoData)
cFields += aCpoData[nI,1] + ','
Next nI
cFields := Left(cFields, Len(cFields) -1)
// Monto o comando SQL
cQuery := "INSERT INTO " + oTable:GetRealName()
cQuery += " (" + cFields + ") "
cQuery += " SELECT "
cQuery += " SE1.E1_FILIAL, "
cQuery += " SE1.E1_CLIENTE, "
cQuery += " SE1.E1_LOJA, "
cQuery += " SE1.E1_PREFIXO, "
cQuery += " SE1.E1_NUM, "
cQuery += " SE1.E1_PARCELA, "
cQuery += " SE1.E1_VALOR, "
cQuery += " SE1.E1_VALLIQ, "
cQuery += " SE1.E1_SALDO "
cQuery += " FROM " + RetSqlName('SE1') + " (NOLOCK) SE1 "
cQuery += " WHERE "
cQuery += " SE1.E1_SALDO > 0 AND "
cQuery += " DATEDIFF(DAY,SE1.E1_VENCREA,CONVERT(DATE,GETDATE())) > 10 AND "
cQuery += " SE1.D_E_L_E_T_ = '' "
// Executo o comando SQL
If(TcSqlExec(cQuery) < 0 .and. !Empty(TcSqlError()))
MsgAlert('Ocorreu um erro ao executar o comando SQL!' + CRLF + CRLF + TcSqlError(), 'Erro ao popular tabela')
Endif
O conteúdo da tabela será preenchido bem mais rápido do que da forma anterior!
GetRealName
Aqui utilizamos o método GetRealName, esse método me retorna o nome físico da tabela no banco de dados, que no meu caso foi ##TMPSC00_209.
Esse nome sempre vai variar de acordo com as tabelas temporárias já existentes no banco.
Uma observação bacana é que você pode executar um comando select nessa tabela via sua IDE SQL. Segue exemplo:

Agora que a tabela já está preenchida, é só utilizar em suas customizações!
Você ainda pode utilizá-la dentro do Protheus para demais consultas SQL, incluindo JOINS, como no exemplo a seguir:
_cAlias := GetNextAlias()
cTblName := '%' + oTable:GetRealName() + '%'
// Executa a consulta SQL
BeginSql Alias _cAlias
%NoParser%
SELECT * FROM %Exp:cTblName% TRB
INNER JOIN %TABLE:SA1% SA1 ON
SA1.A1_FILIAL = %xFilial:SA1% AND
SA1.A1_COD = TRB.TMP_CLIENT AND
SA1.A1_LOJA = TRB.TMP_LOJA AND
SA1.%NOTDEL%
EndSql
(_cAlias)->(DbGoTop())
// Percorro a consulta SQL
While(!(_cAlias)->(EoF()))
ConOut('Título: ' + (_cAlias)->TMP_NUM + ' - Razão Social: ' + (_cAlias)->A1_NOME)
(_cAlias)->(DbSkip())
EndDo
(_cAlias)->(DbCloseArea())
Nesse exemplo, estou fazendo um join da minha tabela temporária de títulos com a minha tabela de clientes. Dessa forma posso buscar outros dados que não estejam em minha tabela temporária. Aqui as possibilidades são várias!
Delete
Por fim, após realizar todos os processamentos desejados, basta chamar o método Delete para efetuar a deleção da tabela temporária do banco de dados. Esse método chama de forma automática a função DbCloseArea para fechar o seu alias.
If(Type('oTable') <> 'U')
oTable:Delete()
FreeObj(oTable)
EndIf
Espero que a dica tenha sido útil!
Referências

Sou formado em Jogos Digitais, atualmente trabalhando com o ERP Protheus.