SQLSERVER2012 列存儲索引的簡單研究和測試


SQLSERVER2012 列存儲索引的簡單研究和測試

 

轉自:微軟技術大會2016

逐行插入:插入數據-》行組 超過100K 102400行才會插入到壓縮行組-》壓縮行組-》移動元組-》列段

大批量插入:插入數據  超過100K 102400行-》壓縮行組-》移動元組-》列段

數據插入到行組時,一旦並發度過高,會臨時生成多個臨時行組,臨時行組最后會合並為一個行組

 

插入數據-》行組 (行組會有X鎖行鎖)影響並發

兩種方式
建表時候直接創建一個聚集列存儲索引表
建表時候先建一個普通聚集索引表,然后在這個普通聚集索引表上建立一個聚集列存儲索引(一個聚集索引表變成了兩個副本兩份數據,一個是聚集索引,一個是聚集列存儲索引)

SQL2014:聚集列存儲索引表不能建立主鍵,不支持有大對象數據類型nvarchar(max)等的表
SQL2016:聚集列存儲索引表可以建立主鍵,支持有大對象數據類型nvarchar(max)等的表

 

 

http://mysql.taobao.org/monthly/2017/03/04/

delta store->列存儲

 

 

看這篇文章之前可以先看一下下面這兩篇文章:

列存儲索引

http://www.cnblogs.com/qanholas/archive/2013/03/08/2949205.html

非聚集索引
http://www.cnblogs.com/lyhabc/p/3196484.html

還有這一篇文章

SQLSERVER中的LOB頁面簡單研究

 

https://www.sqlpassion.at/archive/2017/01/30/columnstore-segment-elimination/?awt_l=BJCrA&awt_m=3mFjBb8vbIYUUTS

行組-》加密,壓縮-》元數據,段-》blob段數據列存儲

 

東方瑞通

一張表水平切為多個行組,各個行組有垂直切為多個段,每個小方塊為一個段(一個表又水平切又垂直切成多個小方塊(段))
段列中的部分行的一個集合
包含相同行的段是一個行組
數據以段為單位進行壓縮
每個段存儲在不同lob頁中
段是IO訪問的最小單位


建立測試環境

 先創建一張表

 1 USE [pratice]
 2 GO
 3 IF (OBJECT_ID('TestTable') IS NOT NULL)
 4 DROP TABLE [dbo].[TestTable]
 5 GO
 6 CREATE TABLE TestTable
 7 (
 8     id INT  ,
 9     c1 INT  
10 )
11 GO


插入1W條測試數據

1 DECLARE @i INT
2 SET @i=1
3 WHILE @i<10001
4 BEGIN
5 INSERT INTO TestTable (id,c1)
6 SELECT @i,@i
7 SET @i=@i+1
8 END
9 GO


看一下插入的記錄是否足夠

1 SELECT COUNT(*) FROM TestTable
2 SELECT TOP 10 * from TestTable

在C1列上創建一個列存儲索引

1 CREATE NONCLUSTERED columnstore INDEX PK__TestTable__ColumnStore ON TestTable(c1)

執行計划

在上面給出的文章里提到 http://www.cnblogs.com/qanholas/archive/2013/03/08/2949205.html

下面幾個SQL語句的執行計划也顯示出列存儲索引不會seek

(2)列存儲索引不支持 SEEK

如果查詢應返回行的一小部分,則優化器不大可能選擇列存儲索引(例如:needle-in-the-haystack 類型查詢)。

如果使用表提示 FORCESEEK,則優化器將不考慮列存儲索引。

 

1 SELECT * FROM TestTable WHERE [C1]=60  --列存儲索引掃描 RID查找
2 SELECT id FROM TestTable WHERE [C1]=60  --列存儲索引掃描 RID查找
3 SELECT c1 FROM TestTable WHERE [C1]=60   --列存儲索引掃描
4 SELECT * FROM TestTable WHERE id=60   --全表掃描
5 SELECT c1 FROM TestTable --列存儲索引掃描
6 SELECT * FROM TestTable   --全表掃描

 


列存儲索引的結構

先創建一張表保存DBCC的結果

 1 USE [pratice]
 2 GO
 3 CREATE TABLE DBCCResult (
 4 PageFID NVARCHAR(200),
 5 PagePID NVARCHAR(200),
 6 IAMFID NVARCHAR(200),
 7 IAMPID NVARCHAR(200),
 8 ObjectID NVARCHAR(200),
 9 IndexID NVARCHAR(200),
10 PartitionNumber NVARCHAR(200),
11 PartitionID NVARCHAR(200),
12 iam_chain_type NVARCHAR(200),
13 PageType NVARCHAR(200),
14 IndexLevel NVARCHAR(200),
15 NextPageFID NVARCHAR(200),
16 NextPagePID NVARCHAR(200),
17 PrevPageFID NVARCHAR(200),
18 PrevPagePID NVARCHAR(200)
19 )
View Code

我們看一下列存儲索引在表中建立了一些什么頁面

1 --TRUNCATE TABLE [dbo].[DBCCResult]
2 INSERT INTO DBCCResult EXEC ('DBCC IND(pratice,TestTable,-1) ')
3 
4 SELECT * FROM [dbo].[DBCCResult] ORDER BY [PageType] DESC 

先說明一下:DBCC IND的結果
PageType          頁面類型:1:數據頁面;2:索引頁面;3:Lob_mixed_page;4:Lob_tree_page;10:IAM頁面
IndexID            索引ID:0 代表堆, 1 代表聚集索引, 2-250 代表非聚集索引 大於250就是text或image字段

 

由於表中的頁面太多,本來想truncate table並只插入1000行記錄到表,讓大家看清楚一下表中的頁面的,但是遇到下面錯誤

http://www.cnblogs.com/qanholas/archive/2013/03/08/2949205.html

文章中里提到:

(14).有列存儲索引后,表變成只讀表,不能進行添加,刪除,編輯的操作。 insert into TestTable(c1,c2) select rand(),rand() 錯誤信息是這樣的:由於不能在包含列存儲索引的表中更新數據,INSERT 語句失敗。

微軟提供了三種方式來解決這個問題,這里簡單介紹兩種: 1) 若要更新具有列存儲索引的表,先刪除列存儲索引,執行任何所需的 INSERT、DELETE、UPDATE 或 MERGE 操作,然后重新生成列存儲索引。

2) 對表進行分區並切換分區。對於大容量插入,先將數據插入到一個臨時表中,在臨時表上生成列存儲索引,然后將此臨時表切換到空分區。對於其他更新,將主表外的一個分區切換到一個臨時表中,禁用或刪除臨時表上的列存儲索引,執行更新操作,在臨時表上重新生成或重新創建列存儲索引,然后將臨時表切換回主表。

 

只能夠先刪除列存儲索引,再truncate table了

1 DROP INDEX PK__TestTable__ColumnStore ON TestTable

 

truncate table,再插入1000條記錄,重新建立列存儲索引,看到DBCC IND的結果如下:

表中有10000條記錄的話,表中的頁面類型又增加了一種,而且可以看到,列存儲索引的表中是沒有索引頁面的,只有LOB頁面

10000條記錄的表比1000條記錄的表多了一種頁面類型:Lob_tree_page

為了避免篇幅過長,有關Lob_tree_page頁面的詳細內容請看我的另一篇文章

SQLSERVER中的LOB頁面簡單研究

這里為什麽要用LOB頁來存放索引數據呢?

本人認為因為要將數據轉為二進制並壓縮,所以用LOB頁來存放索引數據

 


 

測試和比較

下面建立一張非聚集索引表

 1 USE [pratice]
 2 GO
 3 
 4 CREATE TABLE TestTable2
 5 (
 6     id INT  ,
 7     c1 INT ,
 8     c2 INT
 9 )
10 
11 CREATE NONCLUSTERED  INDEX NCL__TestTabl__C1 ON TestTable2(c1)
12 
13 DECLARE @i INT
14 SET @i=1
15 WHILE @i<10001
16 BEGIN
17 INSERT INTO TestTable2 (id,c1)
18 SELECT @i,@i
19 SET @i=@i+1
20 END
21 
22 SELECT COUNT(*) FROM TestTable2
23 SELECT TOP 10 * from TestTable2
View Code

為什么用非聚集索引表來比較?

大家可以看一下列存儲索引的建立語句

1 CREATE NONCLUSTERED columnstore INDEX PK__TestTable__ColumnStore ON TestTable(c1)

在非聚集索引上加多了一個columnstore關鍵字

而且列存儲索引的表的頁面情況跟非聚集索引表的頁面情況幾乎是一樣的

除了LOB頁面,數據頁面還是在堆里面的

測試結果:

 1 SET NOCOUNT ON 
 2 SET STATISTICS IO ON
 3 SET STATISTICS TIME ON
 4 SELECT id FROM TestTable WHERE [C1]=60  --列存儲索引掃描 RID查找
 5 SQL Server 分析和編譯時間: 
 6    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
 7'TestTable'。掃描計數 1,邏輯讀取 37 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
 8 
 9  SQL Server 執行時間:
10    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
11 -------------------------------------------------------------
12 SET NOCOUNT ON 
13 SET STATISTICS IO ON
14 SET STATISTICS TIME ON
15 SELECT id FROM TestTable2 WHERE [C1]=60  --索引查找 RID查找
16 SQL Server 分析和編譯時間: 
17    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
18'TestTable2'。掃描計數 1,邏輯讀取 3 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
19 
20  SQL Server 執行時間:
21    CPU 時間 = 15 毫秒,占用時間 = 17 毫秒。
22 ----------------------------------------------------------------------------------

 

CPU執行時間非聚集索引要多一些

而邏輯讀取非聚集索引表比列存儲索引表少了37-3=34次

因為非聚集索引使用的是索引查找,找到索引頁就可以了,而列存儲索引還要掃描LOB頁面

----------------------------------------------------------------------------------

上面是沒有清空數據緩存和執行計划緩存的情況下的測試結果

下面是清空了數據緩存和執行計划緩存的情況下的測試結果

 1 USE [pratice]
 2 GO
 3 DBCC DROPCLEANBUFFERS
 4 DBCC freeproccache
 5 GO
 6 SET NOCOUNT ON 
 7 SET STATISTICS IO ON
 8 SET STATISTICS TIME ON
 9 SELECT id FROM TestTable2 WHERE [C1]=60  --索引查找 RID查找
10 
11 
12 SQL Server 分析和編譯時間: 
13    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
14 
15  SQL Server 執行時間:
16    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
17 SQL Server 分析和編譯時間: 
18    CPU 時間 = 0 毫秒,占用時間 = 1 毫秒。
19 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
20 
21  SQL Server 執行時間:
22    CPU 時間 = 0 毫秒,占用時間 = 2 毫秒。
23 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
24 
25  SQL Server 執行時間:
26    CPU 時間 = 0 毫秒,占用時間 = 18 毫秒。
27 SQL Server 分析和編譯時間: 
28    CPU 時間 = 63 毫秒,占用時間 = 95 毫秒。
29 
30  SQL Server 執行時間:
31    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
32 
33  SQL Server 執行時間:
34    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
35 
36  SQL Server 執行時間:
37    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
38 SQL Server 分析和編譯時間: 
39    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
40'TestTable2'。掃描計數 1,邏輯讀取 28 次,物理讀取 0 次,預讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
41 
42  SQL Server 執行時間:
43    CPU 時間 = 0 毫秒,占用時間 = 1 毫秒。
44 ---------------------------------------------------------------------
45 USE [pratice]
46 GO
47 DBCC DROPCLEANBUFFERS
48 DBCC freeproccache
49 GO
50 SET NOCOUNT ON 
51 SET STATISTICS IO ON
52 SET STATISTICS TIME ON
53 SELECT id FROM TestTable WHERE [C1]=60  --列存儲索引掃描 RID查找
54 
55 SQL Server 分析和編譯時間: 
56    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
57 
58  SQL Server 執行時間:
59    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
60 SQL Server 分析和編譯時間: 
61    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
62 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
63 
64  SQL Server 執行時間:
65    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
66 DBCC 執行完畢。如果 DBCC 輸出了錯誤信息,請與系統管理員聯系。
67 
68  SQL Server 執行時間:
69    CPU 時間 = 0 毫秒,占用時間 = 13 毫秒。
70 SQL Server 分析和編譯時間: 
71    CPU 時間 = 0 毫秒,占用時間 = 26 毫秒。
72 
73  SQL Server 執行時間:
74    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
75 
76  SQL Server 執行時間:
77    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
78 
79  SQL Server 執行時間:
80    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
81 SQL Server 分析和編譯時間: 
82    CPU 時間 = 0 毫秒,占用時間 = 0 毫秒。
83'TestTable'。掃描計數 1,邏輯讀取 40 次,物理讀取 1 次,預讀 68 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預讀 0 次。
84 
85  SQL Server 執行時間:
86    CPU 時間 = 0 毫秒,占用時間 = 41 毫秒。

 

可以看到列存儲索在執行時間上占優勢,但是在IO上比非聚集索引差一點點

 


 

列存儲索引所申請的鎖

 1 USE [pratice]
 2 GO
 3 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
 4 GO
 5 
 6 BEGIN TRAN
 7 SELECT id FROM TestTable WHERE [C1]=60  --列存儲索引掃描 RID查找
 8 
 9 --COMMIT TRAN
10 
11 USE [pratice] --要查詢申請鎖的數據庫
12 GO
13 SELECT
14 [request_session_id],
15 c.[program_name],
16 DB_NAME(c.[dbid]) AS dbname,
17 [resource_type],
18 [request_status],
19 [request_mode],
20 [resource_description],OBJECT_NAME(p.[object_id]) AS objectname,
21 p.[index_id]
22 FROM sys.[dm_tran_locks] AS a LEFT JOIN sys.[partitions] AS p
23 ON a.[resource_associated_entity_id]=p.[hobt_id]
24 LEFT JOIN sys.[sysprocesses] AS c ON a.[request_session_id]=c.[spid]
25 WHERE c.[dbid]=DB_ID('pratice') AND a.[request_session_id]=@@SPID  ----要查詢申請鎖的數據庫
26 ORDER BY [request_session_id],[resource_type]

可以看到雖然是列存儲索引掃描,但是也沒有在LOB頁面申請鎖,只是在普通數據頁面和真正的數據行上申請了鎖

使用列存儲索引,阻塞的機會也減少了

 


 

SQL Server 2012列存儲索引技術(雲棲社區)

https://yq.aliyun.com/articles/69254?spm=5176.100240.searchblog.59.hrBlST#

Batch Mode Processing 是SQL Server新的查詢處理算法,專門設計來高效地處理大量數據的批處理算法,以加快統計分析類查詢的效率。其實這個算法的原理實現非常簡單,SQL Server有一個專門的系統視圖sys.column_store_segments來存放列存儲索引的每個列的每個Segments的最小值和最大值,當SQL Server執行Batch Mode Processing查詢時,只需要和查詢篩選條件對比,
就可以知道對應的Segment是否包含滿足條件的數據,從而可以實現快速的跳過哪些不滿足條件的Segment
由於每個Segment中包含成千上萬條記錄,所以SQL Server篩選出滿足條件的效率非常高,因此大大節約了磁盤I/O和因此帶來的CPU開銷。這種跳過不滿足條件Segment的算法專業術語叫Segment Elimination。

Segment Elimination的做法:一個傳統聚集索引表,用drop_exist的方式創建聚集列存儲索引並用查詢提示maxdop=1

https://www.sqlpassion.at/archive/2017/01/30/columnstore-segment-elimination/?awt_l=BJCrA&awt_m=3ZdebPPtXEYUUTS

SET STATISTICS IO ON
能看到 Segment skipped 的數量

-- Now we create a traditional RowStore Clustered Index to sort our
-- table data by the column "DateKey".
CREATE CLUSTERED INDEX idx_ci ON FactOnlineSales_Temp(DateKey)
GO
 
-- "Swap" the Clustered Index through a Clustered ColumnStore Index
CREATE CLUSTERED COLUMNSTORE INDEX idx_ci ON FactOnlineSales_Temp
WITH (DROP_EXISTING = ON)
GO
CREATE CLUSTERED COLUMNSTORE INDEX idx_ci ON FactOnlineSales_Temp
WITH (DROP_EXISTING = ON, MAXDOP = 1)
GO

 

 

--查詢列存儲的壓縮方式
USE ColumnStoreDB
GO
SELECT DISTINCT
table_name = object_name(part.object_id)
,ix.name
,part.data_compression_desc 
FROM sys.partitions as part
INNER JOIN sys.indexes as ix
ON part.object_id = ix.object_id
AND part.index_id = ix.index_id
WHERE part.object_id = object_id('dbo.SalesOrder','U')
AND ix.name = 'NCSIX_ALL_Columns'

 

 

--查詢每個列存儲段的最大值和最小值
USE ColumnStoreDB
GO
SELECT 
table_name = object_name(part.object_id)
,col.name
,seg.segment_id
,seg.min_data_id
,seg.max_data_id
,seg.row_count
FROM sys.partitions as part
INNER JOIN sys.column_store_segments as seg
ON part.partition_id = seg.partition_id
INNER JOIN sys.columns as col
ON part.object_id = col.object_id
AND seg.column_id = col.column_id
WHERE part.object_id = object_id('dbo.SalesOrder','U')
AND seg.column_id = 1
ORDER BY seg.segment_id

 

 



row mode改為 batch mode

INNER JOIN使用Batch Mode而OUTER JOIN使用Row Mode

-- Batch mode processing will be used when INNER JOIN
SELECT 
    at.Model
    ,TotalAmount = SUM(ord.UnitPrice)
    ,TotalQty = SUM(ord.OrderQty)
FROM dbo.AutoType AS at
    INNER JOIN dbo.SalesOrder AS ord
    ON ord.AutoID = at.AutoID
GROUP BY at.Model

-- OUTER JOIN workaround
;WITH intermediateData
AS
(
    SELECT 
        at.AutoID
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
    FROM dbo.AutoType AS at
        INNER JOIN dbo.SalesOrder AS ord
        ON ord.AutoID = at.AutoID
    GROUP BY at.AutoID
)
SELECT 
    at.Model
    ,TotalAmount = ISNULL(itm.TotalAmount, 0)
    ,TotalQty = ISNULL(itm.TotalQty, 0)
FROM dbo.AutoType AS at
    LEFT OUTER JOIN intermediateData AS itm
    ON itm.AutoID = at.AutoID
ORDER BY itm.TotalAmount DESC

-------------------------------------------------------------
IN & EXISTS使用Row Mode
-- DEMO 2: IN & EXISTS both use row mode processing

IF OBJECT_ID('dbo.HondaAutoTypes', 'U') IS NOT NULL
BEGIN
    TRUNCATE TABLE dbo.HondaAutoTypes
    DROP TABLE dbo.HondaAutoTypes
END

SELECT *
    INTO dbo.HondaAutoTypes
FROM dbo.AutoType
WHERE make = 'Honda'

-- IN use row mode
SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
FROM dbo.SalesOrder AS ord
WHERE ord.AutoID IN(SELECT AutoID FROM dbo.HondaAutoTypes)
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
ORDER BY 1 DESC

-- EXISTS use row mode too.
SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
FROM dbo.SalesOrder AS ord
WHERE EXISTS(SELECT TOP 1 * FROM dbo.HondaAutoTypes WHERE AutoID = ord.AutoID)
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
ORDER BY 1 DESC


-- IN & EXISTS workaround using INNER JOIN
SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
FROM dbo.SalesOrder AS ord
    INNER JOIN dbo.HondaAutoTypes AS hat
    ON ord.AutoID = hat.AutoID
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
ORDER BY 1 DESC

-- or we also can use IN(<list of constants>) to make it use batch mode.
SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
FROM dbo.SalesOrder AS ord
WHERE ord.AutoID IN(104,106)
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
ORDER BY 1 DESC


-------------------------------------------------------------
UNION ALL

-- DEMO 3: UNION ALL usually use row mode

IF OBJECT_ID('dbo.partSalesOrder', 'U') IS NOT NULL
BEGIN
    TRUNCATE TABLE dbo.partSalesOrder
    DROP TABLE dbo.partSalesOrder
END

SELECT TOP 100 *
    INTO dbo.partSalesOrder
FROM dbo.SalesOrder
WHERE OrderID < 2500000;

-- UNION ALL mostly use row mode
;WITH unionSalesOrder
AS
(
    SELECT *
    FROM dbo.SalesOrder AS ord
    UNION ALL
    SELECT *
    FROM dbo.partSalesOrder AS pord

)

SELECT 
    OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
    ,TotalAmount = SUM(ord.UnitPrice)
    ,TotalQty = SUM(ord.OrderQty)
FROM dbo.AutoType AS at
    INNER JOIN unionSalesOrder AS ord
    ON ord.AutoID = at.AutoID
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
ORDER BY 1 DESC

-- UNION ALL workaround
;WITH unionSaleOrders
AS(
    SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
    FROM dbo.AutoType AS at
        INNER JOIN SalesOrder AS ord
        ON ord.AutoID = at.AutoID
    GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
), unionPartSaleOrders
AS
(
    SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,TotalAmount = SUM(ord.UnitPrice)
        ,TotalQty = SUM(ord.OrderQty)
    FROM dbo.AutoType AS at
        INNER JOIN dbo.partSalesOrder AS ord
        ON ord.AutoID = at.AutoID
    GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
), unionAllData
AS
(
    SELECT *
    FROM unionSaleOrders
    UNION ALL
    SELECT *
    FROM unionPartSaleOrders
)
SELECT 
    OrderDay
    ,TotalAmount = SUM(TotalAmount)
    ,TotalQty = SUM(TotalQty)
FROM unionAllData
GROUP BY OrderDay
ORDER BY OrderDay DESC

-------------------------------------------------------------
Scalar Aggregates

-- DEMO 4: Scalar Aggregates
SELECT COUNT(*)
FROM dbo.SalesOrder

-- workaround 
;WITH salesOrderByAutoId([AutoID], cnt)
AS(
    SELECT [AutoID], count(*)
    FROM dbo.SalesOrder
    GROUP BY [AutoID]
)
SELECT SUM(cnt)
FROM salesOrderByAutoId

-- END DEMO 4

-------------------------------------------------------------
Multiple DISTINCT Aggregates

-- DEMO 5: Multiple DISTINCT Aggregates
SELECT 
        OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
        ,AutoIdCount = COUNT(DISTINCT ord.[AutoID])
        ,UserIdCount = COUNT(DISTINCT ord.[UserID])
FROM dbo.AutoType AS at
    INNER JOIN dbo.SalesOrder AS ord
    ON ord.AutoID = at.AutoID
GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)

-- workaround
;WITH autoIdsCount(orderDay, AutoIdCount)
AS(
    SELECT 
            OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
            ,AutoIdCount = COUNT(DISTINCT ord.[AutoID])
    FROM dbo.AutoType AS at
        INNER JOIN dbo.SalesOrder AS ord
        ON ord.AutoID = at.AutoID
    GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
), userIdsCount(orderDay, UserIdCount)
AS(
    SELECT 
            OrderDay = CONVERT(CHAR(10), ord.OrderDate, 120)
            ,UserIdCount = COUNT(DISTINCT ord.[UserID])
    FROM dbo.AutoType AS at
        INNER JOIN dbo.SalesOrder AS ord
        ON ord.AutoID = at.AutoID
    GROUP BY CONVERT(CHAR(10), ord.OrderDate, 120)
)
SELECT
    auto.orderDay
    ,auto.AutoIdCount
    ,ur.UserIdCount
FROM autoIdsCount AS auto
    INNER JOIN userIdsCount AS ur
    ON auto.orderDay = ur.orderDay
-- END DEMO 5



 

 


 

最后,如有不對的地方,歡迎大家拍磚哦o(∩_∩)o 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM