¿Puedo crear una función de uso único en un script o en un procedimiento almacenado?

109

En SQL Server 2005, ¿existe un concepto de función local o de uso único declarado dentro de un script SQL o un procedimiento almacenado? Me gustaría abstraer algo de complejidad en un script que estoy escribiendo, pero requeriría poder declarar una función.

Sólo curioso.

Mark Carpenter
fuente
Probablemente haya una mejor manera de hacer lo que quiere sin una función. ¿Quizás debería publicar un fragmento del código que desea convertir en una función?
DForck42
¿Está generando una función dinámicamente para que sea diferente cada vez? si tu función es siempre la misma déjala en la base de datos
KM.
1
Estaba tratando de hacerlo como una forma de hacer que la consulta fuera más legible. La idea de crear consultas enormes hace que sea difícil de mantener.
Jp_

Respuestas:

65

Puede llamar CREATE Functioncerca del comienzo de su guión y DROP Functioncerca del final.

Joel Coehoorn
fuente
6
Iba a sugerir esto. Solo tenga cuidado de que termine su guión; si aborta, todavía tendrá la función en la base de datos.
Chocojosh
6
Puede hacer una verificación SI EXISTE antes de cada ejecución y eliminar si encuentra algo.
Adrian Godong
7
@chocojosh, eso debería estar bien si lo envuelve en una transacción. La función no debería estar en la base de datos si la transacción falla.
Jeff LaFay
12
@JoelCoehoorn: esto todavía requiere privilegios de escritura.
user2284570
2
Tenga en cuenta que esto no funcionará dentro de una función; no se permiten funciones temporales dentro de funciones. Ver: technet.microsoft.com/en-us/library/ms191320.aspx#Restrictions
Daniel Neel
95

Puede crear procedimientos almacenados temporales como:

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

en un script SQL, pero no funciones. Sin embargo, podría hacer que proc almacene su resultado en una tabla temporal y luego use esa información más adelante en el script.

Ron Savage
fuente
7
Esta debería ser la respuesta. Esto es realmente de un solo uso si solo la conexión tiene un alcance temporal (# único) y tiene la ventaja de eludir las restricciones de usuario de SQL.
Todd
¿Cómo se usa entonces? ¿No es un error tipográfico en el nombre del procedimiento utilizado en la expresión select into?
jgomo3
Puedo obtener resultados de su procedimiento almacenado de ejemplo cuando elimino la BEGINpalabra clave y la reemplazo ENDpor GO.
Joseph Dykstra
El OP estaba solicitando una FUNCIÓN temporal y al menos SQL Server 2012 no permitirá la # -sintaxis para las funciones. Solo trámites.
Erk
Eso no funciona dentro de un script y aún puede requerir permisos. Para evitar segmentos repetitivos, la única opción que tiene SQL es la declaración WITH.
alex.peter
25

Las expresiones de tabla comunes le permiten definir lo que son esencialmente vistas que duran solo dentro del alcance de sus declaraciones de selección, inserción, actualización y eliminación. Dependiendo de lo que necesite hacer, pueden ser muy útiles.

Welbog
fuente
5
Esto debe aceptarse como respuesta correcta. La respuesta aceptada no es segura para subprocesos.
Kalyan
11
Depende de lo que intentes hacer. Encontré esta pregunta porque estoy escribiendo una sembradora de datos y no quiero repetir 10 líneas de FUSIONAR EN 30 veces. No me preocupan los subprocesos seguros y los CTE no funcionan para mí.
solipsículo
16
Creo que esta respuesta, y las afirmaciones de que es la respuesta correcta, pasan por alto que la pregunta busca una FUNCIÓN temporal, no una TABLA temporal. A menos que me falte algo (no es raro), los CTE son comparables a las tablas temporales.
JD Long
8
Una función puede aceptar argumentos, mientras que un CTE no.
Răzvan Flavius ​​Panda
4
Hay muchas diferencias entre un CTE y un procedimiento almacenado temporal (que es la respuesta correcta aquí en mi opinión). Para empezar, los CTE solo existen para una única declaración, mientras que las variables temporales se pueden usar en todo un script. Otras diferencias incluyen: (1) los CTE no pueden albergar la misma lógica que un SP, (2) los CTE no pueden aceptar variables. Un CTE es simplemente azúcar sintáctico que le permite crear más fácilmente expresiones de tabla anidadas para usar en una declaración. Incluso entonces pueden ser peligrosos en cuanto al rendimiento si no está al tanto de las advertencias.
Torcido
12

Sé que pueden criticarme por sugerir SQL dinámico, pero a veces es una buena solución. Solo asegúrese de comprender las implicaciones de seguridad antes de considerar esto.

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
fuente
4

En los scripts, tiene más opciones y una mejor oportunidad de descomposición racional. Mire en el modo SQLCMD (menú de consulta -> modo SQLCMD), específicamente los comandos: setvar y: r.

Dentro de un procedimiento almacenado, sus opciones son muy limitadas. No puede crear una función definida directamente con el cuerpo de un procedimiento. Lo mejor que puede hacer es algo como esto, con 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

Esto se aproxima a una función temporal global, si tal cosa existiera. Todavía es visible para otros usuarios. Puede agregar el @@ SPID de su conexión para unificar el nombre, pero eso requeriría que el resto del procedimiento también use SQL dinámico.

Peter Radocchia
fuente
3

Lo siguiente es lo que he usado en el pasado para lograr la necesidad de una UDF escalar en 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

Este enfoque que utiliza una variable global para el PROCEDIMIENTO le permite hacer uso de la función no solo en sus scripts, sino también en sus necesidades de SQL dinámico.

Gregory Hart
fuente