Sql-Server

視圖與獲取表頂部記錄的函式?

  • November 23, 2021

我正在與一位同事討論解決問題的方法。

我們有一個跟踪填充過程的表,該表在列LotTestGrade.

最近,我們添加了邏輯,以便在TestGrade創建任何高於 1 的 a 時,創建所有較低的級別。某些邏輯已完成,因此較低級別的行不一定與頂層相同。

例如,我插入一些東西,Lot='ABCD'然後TestGrade=5在我的Fills表中我看到

id    Lot    TestGrade   OtherColumns
======================================
10    'ABCD'     1          blah3
9     'ABCD'     2          bar
8     'ABCD'     3          foo
7     'ABCD'     4          blah1
6     'ABCD'     5          blah

現在的問題是在很多情況下,我只想要 Fills 表的頂級記錄,所以在範例中,我只想要帶有id=6.

關於如何做到這一點,我們有兩個想法。我想做一個看起來像的視圖

CREATE VIEW [dbo].[vwFills]
AS
SELECT t.* FROM [dbo].[Fills] t
JOIN (SELECT [Lot], MAX(TestGrade) as TestGrade FROM dbo.Fills GROUP BY [Lot]) t2 ON t.[Lot] = t2.[Lot] AND t.[TestGrade] = t2.[TestGrade]

我的同事想做類似的事情,但在一個看起來像這樣的函式內部

ALTER FUNCTION [dbo].[fnFills] (
@Fills tyFills READONLY
)
RETURNS @returnTable TABLE(
//Copy of the table definition of Fills, without
   id INT NOT NULL,
   Lot VARCHAR(10) NOT NULL,
   TestGrade INT NOT NULL,
   //rest of the columns..
   )
AS

Begin

insert into @returnTable
   select t2.* from (
       select [Lot], max([TestGrade]) as TestGrade from @Fills) t1
           left join Fills t2
           on t1.[Lot] = t2.[Lot] and t1.[TestGrade] = t2.[TestGrade]

Return

End

然後呼叫這個函式,它看起來像

DECALARE @FillRecords tyFills;
INSERT INTO @FillRecords SELECT * FROM db.Fills;
SELECT * FROM dbo.fnFills(@FillRecords);

我們dbo.Fills確實在Lot和上有索引TestGrade。然而,我的直覺是,在我們插入變數然後將其提供給函式的函式方法中,我們會失去這些索引,因此通過這種連接,我們正在查看 O(n^2) 的性能。這是一個公平的評價嗎?我擔心性能,因為伺服器在硬體方面並不強大,因此任何有助於查詢執行的東西都是首選。

一般來說,在這種情況下,是否有最佳實踐方法 - 視圖或函式是“明顯”還是更好的選擇?

sql server 中的函式通常是個壞主意

SQL Server 中的函式,特別是標量函式和多語句表值函式,存在很多問題。我將跳過標量 UDF,因為這不是您的問題。

對於您的同事建議的函式類型,即多語句表值函式,問題主要@table在於支持它的變數。你會遇到的問題是:

即使在這里索引@table變數也不會對您非常有用。

您可以通過使用內聯表值函式來避免大多數問題。它們的性能僅與您在其中輸入的查詢以及可用的支持索引一樣差。

CREATE OR ALTER FUNCTION 
   dbo.fnFills_Inline 
(
   @Fills tyFills READONLY
)
RETURNS table
AS
RETURN

   SELECT 
       t2.* 
   FROM 
   (
       SELECT 
           [Lot], 
           MAX([TestGrade]) AS TestGrade 
       FROM @Fills
    ) t1
    LEFT JOIN Fills t2
        ON  t1.[Lot] = t2.[Lot] 
        AND t1.TestGrade = t2.[TestGrade]; 

在這種情況下,視圖和函式之間應該沒有實質性區別,但是在涉及視窗函式的視圖中,可能需要一個內聯表值函式來將謂詞推過序列項目運算符

雖然我同意其他答案,即通常視圖比函式遇到性能問題的可能性更小,但它也特別取決於一個人編寫的查詢。我之所以提到這一點,是因為選項 C是使用一個利用ROW_NUMBER()視窗函式有效地獲取每個的頂部記錄的視圖,Lot如下所示:

WITH LotGradesSorted AS
(
   SELECT id, Lot, TestGrade, OtherColumns,
       ROW_NUMBER() OVER (PARTITION BY Lot ORDER BY TestGrade DESC) AS GroupSortId
   FROM dbo.Fills
)

SELECT id, Lot, TestGrade, OtherColumns
FROM LotGradesSorted
WHERE GroupSortId = 1

這會為按欄位降序排序的欄位PARTITION上的分組 ( ) 中的每一行生成一個唯一 ID ,這樣每組s 中最大的行始終是第一行。最終查詢僅返回那些行(使用過濾器)。Lot``TestGrade``TestGrade``Lot``SELECT``WHERE GroupSortId = 1

請注意,這僅適用於ORDER BY視窗函式子句中的欄位在子句中欄位的分組中是唯一的PARTITION(即,如果您在相同的欄位中從未有相同的TestGrade重複項Lot)。否則,如果被排序的欄位不是唯一的,那麼排序是不確定的(因為兩行將具有相同的確切值來排序,因此沒有一致的選擇方法),這可能導致半隨機結果。但是只要您的TestGrade欄位在每個 s 分組中都是唯一的,Lot那麼您就永遠不會遇到這個問題。

您的視圖的此查詢比您建議的查詢更有效的原因是因為它應該導致更少的通過您的表,並且 Microsoft 已將視窗函式設計為在正確情況下使用時的高性能工具。

引用自:https://dba.stackexchange.com/questions/303022