Kann ich eine einmalige Funktion in einem Skript oder einer gespeicherten Prozedur erstellen?

109

Gibt es in SQL Server 2005 ein Konzept für eine einmalige Verwendung oder eine lokale Funktion, die in einem SQL-Skript oder einer gespeicherten Prozedur deklariert ist? Ich möchte einige Komplexitäten in einem Skript, das ich schreibe, abstrahieren, aber es würde erfordern, eine Funktion deklarieren zu können.

Nur neugierig.

Mark Carpenter
quelle
Es gibt wahrscheinlich einen besseren Weg, um ohne Funktion das zu tun, was Sie wollen. Vielleicht sollten Sie einen Ausschnitt des Codes veröffentlichen, den Sie in eine Funktion verwandeln möchten?
DForck42
Generieren Sie eine Funktion dynamisch, sodass sie jedes Mal anders ist? Wenn Ihre Funktion immer gleich ist, lassen Sie sie einfach in der Datenbank
KM.
1
Ich habe versucht, dies zu tun, um die Abfrage besser lesbar zu machen. Die Idee, große Abfragen zu erstellen, ist schwer zu pflegen.
Jp_

Antworten:

65

Sie können CREATE Functionam Anfang Ihres Skripts und DROP Functionam Ende anrufen .

Joel Coehoorn
quelle
6
Ich würde das vorschlagen. Achten Sie nur darauf, dass Ihr Skript fertig ist. Wenn es abgebrochen wird, haben Sie die Funktion immer noch in der Datenbank.
Chocojosh
6
Sie können vor jedem Lauf eine IF EXISTS-Prüfung durchführen und löschen, wenn etwas gefunden wird.
Adrian Godong
7
@chocojosh, das sollte in Ordnung sein, wenn Sie es in eine Transaktion einschließen. Die Funktion sollte nicht in der Datenbank sein, wenn die Transaktion bombardiert.
Jeff LaFay
12
@JoelCoehoorn: Dies erfordert weiterhin Schreibrechte.
user2284570
2
Beachten Sie, dass dies innerhalb einer Funktion nicht funktioniert - temporäre Funktionen innerhalb von Funktionen sind nicht zulässig. Siehe: technet.microsoft.com/en-us/library/ms191320.aspx#Restrictions
Daniel Neel
94

Sie können temporär gespeicherte Prozeduren erstellen wie:

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

in einem SQL-Skript, aber keine Funktionen. Sie könnten den Proc-Speicher jedoch in einer temporären Tabelle speichern lassen und diese Informationen später im Skript verwenden.

Ron Savage
quelle
7
Dies sollte die Antwort sein. Dies ist wirklich einmalig, wenn nur ein temporärer Verbindungsbereich (einzelne #) vorhanden ist, und hat den Vorteil, dass die Einschränkungen für SQL-Benutzer umgangen werden.
Todd
Wie wird es dann verwendet? Ist es nicht ein Tippfehler im Prozedurnamen, der in der Auswahl in Ausdruck verwendet wird?
jgomo3
Ich kann Ergebnisse aus Ihrer gespeicherten Beispielprozedur abrufen, wenn ich das BEGINSchlüsselwort entferne und das ENDSchlüsselwort durch ersetze GO.
Joseph Dykstra
Das OP hat nach einer temporären FUNKTION gefragt und zumindest SQL Server 2012 lässt die # -Syntax für Funktionen nicht zu. Nur Verfahren.
Erk
Dies funktioniert nicht in einem Skript und erfordert möglicherweise noch Berechtigungen. Um sich wiederholende Segmente zu vermeiden, hat SQL nur die WITH-Anweisung.
Alex.peter
25

Mit allgemeinen Tabellenausdrücken können Sie im Wesentlichen Ansichten definieren, die nur im Rahmen Ihrer Anweisungen zum Auswählen, Einfügen, Aktualisieren und Löschen gültig sind. Je nachdem, was Sie tun müssen, können sie sehr nützlich sein.

Welbog
quelle
5
Dies sollte als richtige Antwort akzeptiert werden. Die akzeptierte Antwort ist nicht threadsicher.
Kalyan
11
Kommt darauf an, was du versuchst zu tun. Ich habe diese Frage gefunden, weil ich einen Data Seeder schreibe und nicht 10 Zeilen von MERGE INTO 30 Mal wiederholen möchte. Threadsafe ist mir egal und CTEs funktionieren bei mir nicht.
Solipsicle
16
Ich denke, diese Antwort und die Behauptungen, dass es die richtige Antwort ist, vermissen, dass die Frage nach einer temporären FUNKTION sucht, nicht nach einer temporären TABELLE. Sofern mir nichts fehlt (nicht ungewöhnlich), sind CTEs mit temporären Tabellen vergleichbar.
JD Long
8
Eine Funktion kann Argumente annehmen, ein CTE nicht.
Răzvan Flavius ​​Panda
4
Es gibt viele Unterschiede zwischen einer CTE- und einer temporär gespeicherten Prozedur (was hier IMO die richtige Antwort ist). Für den Anfang existieren CTEs nur für eine einzelne Anweisung, während temporäre Variablen in einem Skript verwendet werden können. Andere Unterschiede sind: (1) CTEs können nicht dieselbe Logik wie ein SP enthalten, (2) CTEs können keine Variablen akzeptieren. Ein CTE ist nur syntaktischer Zucker, mit dem Sie verschachtelte Tabellenausdrücke zur Verwendung in einer Anweisung einfacher erstellen können. Selbst dann können sie in Bezug auf die Leistung gefährlich sein, wenn Sie sich der Einschränkungen nicht bewusst sind.
Einseitig
12

Ich weiß, dass ich möglicherweise dafür kritisiert werde, dynamisches SQL vorzuschlagen, aber manchmal ist es eine gute Lösung. Stellen Sie einfach sicher, dass Sie die Auswirkungen auf die Sicherheit verstanden haben, bevor Sie dies berücksichtigen.

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

In Skripten haben Sie mehr Optionen und eine bessere Möglichkeit zur rationalen Zerlegung. Schauen Sie in den SQLCMD-Modus (Abfragemenü -> SQLCMD-Modus), insbesondere in die Befehle: setvar und: r.

Innerhalb einer gespeicherten Prozedur sind Ihre Optionen sehr begrenzt. Sie können eine Funktion nicht direkt mit dem Hauptteil einer Prozedur definieren. Das Beste, was Sie tun können, ist so etwas mit dynamischem 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

Dies entspricht in etwa einer globalen temporären Funktion, falls so etwas existiert. Es ist immer noch für andere Benutzer sichtbar. Sie können die @@ SPID Ihrer Verbindung anhängen, um den Namen zu eindeutig zu machen. Dies würde jedoch den Rest der Prozedur erfordern, um auch dynamisches SQL zu verwenden.

Peter Radocchia
quelle
3

Folgendes habe ich in der Vergangenheit verwendet, um die Notwendigkeit einer skalaren UDF in MS SQL zu erfüllen:

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

Mit diesem Ansatz, bei dem eine globale Variable für das VERFAHREN verwendet wird, können Sie die Funktion nicht nur in Ihren Skripten, sondern auch in Ihren Dynamic SQL-Anforderungen verwenden.

Gregory Hart
quelle