SQL Server 2012 - Nowe możliwości odczytu metadanych  Udostępnij na: Facebook

Autor: Damian Widera

Opublikowano: 2012-12-12

SQL Server udostępniał zawsze informacje na temat metadanych odnoszących się do obiektów zawartych w bazie danych. Dotyczy to zarówno tabel, widoków, jak i np. procedur składowanych czy funkcji. Informacje te były dostępne po wykonaniu kwerendy na tabelach systemowych, co samo w sobie nie jest dobrym sposobem. Drugą metodą jest możliwość skorzystania z widoków informacyjnych, tzw. INFORMATION_SCHEMA.

Dużo trudniejszym zadaniem jest odczytanie metadanych, zwracanych przez procedurę składowaną, która zawiera skomplikowaną logikę biznesową, np. posiada wiele rozgałęzień w kodzie. Zazwyczaj w takim wypadku programiści korzystali z opcji SET FMTONLY ON, która pozwalała na zwrócenie informacji o metadanych bez wykonania zapytania:

USE AdventureWorks2012
GO
SET FMTONLY ON
SELECT * FROM HumanResources.Employee;
SET FMTONLY OFF

W powyższym przykładzie zapytanie nie zwróci wierszy z tabeli HumanResources.Employee, a jedynie informacje o kolumnach.

W SQL Server 2012 ułatwiono, w znaczny sposób, programistom pracę z metadanymi, wprowadzając kilka nowych systemowych procedur składowanych oraz funkcji. Obiekty te, to:

  • sys.sp_describe_first_result_set,
  • sys.dm_exec_describe_first_result_set,
  • sys.dm_exec_describe_first_result_set_for_object,
  • sys.sp_describe_undeclared_parameters.

Procedura sys.sp_describe_first_result_set przyjmuje jako parameter wyrażenie TSQL i jako wynik zwraca bardzo dokładne informacje dotyczące metadanych wyrażenia. Na poniższym przykładzie pokazano działanie tej procedury:

EXEC sp_describe_first_result_set  @tsql = N'SELECT * FROM HumanResources.Employee'

Poniżej przedstawiony został fragment informacji, zwracanych przez procedurę dla zapytania, jak powyżej:

Funkcja sys.dm_exec_describe_first_result_set działa bardzo podobnie do systemowej procedury składowanej sys.sp_describe_first_result_set, opisanej powyżej. Użycie funkcji pozwala na łatwiejsze filtrowanie wyników, np. w podanym przykładzie wyświetlone zostaną tylko te kolumny wynikowe, których atrybut wynosi Is_Nullable=1:

SELECT *
FROM sys.dm_exec_describe_first_result_set
('SELECT * FROM HumanResources.Employee', NULL, 1)
WHERE is_nullable = 1

Poniżej zaprezentowany został przykład, w którym pokazano, w jaki sposób użyć parametryzacji zapytania:

SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set
('
        IF @SortOrder = 1
            SELECT OrderDate, TotalDue
            FROM Sales.SalesOrderHeader
            ORDER BY SalesOrderID ASC
        ELSE IF @SortOrder = 0
            SELECT OrderDate, TotalDue
            FROM Sales.SalesOrderHeader
            ORDER BY SalesOrderID DESC',
        '@SortOrder AS tinyint', 1
)

Jeśli parametr @SortOrder zostanie ustawiony na wartość 1 (jak powyżej), to funkcja zwróci następujący wynik:

Jeśli jednak wywołamy to zapytanie, zmieniając wartość parametru @SortOrder na 0, to otrzymamy wynik nieco inny:

SELECT name, system_type_name
FROM sys.dm_exec_describe_first_result_set
('
        IF @SortOrder = 1
            SELECT OrderDate, TotalDue
            FROM Sales.SalesOrderHeader
            ORDER BY SalesOrderID ASC
        ELSE IF @SortOrder = 0
            SELECT OrderDate, TotalDue
            FROM Sales.SalesOrderHeader
            ORDER BY SalesOrderID DESC',
        '@SortOrder AS tinyint', 0
)

Nie należy sugerować się tym, że w jednym z wywołań uzyskaliśmy kolumnę SalesOrderID. Ta kolumna nie jest zwracana do aplikacji klienckiej (nie jest na liście SELECT), a służy do przeszukiwania wyników zapytania. Najłatwiej potwierdzić to, sprawdzając jej własność Is_Hidden.

Należy pamiętać, że wywołanie tej procedury zakończy się błędem, jeśli SQL Server stwierdzi, że schematy nie są kompatybilne. Taka sytuacja ma miejsce, jeśli w wyniku zastosowania logiki zapytania otrzymujemy różne zestawy kolumn, jak pokazano poniżej:

 

EXEC sys.sp_describe_first_result_set
    @tsql = N'
    IF @IncludeCurrencyRate = 1
    SELECT OrderDate, TotalDue, CurrencyRateID
    FROM Sales.SalesOrderHeader
    ELSE
    SELECT OrderDate, TotalDue
    FROM Sales.SalesOrderHeader'

Po uruchomieniu zapytania otrzymujemy błąd:

Msg 11509, Level 16, State 1, Procedure sp_describe_first_result_set, Line 1
The metadata could not be determined because the statement 'SELECT OrderDate, TotalDue, CurrencyRateID
FROM Sales.SalesOrderHeader' is not compatible with the statement 'SELECT OrderDate, TotalDue
FROM Sales.SalesOrderHeader'.

Jeśli zamiast procedury sys.sp_describe_first_result_set użyta zostanie funkcja sys.dm_exec_describe_result_set, to wywołanie nie zakończy się błędem, a zostanie zwrócony wynik w postaci NULL.

Funkcja sys.dm_exec_describe_first_result_set_for_object pozwala uzyskać powyższe informacje dla obiektów bazodanowych, ponieważ przyjmuje jako parametr identyfikator (Object_ID) obiektu:

SELECT name, system_type_name, is_hidden
FROM sys.dm_exec_describe_first_result_set_for_object
(
OBJECT_ID('[dbo].[uspGetBillOfMaterials]'), 1
)

Procedura składowana sys.sp_describe_undeclared_parameters parsuje podane wyrażenie TSQL i próbuje odgadnąć informacje o typach parametrów. Poniżej przedstawiony został przykład prezentujący jej działanie:

EXEC sys.sp_describe_undeclared_parameters N'IF @IsFlag = 1 SELECT 1 ELSE SELECT 0'

Po jej uruchomieniu uzyskujemy informacje o parametrze @IsFlag (przedstawiono fragment informacji). SQL Server zidentyfikował ten parametr jako integer, na podstawie kontekstu zapytania TSQL:

EXEC sys.sp_describe_undeclared_parameters N'IF @IsFlag = 1 SELECT 1 ELSE SELECT 0'

Na zakończenie warto pamiętać, że póki co, za pomocą omówionych procedur składowanych, nie da się odczytać metadanych procedur, wykonanych w CLR, a także tzw. extended SP.