TSQL - Como usar GO dentro de um bloco BEGIN .. END?


96

Estou gerando um script para migrar automaticamente as alterações de vários bancos de dados de desenvolvimento para teste / produção. Basicamente, ele pega um monte de scripts de mudança e os mescla em um único script, envolvendo cada script em uma IF whatever BEGIN ... ENDinstrução.

No entanto, alguns dos scripts requerem uma GOinstrução para que, por exemplo, o analisador SQL saiba sobre uma nova coluna após sua criação.

ALTER TABLE dbo.EMPLOYEE 
ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO -- Necessary, or next line will generate "Unknown column:  EMP_IS_ADMIN"
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

No entanto, depois de embrulhar isso em um IFbloco:

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
    GO
    UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever
END

Falha porque estou enviando um BEGINsem correspondência END. Porém, se eu remover o GOele reclamará novamente de uma coluna desconhecida.

Existe alguma maneira de criar e atualizar a mesma coluna em um único IFbloco?



2
@gbn: Sim, eu entendo por que isso acontece (veja o segundo parágrafo) ; mas não tenho ideia de como contornar isso - realmente preciso transformar cada consulta em um monte de strings !?
— BlueRaja - Danny Pflughoeft

@BlueRaja: Qual é a preocupação? Se funcionar, isso é tudo que importa no final do dia. Se houver um problema comercial legítimo com a solução fornecida, expresse isso. Existe algo especificamente desconcertante em converter cada consulta em um monte de strings?
— mellamokb

1
@mellamokb: Sim, há um problema; se a palavra GO for usada em qualquer outro contexto (como um comentário ou uma string), o script não funcionará. Além disso, perdemos os números de linha úteis em mensagens de erro no caso de algo dar errado. Não há como fazer isso com transações? Ou tente / pegar?
— BlueRaja - Danny Pflughoeft

@BlueRaja: 1) Acredito que deva GOestar em uma linha por si só, então você pode pesquisar apenas esse caso, e não todas as ocorrências da palavra GO. 2) Você sempre pode registrar quais declarações foram concluídas com êxito. Ou você poderia embrulhar tudo em um try / catch e usar seus próprios números de linha usando alguma variável, como @lineNo, que você acompanha e relata o erro. Como você os está gerando automaticamente, fazer alterações como essa deve ser muito fácil. Parece que você simplesmente não quer explorar essa rota, quando penso que há soluções a serem encontradas para todas as suas preocupações.
— mellamokb

Respostas:


44

Tive o mesmo problema e finalmente consegui resolvê-lo usando SET NOEXEC .

IF not whatever
BEGIN
    SET NOEXEC ON; 
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

SET NOEXEC OFF; 

2
Esta é uma otima soluçao!
— Bazinga de

+1! Esta é a ÚNICA resposta prática até agora para uso em um SQLCMDscript de modo SS (ou seja, um script de implantação mestre) que chama (via :rcomando) outros scripts SS (ou seja, scripts de sub-implantação) com algumas dessas chamadas dentro de ifinstruções. As respostas de Oded, mellamokb e Andy Joiner de incluir todas essas declarações em execChamadas / begin- endnão são iniciais. Além disso, o método begin- endnão funcionará se houver uma createdeclaração (por exemplo, requer um explícito goantes dela). Mas, cara, "Santo duplo negativo, Batman!" ;)
— Tom

Para facilitar a leitura (por exemplo, para ajudar a superar os duplos negativos e deixar mais claro que ele está simulando um bloco virtual if ), prefixaria o bloco com um -- If whatevercomentário, indentaria o bloco e pós-fixaria o bloco com um --end If whatevercomentário.
— Tom

Você salvou meu bacon! Eu estava executando declarações de mesclagem e aqueles GO idiotas não gostam de estar dentro de um IF BEGIN END ELSE
— Omzig

Hm, estou recebendo um erro na atualização de alguma forma depois que set noexec on foi executado? (erro de que o nome da coluna a ser atualizado é inválido) Executando em MSSQL 2014 no editor de consultas. Funciona bem se a condição se tornar falsa (portanto, noexec permanece desativado)
— Jerry

43

GO não é SQL - é simplesmente um separador de lote usado em algumas ferramentas MS SQL.

Se você não usar isso, precisará garantir que as instruções sejam executadas separadamente - em lotes diferentes ou usando SQL dinâmico para a população (obrigado @gbn):

IF whatever
BEGIN
    ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL;

    EXEC ('UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever')
END

8
Sim eu entendo isso. Isso não responde à pergunta - eu preciso criar e atualizar uma coluna no mesmo IFbloco.
— BlueRaja - Danny Pflughoeft

@Oded: Será que a ;ajuda aqui? - Você acabou de editar sua resposta: o)
— Neil Knight

@Neil - esse é o meu pensamento, sim.
— Oded de

;também não funciona - o analisador ainda está fornecendo "Nome de coluna inválido 'EMP_IS_ADMIN'."
— BlueRaja - Danny Pflughoeft

Quando o lote é compilado, EMP_IS_ADMIN não existe. stackoverflow.com/questions/4855537/…
— gbn

16

Você pode tentar sp_executesql, dividir o conteúdo entre cada GOinstrução em uma string separada para ser executada, conforme demonstrado no exemplo abaixo. Além disso, há uma variável @statementNo para rastrear qual instrução está sendo executada para facilitar a depuração onde ocorreu uma exceção. Os números das linhas serão relativos ao início do número da instrução relevante que causou o erro.

BEGIN TRAN

DECLARE @statementNo INT
BEGIN TRY
    IF 1=1
    BEGIN
        SET @statementNo = 1
        EXEC sp_executesql
            N'  ALTER TABLE dbo.EMPLOYEE
                    ADD COLUMN EMP_IS_ADMIN BIT NOT NULL'

        SET @statementNo = 2
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1'

        SET @statementNo = 3
        EXEC sp_executesql
            N'  UPDATE dbo.EMPLOYEE
                    SET EMP_IS_ADMIN = 1x'
    END
END TRY
BEGIN CATCH
    PRINT 'Error occurred on line ' + cast(ERROR_LINE() as varchar(10)) 
       + ' of ' + 'statement # ' + cast(@statementNo as varchar(10)) 
       + ': ' + ERROR_MESSAGE()
    -- error occurred, so rollback the transaction
    ROLLBACK
END CATCH
-- if we were successful, we should still have a transaction, so commit it
IF @@TRANCOUNT > 0
    COMMIT

Você também pode executar facilmente instruções de várias linhas, conforme demonstrado no exemplo acima, simplesmente colocando-as entre aspas simples ( '). Não se esqueça de deixar de lado as aspas simples contidas na string com aspas simples duplas ( '') ao gerar os scripts.


Não acho que isso funcionaria para comandos divididos em várias linhas, certo?
— BlueRaja - Danny Pflughoeft

@BlueRaja: Eu atualizei o exemplo para mostrar como funcionaria. Essas strings podem ser multilinhas, desde que as aspas simples (') contidas dentro dele sejam escapadas usando aspas simples duplas (' ')
— mellamokb

1
@mellamokb: estritamente falando, apenas o UPDATE precisa de sp_executesql ... stackoverflow.com/questions/4855537/…
— gbn

1
@gbn: Verdadeiro. Mas se você vai automatizar isso para centenas de declarações, será mais fácil apenas aplicá-lo cegamente em todas as declarações, em vez de decidir quando e onde você precisa.
— mellamokb de

@gbn @mellamokb: Eu quis dizer declarações como SELECT * <newline> FROM whatever. Se eu executar cada linha com sua própria instrução EXEC, haverá falha. Ou você está sugerindo que eu interrompa a cada GOafirmação?
— BlueRaja - Danny Pflughoeft

9

Por fim, consegui fazê-lo funcionar substituindo cada instância de GOem sua própria linha por

END
GO

---Automatic replacement of GO keyword, need to recheck IF conditional:
IF whatever
BEGIN

Isso é muito preferível a envolver cada grupo de instruções em uma string, mas ainda está longe do ideal. Se alguém encontrar uma solução melhor, poste e eu aceitarei.


6
Se a primeira condicional for "se esta coluna não existe", a primeira declaração no bloco é "adicionar esta coluna", então a segunda verificação da condicional encontrará a coluna e não executará a segunda declaração,
— Damien_The_Unbeliever

@Damien: True; felizmente, isso nunca acontecerá no meu caso (a condicional é sempre uma verificação de um valor específico em uma tabela específica, que sempre é adicionada como a última instrução do IFbloco). Parece que não há uma boa maneira de fazer isso no SQL.
— BlueRaja - Danny Pflughoeft

A set noexecresposta de Mina Jacob é a ÚNICA resposta prática até agora para uso em um SQLCMDscript de modo SS (ou seja, um script de implantação mestre) que chama (via :rcomando) outros scripts SS (ou seja, scripts de sub-implantação) com algumas dessas chamadas dentro de ifinstruções. As respostas de Oded, mellamokb e Andy Joiner de incluir todas essas declarações em execChamadas / begin- endnão são iniciais. Além disso, o método begin- endnão funcionará se houver uma createDeclaração (por exemplo, requer um explícito goantes dela).
— Tom

8

Você pode incluir as instruções em BEGIN e END em vez de GO no meio

IF COL_LENGTH('Employees','EMP_IS_ADMIN') IS NULL --Column does not exist
BEGIN
    BEGIN
        ALTER TABLE dbo.Employees ADD EMP_IS_ADMIN BIT
    END

    BEGIN
        UPDATE EMPLOYEES SET EMP_IS_ADMIN = 0
    END
END

(Testado no banco de dados Northwind)

Editar: (Provavelmente testado em SQL2012)


1
Por favor, dê a razão para -1
— Andy Joiner

1
Não sei por que foi rejeitado ... funciona como um encanto para mim.
— Thorarin

10
Usando o SQL Server 2008 R2, isso não parece funcionar para mim, ainda recebo um erro 'Nome de coluna inválido' EMP_IS_ADMIN '.' na linha UPDATE.
— MerickOWA

O batch BEGIN-END funcionou para mim usando o SQL Server 2016. IMO, esta é a sintaxe mais limpa.
— Uber Schnoz

A set noexecresposta de Mina Jacob é a ÚNICA resposta prática até agora para uso em um SQLCMDscript de modo SS (ou seja, um script de implantação mestre) que chama (via :rcomando) outros scripts SS (ou seja, scripts de subdesenvolvimento) com algumas dessas chamadas dentro de ifinstruções. As respostas de Oded, mellamokb e Andy Joiner de incluir todas essas declarações em execChamadas / begin- endnão são iniciais. Além disso, o método begin- endnão funcionará se houver uma createDeclaração (por exemplo, requer um explícito goantes dela).
— Tom

1

Você pode tentar esta solução:

if exists(
SELECT...
)
BEGIN
PRINT 'NOT RUN'
RETURN
END

--if upper code not true

ALTER...
GO
UPDATE...
GO

1
Não é muito útil se você tiver vários blocos if-else, um após o outro, certo?
— Jerry

0

Eu tenho usado RAISERRORno passado para este

IF NOT whatever BEGIN
    RAISERROR('YOU''RE ALL SET, and sorry for the error!', 20, -1) WITH LOG
END

ALTER TABLE dbo.EMPLOYEE ADD COLUMN EMP_IS_ADMIN BIT NOT NULL
GO
UPDATE dbo.EMPLOYEE SET EMP_IS_ADMIN = whatever

-1

Você pode incorporar as instruções GOTOe LABELpara ignorar o código, deixando assim as GOpalavras - chave intactas.


5
Parece que os LABELs não podem ser referenciados nas instruções GO, pois eles não estão no lote enviado para SQL
— berkeleybross
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.