Posso criar uma função de uso único em um script ou procedimento armazenado?

109

No SQL Server 2005, há um conceito de função única ou local declarada dentro de um script SQL ou procedimento armazenado? Eu gostaria de abstrair alguma complexidade em um script que estou escrevendo, mas exigiria ser capaz de declarar uma função.

Apenas curioso.

Mark Carpenter
fonte
provavelmente existe uma maneira melhor de fazer o que você deseja sem uma função. talvez você deva postar um trecho do código que deseja transformar em uma função?
DForck42
você está gerando uma função dinamicamente para que seja diferente a cada vez? se sua função é sempre a mesma é só deixar no banco de dados
KM.
1
Estava tentando fazer isso como uma forma de deixar a consulta mais legível. A ideia de criar consultas enormes torna difícil a manutenção.
Jp_

Respostas:

65

Você pode ligar CREATE Functionperto do início de seu script e DROP Functionperto do final.

Joel Coehoorn
fonte
6
Eu ia sugerir isso. Apenas tome cuidado para que seu script termine; se abortar, você ainda terá a função no banco de dados.
chocojosh
6
Você pode fazer uma verificação IF EXISTS antes de cada execução e excluir se algo for encontrado.
Adrian Godong
7
@chocojosh, isso deve estar ok se você envolvê-lo em uma transação. A função não deve estar no banco de dados se a transação falhar.
Jeff LaFay
12
@JoelCoehoorn: isso ainda requer privilégios de gravação.
user2284570
2
Observe que isso não funcionará dentro de uma função - funções temporárias dentro de funções não são permitidas. Consulte: technet.microsoft.com/en-us/library/ms191320.aspx#Restrictions
Daniel Neel
95

Você pode criar procedimentos armazenados temporários como:

create procedure #mytemp as
begin
   select getdate() into #mytemptable;
end

em um script SQL, mas não funções. Você pode fazer com que o proc armazene seu resultado em uma tabela temporária e use essa informação posteriormente no script.

Ron Savage
fonte
7
Esta deve ser a resposta. Isso é verdadeiramente de uso único se apenas com escopo de conexão temporário (número único) e tem a vantagem de contornar as restrições do usuário sql.
Todd
Como é usado então? Não é um erro de digitação no nome do procedimento usado na expressão select into?
jgomo3
Consigo obter resultados do seu procedimento armazenado de exemplo quando removo a BEGINpalavra - chave e substituo a ENDpalavra-chave por GO.
Joseph Dykstra
O OP estava pedindo uma FUNCTION temporária e pelo menos o SQL server 2012 não permite a sintaxe # -sintaxe para funções. Apenas procedimentos.
Erk
Isso não funciona em um script e ainda pode exigir permissões. Para evitar segmentos repetitivos, a única opção que o SQL tem é a instrução WITH.
alex.peter
25

Expressões de tabela comuns permitem definir o que são essencialmente visualizações que duram apenas dentro do escopo de suas instruções de seleção, inserção, atualização e exclusão. Dependendo do que você precisa fazer, eles podem ser extremamente úteis.

Welbog
fonte
5
Isso deve ser aceito como resposta correta. A resposta aceita não é thread-safe.
Kalyan,
11
Depende do que você está tentando fazer. Achei essa pergunta porque estou escrevendo um semeador de dados e não quero repetir 10 linhas de MERGE INTO 30 vezes. Eu não me importo com threadsafe e CTEs não funcionarão para mim.
solipsicle
16
Acho que essa resposta, e as afirmações de que é a resposta correta, deixam de lado que a questão está procurando uma FUNÇÃO temporária, não uma TABELA temporária. A menos que eu esteja perdendo algo (não incomum), os CTEs são comparáveis ​​às tabelas temporárias.
JD Long
8
Uma função pode receber argumentos enquanto um CTE não pode.
Răzvan Flavius ​​Panda
4
Existem muitas diferenças entre um CTE e um procedimento armazenado temporário (que é a resposta correta aqui IMO). Para começar, os CTEs existem apenas para uma única instrução, enquanto as variáveis ​​temporárias podem ser usadas em todo o script. Outras diferenças incluem: (1) CTEs não podem abrigar a mesma lógica que um SP, (2) CTEs não podem aceitar variáveis. Uma CTE é apenas um açúcar sintático para permitir que você construa mais facilmente expressões de tabela aninhadas para uso em uma instrução. Mesmo assim, eles podem ser perigosos em termos de desempenho, se você não estiver ciente das advertências.
Desequilibrado em
12

Eu sei que posso ser criticado por sugerir SQL dinâmico, mas às vezes é uma boa solução. Apenas certifique-se de entender as implicações de segurança antes de considerar isso.

DECLARE @add_a_b_func nvarchar(4000) = N'SELECT @c = @a + @b;';
DECLARE @add_a_b_parm nvarchar(500) = N'@a int, @b int, @c int OUTPUT';

DECLARE @result int;
EXEC sp_executesql @add_a_b_func, @add_a_b_parm, 2, 3, @c = @result OUTPUT;
PRINT CONVERT(varchar, @result); -- prints '5'
Tmdean
fonte
4

Nos scripts, você tem mais opções e uma melhor chance de decomposição racional. Observe o modo SQLCMD (Menu Consulta -> modo SQLCMD), especificamente os comandos: setvar e: r.

Em um procedimento armazenado, suas opções são muito limitadas. Você não pode criar definir uma função diretamente com o corpo de um procedimento. O melhor que você pode fazer é algo assim, com SQL dinâmico:

create proc DoStuff
as begin

  declare @sql nvarchar(max)

  /*
  define function here, within a string
  note the underscore prefix, a good convention for user-defined temporary objects
  */
  set @sql = '
    create function dbo._object_name_twopart (@object_id int)
    returns nvarchar(517) as
    begin
      return 
        quotename(object_schema_name(@object_id))+N''.''+
        quotename(object_name(@object_id))
    end
  '

  /*
  create the function by executing the string, with a conditional object drop upfront
  */
  if object_id('dbo._object_name_twopart') is not null drop function _object_name_twopart
  exec (@sql)

  /*
  use the function in a query
  */
  select object_id, dbo._object_name_twopart(object_id) 
  from sys.objects
  where type = 'U'

  /*
  clean up
  */
  drop function _object_name_twopart

end
go

Isso se aproxima de uma função temporária global, se tal coisa existisse. Ainda está visível para outros usuários. Você poderia acrescentar o @@ SPID de sua conexão para unificar o nome, mas isso exigiria que o restante do procedimento também usasse SQL dinâmico.

Peter Radocchia
fonte
3

A seguir está o que eu usei no passado para atender à necessidade de um UDF escalar no MS SQL:

IF OBJECT_ID('tempdb..##fn_Divide') IS NOT NULL DROP PROCEDURE ##fn_Divide
GO
CREATE PROCEDURE ##fn_Divide (@Numerator Real, @Denominator Real) AS
BEGIN
    SELECT Division =
        CASE WHEN @Denominator != 0 AND @Denominator is NOT NULL AND  @Numerator != 0 AND @Numerator is NOT NULL THEN
        @Numerator / @Denominator
        ELSE
            0
        END
    RETURN
END
GO

Exec ##fn_Divide 6,4

Esta abordagem que usa uma variável global para PROCEDURE permite que você faça uso da função não apenas em seus scripts, mas também em suas necessidades de SQL Dinâmico.

Gregory Hart
fonte