Таблиця з багатовикладною функцією та функція вбудованої таблиці


198

Кілька прикладів, які потрібно показати, просто майте на увазі:

Вбудована таблиця з оцінкою

CREATE FUNCTION MyNS.GetUnshippedOrders()
RETURNS TABLE
AS 
RETURN SELECT a.SaleId, a.CustomerID, b.Qty
    FROM Sales.Sales a INNER JOIN Sales.SaleDetail b
        ON a.SaleId = b.SaleId
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.ShipDate IS NULL
GO

Таблиця з декількома заявами

CREATE FUNCTION MyNS.GetLastShipped(@CustomerID INT)
RETURNS @CustomerOrder TABLE
(SaleOrderID    INT         NOT NULL,
CustomerID      INT         NOT NULL,
OrderDate       DATETIME    NOT NULL,
OrderQty        INT         NOT NULL)
AS
BEGIN
    DECLARE @MaxDate DATETIME

    SELECT @MaxDate = MAX(OrderDate)
    FROM Sales.SalesOrderHeader
    WHERE CustomerID = @CustomerID

    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a INNER JOIN Sales.SalesOrderHeader b
        ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c ON b.ProductID = c.ProductID
    WHERE a.OrderDate = @MaxDate
        AND a.CustomerID = @CustomerID
    RETURN
END
GO

Чи є перевага в застосуванні одного типу (рядкового або багатовимірного) перед іншим? Чи існують певні сценарії, коли один кращий за іншого або відмінності суто синтаксичні? Я усвідомлюю, що два приклади запитів роблять різні речі, але чи є причина, щоб я написав їх таким чином?

Читання про них та переваги / відмінності насправді не пояснено.


Також однією з величезних переваг вбудованої функції є те, що ви можете вибирати стовпці ROWID (TIMESTAMP), тоді як ви не можете вставляти дані TIMESTAMP у таблицю повернення у багатоступеневій функції!
Artru

3
Дякую за відмінну нитку. Я багато чого навчився. Однак слід пам’ятати про те, що ПІДПРИЄМЦЯ функції, яка була ITV на MSTV, профілер вважає, що ви змінюєте ITV. Незалежно від того, що ви робите для отримання синтаксису прямо з точки зору MSTV, перекомпіляція завжди виходить з ладу, як правило, навколо першого заяви після BEGIN. Єдиним способом цього було скинути стару функцію та створити нову як MSTV.
Fandango68

Відповіді:


141

Досліджуючи коментар Метта, я переглянув своє первісне твердження. Він правильний, буде різниця в продуктивності між функцією, яка оцінюється вбудованою таблицею (ITVF), і функцією, що оцінюється таблицею з декількома заявами (MSTVF), навіть якщо вони обидва просто виконують оператор SELECT. SQL Server буде трактувати ITVF дещо якVIEWоскільки він обчислює план виконання, використовуючи останні статистичні дані щодо відповідних таблиць. MSTVF еквівалентний заповненню всього вмісту вашого оператора SELECT в змінну таблиці, а потім приєднання до цього. Таким чином, компілятор не може використовувати будь-яку табличну статистику для таблиць у MSTVF. Отже, при рівних рівнях (якими вони рідко є) ITVF буде працювати краще, ніж MSTVF. У моїх тестах різниця в продуктивності в часі завершення була незначною, однак з точки зору статистики це було помітно.

У вашому випадку дві функції функціонально не рівноцінні. Функція MSTV виконує додатковий запит щоразу, коли він викликається, і, головне, фільтрує ідентифікатор клієнта. У великому запиті оптимізатор не зможе скористатися іншими типами приєднань, оскільки потрібно буде викликати функцію для кожного клієнта, що передається. Однак якщо ви переписали свою функцію MSTV так:

CREATE FUNCTION MyNS.GetLastShipped()
RETURNS @CustomerOrder TABLE
    (
    SaleOrderID    INT         NOT NULL,
    CustomerID      INT         NOT NULL,
    OrderDate       DATETIME    NOT NULL,
    OrderQty        INT         NOT NULL
    )
AS
BEGIN
    INSERT @CustomerOrder
    SELECT a.SalesOrderID, a.CustomerID, a.OrderDate, b.OrderQty
    FROM Sales.SalesOrderHeader a 
        INNER JOIN Sales.SalesOrderHeader b
            ON a.SalesOrderID = b.SalesOrderID
        INNER JOIN Production.Product c 
            ON b.ProductID = c.ProductID
    WHERE a.OrderDate = (
                        Select Max(SH1.OrderDate)
                        FROM Sales.SalesOrderHeader As SH1
                        WHERE SH1.CustomerID = A.CustomerId
                        )
    RETURN
END
GO

У запиті оптимізатор зможе викликати цю функцію один раз і створити кращий план виконання, але він все одно не буде кращим, ніж еквівалентний, не параметризований ITVS або a VIEW.

ITVF слід віддавати перевагу над MSTVF, коли це можливо, тому що типи даних, зведеність та зіставлення з стовпців таблиці, тоді як ви заявляєте ці властивості в таблиці, що оцінюється, у багатовикладному значенні, і, що важливо, ви отримаєте кращі плани виконання від ITVF. На моєму досвіді я не знайшов багатьох обставин, коли ITVF був кращим варіантом, ніж ГЛЯД, але пробіг може змінюватися.

Завдяки Метту.

Доповнення

Оскільки я нещодавно побачив це, ось чудовий аналіз, виконаний Уейн Шеффілдом, порівнюючи різницю продуктивності між функціями Inline Table Valueeded та Multi-Statement.

Його оригінальна публікація в блозі.

Скопіюйте на SQL Server Central


40
Це просто не відповідає дійсності. Функції з декількома операторами дуже часто вражають велику продуктивність, оскільки вони не дозволяють оптимізатору запитів використовувати статистику. Якби у мене було 1 долар кожного разу, коли я бачив, як використання функцій з декількома заявами спричиняє дуже поганий вибір плану виконання (здебільшого тому, що зазвичай оцінюється кількість повернених рядків як 1), мені вистачило б придбати невелику машину.
Метт Уітфілд

Найкраще пояснення, яке я коли-небудь знайшов, - це перша відповідь та пов’язана публікація: stackoverflow.com/questions/4109152/… Не пропустіть відповідний документ, його можна швидко прочитати, і це надзвичайно цікаво.
JotaBe

1
Чи буде оновлення цієї відповіді для SQL Server 2017 ?: youtube.com/watch?time_continue=2&v=szTmo6rTUjM
Ральф

29

Внутрішньо SQL Server розглядає функцію, яка оцінюється вбудованою таблицею, так само, як і перегляд, і розглядає функцію, яка оцінюється в таблиці з декількома операторами, аналогічно тому, як це буде збережена процедура.

Коли функція вбудованої таблиці з використанням вбудованої частини використовується як частина зовнішнього запиту, процесор запитів розширює визначення UDF та створює план виконання, який здійснює доступ до базових об'єктів, використовуючи індекси на цих об'єктах.

Для функції, що оцінює таблицю з декількома операторами, план виконання створюється для самої функції та зберігається в кеші плану виконання (як тільки функція виконується вперше). Якщо функції, що оцінюються в таблиці з кількома висловлюваннями, використовуються як частина великих запитів, то оптимізатор не знає, до чого повертається функція, і тому робить деякі стандартні припущення - фактично передбачає, що функція поверне один рядок і що повернення Доступ до функції здійснюється за допомогою сканування таблиці проти таблиці з одним рядком.

Там, де функції багатозначних таблиць, що оцінюються, можуть погано виконуватись, коли вони повертають велику кількість рядків і об'єднуються у зовнішніх запитах. Питання щодо продуктивності зводиться насамперед до того, що оптимізатор створить план, припускаючи, що повернеться один рядок, що не обов'язково буде найбільш відповідним планом.

Як загальне правило, ми виявили, що там, де це можливо, функції вбудованої таблиці впорядковані повинні використовуватись на відміну від функцій з декількома операторами (коли UDF буде використовуватися як частина зовнішнього запиту) через ці потенційні проблеми продуктивності.


2
Хоча вона може обробляти функції багатозначних таблиць, що оцінюються, аналогічно збереженій процедурі, функціонально однакова збережена процедура набагато швидша, ніж функція, що оцінюється в таблиці, для великих наборів даних. Я дотримуюся збережених проектів над функціями, що оцінюються в таблиці з декількома заявами.
Кекоа

6
Якщо вам не потрібно приєднати ці результати до іншого запиту.
Гільєрмо Гутьеррес

чому б не використовувати обидва? Збережене програмне забезпечення, яке повертає результат функції багатозначних табличних значень. Найкраще з обох світів.
Робіно

13

Є ще одна відмінність. Функцію вбудованої таблиці, яку можна оцінювати, можна вставляти, оновлювати та видаляти з - подібно до представлення. Подібні обмеження застосовуються - не можна оновлювати функції за допомогою агрегатів, не може оновлювати обчислені стовпці тощо.


3

Думаю, ваші приклади дуже добре відповідають на питання. Перша функція може бути виконана як один вибір, і є вагомим приводом для використання вбудованого стилю. Другий, ймовірно, може бути виконано як одне твердження (використовуючи підзапит, щоб отримати максимальну дату), але деяким кодерам може бути легше читати чи більш природним це зробити у кількох операторах, як ви це зробили. Деякі функції просто не вдається виконати в одному операторі, і тому потрібна версія з декількома заявами.

Я пропоную використовувати найпростіший (вбудований), коли це можливо, і використовувати мульти-оператори, коли це необхідно (очевидно) або коли особисті переваги / читанність робить це додатковим введенням тексту.


Дякую за відповідь. Отже, багатозвернене дійсно використовуватиметься лише тоді, коли функція є складнішою, ніж це можливо зробити у вбудованій функції, заради читабельності? Чи є взагалі якісь переваги від продуктивності багатозакладної?
AndrewC

Я не знаю, але я б не вважав цього. Напевно, краще дозволити серверу sql розібратися в оптимізаціях, які ви можете спробувати зробити вручну (використовуючи змінні, тимчасові таблиці чи інше). Хоча ви, звичайно, можете зробити тестування ефективності, щоб довести / спростувати це в конкретних випадках.
Рей

Ще раз дякую. Я можу заглянути далі в це, коли в мене буде більше часу! :)
AndrewC


0

Я цього не перевіряв, але функція з декількома операторами кешує набір результатів. Можуть бути випадки, коли оптимізатору вбудовано функцію оптимізатора занадто багато. Наприклад, припустимо, у вас є функція, яка повертає результат з різних баз даних, залежно від того, що ви передаєте як "номер компанії". Як правило, ви можете створити представлення з об'єднанням, а потім фільтрувати за номером компанії, але я виявив, що іноді сервер sql відтягує весь союз і недостатньо розумний, щоб викликати вибраний. Функція таблиці може мати логіку для вибору джерела.


0

Іншим випадком використання багаторядкової функції буде обхід сервера sql від натискання на клавішу where.

Наприклад, у мене є таблиця з іменами таблиць, а деякі назви таблиць відформатовані як C05_2019 та C12_2018, і всі таблиці, відформатовані таким чином, мають однакову схему. Я хотів об'єднати всі ці дані в одну таблицю і проаналізувати 05 і 12 в колонку CompNo, а 2018,2019 - у колонку року. Однак є й інші таблиці, такі як ACA_StupidTable, які я не можу витягти CompNo та CompYr і отримав би помилку перетворення, якщо б спробував. Отже, мій запит був у двох частинах, внутрішній запит, який повертав лише таблиці, форматовані як "C_______", тоді зовнішній запит робив перетворення підрядків та int. тобто Cast (Substring (2, 2) як int) як CompNo. Все виглядає добре, за винятком того, що сервер sql вирішив поставити мою функцію Cast до того, як результати будуть відфільтровані, і тому я отримую розум помилки конверсії. Функція таблиці операторів з декількома операторами може запобігти тому,


0

Можливо, дуже стисло. ITVF (вбудований TVF): більше, якщо ви є особою БД, це вид параметризованого виду, зробіть один SELECT st

MTVF (Multi-statement TVF): розробник, створює та завантажує змінну таблиці.


-2

якщо ви збираєтеся робити запит, ви можете приєднатися до функції вбудованої таблиці з оцінкою, наприклад:

SELECT
    a.*,b.*
    FROM AAAA a
        INNER JOIN MyNS.GetUnshippedOrders() b ON a.z=b.z

це буде мати невеликі накладні витрати і працювати нормально.

якщо ви спробуєте використати таблицю з декількома записами, оцінену в аналогічному запиті, у вас виникнуть проблеми з ефективністю:

SELECT
    x.a,x.b,x.c,(SELECT OrderQty FROM MyNS.GetLastShipped(x.CustomerID)) AS Qty
    FROM xxxx   x

оскільки ви будете виконувати функцію 1 раз для кожного повернутого рядка, оскільки набір результатів стає більшим, він працюватиме повільніше і повільніше.


Так, ви б сказали, що Inline набагато краща з точки зору продуктивності?
AndrewC

1
Ні, вони обидва повертають таблицю, що робить ваш другий SQL недійсним, коли ви намагаєтесь поставити таблицю в стовпчик.
cjk

1
@ck, я оновив запит, який я коментував. параметри функції, що використовується у другій функції, дозволяють використовувати її як підзапит, що призведе до гіршої продуктивності.
КМ.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.