Nesta página5 seções
Antes de começar
- Tabela customizada criada no dicionário (no exemplo, a ZA1 — Cadastro de motoristas)
- Acesso ao Configurador com permissão de alterar o dicionário de dados
O campo memo (tipo M) é o campo de texto livre do Protheus: observação,
histórico, descrição longa — muito texto sem precisar de um campo caractere
gigante. No banco, o memo de uma tabela comum fica numa coluna binária — no
SQL Server, varbinary(max) — e um SELECT direto devolve bytes, não o texto.
Para ler, é preciso converter.
Existe ainda um segundo tipo de memo, mais antigo, que nem fica na tabela: o
memo virtual, cujo texto mora na tabela SYP (ou na RDY, nas tabelas dos
módulos de RH). Saber qual dos dois você tem na frente decide qual consulta
funciona.
Memo real e memo virtual
| Memo real | Memo virtual | |
|---|---|---|
X3_CONTEXT | R | V |
| Onde fica o texto | Numa coluna da própria tabela | Na tabela SYP, quebrado em linhas |
| O que a tabela guarda | O texto (em formato binário, no padrão) | Só um código, num campo caractere à parte |
| Como a tela lê | Direto da coluna | Pelo inicializador, com a função MSMM() |
| Exemplo no padrão | B1_MOPC | B1_OBS, lido a partir do B1_CODOBS |
No dicionário padrão do Protheus 12, a maior parte dos campos memo é real. O
cadastro de produtos ainda tem vários virtuais, e é neles que a SYP aparece.
Para um campo novo, o caminho é o memo real: um campo só, sem código auxiliar, sem tabela intermediária.
A ficha do ZA1_OBS
O campo é criado como os outros, em Base de Dados > Dicionário > Arquivos, editando a tabela e incluindo o campo na pasta Campos.
| Propriedade (coluna da SX3) | Valor |
|---|---|
Tipo (X3_TIPO) | M |
Tamanho (X3_TAMANHO) | 10 |
Título (X3_TITULO) | Observacao |
Contexto (X3_CONTEXT) | R |
| Obrigatório (pasta Uso) | desmarcado |
Browse (X3_BROWSE) | N |
Pasta (X3_FOLDER) | 2 |
Proprietário (X3_PROPRI) | U |
O tamanho 10 é o valor usual, não uma regra. A página de tipos de dados do
TDN diz que memo tem sempre tamanho 10, mas o dicionário padrão mostra outra
coisa: o memo real mais comum tem 10, e há memos com 60, 80, 250 e 999 — o
B1_MOPC, citado acima, tem 80. Contexto R porque é memo
real. Observação é opcional por natureza, e texto longo não cabe numa coluna
da lista — fora do browse também é o que o padrão faz na maior parte dos
memos.
O que o X3_TAMANHO não faz num memo real é dimensionar a coluna. No
banco que conferimos, memos com tamanho 10, 80 e 250 viraram todos
varbinary(max): a coluna é a mesma, qualquer que seja o número. No memo
virtual o tamanho tem uso concreto, e ele aparece mais abaixo.
X3_FOLDER guarda o número da pasta da tela. No cadastro de motoristas
da série, a pasta 1 é “Dados do motorista” e a 2 é “Observacoes”. Como as
pastas são cadastradas, em
Pastas e agrupamentos.
Por que o SELECT devolve bytes
O Protheus cria o memo real como objeto binário, e no SQL Server o tipo usado
é varbinary(max) (em versões antigas era image). O conteúdo continua
sendo o texto digitado; só está guardado como bytes.
Confira o tipo físico da coluna no seu banco antes de escrever a consulta:
SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'ZA1010'
AND COLUMN_NAME = 'ZA1_OBS'Num memo binário, a resposta é varbinary com CHARACTER_MAXIMUM_LENGTH
igual a -1: é assim que o SQL Server mostra o (max). O 010 do nome da
tabela é o sufixo da empresa e muda de ambiente para ambiente. Troque pelo
seu.
No SQL Server Management Studio, um SELECT ZA1_OBS direto mostra um valor
começando por 0x seguido de pares hexadecimais: cada par é um caractere do
texto (54 é T, 45 é E, 20 é espaço). Repare no último par. Para
vê-lo sem despejar o texto inteiro, peça só o último byte:
SELECT TOP 5 ZA1_COD,
DATALENGTH(ZA1_OBS) AS BYTES,
SUBSTRING(ZA1_OBS, DATALENGTH(ZA1_OBS), 1) AS ULTIMO_BYTE
FROM ZA1010
WHERE DATALENGTH(ZA1_OBS) > 0
AND D_E_L_E_T_ = ' 'No banco que conferimos, todo memo preenchido terminava assim:
ULTIMO_BYTE
0x00Esse 00 é um caractere nulo gravado depois do texto. Se a coluna vier
varbinary, converta. É a mesma conversão que as rotinas do padrão usam
nas queries deles para SQL Server:
SELECT ZA1_COD, ZA1_NOME,
ISNULL(CONVERT(VARCHAR(2047), CONVERT(VARBINARY(2047), ZA1_OBS)), '') AS OBS
FROM ZA1010
WHERE D_E_L_E_T_ = ' 'O número entre parênteses é o limite da leitura: texto maior que 2047
caracteres vem cortado. VARCHAR com número aceita no máximo 8000; acima
disso o SQL Server recusa a consulta. Para observação longa, use MAX nos
dois lados:
SELECT ZA1_COD,
ISNULL(CONVERT(VARCHAR(MAX), CONVERT(VARBINARY(MAX), ZA1_OBS)), '') AS OBS
FROM ZA1010
WHERE D_E_L_E_T_ = ' 'A tabela pode criar o memo como texto longo em vez de binário — o formato
que a TOTVS chama de CLOB. A escolha é por tabela: no Configurador, na
edição da tabela em Base de Dados > Dicionário, o campo “Memo CLOB” fica
“Sim”. A documentação avisa que a alteração precisa ser feita antes de a
tabela ser criada no banco; depois disso, os campos existentes não mudam.
Numa tabela CLOB, a coluna é de tipo caractere (no SQL Server,
varchar(max)) e o SELECT direto já mostra o texto.
A documentação chama esse controle de X2_MEMTYPE; no dicionário do Protheus
12, a coluna da SX2 que o guarda é a X2_CLOB. O padrão trata 2 como
memo binário, e é o valor da grande maioria das tabelas; 1 marca a tabela
CLOB:
SELECT X2_CHAVE, X2_NOME, X2_CLOB
FROM SX2010
WHERE X2_CHAVE = 'ZA1'
AND D_E_L_E_T_ = ' 'Ainda assim, a palavra final é do INFORMATION_SCHEMA. No banco que
conferimos há tabela com X2_CLOB = '1' e memo ainda varbinary(max), o
que é compatível com o caso descrito pela documentação: a definição mudou,
mas a coluna já existente não.
Lendo o memo virtual na SYP
No memo virtual, a tabela guarda só um código. No produto, o campo
B1_CODOBS (caractere, 6 posições) guarda a chave, e o inicializador do
B1_OBS chama MSMM(SB1->B1_CODOBS,60) para montar o texto na tela.
Aqui o tamanho trabalha: o segundo parâmetro da MSMM é a largura das
linhas em que o texto é quebrado, e o 60 do inicializador é o
X3_TAMANHO do B1_OBS. Na receita da TOTVS para memo virtual, a gravação
passa o tamanho do campo lido com TamSX3().
A SYP guarda o texto em pedaços de até 80 caracteres, um por linha, na
ordem de YP_SEQ:
| Coluna | Conteúdo |
|---|---|
YP_CHAVE | O código gravado no campo da tabela de origem |
YP_SEQ | A ordem do pedaço |
YP_TEXTO | O pedaço do texto |
YP_CAMPO | O nome do campo de código que a rotina informou à MSMM ao gravar |
O código não nasce da tabela de origem: na inclusão sem chave, a MSMM tira
um número novo da própria SYP, com GetSX8Num("SYP","YP_CHAVE"), e grava
esse número no campo de código. Na leitura, ela procura só por filial e
YP_CHAVE. O YP_CAMPO guarda o nome do campo de código passado na
gravação — no cadastro de clientes, por exemplo, a rotina passa A1_OBS,
o código do memo A1_VM_OBS.
Para ler a observação de um produto já remontada, numa coluna só:
SELECT B1.B1_COD,
STRING_AGG(
REPLACE(RTRIM(CAST(YP.YP_TEXTO AS VARCHAR(MAX))),
'\13\10', CHAR(13) + CHAR(10)),
'') WITHIN GROUP (ORDER BY YP.YP_SEQ) AS OBS
FROM SB1010 B1
JOIN SYP010 YP
ON YP.YP_FILIAL = B1.B1_FILIAL
AND YP.YP_CHAVE = B1.B1_CODOBS
AND YP.D_E_L_E_T_ = ' '
WHERE B1.B1_COD = 'PGV001'
AND B1.D_E_L_E_T_ = ' '
GROUP BY B1.B1_CODTrês detalhes fazem a diferença:
RTRIMem cada pedaço. OYP_TEXTOé gravado completado com espaços até as 80 posições; sem o corte, os espaços entram no meio do texto.REPLACEdo\13\10. Quando o texto tem quebra de linha, aMSMMnão grava a quebra: grava os seis caracteres\13\10no fim do pedaço. A consulta troca esse marcador pela quebra de verdade. Há um segundo marcador: quando um pedaço tem só espaços, aMSMMgrava os espaços seguidos de\14\10, e na leitura troca esse marcador por seis espaços. Se o seu texto tiver trechos em branco, aninhe mais umREPLACEna remontagem,REPLACE(REPLACE(RTRIM(...), '\14\10', SPACE(6)), '\13\10', CHAR(13) + CHAR(10)); sem ele, o\14\10aparece literal no resultado.CASTparaVARCHAR(MAX). Sem ele, quando o texto remontado passa de 8000 bytes, a consulta não devolve texto cortado: ela falha com o erroSTRING_AGG aggregation result exceeded the limit of 8000 bytes. Use LOB types to avoid result truncation.
No SQL Server Management Studio, a grade de resultados não mostra a quebra de
linha que o REPLACE devolveu: a documentação da Microsoft diz que o modo
“Results to Grid” “can’t implement the carriage return”. Para ver o texto
como foi digitado, mude para Resultados em Texto (que corta cada coluna
em 256 caracteres, a menos que você aumente esse limite nas opções) ou copie
a célula e cole num editor.
A STRING_AGG existe a partir do SQL Server 2017 e funciona em qualquer
nível de compatibilidade do banco; a ordenação com WITHIN GROUP exige nível
110 ou maior. O JOIN pela filial repete o que a MSMM faz; se a SYP for
compartilhada no seu ambiente e o produto não, troque a comparação pelo valor
de YP_FILIAL gravado.
Para conferir as linhas cruas antes de agrupar, incluindo o YP_CAMPO:
SELECT YP.YP_FILIAL, YP.YP_CHAVE, YP.YP_SEQ, YP.YP_CAMPO, YP.YP_TEXTO
FROM SB1010 B1
JOIN SYP010 YP
ON YP.YP_FILIAL = B1.B1_FILIAL
AND YP.YP_CHAVE = B1.B1_CODOBS
AND YP.D_E_L_E_T_ = ' '
WHERE B1.B1_COD = 'PGV001'
AND B1.D_E_L_E_T_ = ' '
ORDER BY YP.YP_SEQSe quiser uma proteção extra na remontagem, acrescente ao JOIN dela
AND YP.YP_CAMPO = '...', com o valor que essa consulta mostrou.
Nas tabelas de RH, a tabela é a RDY
A MSMM decide onde gravar pelo módulo da tabela de origem, o X2_MODULO
da SX2: nas tabelas dos módulos de RH, ela usa por padrão a RDY no lugar
da SYP. A RDY tem as mesmas colunas com outro prefixo (RDY_CHAVE,
RDY_SEQ, RDY_TEXTO, RDY_CAMPO) e mais a RDY_FILTAB, com a filial da
tabela de origem. Num memo virtual de RH, a consulta na SYP não encontra
nada: use a RDY010, com o prefixo RDY_ no lugar de YP_.
A troca de tabela não basta. Na leitura, a MSMM não procura a RDY pela
RDY_FILIAL: ela usa o índice 2 da tabela, RDY_FILTAB+RDY_CHAVE+RDY_SEQ, e
compara a RDY_FILTAB com a filial do registro de origem. No dicionário
padrão, a RDY é compartilhada (X2_MODO = C) e a SRA é exclusiva
(X2_MODO = E); por isso, ligar RDY_FILIAL à RA_FILIAL devolve vazio.
No JOIN, troque a comparação de filial por RDY.RDY_FILTAB = <filial da tabela de origem> — na SRA, RDY.RDY_FILTAB = RA.RA_FILIAL — e mantenha
RDY.RDY_CHAVE igual ao campo de código do memo.
Em AdvPL, a função que lê e grava esse texto é
MSMM(cChave, nTam, nLin, cString, nOpc, ...). Criar um memo virtual novo
dá mais trabalho que um real, e a documentação da TOTVS descreve a receita:
dois campos (um código caractere real e o memo virtual), o inicializador
IIF(!INCLUI,MSMM(...),"") no memo, a gravação com MSMM na inclusão e na
alteração, e a exclusão das linhas da SYP quando o registro é excluído —
no exemplo oficial, tudo feito por pontos de entrada da rotina de cadastro.
É mais uma razão para preferir o memo real num campo novo. O que
separa campo real de virtual, para qualquer tipo, está em
Campo real ou virtual.
Conferir o dicionário
SELECT X3_CAMPO, X3_TIPO, X3_TAMANHO, X3_CONTEXT, X3_RELACAO, X3_BROWSE
FROM SX3010
WHERE X3_ARQUIVO = 'ZA1'
AND X3_TIPO = 'M'
AND D_E_L_E_T_ = ' 'Referências
TDNtdn.totvs.com
TDN — Tipos de dados (Dicionário de Dados)TDNtdn.totvs.com
TDN — Campos MEMO estilo "CLOB" nas tabelas de dadosTDNtdn.totvs.com
TDN — Criação de campo Memo virtual usando SYPTDNtdn.totvs.com
TDN — Melhoria: troca do tipo de campo MEMO para MSSQLOutrolearn.microsoft.com
Microsoft Learn — STRING_AGG (Transact-SQL)
Continue por aqui
- Vídeo
ProtheusTutorial
Campo numérico: tamanho, decimais e a máscara
Campo numérico no Protheus: o tamanho já inclui ponto e decimais. Como criar no Configurador, escrever a máscara certa e barrar negativo com Positivo().