Concatenar conteúdo de um registro SQL Server 2005

SQL Server

Delphi

13/05/2015

Boa tarde,

Estou com uma necessidade de retornar o conteúdo de um registro de uma determinada tabela. Exemplo:

Tenho uma tabela de PRODUTOS com os seguintes campos:

CODIGO NOME MARCA VALOR
------------ -------------------------------- -------------- ---------
2514 MOUSE 3 BOTOES USB LOGITECH 45,00


Gostaria de saber se existe algum comando SQL ou Função ou StoreProcedure para que ao passar o nome da tabela como parâmetro a mesma me retorne o conteúdo do registro devidamente concatenado. Seguindo o exemplo do registro acima ficaria assim:
2514MOUSE 3 BOTÕES USBLOGITECH45,00

Atualmente estou utilizando uma função em Delphi7 para essa finalidade mas devido a alguns clientes terem muitos registros esta um pouco lento.


if not QAux.FieldByName(CampoChave).IsNull then
begin
//Loop nos Fields
for i := 0 to QAux.FieldCount -1 do
begin
if QAux.FieldDefs[i].Name <> NomeCampo then //elimina o campo _HASH para que o mesmo não faça parte do calculo MD5
StringAux := StringAux + QAux.Fields.Fields[i].AsString;
end;
end;


Minha necessidade desta rotina é para atender um requisito do PAF-ECF para detectar se houve algum tipo de manipulação no banco de dados que não passou pelo meu sistema.
Carlos Eduardo

Carlos Eduardo

Curtidas 0

Melhor post

Carlos Eduardo

Carlos Eduardo

13/05/2015

Um detalhe, pode usar o convert em campos que já sao do tipo varchar que não da erro.


Vou testar a consulta sql e assim que possível postarei o resultado.

Grato pela contribuição.
GOSTEI 1

Mais Respostas

Marcos P

Marcos P

13/05/2015

Se todos os campos forem char, você pode concatenar direto :

select CODIGO+NOME+MARCA+VALOR 


Se existirem campos numéricos, converta-os para char ( via convert, rtrim, str,... como no exemplo abaixo ) e depois execute a concatenação :

declare @Numerico1 Int
declare @Numerico2 Float

set @Numerico1 = 83
set @Numerico2 = 15.76

select @Numerico1, @Numerico2, rtrim(convert(varchar(20),@Numerico1)), rtrim(str(@Numerico2,10,2))
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Se todos os campos forem char, você pode concatenar direto :

select CODIGO+NOME+MARCA+VALOR 


Se existirem campos numéricos, converta-os para char ( via convert, rtrim, str,... como no exemplo abaixo ) e depois execute a concatenação :

declare @Numerico1 Int
declare @Numerico2 Float

set @Numerico1 = 83
set @Numerico2 = 15.76

select @Numerico1, @Numerico2, rtrim(convert(varchar(20),@Numerico1)), rtrim(str(@Numerico2,10,2))


Marcos,

Como citado no post preciso de uma função ou algo parecido onde passo o nome da tabela como parâmetro sem a necessidade de informar os campos pois são varias tabelas.

Já consegui pegar o nome dos campos. rs

 SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'PRODUTOS' 


Grato pela atenção!
GOSTEI 0
Ceilton M

Ceilton M

13/05/2015

Carlos, defina melhor sua necessidade:
"detectar se houve algum tipo de manipulação no banco de dados que não passou pelo meu sistema. "

Você quer saber se a estrutura da tabela foi alterada?

Se for isso você tem que estudar triggers DDL....
GOSTEI 0
Ceilton M

Ceilton M

13/05/2015

Exemplo:
Esse trigger impede que tabelas sejam alteradas ou apagadas de um banco.

CREATE TRIGGER safety
ON DATABASE
FOR DROP_TABLE, ALTER_TABLE
AS
PRINT 'You must disable Trigger "safety" to drop or alter tables!'
ROLLBACK ;
GOSTEI 0
Marcos P

Marcos P

13/05/2015

Carlos,

Se sua ideia é fazer isso dinamicamente, crie uma stored procedure no banco que, passando o nome da tabela, leia sua estrutura e gere a concatenação conforme os campos disponíveis.

Você fará isso através de uma query dinâmica, tratando das devidas conversões para char / varchar.

Vai dar um "trabalhinho", mas eu testaria isso antes de maneira fixa, para verificar se a performance do lado da aplicação melhora...
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Carlos, defina melhor sua necessidade:
"detectar se houve algum tipo de manipulação no banco de dados que não passou pelo meu sistema. "

Você quer saber se a estrutura da tabela foi alterada?

Se for isso você tem que estudar triggers DDL....


Minha necessidade não consiste em saber se a estrutura do banco foi alterada e sim um registro. Exemplo:

Tenho um sistema de Frente de Loja onde é feito uma emissão de cupom fiscal. Ao gravar o registro pertinente a venda eu também calculo o HASH do registro e armazeno num campo da tabela para que depois eu possa verificar se o conteúdo do registro esta igual ao que foi gravado.

Desta forma se alguém acessar uma ferramente de manutenção e modificar os dados deste registro eu tenho como saber se o mesmo foi manipulado.

Esta é uma exigência para quem vai homologar o sistema na legislação PAF-ECF.

Existe um arquivo eletrônico que é gerado todos os dias após a Redução Z e caso algum registro de produto, cliente, venda, estoque, etc... for manipulado eu tenho que gerar interrogações no lugar do campo. "????????"
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Exemplo:
Esse trigger impede que tabelas sejam alteradas ou apagadas de um banco.

CREATE TRIGGER safety
ON DATABASE
FOR DROP_TABLE, ALTER_TABLE
AS
PRINT 'You must disable Trigger "safety" to drop or alter tables!'
ROLLBACK ;


Vou guardar essa trigger achei bem interessante.

Obrigado!
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Carlos,

Se sua ideia é fazer isso dinamicamente, crie uma stored procedure no banco que, passando o nome da tabela, leia sua estrutura e gere a concatenação conforme os campos disponíveis.

Você fará isso através de uma query dinâmica, tratando das devidas conversões para char / varchar.

Vai dar um "trabalhinho", mas eu testaria isso antes de maneira fixa, para verificar se a performance do lado da aplicação melhora...


Marcos, vou tentar criar essa StoreProcedure e caso eu consiga postarei aqui o resultado e principalmente a performance. Pois hoje já faço isso via Delphi a qual está muito lento pois um de meus clientes tem mais de 50 mil produtos. Atualmente minha estrutura de Produtos tem 60 campos. Fazendo uma conta rápida temos:

60 X 50.000 = 3.000.000 (essa é a quantidade de vezes que meu programa executa).

O cliente me disse que todo final de dia após a Redução Z todo o processo de geração do arquivo eletrônico demora aproximadamente 15 minutos.
GOSTEI 0
Marcos P

Marcos P

13/05/2015

Qual a versão do Sql Server ?

Tente desenvolver a procedure, caso você tenha dificuldades... retorne a esse post que posso lhe ajudar via Skype.
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Qual a versão do Sql Server ?

Tente desenvolver a procedure, caso você tenha dificuldades... retorne a esse post que posso lhe ajudar via Skype.


SQL Server 2005 Express

Beleza Marcos assim que eu conseguir fazer algo lhe aviso sim.

Grato pela atenção!
GOSTEI 0
Ceilton M

Ceilton M

13/05/2015

Segue um exemplo
Só deu erro em colunas do tipo timestamp.


declare @nometabela varchar(200) = 'TABELA';
declare @objectid int --= 124956663;
declare @name varchar(200), @system_type_id int;
declare @SQL varchar(2000) = ' select '
DECLARE @UID INT, @schemaname varchar(20)

select @objectid = id from sysobjects where type = 'U' and name = @nometabela

declare cursorcolumns cursor for SELECT name,system_type_id FROM sys.columns where object_id = @objectid
open cursorcolumns;

fetch cursorcolumns into @name, @system_type_id;

while @@FETCH_STATUS = 0
begin
if len (@SQL) > 8 set @SQL = @SQL + ' + ';
set @SQL = @SQL + ' convert(varchar(200), isnull(' + @name + ','''') ) ';
/*
if @system_type_id not in (35, 167, 175,231,239) -- Se nao for do tipo character
begin

set @SQL = @SQL + ' convert(varchar(50), ' + @name + ' ) '

end;
*/
fetch cursorcolumns into @name, @system_type_id
end;
close cursorcolumns;
deallocate cursorcolumns;

select @UID = UID from sysobjects where name IN ( 'GN_USUARIO' )
SELECT @schemaname = name from sys.SCHEMAS WHERE SCHEMA_ID = @UID

set @SQL = @SQL + ' from ' + @schemaname + '.' + @nometabela

select @SQL;

exec @SQL;
GOSTEI 0
Ceilton M

Ceilton M

13/05/2015

Um detalhe, pode usar o convert em campos que já sao do tipo varchar que não da erro.
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Boa tarde!

Caro Ceilton,

Fiz testes no exemplo que você disponibilizou mas não obtive sucesso pois ao executar a consulta informando minha tabela (@nometabela = 'PRODUTOS') a consulta esta retornando vazia.

Hoje consegui fazer uma store procedure e estou quase lá, só está faltando o procedimento me retornar o conteúdo do select que estou compondo no loop. Não consegui fazer com que este procedimento me retornasse o resultado do select mas sim a instrução sql. Exemplo: "select campo1, campo2 from PRODUTOS"

Estou postando meu procedimento para que vocês possam me ajudar:

CREATE PROCEDURE [dbo].[sp_Retorna_Conteudo_Registro](
@nome_tabela varchar(50),
@campo_chave varchar(50),
@codigo varchar(10),
@resultado varchar(8000) OUTPUT)
AS
set nocount on

declare cursorcolumns cursor for
select column_name, data_type
from information_schema.columns
where table_name = @nome_tabela
and (column_name not like '%HASH')

declare @column_name varchar(100), @data_type varchar(100), @SQL varchar(5000)
set @SQL = 'select '
open cursorcolumns

fetch next from cursorcolumns into @column_name, @data_type

while (@@FETCH_STATUS = 0)
begin
if @data_type in ('char', 'nchar', 'nvarchar', 'varchar')
set @SQL = @SQL + 'coalesce(' + @column_name + ','''') + '
else
set @SQL = @SQL + 'coalesce(convert(varchar(8000), ' + @column_name + '),'''') + '
fetch next from cursorcolumns into @column_name, @data_type
end
close cursorcolumns
deallocate cursorcolumns

if right(@SQL,2) = '+ ' --remove o caracter "+" resultado da concatenação no loop dos campos
set @SQL = left(@SQL,(len(@SQL)-1))

----------------- É neste ponto que não consigo retornar o resultado deste select -----------------
select @Resultado = (@SQL + ' from ' + @nome_tabela + ' where ' + @campo_chave + ' = ' + @codigo)

set nocount off


Estou realizando os testes no Delphi:
procedure TForm1.btnExecutarClick(Sender: TObject);
Var
sp_Retorna_Conteudo_Registro: TSQLStoredProc;
begin
//Store Procedure
try
try
sp_Retorna_Conteudo_Registro := TSQLStoredProc.Create(nil);
sp_Retorna_Conteudo_Registro.SQLConnection := AutoConnection;
sp_Retorna_Conteudo_Registro.SchemaName := 'dbo';
sp_Retorna_Conteudo_Registro.StoredProcName := 'sp_Retorna_Conteudo_Registro';
sp_Retorna_Conteudo_Registro.Params.FindParam('@nome_tabela').AsString := lbledtTabela.Text;
sp_Retorna_Conteudo_Registro.Params.FindParam('@campo_chave').AsString := lbledtChave.Text;
sp_Retorna_Conteudo_Registro.Params.FindParam('@codigo').AsString := lbledtCodigo.Text;
sp_Retorna_Conteudo_Registro.ExecProc;

lbledtResultSP.Text := sp_Retorna_Conteudo_Registro.Params.FindParam('@resultado').AsString;
finally
FreeAndNil(sp_Retorna_Conteudo_Registro);
end;
except on E: Exception do
Application.MessageBox(PChar(E.Message), 'Erro', MB_ICONERROR);
end
end;
GOSTEI 0
Marcos P

Marcos P

13/05/2015

select @Resultado = (@SQL + ' from ' + @nome_tabela + ' where ' + @campo_chave + ' = ' + @codigo)

Não rola !

Faça...

set @SQL = @SQL + ' from ' + @nome_tabela + ' where ' + @campo_chave + ' = ' + @codigo
exec(@SQL)

Se quiser ver a query antes de rodar, troque o "exec" por "print".

Uma pergunta... um item cuja descrição é "SUPORTE PARA MONITOR", deve retornar "SUPORTE PARA MONITOR" ou "SUPORTEPARAMONITOR" ?
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Marcos, agora a situação é a seguinte.

O procedimento que você pediu para alterar funcionou somente quando executo o procedimento no "SQL Management Studio":

exec sp_Retorna_Conteudo_Registro 'PRODUTOS', 'PRD_CODIGO', 1, ''


Resultado:
1CONJUNTO DIVS.Sep 18 2005 12:00AM CJ0.00000.00000.00001020.000017.000000.00009949.000049.00000.000025.00000.00000.00000.000067.379921100.0000.00000.0000R$Sep 29 2014 12:00AMTT0


Mas no Delphi executa sem da erro mas nao traz nada no retorno. Alguma sugestão? No post anterior eu inseri como estou utilizando a Store Procedure no Delphi.

Respondendo sua pergunta a descrição deve retornar com os devidos espaços: "SUPORTE PARA MONITOR"

Esse ta dando trabalho rs.

Grato pela atenção!
GOSTEI 0
Marcos P

Marcos P

13/05/2015

Já é um avanço...

Vai ser necessário debug do código Delphi !

O Delphi gera algum erro ou somente não retorna nada ?

Caso retorne erro, cole ele aqui...

Caso não retorne, você provavelmente está chamando errado a SP...
> Confirme certinho a passagem de parâmetros para a procedure...
> Abra um "Sql Profiler" no server e debug a chamada à procedure...
> Pegue o comando gerado no banco e rode no Management Studio... ajustando a sintaxe de chamada no Delphi, até ficar certo !
GOSTEI 0
Marcos P

Marcos P

13/05/2015

Em tempo... qual a versão do seu Delphi ?

Se for superior a 7, troque a passagem de parâmetros "AsString", para : "AsAnsiString".
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Estou utilizando o Delphi 7.

O engraçado é que antes quando estava com a linha abaixo na SP estava retornando pelo Delphi normalmente porém não com o conteúdo que preciso.

select @Resultado = (@SQL + ' from ' + @nome_tabela + ' where ' + @campo_chave + ' = ' + @codigo)


Não está ocorrendo erro.

Estou utilizando o DBExpress tentarei usar o ADO para fim de testes.

Qualquer novidade posto aqui.

Grato!
GOSTEI 0
Marcos P

Marcos P

13/05/2015


Não consegui fazer com que este procedimento me retornasse o resultado do select mas sim a instrução sql. Exemplo: "select campo1, campo2 from PRODUTOS"


Não entendi...

Conforme você mesmo disse a query anterior ( com select @Resultado ), não estava funcionando... pois retornava a instrução e não os dados !

Agora, você afirma que estava retornando normalmente no Delphi...

Você chegou a fazer o debug no banco ?

O execute está sendo gerado corretamente, pelo Delphi ?
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Marcos, desculpe a confusão... mas na verdade o que eu quis dizer e que no Delphi que não mais esta pegando o retorno do procedimento. O procedimento em si quando executo pelo SQL Management Studio esta OK.

Vou postar o código do Delphi que estou tendo problemas com o retorno do procedimento. O parâmetro "@resultado" esta retornando vazio.

  try
    try
      sp_Retorna_Conteudo_Registro := TSQLStoredProc.Create(nil);
      sp_Retorna_Conteudo_Registro.SQLConnection  := AutoConnection;
      sp_Retorna_Conteudo_Registro.SchemaName     := 'dbo';
      sp_Retorna_Conteudo_Registro.StoredProcName := 'sp_Retorna_Conteudo_Registro';
      sp_Retorna_Conteudo_Registro.Params.FindParam('@nome_tabela').AsString := lbledtTabela.Text;
      sp_Retorna_Conteudo_Registro.Params.FindParam('@campo_chave').AsString := lbledtChave.Text;
      sp_Retorna_Conteudo_Registro.Params.FindParam('@codigo').AsString      := lbledtCodigo.Text;
      sp_Retorna_Conteudo_Registro.ExecProc;
      lbledtResultSP.Text := sp_Retorna_Conteudo_Registro.Params.FindParam('@resultado').AsString;
    finally
      FreeAndNil(sp_Retorna_Conteudo_Registro);
    end;
  except on E: Exception do
    Application.MessageBox(PChar(E.Message), 'Erro', MB_ICONERROR);
  end
GOSTEI 0
Marcos P

Marcos P

13/05/2015

Crie o componente em tempo de design, revise a declaração de parâmetros ( inclusive o result ) e tente ajustar o código para :
( assumindo que a SP tem somente três parâmetros )
try
  sp_Retorna_Conteudo_Registro.Close;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@nome_tabela').AsAnsiString := lbledtTabela.Text;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@campo_chave').AsAnsiString := lbledtChave.Text;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@codigo').AsAnsiString      := lbledtCodigo.Text;
  sp_Retorna_Conteudo_Registro.ExecProc;
  ShowMessage('Registros retornados : '+ IntToStr(sp_Retorna_Conteudo_Registro.RecordCount));
except on E: Exception do
  Application.MessageBox(PChar(E.Message), 'Erro', MB_ICONERROR);
end;
GOSTEI 0
Carlos Eduardo

Carlos Eduardo

13/05/2015

Crie o componente em tempo de design, revise a declaração de parâmetros ( inclusive o result ) e tente ajustar o código para :
( assumindo que a SP tem somente três parâmetros )
try
  sp_Retorna_Conteudo_Registro.Close;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@nome_tabela').AsAnsiString := lbledtTabela.Text;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@campo_chave').AsAnsiString := lbledtChave.Text;
  sp_Retorna_Conteudo_Registro.Params.ParamByName('@codigo').AsAnsiString      := lbledtCodigo.Text;
  sp_Retorna_Conteudo_Registro.ExecProc;
  ShowMessage('Registros retornados : '+ IntToStr(sp_Retorna_Conteudo_Registro.RecordCount));
except on E: Exception do
  Application.MessageBox(PChar(E.Message), 'Erro', MB_ICONERROR);
end;


Boa noite Marcos,

Fiz isso tudo que você disse, mas ao passar no RecordCount da esse erro:
dbExpress Error: Operation Not Supported


OBS: Testei também no XE2.

Fiz testes na SP e também cheguei a criar uma função para testes mas ambas consegui retornar de tudo, menos o resultado do select "exec(@SQL)".

Só consegui o que eu queria realizando em duas etapas:
1) Criei uma função que retorne o select. Ex: "select prd_codigo + prd_nome + prd_marca, etc... from PRODUTOS where PRD_CODIGO = 1"
2) Depois peguei o resultado deste select e mandei executar outra consulta.

Tudo isso via Delphi. Vou postar minha função e um pedaço do código em Delphi para conhecimento. Mas ainda não sei se vai valer a pena fazer uso destes procedimentos, pois depois disso tudo ainda tenho que utilizar uma rotina que me retorna o MD5 do conteúdo para saber se houve ou não manipulação no banco de dados.

Vou finalizar este post, mas em breve vou criar um novo para que alguém possa me ajudar de outra forma como identificar se houve manipulação no banco de dados que não passou pelo meu sistema.

Grato!

Código da Função:
CREATE FUNCTION [dbo].[fc_Retorna_Conteudo_Registro](
@nome_tabela varchar(50),
@campo_chave varchar(50),
@codigo varchar(10))
RETURNS varchar(5000)
AS
BEGIN
declare cursorcolumns cursor for
  select column_name, data_type 
  from information_schema.columns 
  where table_name = @nome_tabela
  and (column_name not like '%HASH')
  
declare @column_name varchar(100), @data_type varchar(100), @SQL varchar(5000)
set @SQL = 'select '
open cursorcolumns

fetch next from cursorcolumns into @column_name, @data_type

while (@@FETCH_STATUS = 0)
begin
  if @data_type in ('char', 'nchar', 'nvarchar', 'varchar')
    set @SQL = @SQL + 'coalesce(' + @column_name + ','''') + '
  else
    if @data_type in ('datetime', 'date')
      set @SQL = @SQL + 'Convert(Varchar(10), ' + @column_name + ',103) + '
    else
      if @data_type = ('decimal')
        set @SQL = @SQL + 'case when (' + @column_name + ' = 0) or (' + @column_name + ' is null) then ''0''' +
                          'else                          convert(varchar,' + @column_name + ')' +
                          'end + '
      else
        set @SQL = @SQL + 'coalesce(convert(varchar(8000), ' + @column_name + '),'''') + '
  fetch next from cursorcolumns into @column_name, @data_type
end
close cursorcolumns
deallocate cursorcolumns

if right(@SQL,2) = '+ '  --remove o caracter "+" resultado da concatenação no loop dos campos
   set @SQL = left(@SQL,(len(@SQL)-1)) 

RETURN (@SQL + 'as Conteudo from ' + @nome_tabela + ' where ' + @campo_chave + ' = ' + @codigo)

END


Código Delphi
  try
    SQLQuery1.Close;
    SQLQuery1.SQL.Text := 'select dbo.fc_Retorna_Conteudo_Registro(''PRODUTOS'', ''PRD_CODIGO'', ''1'') as Resultado';
    SQLQuery1.Open;

    SQL := SQLQuery1.FieldByName('Resultado').AsString;

    SQLQuery1.Close;
    SQLQuery1.SQL.Text := SQL;
    SQLQuery1.Open;
    mmoResultSP.Text := SQLQuery1.FieldByName('Conteudo').AsString;
    SQLQuery1.Close;
  except on E: Exception do
    Application.MessageBox(PChar(E.Message), 'Erro', MB_ICONERROR);
  end
GOSTEI 0
Marcos P

Marcos P

13/05/2015

A solução que você deu é equivalente a criação da procedure e, portanto, perfeitamente funcional... devendo resolver seu problema original de performance.

Quanto ao MD5, você consegue gerá-lo no próprio Sql Server. Existem funções na WEB disponíveis para isso... vale uma pesquisada !
GOSTEI 1
Carlos Eduardo

Carlos Eduardo

13/05/2015

A solução que você deu é equivalente a criação da procedure e, portanto, perfeitamente funcional... devendo resolver seu problema original de performance.

Quanto ao MD5, você consegue gerá-lo no próprio Sql Server. Existem funções na WEB disponíveis para isso... vale uma pesquisada !


Marcos, realmente é equivalente sim, mas achei melhor criar uma função por ser mais fácil sua utilização no Delphi.

Referente ao MD5 vou dar uma pesquisada sim. Quando eu tiver um resultado satisfatório postarei aqui a solução encontrada e se realmente obtive uma melhora na performance que é meu objetivo final.

Grato pela ajuda, pois foi de grande valia.
GOSTEI 0
POSTAR