sql >> Databasteknik >  >> RDS >> Sqlserver

SQL Server välj slumpmässigt (eller första) värde med aggregering

Det finns en odokumenterat aggregat kallas ANY som inte är giltig syntax men är möjlig att få synas i dina körplaner. Detta ger dock ingen prestandafördel.

Om vi ​​antar följande tabell- och indexstruktur

CREATE TABLE T
(
id int identity primary key,
[group] char(1) 
)

CREATE NONCLUSTERED INDEX ix ON T([group])

INSERT INTO T
SELECT TOP 1000000 CHAR( 65 + ROW_NUMBER() OVER (ORDER BY @@SPID) % 3)
FROM sys.all_objects o1, sys.all_objects o2, sys.all_objects o3

Jag har också fyllt på med exempeldata så att det finns många rader per grupp.

Din ursprungliga fråga

SELECT MAX(id),
       [group]
FROM   T
GROUP  BY [group]  

Ger Table 'T'. Scan count 1, logical reads 1367 och planen

  |--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([Expr1003]=MAX([[T].[id])))
       |--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)

Skrivs om för att få ANY sammanställt...

;WITH cte AS
(
SELECT *,
        ROW_NUMBER() OVER (PARTITION BY [group] ORDER BY [group] ) AS RN
FROM T)
SELECT id,
       [group]
FROM    cte     
WHERE RN=1

Ger Table 'T'. Scan count 1, logical reads 1367 och planen

  |--Stream Aggregate(GROUP BY:([[T].[group]) DEFINE:([[T].[id]=ANY([[T].[id])))
       |--Index Scan(OBJECT:([[T].[ix]), ORDERED FORWARD)

Även om SQL Server potentiellt kan sluta bearbeta gruppen så snart det första värdet hittas och hoppa till nästa gör det inte det. Den bearbetar fortfarande alla rader och de logiska läsningarna är desamma.

För detta specifika exempel med många rader i gruppen skulle en mer effektiv version vara en rekursiv CTE.

WITH    RecursiveCTE
AS      (
        SELECT TOP 1 id, [group]
        FROM T
        ORDER BY [group]
        UNION   ALL
        SELECT  R.id, R.[group]
        FROM    (
                SELECT  T.*,
                        rn = ROW_NUMBER() OVER (ORDER BY (SELECT 0))
                FROM    T
                JOIN    RecursiveCTE R
                        ON  R.[group] < T.[group]
                ) R
        WHERE   R.rn = 1
        )
SELECT  *
FROM    RecursiveCTE
OPTION  (MAXRECURSION 0);

Vilket ger

Table 'Worktable'. Scan count 2, logical reads 19
Table 'T'. Scan count 4, logical reads 12

De logiska läsningarna är mycket mindre eftersom den hämtar den första raden per grupp och sedan söker in i nästa grupp snarare än att läsa en mängd poster som inte bidrar till det slutliga resultatet.




  1. De bästa misstagen att undvika i MySQL-replikering

  2. hur man ger nummer till förekomst i sql

  3. Använda Working Folder till Source Control Database

  4. Få summan av MySQL-kolumnen i PHP