Czy mogę utworzyć funkcję jednorazowego użytku w skrypcie lub procedurze składowanej?

109

Czy w SQL Server 2005 istnieje koncepcja jednorazowej lub lokalnej funkcji zadeklarowanej w skrypcie SQL lub procedurze składowanej? Chciałbym odrzucić pewną złożoność skryptu, który piszę, ale wymagałoby to możliwości zadeklarowania funkcji.

Po prostu ciekawy.

Mark Carpenter
źródło
prawdopodobnie istnieje lepszy sposób robienia tego, co chcesz, bez funkcji. może powinieneś opublikować fragment kodu, który chcesz przekształcić w funkcję?
DForck42
czy generujesz funkcję dynamicznie, więc za każdym razem jest inna? jeśli funkcja jest zawsze taka sama po prostu zostaw ją w bazie danych
KM.
1
Próbowałem to zrobić, aby zapytanie było bardziej czytelne. Pomysł tworzenia ogromnych zapytań jest trudny do utrzymania.
Jp_

Odpowiedzi:

66

Możesz zadzwonić CREATE Functionpod koniec skryptu i DROP Functionpod koniec.

Joel Coehoorn
źródło
6
Miałem zamiar to zasugerować. Uważaj tylko, żeby twój skrypt się skończył; jeśli się przerwie, nadal będziesz mieć tę funkcję w bazie danych.
chocojosh
6
Możesz sprawdzić JEŚLI ISTNIEJE przed każdym uruchomieniem i usunąć, jeśli cokolwiek zostanie znalezione.
Adrian Godong
7
@chocojosh, to powinno być w porządku, jeśli zapakujesz to w transakcję. Funkcja nie powinna znajdować się w bazie danych, jeśli transakcja bombarduje.
Jeff LaFay,
12
@JoelCoehoorn: to nadal wymaga uprawnień do zapisu.
user2284570
2
Zauważ, że to nie zadziała wewnątrz funkcji - tymczasowe funkcje wewnątrz funkcji nie są dozwolone. Zobacz: technet.microsoft.com/en-us/library/ms191320.aspx#Restrictions
Daniel Neel
95

Możesz tworzyć tymczasowe procedury składowane, takie jak:

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

w skrypcie SQL, ale nie funkcje. Mógłbyś jednak zapisać wynik w tabeli tymczasowej, a następnie użyć tych informacji później w skrypcie.

Ron Savage
źródło
7
To powinna być odpowiedź. Jest to naprawdę jednorazowe użycie, jeśli tylko tymczasowe połączenie ma zasięg (pojedynczy #), i ma tę zaletę, że omija ograniczenia użytkowników sql.
Todd,
Jak to jest używane? Czy nie jest to literówka w nazwie procedury użytej w wyrażeniu select into?
jgomo3,
Jestem w stanie uzyskać wyniki z przykładowej procedury składowanej, gdy usunę BEGINsłowo kluczowe i zastąpię je ENDsłowem kluczowym GO.
Joseph Dykstra
OP prosił o tymczasową FUNKCJĘ i przynajmniej SQL Server 2012 nie zezwoli na # -syntax dla funkcji. Tylko procedury.
Erk,
To nie działa w skrypcie i może nadal wymagać uprawnień. Aby uniknąć powtarzających się segmentów, jedyną opcją SQL jest instrukcja WITH.
alex.peter
25

Wspólne wyrażenia tabelowe pozwalają zdefiniować, które zasadniczo są widokami, które trwają tylko w zakresie instrukcji select, insert, update i delete. W zależności od tego, co musisz zrobić, mogą być strasznie przydatne.

Welbog
źródło
5
Należy to zaakceptować jako poprawną odpowiedź. Zaakceptowana odpowiedź nie jest bezpieczna wątkowo.
kalyan
11
Zależy od tego, co próbujesz zrobić. Znalazłem to pytanie, ponieważ piszę selektor danych i nie chcę powtarzać 10 wierszy MERGE INTO 30 razy. Nie obchodzi mnie ochrona wątków, a CTE nie będą działać dla mnie.
solipsicle
16
Myślę, że ta odpowiedź i twierdzenia, że ​​jest to poprawna odpowiedź, pomijają, że pytanie szuka tymczasowej FUNKCJI, a nie tymczasowej TABELI. O ile czegoś nie brakuje (nie jest to rzadkie) CTE są porównywalne do tabel tymczasowych.
JD Long
8
Funkcja może przyjmować argumenty, podczas gdy CTE nie.
Răzvan Flavius ​​Panda
4
Istnieje wiele różnic między CTE a tymczasową procedurą składowaną (co jest poprawną odpowiedzią tutaj IMO). Na początek wartości CTE istnieją tylko dla jednej instrukcji, podczas gdy zmienne tymczasowe mogą być używane w całym skrypcie. Inne różnice obejmują: (1) CTE nie mogą zawierać tej samej logiki, co SP, (2) CTE nie mogą akceptować zmiennych. CTE to po prostu cukier składniowy, który pozwala łatwiej budować zagnieżdżone wyrażenia tabelowe do użycia w instrukcji. Nawet wtedy mogą być niebezpieczne pod względem wydajności, jeśli nie jesteś świadomy zastrzeżeń.
Krzywo
12

Wiem, że mogę zostać skrytykowany za sugerowanie dynamicznego SQL, ale czasami jest to dobre rozwiązanie. Upewnij się tylko, że rozumiesz konsekwencje dla bezpieczeństwa, zanim się nad tym zastanowisz.

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
źródło
4

W skryptach masz więcej opcji i lepsze podejście do racjonalnego rozkładu. Zajrzyj do trybu SQLCMD (Menu zapytań -> tryb SQLCMD), a konkretnie do poleceń: setvar i: r.

W ramach procedury składowanej opcje są bardzo ograniczone. Nie można utworzyć funkcji zdefiniowanej bezpośrednio w treści procedury. Najlepsze, co możesz zrobić, to coś takiego, z dynamicznym SQL:

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

To przybliża globalną funkcję tymczasową, jeśli taka istniała. Nadal jest widoczny dla innych użytkowników. Możesz dołączyć @@ SPID połączenia, aby ujednolicić nazwę, ale wtedy reszta procedury wymagałaby również używania dynamicznego SQL.

Peter Radocchia
źródło
3

Poniżej przedstawiono to, czego użyłem w przeszłości, aby spełnić potrzebę skalarnego UDF w 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

To podejście, które wykorzystuje globalną zmienną dla PROCEDURY, umożliwia wykorzystanie tej funkcji nie tylko w skryptach, ale także w przypadku potrzeb związanych z dynamicznym SQL.

Gregory Hart
źródło