none
Seleção dinâmica de TABELA destino, conforme a linha de arquivo TXT! RRS feed

  • Discussão Geral

  • Em primeiro lugar vou agradecer as diversas ajudas recebidas em alguns Tópicos que criei. Na verdade, todos estão relacionados à um código maior que estou elaborando graças a vocês, valeu!

    Relembrando, Eí-los:
    https://social.msdn.microsoft.com/Forums/office/pt-BR/5a90cf8c-db90-441d-aeab-3a130e4de494/converso-de-cdigo-vba-para-sql
    https://social.msdn.microsoft.com/Forums/office/pt-BR/e8d9caf8-93c6-4a23-b3d8-659eca595a6d/problemas-com-insert-into
    https://social.msdn.microsoft.com/Forums/office/pt-BR/c41fd1cd-635b-413e-af65-03f92908c104/bulk-insert-de-arquivo-txt-para-tabela-com-uso-de-identity

    Agora segue o código depois de esclarecer as dúvidas 'iniciais', pois chegou a grande dúvida.
    Tentei inserir diversos comentários para deixar o mais claro possível quanto a execução do código.
    O objetivo é: primeiro, ler um arquivo EFD contribuição para a tabela TEMPOR (o arquivo TXT em questão) e posteriormente ler linha-a-linha a tabela TEMPOR para inserí-lo na tabela correspondente ao TIPO de registro, ou seja 0150 para table de Participantes, 0200 para tabela de Produtos e assim por diante! 

    -- Seleção do BD
    USE TESTE
    -- se existe apaga/recria tabela TEMPOR e faz a leitura do arquivo
    IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'TEMPOR')
         DROP TABLE TEMPOR
    CREATE TABLE TEMPOR(
         ID INTEGER IDENTITY (1,1),
      LINHA VARCHAR(999))
    -- Leitura do arquivo para tabela TEMPOR
    INSERT INTO TEMPOR EXEC xp_cmdshell 'findstr /b "|" C:\@IMPORTA\TESTE\teste.txt'
    -- Declaração de variáveis
    DECLARE @MINID INTEGER
    DECLARE @MAXID INTEGER
    DECLARE @LINHA VARCHAR(999)
    -- Primeiro (@MINID) e último (@MAXID) registro de TEMPOR
    SELECT @MINID = MIN(ID), @MAXID = MAX(ID) from TEMPOR
    -- Leitura de TEMPOR linha-a-linha
    WHILE @MINID <= @MAXID
    BEGIN
         SELECT @LINHA = LINHA FROM TEMPOR WHERE ID = @MINID
         IF SUBSTRING(@LINHA, 2,4) = '0150' BEGIN
              -- comandos
              ????? SELECT INTO TAB_0150 FROM dbo.SplitString(@LINHA, '|')
         END
         ELSE IF SUBSTRING(@LINHA, 2,4) = '0200' BEGIN
              -- comandos
              ????? SELECT INTO TAB_0200 FROM dbo.SplitString(@LINHA, '|')
         END
         SET @MINID = @MINID + 1
    END

    Pois bem, a grande dúvida é a seguinte: cada registro lido (@LINHA) possui como delimitador o pipe (|) e supondo que já tenho cada uma das tabelas com TODOS os campos necessários (0150, 0200) já preparados quero carregá-lo na tabela correspondente!
    Como? Li sobre as funções SPLIT que "quebram a frase" conforme necessidade... mas não encontrei um exemplo SIMPLES! Estilo SELECT INTO TABLE FROM dbo.SplitString(@LINHA, '|')

    Desde já agradeço
    • Editado MLRamos sexta-feira, 22 de janeiro de 2016 14:40 faltou identação no codigo
    • Tipo Alterado Marcos SJ terça-feira, 23 de fevereiro de 2016 19:06
    sexta-feira, 22 de janeiro de 2016 14:27

Todas as Respostas

  • MLRamos,

    Sugiro avaliar a utilização do comando BULK INSERT que já possui parâmetros para delimitadores de campo e linha, inserir o conteúdo do arquivo em uma tabela temporária e depois realizar o tratamento necessário para inserir nas suas tabelas de destino.

    Segue abaixo link sobre o comando BULK INSERT para leitura.

    https://msdn.microsoft.com/pt-br/library/ms188365%28v=sql.120%29.aspx?f=255&MSPPError=-2147217396


    Felipe Lauffer MCSA: SQL Server | MCP


    • Editado FLauffer sexta-feira, 22 de janeiro de 2016 15:05
    sexta-feira, 22 de janeiro de 2016 14:57
  • A leitura do TXT já foi feita.

    A informação já está na coluna LINHA da tabela TEMPOR e os caracteres 2 até 5 da mesma definem a tabela destino correta.


    • Editado Marcos SJ sexta-feira, 22 de janeiro de 2016 15:19 Edição
    sexta-feira, 22 de janeiro de 2016 15:14
  • Estou quase... vejam esses dois exemplos de função SPLIT:

    -- http://zavaschi.com/index.php/2009/06/repostagem-funo-split-no-sql-server/

    USE TESTE
    GO

    drop function dbo.FnSplit
    GO

    CREATE FUNCTION  dbo.FnSplit(
       @frase VARCHAR(max),
       @delimitador VARCHAR(max) = '|')
    RETURNS @result TABLE (item VARCHAR(8000))

    BEGIN
       DECLARE @parte VARCHAR(8000)
       WHILE CHARINDEX(@delimitador,@frase,0) <> 0 BEGIN
          SELECT
             @parte=RTRIM(LTRIM(
                SUBSTRING(@frase,1,
                CHARINDEX(@delimitador,@frase,0)-1))),
             @frase=RTRIM(LTRIM(SUBSTRING(@frase,
                CHARINDEX(@delimitador,@frase,0)
                + LEN(@delimitador), LEN(@frase))))
          IF LEN(@parte) > 0
            INSERT INTO @result SELECT @parte
       END 
       IF LEN(@frase) > 0
          INSERT INTO @result SELECT @frase
       RETURN
    END
    GO

    =============================================
    -- https://ole.michelsen.dk/blog/split-string-to-table-using-transact-sql.html

    USE TESTE
    GO

    drop function dbo.FnSplit
    GO

    create FUNCTION  dbo.FnSplit(
        @String NVARCHAR(4000),
        @Delimiter NCHAR(1)
    )
    RETURNS TABLE
    AS
    RETURN
    (
        WITH Split(stpos,endpos)
        AS(
            SELECT 0 AS stpos, CHARINDEX(@Delimiter,@String) AS endpos
            UNION ALL
            SELECT endpos+1, CHARINDEX(@Delimiter,@String,endpos+1)
                FROM Split
                WHERE endpos > 0
        )
        SELECT -- 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
            'Data' = SUBSTRING(@String,stpos,COALESCE(NULLIF(endpos,0),LEN(@String)+1)-stpos)
        FROM Split
    )
    GO

    A grande diferença entre elas é que no primeiro exemplo quando houverem dois delimitadores consecutivos (coluna em branco) a função concatena tudo, eliminando aquela coluna e isso não interessa...

    No segundo caso fica OK, ou seja, ele traz a coluna EM BRANCO. Entretanto preciso levar em consideração que estou passando valor para uma tabela ... deveria estar NULL ? Não sei... 

    =(


    • Editado MLRamos sexta-feira, 22 de janeiro de 2016 18:41 identação do codigo
    sexta-feira, 22 de janeiro de 2016 18:37
  • Ramos,

    usa o MERGE, já que você já colocou o TXT em uma tabela, abaixo um exemplo básico e você pode enquadrar na sua realidade

    --USANDO O MERGE T-SQL
    
    -- Cara se voce quiser sincronizar uma tabela com outra com base nas diferenças em relação aos dados
    -- de uma tabela, voce deve usar o Merge.
    
    CREATE TABLE #TAB1 ( ID INT, NOME VARCHAR(10));
    CREATE TABLE #TAB2 ( ID INT, NOME VARCHAR(10));
    
    INSERT INTO #TAB1 VALUES(100, 'MONSTRAO');
    INSERT INTO #TAB1 VALUES(101, 'MLK DOIDO')
    INSERT INTO #TAB1 VALUES(102, 'SHOUGLAS')
    
    INSERT INTO #TAB2 VALUES(103, 'DOUGLAS')
    INSERT INTO #TAB2 VALUES(104, 'MOUGLAS')
    
    SELECT * FROM #TAB1
    SELECT * FROM #TAB2
    
    MERGE #TAB1 AS TAB1 -- DESTINO DAS OPERAÇÕES
    USING #TAB2 AS TAB2 -- FONTE DE DADOS DO DESTINO.
    ON(TAB1.ID = TAB2.ID) -- CLAUSULA PARA JUNTAR #TAB1 E #TAB2
    WHEN NOT MATCHED BY TARGET AND TAB2.NOME LIKE '%S'
    	THEN INSERT(ID,NOME) VALUES(TAB2.ID, TAB2.NOME)
    WHEN MATCHED
    	THEN UPDATE SET TAB1.NOME = TAB2.NOME
    WHEN NOT MATCHED BY SOURCE AND TAB1.NOME LIKE '%S'
    	THEN DELETE
    OUTPUT $ACTION, INSERTED.*, DELETED.*; -- RETORNA UMA LINHA PARA CADA LINHA DO DESTINO QUE É INSERIDA, ATUALIZADA OU EXCLUIDA.
    

    segunda-feira, 25 de janeiro de 2016 12:51
  • Ok, vamos a um exemplo pratico. Vejam as estrutura das duas tabelas citadas (0150-Participante e 0200-Produtos).

    Tabela 0150 - Produtos

    Tabela 0200 - Produtos

    Como podem observar as tabelas são muito diferentes entre si: quantidade de colunas (campos), etc, etc. Igualmente, há outras MUITAS tabelas e, conforme dito anteriormente, o TXT lido na primeira parte do código menciona tal código (DA TABELA DESTINO), exemplos:
    ...
    |0150|0000000001|NOME DA EMPRESA LTDA|1058|12345678000134|||0000001||RUA DA EMPRESA|99||BAIRRO|
    |0150|0000000002|FULANO DA COSTA|1058||12345678901||0000001||RUA DO FULANO DA COSTA|999||BAIRRO|
    ...
    |0200|00000001|NOME DO PRODUTO 1|||UN|00|12345678||||18|
    |0200|00000002|NOME DO PRODUTO 2|||UN|00|12345678||||18|
    ....

    Agora, preciso pegar a @LINHA (da tabela TEMPOR, já lida) e jogar para a tabela correspondente, ficou claro !?
    =)




    • Editado MLRamos terça-feira, 26 de janeiro de 2016 12:15 Correção de tabelas
    segunda-feira, 25 de janeiro de 2016 19:08
  • Tabelas corrigidas !

    =D

    • Editado MLRamos terça-feira, 26 de janeiro de 2016 12:15
    segunda-feira, 25 de janeiro de 2016 19:09
  • UP !

    Alguém ?

    =(

    segunda-feira, 1 de fevereiro de 2016 11:54
  • MLRamos,

    Posso te dar uma sugestão?

    Ao invés de tentar fazer o processo usando loops e "N" funções para quebrar a string, porque você não faz uma importação do arquivo usando o OPENROWSET? Assim, você consegue fazer a filtragem das linhas da forma como precisa.

    Outra opção:

    1. Importar o arquivo todo (Como já faz)
    2. Exportar os registros em arquivos separados (0150 em um arquivo, 0200 em outro)
    3. Importar diretamente na tabela de destino, usando o BULK INSERT.

    Apesar de obrigar a exportação em novo arquivos, todo o processo fica mais fácil e direto.

    []'s!


    /* Logan Destefani Merazzi - DBA | @LoganMerazzi | http://www.merazzi.eti.br
    Se a resposta for útil, vote nela. Se resolveu, marque-a como resposta. */

    segunda-feira, 1 de fevereiro de 2016 12:24
  • Logan, desconheço tal função. Vou estudá-la , mas se puderes exemplificar agradeço.

    Noutro Tópico que criei melhorei o código utilizando CURSOR - em desempenho pelo menos melhorou muito - e ficou conforme segue:

    USE TESTE
    SET NOCOUNT ON
    IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'TEMPOR')
         DROP TABLE TEMPOR
    CREATE TABLE TEMPOR(
         ID INTEGER IDENTITY (1,1) PRIMARY KEY,
         LINHA VARCHAR(999))
    INSERT INTO TEMPOR EXEC xp_cmdshell 'findstr /b "|" C:\@IMPORTA\TESTE\teste.txt'
    DECLARE @LINHA VARCHAR(999)
    DECLARE TAB_CURSOR CURSOR LOCAL FAST_FORWARD FOR SELECT LINHA FROM TEMPOR ORDER BY ID
    OPEN TAB_CURSOR
    FETCH NEXT FROM TAB_CURSOR INTO @LINHA -- primeiro registro da tabela
    WHILE(@@FETCH_STATUS = 0)
    BEGIN
         IF SUBSTRING(@LINHA, 2, 4) = '0150' BEGIN
              -- comandos
              ????? SELECT INTO TAB_0150 FROM dbo.SplitString(@LINHA, '|')
         END
         IF SUBSTRING(@LINHA, 2, 4) = '0200' BEGIN
              -- comandos
              ????? SELECT INTO TAB_0200 FROM dbo.SplitString(@LINHA, '|')
         END
         FETCH NEXT FROM TAB_CURSOR INTO @LINHA
    END
    CLOSE TAB_CURSOR
    DEALLOCATE TAB_CURSOR
    SET NOCOUNT OFF

    segunda-feira, 1 de fevereiro de 2016 12:45
  • MLRamos,

    Posso te dar uma sugestão?

    Ao invés de tentar fazer o processo usando loops e "N" funções para quebrar a string, porque você não faz uma importação do arquivo usando o OPENROWSET? Assim, você consegue fazer a filtragem das linhas da forma como precisa.

    Outra opção:

    1. Importar o arquivo todo (Como já faz)
    2. Exportar os registros em arquivos separados (0150 em um arquivo, 0200 em outro)
    3. Importar diretamente na tabela de destino, usando o BULK INSERT.

    Apesar de obrigar a exportação em novo arquivos, todo o processo fica mais fácil e direto.

    []'s!


    /* Logan Destefani Merazzi - DBA | @LoganMerazzi | http://www.merazzi.eti.br
    Se a resposta for útil, vote nela. Se resolveu, marque-a como resposta. */

    Logan,

    Pelo que entendi OPENROWSET também serve para carga em massa e filtro... 

    No entanto eu teria que executá-lo cerca de 100 vezes! Uma para cada filtro.

    Nesse formato eu fiz apenas 1 leitura de carga (para tabela TEMPOR) e trabalho com ela no loop para selecionar a tabela destino correta, entendeu ?

    Ou seja, meu problema está no segundo ponto descrito por você... 

    segunda-feira, 1 de fevereiro de 2016 13:35