Pesquisar este blog

domingo, 14 de agosto de 2011

Importar um arquivo texto direto para o SQL Server

Imagine que você tem um arquivo texto, delimitado por ponto-e-vírgula (um CSV) e você deseja inserí-lo diretamente dentro de uma tabela. Porém, eu não vou mostrar como fazer isso no Management Studio, que é fácil. Eu vou mostrar como fazer isso via T-SQL! E porque? Bom, para mim isto foi muito útil para automatizar uma rotina de comunicação entre sistemas, rodando automaticamente.

Vamos então para a parte prática. É muito simples, primeiro analise o arquivo texto a ser importado, e identifique todas as colunas do arquivo com seu respectivo tipo de dado (data, texto, número, money, decimal, etc). É muito importante este trabalho de análise, pois será necessário criar uma tabela exatamente com as mesmas especificações.

Vamos ver um exemplo, lançamentos contábeis. Eu tenho aqui um arquivo texto, delimitado por vírgulas gerado a partir de um sistema ERP tosco em que a contabilidade não funciona, e nós precisávamos importar para um que funcionasse, de fato, porém em SQL Server. Este tipo de coisa sempre surge no meu dia a dia, a quantidade de sistemas e a necessidade de comunicação entre eles tem crescido exponencialmente.

Uma sugestão é que sempre (quando possível) abra o arquivo no excel, estude a estrutura das colunas e seus valores, veja se não existem coisas que podem impactar na sua importação (como por exemplo aspas simples ‘ , arquivo delimitado por ponto e vírgula e o infeliz usar ponto-e-vírgula na observação do lançamento e o sistema não tratar isso) entre outras coisas… o excel ajuda muito, nomeie as colunas, insira observações, planeje, para depois partir para  parte prática.

*desculpem pela formatação do blog, estou revendo este layout para permitir inserir grandes imagens…

Vamos olhar o arquivo:

 

image

 

Então temos algum punhado de colunas, algumas DATETIME compatível com SQL Server, outros valores texto e money, e assim por diante. Para que possamos executar esta tarefa diretamente via T-SQL, é necessário criar a tabela com precisão, para que não ocorram erros de importação, que podem comprometer a qualidade da ferramenta desenvolvida. Para o exemplo acima, eu criei a seguinte tabela:

 

  1. CREATE TABLE [dbo].[ctb_movimento_202011](
  2.     [Coluna 0]  bigint NULL,
  3.     [Coluna 1]  bigint NULL,
  4.     [Coluna 2]  bigint NULL,
  5.     [Coluna 3]  datetime NULL,
  6.     [Coluna 4]  varchar(10) NULL,
  7.     [Coluna 5]  varchar(500) NULL,
  8.     [Coluna 6]  varchar(1) NULL,
  9.     [Coluna 7]  money NULL,
  10.     [Coluna 8]  bigint NULL,
  11.     [Coluna 9]  varchar(50) NULL,
  12.     [Coluna 10] bigint NULL,
  13.     [Coluna 11] varchar(1) NULL,
  14.     [Coluna 12] bigint NULL,
  15.     [Coluna 13] bigint NULL,
  16.     [Coluna 14] bigint NULL,
  17.     [Coluna 15] bigint NULL,
  18.     [Coluna 16] bigint NULL,
  19.     [Coluna 17] bigint NULL,
  20.     [Coluna 18] bigint NULL,
  21.     [Coluna 19] bigint NULL,
  22.     [Coluna 20] bigint NULL
  23. )

 

Coisas de programador… coloquei estes nomes de coluna por que previamente havia feito o mapeamento em outra ferramenta (excel). Enfim, a parte prática. Para importar o arquivo, basta utilizar uma ferramenta muito útil, chamada BULK INSERT (inclusive, muito mais ampla do que este simples exemplo). Bulk significa “grande volume”, e Insert é inserir. Então vamos inserir um grande volume de dados em uma tabela.

Vamos fazer um único exemplo, porém acredito que seja o mais comum deste tipo de aplicação, o arquivo delimitado por um caracter de controle.

 

  1. BULK INSERT ctb_movimento_202011
  2.     FROM 'C:\Users\nms.cores\Desktop\districomp-20052011\Cordilheira\CTBMOV_D.txt'
  3.     WITH
  4.     (
  5.         FIELDTERMINATOR = ';',
  6.         ROWTERMINATOR = '\n'
  7.     )

 

Este comando informa o nome da tabela que deverá ser inserida a informação (no caso, ctb_movimento_202011), a cláusula from (origem dos dados), onde especificamos a localização do arquivo texto, e logo em seguida alguns parâmetros para esta importação (dentro do WITH), como o FIELDTERMINATOR, que especifica qual é o delimitador para cada coluna (no caso, ponto e vírgula), e também qual é o delimitador de cada linha (\n no caso separado por salto de linha, veja no Books Online as opções disponíveis).

 

Voilà

 

image

 

image

 

Espero que seja útil!

 

image

domingo, 26 de junho de 2011

Automação de impressoras térmicas diretamente pelo SQL Server

*Este artigo se aplica a versões 2005 ou superior do SQL Server. Na versão 2000, haverá restrições de 8000 caracteres na geração de arquivos, devido a limitação do varchar(max) do SQL 2000.

       O título pode soar um tanto estranho. Mas o fato é que esta se revelou uma grande solução “surpresa” durante o desenvolvimento de um projeto de automação através de coletores de dados.

       Para explicar melhor o contexto onde isto foi adotado, tudo começou durante a implantação de um projeto de automação na conferência de mercadorias, confecção de embalagens e expedição de volumes. Adotamos 3 coletores ARGOX PT-60 (fig.1) com Windows CE 5.0 e conexão wireless e 3 impressoras ZEBRA GK420t (fig.2) instaladas em um único computador dedicado, compartilhando as impressoras pela rede.                      

Fig.1 Coletor Argox PT-60 Fig.2 Impressora Térmica ZEBRA GK420t

       O sistema, nomeado ARKAB, foi desenvolvido em vb.net utilizando o MS Compact framework. Basicamente, o sistema atuava de forma on-line com o servidor SQL, buscando dados referente os pedidos de venda, e alimentando tabelas durante a conferência, possibilitando que dois operadores pudessem conferir o mesmo pedido.

        Este sistema permite conferir as mercadorias que estão no pedido de venda. O separador pega todos os produtos utilizando o romaneio de separação (fig.3), e entrega em um carrinho para o conferente. Este identifica o pedido no coletor através de código de barras (fig.3), identifica-se através do seu crachá pessoal e também identifica qual impressora irá utilizar (código de barras na impressora, conf. fig.4).

 

image image
Fig.3 Romaneio de Separação Fig.4 Identificação da impressora

     Mas aí surgiram problemas, que prejudicaram o funcionamento do projeto. Durante o final do mês, a empresa realiza muito processamento no servidor na geração de lotes contábeis, fechamentos fiscais, emissão de DREs pesadas e abarrotadas de dados. E os coletores começaram a ficar muito lentos, prejudicando significativamente a performance da equipe de separação.

         Outro problema que ocorria sempre era ao enviar uma impressão pela rede. De alguma forma, o Windows CE exibe uma messagebox com o resultado do comando de impressão ao usuário, o que atrapalha (e muito!) durante a conferência. A mensagem aparece na tela, o usuário não percebe, e ele bipa um produto. Como no final do comando sempre tem um ENTER, a tela de mensagem é fechada, e o usuário nem teria percebido que aquele produto bipado somente fechou a mensagem, e não foi processado pelo software. Isto acontecia por que eu gerava um arquivo texto temporário no coletor, e o copiava pela rede diretamente para a impressora, e o próprio Windows CE gerencia esta mensagem, o que parece não ser possível desativar.

image

          Para resolver isto, modifiquei o sistema do coletor, fazendo com que carregasse em DATASET todos os dados necessários para realizar a conferência, de forma que acessasse o banco de dados duas vezes (na abertura e no fechamento da conferência), e não mais em cada operação. O aumento de performance foi absoluto, o coletor não dependia mais da rede para ter um bom desempenho ou não. Mas ainda restava a etiqueta. Como fazer a impressão de forma que não aparecesse esta mensagem de status de impressão ao usuário?

Resolvi então fazer a impressão da etiqueta térmica utliizando o próprio SQL Server, de forma que o servidor enviasse o comando para a impressora, e não mais o coletor. A solução aparentemente muito simples e funcional me fez perceber que podemos fazer qualquer sistema que utilize base SQL Server imprimir diretamente em impressoras térmicas de linguagem específica (PPLA, PPLB, ZPL, etc).

Para isso, vamos dividir esta informação em três partes fundamentais: geração dos dados da etiqueta, salvar estes dados em um arquivo texto e finalmente copiar este arquivo diretamente à impressora desejada pela rede.

1. Geração dos dados da etiqueta

O primeiro procedimento que montei é o que realiza a busca de todas as informações no banco de dados para a impressão da etiqueta, e monta dinamicamente todos os comandos ZPL necessários para a impressão da etiqueta:

Code Snippet
  1. -- set nocount on
  2. -- exec spArkabEtiqueta 9
  3. create proc  [dbo].[spArkabEtiqueta]
  4.   @id_volume int
  5. , @cd_impressora int
  6. as
  7.  
  8.   /*
  9.   Autor: Fabiano Cores
  10.   Data : 06/2011
  11.   Objetivo: Gera a etiqueta através de comandos ZPL, salva em arquivo
  12.   através de automação OLE e lança o arquivo diretamente para a impressora
  13.   térmica na rede através de simples compartilhamento, somente copiando
  14.   o arquivo para o dispositivo (como \\pc-na-rede\nome_imp_zebra)
  15.   */
  16.  
  17.   declare @t table (texto varchar(8000) null, ordem int not null identity)
  18.   
  19.   declare @cd_empresa     int
  20.   declare @nr_pedido      int
  21.   declare @nr_volume      int
  22.   declare @qt_volume      int
  23.   declare @cd_usuario     int
  24.   declare @qt_pecas       int
  25.   declare @cliente        varchar(60)
  26.   declare @transportadora varchar(60)
  27.   declare @endereco       varchar(200)
  28.   declare @complemento    varchar(200)
  29.   declare @pedido         varchar(20)
  30.   declare @volume         varchar(20)
  31.   declare @responsavel    varchar(50)
  32.   declare @data           varchar(20)
  33.   declare @quantidade     varchar(20)
  34.   declare @cdb            varchar(50)
  35.   declare @vtexto         varchar(8000)
  36.   declare @texto          varchar(max)
  37.   declare @diretorio      varchar(255)
  38.   declare @arquivo        varchar(100)
  39.   declare @origem         varchar(500)
  40.   declare @destino        varchar(500)
  41.   
  42.   select @cd_empresa = cd_empresa
  43.        , @nr_pedido  = nr_pedido
  44.        , @nr_volume  = nr_volume
  45.        , @cd_usuario = cd_usuario
  46.        , @cdb        = cdb
  47.     from ArkabVolume
  48.    where id_volume   = @id_volume
  49.    
  50.   select @qt_pecas = sum(qt_item)
  51.     from arkabvolumeitem
  52.    where id_Volume = @id_volume
  53.   
  54.   select @qt_volume = count(*)
  55.     from ArkabVolume
  56.    where cd_empresa = @cd_empresa
  57.      and nr_pedido  = @nr_pedido
  58.    
  59.   select @cliente = rtrim(cadastro.razao)
  60.        , @transportadora = isnull(rtrim(transp.razao),'')
  61.        , @endereco = rtrim(cadastro.endereco)
  62.        , @complemento = rtrim(ltrim( rtrim(isnull(cadastro.complemento, ''))
  63.            + ' ' + rtrim(cidade.nome) + '/' + rtrim(cidade.cd_estado)
  64.            + ' - ' + left(rtrim(cadastro.cep),5) + '-' + right(rtrim(cadastro.cep),3)))
  65.        , @pedido = right('000000' + rtrim(@nr_pedido), 6)
  66.        , @volume = right('000' + rtrim(@nr_volume),3)--  + '/' + right('00' + rtrim(@qt_volume),2)
  67.        , @responsavel = rtrim(usuario.login)
  68.        , @data = convert(char(10), getdate(), 103) + ' '
  69.          + right('00' + rtrim(datepart(hour, getdate())),2) + 'h'
  70.        , @quantidade = rtrim(@qt_pecas) + ' PC'
  71.        
  72.     from cadastro, cidade, usuario, pedidovenda, cadastro transp
  73.    where pedidovenda.cd_cadastro = cadastro.cd_cadastro
  74.      and cidade.cd_Cidade = cadastro.cd_cidade
  75.      and pedidovenda.cd_empresa = @cd_empresa
  76.      and pedidovenda.nr_pedido  = @nr_pedido
  77.      and usuario.cd_usuario = @cd_usuario
  78.      and pedidovenda.cd_transportador *= transp.cd_cadastro
  79.      
  80.   select @texto = '
  81. ^XA
  82. ^MD10
  83. ^LT0
  84. ^JMA^FS
  85. ^MUd,200,200
  86. ^LH6,0
  87. ^FO20,7
  88. ^GB780,625,5,,0
  89. ^FS
  90. ^FO20,70
  91. ^GB645,120,5,,0
  92. ^FS
  93. ^FO20,7
  94. ^GB780,70,70,,0
  95. ^FS
  96. ^FO660,70
  97. ^GB140,562,5,,0
  98. ^FS
  99. ^FO20,275
  100. ^GB235,160,5,,0
  101. ^FS
  102. ^FO30,305
  103. ^GB215,120,120,,0
  104. ^FS
  105. ^FO250,275
  106. ^GB180,160,5,,0
  107. ^FS
  108. ^FO260,305
  109. ^GB160,120,120,,0
  110. ^FS
  111. ^FO425,275
  112. ^GB240,85,5,,0
  113. ^FS
  114. ^FO425,355
  115. ^GB240,80,5,,0
  116. ^FS
  117. ^FO425,430
  118. ^GB240,75,5,,0
  119. ^FS
  120. ^FO425,500
  121. ^GB240,132,5,,0
  122. ^FS
  123. ^FO90,20,0
  124. ^ATN,30,20
  125. ^FR
  126. ^FD
  127. DECORLUX MATERIAL ELETRICO LTDA
  128. ^FS
  129. ^FO30,80,0
  130. ^AQN,20,10
  131. ^FD
  132. CLIENTE
  133. ^FS
  134. ^FO30,103,0
  135. ^ATN,30,20
  136. ^FD
  137. ' + left(rtrim(@cliente), 29) + '
  138. ^FS
  139. ^FO30,138,0
  140. ^ATN,30,20
  141. ^FD
  142. ' + substring(@cliente, 30, 29) + '
  143. ^FS
  144. ^FO30,138,0
  145. ^ASN,30,20
  146. ^FD
  147. ^FS
  148. ^FO30,200,0
  149. ^AQN,20,10
  150. ^FD
  151. TRANSPORTADORA
  152. ^FS
  153. ^FO30,223,0
  154. ^ASN,30,20
  155. ^FD
  156. ' + rtrim(@transportadora) + '
  157. ^FS
  158. ^FO30,280,0
  159. ^AQN,20,10
  160. ^FD
  161. PEDIDO DE VENDA
  162. ^FS
  163. ^FO40,290,0
  164. ^AVN,120,100
  165. ^FR
  166. ^FD
  167. ' + rtrim(@pedido) + '
  168. ^FS
  169. ^FO265,280,0
  170. ^AQN,20,10
  171. ^FD
  172. VOLUME
  173. ^FS
  174. ^FO290,290,0
  175. ^AVN,120,100
  176. ^FR
  177. ^FD
  178. ' + rtrim(@volume) + '
  179. ^FS
  180. ^FO435,280,0
  181. ^AQN,20,10
  182. ^FD
  183. RESPONSAVEL
  184. ^FS
  185. ^FO435,310,0
  186. ^ASN,20,10
  187. ^FD
  188. ' + rtrim(@responsavel) + '
  189. ^FS
  190.  
  191. ^FO435,360,0
  192. ^AQN,20,10
  193. ^FD
  194. DATA
  195. ^FS
  196.  
  197. ^FO435,385,0
  198. ^ASN,30,20
  199. ^FD
  200. ' + rtrim(@data) + '
  201. ^FS
  202.  
  203. ^FO435,435,0
  204. ^AQN,20,10
  205. ^FD
  206. QUANTIDADE
  207. ^FS
  208.  
  209. ^FO435,460,0
  210. ^ASN,30,20
  211. ^FD
  212. ' + rtrim(@quantidade) + '
  213. ^FS
  214.  
  215. ^FO30,435,0
  216. ^AQN,20,10
  217. ^FD
  218. NOTA FISCAL E VISTO
  219. ^FS
  220.  
  221. ^FO690,97
  222. ^BY2
  223. ^BCR,100,Y,N,N
  224. ^FD
  225. ' + rtrim(@cdb) + '
  226. ^FS
  227. ^XZ'
  228.  
  229.   -- Diretorio para gravar o arquivo temporário
  230.   set @diretorio = 'D:\Arkab\impressoes\'
  231.   
  232.   -- Definição do nome do arquivo, coloca dia, mes, ano,
  233.   -- hora, minuto, segundo e ID do volume.
  234.   select @arquivo = 'tmp'
  235.                   + right('00' + rtrim(datepart(day, getdate())),2)
  236.                   + right('00' + rtrim(datepart(month, getdate())),2)
  237.                   + rtrim(datepart(year, getdate()))
  238.                   + right('00' + rtrim(datepart(hour, getdate())),2)
  239.                   + right('00' + rtrim(datepart(minute, getdate())),2)
  240.                   + right('00' + rtrim(datepart(second, getdate())),2)
  241.                   + '_vol_' + rtrim(@id_volume) + '.txt'
  242.                   
  243.   -- Gravo o arquivo
  244.   exec spArkabGravaArquivo
  245.     @texto, @diretorio, @arquivo
  246.     
  247.   -- Definição da origem e destino do arquivo salvo.
  248.   select @origem = rtrim(@diretorio) + rtrim(@arquivo)
  249.  
  250.   -- Algo como '\\PC-CONF01\ZEBRA-SEP1'
  251.   select @destino = caminho_de_rede
  252.     from ArkabImpressora
  253.    where cd_impressora = @cd_impressora
  254.   
  255.   -- Envio o arquivo texto pela rede diretamente na impressora térmica.
  256.   exec spArkabCopiaArquivo
  257.     @origem, @destino

Este procedimento coleta todos os dados, concatena em um varchar(max), e passa esta string para a segunda etapa, que é gravar este texto em um arquivo em uma pasta específica no servidor.

Exemplo do texto gerado pelo procedimento:

^XA
^MD10
^LT0
^JMA^FS
^MUd,200,200
^LH6,0
^FO20,7
^GB780,625,5,,0
^FS
^FO20,70
^GB645,120,5,,0
^FS
^FO20,7
^GB780,70,70,,0
^FS
^FO660,70
^GB140,562,5,,0
^FS
^FO20,275
^GB235,160,5,,0
^FS
^FO30,305
^GB215,120,120,,0
^FS
^FO250,275
^GB180,160,5,,0
^FS
^FO260,305
^GB160,120,120,,0
^FS
^FO425,275
^GB240,85,5,,0
^FS
^FO425,355
^GB240,80,5,,0
^FS
^FO425,430
^GB240,75,5,,0
^FS
^FO425,500
^GB240,132,5,,0
^FS
^FO90,20,0
^ATN,30,20
^FR
^FD
DECORLUX MATERIAL ELETRICO LTDA
^FS
^FO30,80,0
^AQN,20,10
^FD
CLIENTE
^FS
^FO30,103,0
^ATN,30,20
^FD
TORNADO INDUSTRIA E PECAS LTD
^FS
^FO30,138,0
^ATN,30,20
^FD
A
^FS
^FO30,138,0
^ASN,30,20
^FD
^FS
^FO30,200,0
^AQN,20,10
^FD
TRANSPORTADORA
^FS
^FO30,223,0
^ASN,30,20
^FD
NATALIN DA SILVA RAMOS
^FS
^FO30,280,0
^AQN,20,10
^FD
PEDIDO DE VENDA
^FS
^FO40,290,0
^AVN,120,100
^FR
^FD
056783
^FS
^FO265,280,0
^AQN,20,10
^FD
VOLUME
^FS
^FO290,290,0
^AVN,120,100
^FR
^FD
009
^FS
^FO435,280,0
^AQN,20,10
^FD
RESPONSAVEL
^FS
^FO435,310,0
^ASN,20,10
^FD
AUTOMATICO
^FS

^FO435,360,0
^AQN,20,10
^FD
DATA
^FS

^FO435,385,0
^ASN,30,20
^FD
26/06/2011 21h
^FS

^FO435,435,0
^AQN,20,10
^FD
QUANTIDADE
^FS

^FO435,460,0
^ASN,30,20
^FD
1 PC
^FS

^FO30,435,0
^AQN,20,10
^FD
NOTA FISCAL E VISTO
^FS

^FO690,97
^BY2
^BCR,100,Y,N,N
^FD
00020567830099699335
^FS
^XZ

Veja que depois defino qual o destino final do arquivo (no caso, uma impressora zebra na rede). No caso do sistema Arkab, a definição da impressora é dinâmica, no momento da abertura da conferência o usuário seleciona qual irá utilizar. Ao imprimir a etiqueta, o sistema passa para o procedimento como parâmetro (@cd_impressora) qual será a impressora que deverá enviar o comando. Uma simples tabela permite referenciar o código desta impressora ao seu caminho de rede.

E logo no final, temos o procedimento spArkabCopiaArquivo, que faz a cópia do arquivo do servidor para a impressora utilizando a rede. Vamos então falar de cada um deles agora.

 

2. spArkabGravaArquivo - Este procedimento grava o texto desejado em um arquivo em disco:

Code Snippet
  1. create proc spArkabGravaArquivo
  2.   @texto varchar(max), --8000 in SQL Server 2000
  3.   @diretorio varchar(255),
  4.   @arquivo varchar(100)
  5.  
  6. as
  7.  
  8.   /*
  9.   Autor: Fabiano Cores
  10.   Data : 06/2011
  11.   Objetivo: Permite gravar arquivos de texto em disco diretamente
  12.   através do T-SQL.
  13.  
  14.   Observação importante: pode haver a necessidade de ativar a
  15.   opção 'Ole Automation Procedures' no SQL Server através do comando
  16.   sp_reconfigure. Para isto, utilize o script abaixo:
  17.   
  18.   -- Mostrar opções avançadas.
  19.   exec sp_configure 'show advanced options', 1
  20.   GO
  21.   -- Ativar a opção anterior
  22.   reconfigure
  23.   GO
  24.   -- Ativa a automação OLE
  25.   exec sp_configure 'Ole Automation Procedures', 1
  26.   GO
  27.   -- Reconfigura para aceitar a modificação
  28.   reconfigure
  29.   GO
  30.   */
  31.  
  32.   declare @objFileSystem   int
  33.   declare @objTextStream   int
  34.   declare @hr              int
  35.   declare @fileAndPath     varchar(80)
  36.  
  37.   exec @hr = sp_OACreate 'Scripting.FileSystemObject'
  38.                        , @objFileSystem output
  39.  
  40.   select @FileAndPath = @diretorio + '\' + @arquivo
  41.   
  42.   if @hr = 0
  43.     exec @hr = sp_OAMethod @objFileSystem
  44.                          , 'CreateTextFile'
  45.                          , @objTextStream output
  46.                          , @FileAndPath
  47.                          , 2
  48.                          , True
  49.  
  50.   if @hr=0
  51.     exec @hr = sp_OAMethod @objTextStream
  52.                          , 'Write'
  53.                          , Null
  54.                          , @texto
  55.  
  56.   if @hr = 0
  57.     exec @hr = sp_OAMethod @objTextStream, 'Close'
  58.  
  59.   exec sp_OADestroy @objTextStream
  60.   exec sp_OADestroy @objTextStream

 

3. spArkabCopiaArquivo – Realiza a cópia do arquivo da etiqueta diretamente para a impressora:

  1. /*
  2. -- Para testar, copia o arquivo direto para impressora SEP1.
  3. exec spArkabCopiaArquivo
  4.     'D:\Arkab\Impressoes\tmp26062011030828_vol_4.txt'
  5.   , '\\PC-CONF01\ZEBRA-SEP1'
  6. */
  7. create proc spArkabCopiaArquivo
  8.   @origem varchar(200)
  9. , @destino varchar(200)
  10.  
  11. as
  12.  
  13.   /*
  14.   Autor: Fabiano Cores
  15.   Data : 06/2011
  16.   Objetivo: Permite copiar arquivos, informando origem e destino.
  17.  
  18.   Observação importante: pode haver a necessidade de ativar a
  19.   opção 'xp_cmdshell' no SQL Server através do comando
  20.   sp_reconfigure. Para isto, utilize o script abaixo:
  21.   
  22.   -- Mostrar opções avançadas.
  23.   exec sp_configure 'show advanced options', 1
  24.   GO
  25.   -- Ativar a opção anterior
  26.   reconfigure
  27.   GO
  28.   -- Ativa o comando
  29.   exec sp_configure 'xp_cmdshell', 1
  30.   GO
  31.   -- Reconfigura para aceitar a modificação
  32.   reconfigure
  33.   GO
  34.   */
  35.  
  36.   declare @string varchar(400)
  37.   
  38.   select @string = 'copy ' + rtrim(@origem) + ' ' + rtrim(@destino)
  39.  
  40.   exec master.dbo.xp_cmdshell @string, no_output

 

O resultado, é uma etiqueta saindo pela impressora, como esta daqui de exemplo (exatamente a mesma do procedimento acima):

2406201168624062011683 

24062011685 24062011684

 

Uma solução interessante…

sexta-feira, 17 de junho de 2011

SPLIT STRING – Função para dividir uma string por um caracter de controle

Imagine a seguinte situação: você possui uma string, separada por ponto e vírgula, e você deseja separar estas strings para realizar um tratamento. Vamos ver um exemplo abaixo:

 

string1; string2; "string 3 com ; no meio"; string4

 

Então nós temos quatro valores concatenados em uma única string. Observe que o terceiro valor utiliza o ponto e vírgula como texto da própria string, porém a sentença toda está entre aspas (“), o que determina que o ponto e virgula desta frase faz parte da string, e não é um caracter separador.

Para resolver esta questão, eu desenvolvi o seguinte procedimento:

 

  1. -- exec spSplit 'string1; string2; "string3 com ; no meio"; string4', ';'
  2. alter proc spSplit
  3.   @grupo varchar(8000),
  4.   @caracter char(1) = NULL
  5. as
  6.  
  7. begin
  8.  
  9.   -- ***************
  10.   -- SQL Burger 2011
  11.   -- Função SPLIT, separa string dividida por
  12.   -- caracter de controle. Esta função só pode ser
  13.   -- utilizada e distribuída caso os créditos ao autor
  14.   -- sejam citados.
  15.   -- ***************
  16.   
  17.   if @caracter is null set @caracter = ','
  18.  
  19.   declare @pos int
  20.   declare @buffer varchar(8000)
  21.   declare @buffer2 varchar(8000)
  22.  
  23.   create table #ret (texto varchar(8000) null, ordem int not null identity(1,1))
  24.  
  25.   while charindex('"', @grupo) > 0
  26.   begin
  27.  
  28.     select @pos = charindex('"', @grupo)
  29.  
  30.     select @buffer = left(@grupo, @pos)
  31.  
  32.     select @grupo = substring(@grupo, @pos + 1, len(@grupo))
  33.  
  34.     select @pos = charindex('"', @grupo)
  35.  
  36.     select @buffer2 = left(@grupo, @pos)
  37.  
  38.     select @grupo = substring(@grupo, @pos + 1, len(@grupo))
  39.  
  40.     select @buffer = replace(@buffer, '"','')
  41.          , @buffer2 = replace(@buffer2, '"', '')
  42.  
  43.     select @buffer2 = replace(@buffer2, @caracter, char(254))
  44.     
  45.     select @buffer2 = replace(@buffer2, ';', char(253))
  46.  
  47.     select @grupo = rtrim(@buffer) + rtrim(@buffer2) + rtrim(@grupo)
  48.  
  49.   end
  50.   
  51.   while charindex(@caracter,@grupo) > 0
  52.   begin
  53.     select @pos = charindex(@caracter,@grupo)
  54.     select @buffer = left(@grupo,@pos - 1)
  55.     select @grupo = right(@grupo,len(rtrim(@grupo)) - @pos)
  56.     
  57.     select @buffer = replace(@buffer, char(254), @caracter)
  58.     select @buffer = replace(@buffer, char(253), ';')
  59.     
  60.     insert into #ret (texto)
  61.       select rtrim(ltrim(@buffer))
  62.   end
  63.  
  64.   if @grupo <> ''
  65.   begin
  66.     select @buffer =replace(@grupo,@caracter,'')
  67.  
  68.     insert into #ret (texto)
  69.       select rtrim(ltrim(@buffer))
  70.   end
  71.  
  72.   select texto from #ret order by ordem
  73.  
  74. end

 

O procedimento recebe dois parâmetros: @grupo (que é string a ser tratada) e @caracter (o caracter de controle a ser utilizado para separar a string). O resultado é o seguinte:

 

  1. exec spSplit 'string1; string2; "string3 com ; no meio"; string4', '!'

 

 image

 

A procedure irá retornar cada string em uma linha, permitindo tratá-las da maneira como quiser. Uma dica, utilize uma tabela temporária com uma coluna auto-numérica, e você saberá exatamente qual linha ler de acordo com a posição desejada:

 

  1. set nocount on
  2.  
  3. create table #split (
  4.   texto varchar(100) null
  5. , ordem int not null identity )
  6.  
  7. insert into #split (texto)
  8.   exec spSplit 'string1; string2; "string3 com ; no meio"; string4', ';'
  9.   
  10. select texto as 'Primeiro parametro' from #split where ordem = 1
  11. select texto as 'Segundo parametro' from #split where ordem = 2
  12. select texto as 'Terceiro parametro' from #split where ordem = 3
  13. select texto as 'Quarto parametro' from #split where ordem = 4
  14.  
  15.  
  16. drop table #split

 

Desta forma, o número da linha corresponderá ao parâmetro:

 

 image

 

Outros caracteres de controle poderão ser utilizados, trazendo o mesmo resultado:

 

  1. exec spSplit 'string1! string2! "string3 com ; no meio"! string4', '!'

 

image

 

  1. exec spSplit 'string1! string2! "string3 com ! no meio"! string4', '!'

 

 image

 

Espero que seja útil!

quarta-feira, 25 de maio de 2011

row_number : Use o número da linha em suas consultas

Durante o desenvolvimento de um layout de exportação de dados por arquivo texto, utilizei uma ferramenta muito prática do Transact-SQL, que gostaria de compartilhar aqui no meu blog. A função ROW_NUMBER é utilizada para retornar o número da linha dentro da consulta, de acordo com o “partition_by” e o “order_by” informados. Vamos ver a estrutura da função:

 

  1. ROW_NUMBER () OVER ( [ <partition_by_clause> ] <order_by_clause> )

 

[ <partition_by_clause> ]

Este parâmetro não é obrigatório. Permite especificar o particionamento da numeração da linha. É um agrupamento, que permitirá que a numeração seja sempre iniciada a cada mudança do agrupamento / particionamento.

<order_by_clause>

Determina qual será a ordenação das linhas do select na atribuição da numeração. A ordenação da função row_number não precisa necessariamente ser a mesma ordenação do select principal onde a função ROW_NUMBER foi aplicada.

A função irá retornar um BIGINT com o respectivo número da linha. Vamos então para a parte prática!

Crie a seguinte tabela e execute uma carga de exemplo:

  1. create table exemplo_rn (
  2.   funcao char(15) null
  3. , nome char(15) null
  4. )
  5.  
  6. insert into exemplo_rn ( funcao, nome ) values ( 'PROGRAMADOR' , 'JOAO' )
  7. insert into exemplo_rn ( funcao, nome ) values ( 'PROGRAMADOR' , 'MARIA' )
  8. insert into exemplo_rn ( funcao, nome ) values ( 'PROGRAMADOR' , 'MARCOS' )
  9. insert into exemplo_rn ( funcao, nome ) values ( 'PROGRAMADOR' , 'VERIDIANA' )
  10.  
  11. insert into exemplo_rn ( funcao, nome ) values ( 'ANALISTA' , 'IGOR' )
  12. insert into exemplo_rn ( funcao, nome ) values ( 'ANALISTA' , 'PEDRO' )
  13.  
  14. insert into exemplo_rn ( funcao, nome ) values ( 'COORDENADOR' , 'OSVALDO' )

 

Nós vamos utilizar como exemplo uma pequena tabela, que possui função e nome de colaboradores. Claro, reduzimos apenas às colunas que nos interessa para exemplificarmos.

Vamos então consultar nossa tabela de exemplo:

 

  1. select * from exemplo_rn

 

image

 

Então nós temos 7 linhas no total. Vamos agora retornar uma terceira coluna, que trará automaticamente o número da linha:

 

  1. select funcao, nome, row_number() over ( order by nome ) as 'ID'
  2.   from exemplo_rn
  3. order by nome

 

image

 

A coluna ID é resultado da função row_number(). Neste primeiro exemplo, utilizamos a ordenação alfabética da coluna “nome” como condição de numeração das linhas. Observe que a ordenação dentro da cláusula OVER () e a ordenação do select principal não estão diretamente interligadas, porém a ordenação dentro de OVER() é obrigatória:

 

  1. select funcao, nome, row_number() over ( order by funcao, nome ) as 'ID'
  2.   from exemplo_rn
  3. order by funcao, nome

 

image

 

Neste segundo exemplo, a numeração foi feita através da ordenação da função e nome, na respectiva ordem. O próximo exemplo mostra a utilização de PARTITION_BY, condicionando a numeração das linhas ao agrupamento de respectivos campos:

 

  1. select funcao
  2.      , nome
  3.      , row_number() over ( partition by funcao order by nome ) as 'ID'
  4.   from exemplo_rn
  5. order by funcao

 

image

 

Observe no resultado que a numeração da linha foi feita para cada agrupamento utilizado.

 

=)

quinta-feira, 17 de março de 2011

Crystal Reports – Como buscar uma imagem de arquivo para ser exibida no relatório

Olá caros sqlnautas, gostaria de retornar aqui com mais uma dica importante sobre imagens no crystal reports. Imagine o seguinte cenário: você está montando um relatório em crystal reports que deverá exibir imagens que, ao invés de estarem armazenadas no banco de dados, estão em arquivos no disco rígido do servidor, e no banco de dados você só tem a localização deste arquivo, o que é muito comum, pois armazenar arquivos em banco de dados é extremamente desaconselhável quando se pensa em performance.

O que precisamos fazer, é utilizar uma ferramenta muito simples, chamada BULK INSERT. Este comando é utilizado para importar dados em massa provenientes de arquivos para o banco de dados. Mas nós não vamos armazenar esta imagens, e sim converter os arquivos de imagem em formato BLOB, ou image. O conceito é simples: realizo o bulk insert do arquivo dentro de uma tabela temporária, que retorna um select ao crystal reports com a coluna image da tabela:

 

Code Snippet
  1. -- exec spcAmplaPedidoCompraImagem 3, 2685
  2. alter proc spcAmplaPedidoCompraImagem  
  3.   @cd_empresa int
  4. , @nr_pedido int
  5.  
  6. as
  7.  
  8.   declare @nr_documento int
  9.   declare @revisao int
  10.   declare @vcd_item char(13)
  11.   declare @vdocumento varchar(4000)
  12.   declare @sql varchar(8000)
  13.   
  14.   -- ***************
  15.   -- SQL Burger 2011
  16.   -- Procedure que retorna as imagens provenientes de arquivos
  17.   -- para exibição em relatório crystal reports.
  18.   -- ***************
  19.   
  20.   -- Inicio a montagem de uma query dinâmica, necessária para a execução
  21.   -- do comando bulkinsert com parâmetros dinâmicos, provenientes da tabela
  22.   -- que possui a localização dos arquivos.
  23.   select @sql = '
  24.  
  25.   create table #tmpImagem (imagem image)
  26.   
  27.   '
  28.   
  29.   -- Insiro em uma tabela auxiliar todos os itens do pedido de compra
  30.   -- para verificar quais possuem documentos que deverão ser impressos
  31.   -- junto com o pedido.
  32.   create table #tmpItem (cd_item char(13) collate database_Default null)
  33.   
  34.   insert into #tmpItem
  35.     select cd_item from pedidocitem
  36.      where cd_empresa = @cd_empresa
  37.        and nr_pedido = @nr_pedido
  38.        
  39.   insert into #tmpItem
  40.     select cd_item from AdhEPTRomaneioPedidoCompra
  41.      where cd_empresa = @cd_empresa
  42.        and nr_pedido = @nr_pedido
  43.        
  44.   -- Faço join da tabela temporária de itens com a de documentos
  45.   -- configurados a serem impressos nos pedidos de compra, e valido
  46.   -- se o arquivo está ativo e dentro da validade.
  47.   declare cp1 cursor local fast_forward read_only for
  48.     select distinct a.cd_item, b.documento
  49.       from #tmpItem a, itemdocumentorev b
  50.      where a.cd_item = b.cd_item
  51.        and b.imprime_pc = 'S'
  52.        and b.cd_situacao = 'A' -- ativos apenas
  53.        and b.dt_validade >= convert(char(10), getdate(), 120)
  54.      order by a.cd_Item
  55.        
  56.   open cp1
  57.   
  58.   while 1 = 1
  59.   begin
  60.     fetch next from cp1 into @vcd_item, @vdocumento
  61.     if @@fetch_status <> 0 break
  62.     
  63.     -- Para cada arquivo retornado, monto na query dinâmica o comando bulk insert
  64.     -- que irá inserir o stream da imagem na tabela #tmpImagem, criada no começo
  65.     -- do procedimento.
  66.     select @sql = @sql + '  insert into #tmpImagem (imagem)
  67.       select Bulkcolumn
  68.         from OPENROWSET(
  69.                BULK N''' + rtrim(@vdocumento) + ''', SINGLE_BLOB
  70.                        ) as picture      
  71.     '
  72.   
  73.   end
  74.   close cp1
  75.   deallocate cp1
  76.   
  77.   -- No final, retorno as imagens.
  78.   select @sql = @sql + '
  79.   
  80.     select imagem from #tmpImagem
  81.     
  82.   '
  83.   
  84.   -- Executo a query dinâmica
  85.   exec (@sql)

 

A string que contem a query dinâmica (@sql) foi executada da seguinte forma:

 

Code Snippet
  1. create table #tmpImagem (imagem image)
  2.  
  3.   insert into #tmpImagem (imagem)
  4.     select Bulkcolumn
  5.       from OPENROWSET(
  6.              BULK N'C:\MSSQL\Novo\4002-109 Base Fixação Encoder0001.jpg', SINGLE_BLOB
  7.                      ) as picture      
  8.     insert into #tmpImagem (imagem)
  9.     select Bulkcolumn
  10.       from OPENROWSET(
  11.              BULK N'C:\MSSQL\Novo\4004-057 Suporte Sensor Ótico0001.jpg', SINGLE_BLOB
  12.                      ) as picture      
  13.     insert into #tmpImagem (imagem)
  14.     select Bulkcolumn
  15.       from OPENROWSET(
  16.              BULK N'C:\MSSQL\Novo\4004-061 Suporte Direito Bandeja0001.jpg', SINGLE_BLOB
  17.                      ) as picture      
  18.     insert into #tmpImagem (imagem)
  19.     select Bulkcolumn
  20.       from OPENROWSET(
  21.              BULK N'C:\MSSQL\Novo\4004-064 Suporte Esquerdo Bandeja0001.jpg', SINGLE_BLOB
  22.                      ) as picture      
  23.     insert into #tmpImagem (imagem)
  24.     select Bulkcolumn
  25.       from OPENROWSET(
  26.              BULK N'C:\MSSQL\Novo\4004-119B Placa Fixação Servo0001.jpg', SINGLE_BLOB
  27.                      ) as picture      
  28.   
  29.  
  30.   select imagem from #tmpImagem

 

Para cada arquivo, ele montou a string executando o bulk insert de cada imagem. No final, o select retornando as imagens da temporária. O resultado do procedimento, via query no SQL Server:

 

image

 

Já no relatório crystal reports, ao importar esta coluna do tipo IMAGE, aparecerá no seu relatório:

 

image

 

Easy, don’t you think ? =)

 

Até a próxima!!