Antworten:
Dies hängt davon ab, wie Sie die berechnete Spalte definieren. Eine PERSISTED
berechnete Spalte wird berechnet und dann als Daten in der Tabelle gespeichert. Wenn Sie die Spalte nicht als definieren PERSISTED
, wird sie beim Ausführen Ihrer Abfrage berechnet.
Bitte sehen Sie Aarons Antwort für eine gute Erklärung und einen Beweis.
Pinal Dave beschreibt dies ebenfalls ausführlich und zeigt den Nachweis der Lagerung in seiner Serie:
Dies ist sehr einfach selbst zu beweisen. Wir können eine Tabelle mit einer berechneten Spalte erstellen, die eine skalare benutzerdefinierte Funktion verwendet, und dann Pläne und Funktionsstatistiken vor und nach einer Aktualisierung und Auswahl überprüfen und sehen, wann eine Ausführung aufgezeichnet wird.
Nehmen wir an, wir haben diese Funktion:
CREATE FUNCTION dbo.mask(@x varchar(32))
RETURNS varchar(32) WITH SCHEMABINDING
AS
BEGIN
RETURN (SELECT 'XX' + SUBSTRING(@x, 3, LEN(@x)-4) + 'XXXX');
END
GO
Und dieser Tisch:
CREATE TABLE dbo.Floobs
(
FloobID int IDENTITY(1,1),
Name varchar(32),
MaskedName AS CONVERT(varchar(32), dbo.mask(Name)),
CONSTRAINT pk_Floobs PRIMARY KEY(FloobID),
CONSTRAINT ck_Name CHECK (LEN(Name)>=8)
);
GO
Überprüfen wir sys.dm_exec_function_stats
(neu in SQL Server 2016 und Azure SQL-Datenbank) vor und nach dem Einfügen und anschließend nach einer Auswahl:
SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();
INSERT dbo.Floobs(Name) VALUES('FrankieC');
SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();
SELECT * FROM dbo.Floobs;
SELECT o.name, s.execution_count
FROM sys.dm_exec_function_stats AS s
INNER JOIN sys.objects AS o
ON o.[object_id] = s.[object_id]
WHERE s.database_id = DB_ID();
Ich sehe keinen Funktionsaufruf am Insert, nur am Select.
Löschen Sie nun die Tabellen und wiederholen Sie den Vorgang. Ändern Sie diesmal die Spalte in PERSISTED
:
DROP TABLE dbo.Floobs;
GO
DROP FUNCTION dbo.mask;
GO
...
MaskedName AS CONVERT(varchar(32), dbo.mask(Name)) PERSISTED,
...
Und ich sehe das Gegenteil: Ich bekomme eine Ausführung auf dem Insert protokolliert, aber nicht auf dem Select.
Sie haben keine moderne Version von SQL Server sys.dm_exec_function_stats
? Keine Sorge, dies ist auch in den Ausführungsplänen festgehalten .
Für die nicht persistente Version können wir die Funktion, auf die verwiesen wird, nur in select sehen:
Während die persistierte Version nur die Berechnung beim Einfügen anzeigt:
Jetzt bringt Martin einen wichtigen Punkt in einem Kommentar vor : Das wird nicht immer wahr sein. Erstellen wir einen Index, der die persistierte berechnete Spalte nicht abdeckt, und führen Sie eine Abfrage aus, die diesen Index verwendet, und prüfen Sie, ob bei der Suche die Daten aus den vorhandenen persistierten Daten abgerufen werden, oder berechnen Sie die Daten zur Laufzeit (Funktion zum Löschen und erneuten Erstellen) und Tabelle hier):
CREATE INDEX x ON dbo.Floobs(Name);
GO
INSERT dbo.Floobs(name)
SELECT LEFT(name, 32)
FROM sys.all_columns
WHERE LEN(name) >= 8;
Jetzt führen wir eine Abfrage aus, die den Index verwendet (tatsächlich wird der Index in diesem speziellen Fall standardmäßig verwendet, auch ohne eine where-Klausel):
SELECT * FROM dbo.Floobs WITH (INDEX(x))
WHERE Name LIKE 'S%';
Ich sehe zusätzliche Ausführungen in den Funktionsstatistiken und der Plan lügt nicht:
Die Antwort ist also IT-ABHÄNGIGKEIT . In diesem Fall hielt es SQL Server für billiger, die Werte neu zu berechnen, als Suchvorgänge durchzuführen. Dies kann sich aufgrund verschiedener Faktoren ändern. Verlassen Sie sich also nicht darauf. Dies kann in beide Richtungen geschehen, unabhängig davon, ob eine benutzerdefinierte Funktion verwendet wird oder nicht. Ich habe es hier nur verwendet, weil es so viel einfacher zu veranschaulichen war.
Die Antwort auf diese Frage lautet wirklich "es kommt darauf an". Ich bin gerade auf ein Beispiel gestoßen, in dem SQL Server den Index für die persistierte berechnete Spalte verwendet, aber die Funktion weiterhin ausführt, als ob die Werte von Anfang an nie persistiert würden. Möglicherweise hat dies mit dem Datentyp der Spalte ( nvarchar(37)
) oder möglicherweise mit der Größe der Tabelle (etwa 7 Millionen Zeilen) zu tun , doch SQL Server hat sich persisted
in diesem speziellen Fall entschlossen, das Schlüsselwort zu ignorieren .
In diesem Fall ist der Primärschlüssel in der Tabelle "TransactionID" (Transaktions-ID), bei der es sich ebenfalls um eine berechnete und beständige Spalte handelt. Der Ausführungsplan generiert einen Index-Scan und in einer Tabelle mit nur 7 Millionen Zeilen dauert die Ausführung dieser einfachen Abfrage mehr als 2-3 Minuten, da die Funktion für jede Zeile erneut ausgeführt wird und die Werte nicht beibehalten zu werden scheinen Der Index.