选择未聚合的变量,功能上依赖于分组变量

问题描述 投票:0回答:0

背景

我正在一个非常受限的 T-SQL 环境中工作,在该环境中,人们只能定义

VIEW
的“主体”:大概是
...
中的

CREATE VIEW My_View AS ...

在引擎盖下。这是我的

@@VERSION
:

Microsoft SQL Server 2019 (RTM-CU19) (KB5023049) - 15.0.4298.1 (X64)
2023 年 1 月 27 日 16:44:09
版权所有 (C) 2019 微软公司
Linux (Amazon Linux 2) 上的网络版(64 位)

我应该注意,这些表格是从“平面文件”同步的,因此,原始来源中没有保留“正式”原理图结构。也就是说,所有“功能依赖关系”都只是从列名称和业务概念推断(尽管可靠)。 问题 假设我有下表,名为

My_Measures

...


人员_ID姓名1格雷格格雷格德文郡德文郡...其中 Name
测量
0 1
10 2
20 2
30
在功能上依赖于
Person_ID

平常
现在假设我希望将 

Measure

聚合

为每个人的各种汇总统计数据。这在 SQL 中很简单... SELECT Person_ID, MIN(Measure) AS Min_Measure, MAX(Measure) AS Max_Measure, AVG(Measure) AS Avg_Measure FROM My_Measures GROUP BY Person_ID

...并产生以下结果:


人员_ID最小_测量1020扭曲
最大测量值 平均_测量
10 5 2
30 25
但是假设我希望在每个
Name

旁边包含

Person_ID

,如下所示:



人员_ID姓名1格雷格德文郡显然,以下尝试...
最小_测量 最大测量值 平均_测量
0 10 5 2
20 30 25
SELECT Person_ID, -- ⌄⌄⌄⌄⌄ Name, -- ^^^^^ MIN(Measure) AS Min_Measure, MAX(Measure) AS Max_Measure, AVG(Measure) AS Avg_Measure FROM My_Measures GROUP BY Person_ID

...将因以下错误而失败:

列“My_Measures.Name”在选择列表中无效,因为它未包含在聚合函数或 GROUP BY 子句中。

尝试

我找到了几种
un

令人满意的方法来产生预期的输出。

(1) GROUP BY 因变量

一种方法是
GROUP BY

Name

after
Person_ID
;更一般地说,将因变量附加在 GROUP BY 子句的
end
处: SELECT Person_ID, -- ⌄⌄⌄⌄⌄ Name, -- ^^^^^ MIN(Measure) AS Min_Measure, MAX(Measure) AS Max_Measure, AVG(Measure) AS Avg_Measure FROM My_Measures GROUP BY -- ⌄⌄⌄⌄⌄⌄ Person_ID, Name -- ^^^^^^
这使得分组保持不变,因为“真实”分组变量(此处

Person_ID
)已经定义了它,而因变量只是“标记”。然而,这会浪费处理(任意多个)因变量的性能,出于索引目的,这些因变量可能会更复杂(
CHAR

字符串表示

Name
)。
(2)“聚合”因变量
另一种方法是“聚合”

Name

列,使用一些函数(如

MIN()

)为我们提供来自许多相同重复项(如

'Greg'
)的一个代表值(如
('Greg', 'Greg')
)。
SELECT
    Person_ID,
--  ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
    MIN(Name)    AS Name,
--  ^^^^^^^^^^^^^^^^^^^^^
    MIN(Measure) AS Min_Measure,
    MAX(Measure) AS Max_Measure,
    AVG(Measure) AS Avg_Measure
FROM 
    My_Measures
GROUP BY 
    Person_ID
这同样达到了预期的结果,但同样浪费了计算许多相同值的聚合的性能。此外,它只适用于 

comparable
 并因此具有 
MIN()

的值;但对于可比较的数据类型来说,它显然会失败。

(3) 聚合后重新JOIN

也许最令人失望的方法是简单地计算聚合,然后通过

Person_ID
:

Name

与其

JOIN
重新关联
-- Aggregate by ID.
WITH agg AS(
    SELECT
        Person_ID,
        MIN(Measure) AS Min_Measure,
        MAX(Measure) AS Max_Measure,
        AVG(Measure) AS Avg_Measure
    FROM 
        My_Measures
    GROUP BY 
        Person_ID
    
-- Deduplicate names for the JOIN. Given functional dependency, DISTINCT suffices.
), msr AS (
    SELECT DISTINCT
        Person_ID,
        Name
    FROM My_Measures
    
-- Reassociate the names with their IDs.
) SELECT
    agg.Person_ID   AS Person_ID,
--  ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
    msr.Name        AS Name,
--  ^^^^^^^^^^^^^^^^^^^^^^^^
    agg.Min_Measure AS Min_Measure,
    agg.Max_Measure AS Max_Measure,
    agg.Avg_Measure AS Avg_Measure
FROM
--      ⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄⌄
    agg INNER JOIN msr
--      ^^^^^^^^^^^^^^
    ON agg.Person_ID = msr.Person_ID
显然,这在不必要的
JOIN

和多个CTE上浪费了大量资源,所有这些都是为了
恢复
我们最初

拥有

的数据(如
Name)!
(4) 将 
FIRST_VALUE() 越过 PARTITION

我在 T-SQL 中搜索了 R 中 
first()

函数的等效项。在 SQL 中,这样的

FIRST() 只需从许多相同的重复值 (

) 中选择非常 
first 值 (
'Greg'
) ('Greg', 'Greg'))在GROUP
内,不需要任何昂贵的计算。此外,无论可比性如何,这都会起作用。
我偶然发现了 
FIRST_VALUE()
函数,但这似乎每次使用都需要一个 PARTITION

,而且——由于我对优化

PARTITION 相对缺乏经验——我担心如果

很多的话会对性能产生影响
因变量必须是 SELECT
ed。
它看起来也很丑。 ́\
(ツ)
问题

将任意一组因变量(如

SELECT)与分组变量(如Name

)一起使用的最佳方法是什么?请优先考虑

性能

,但也要考虑

优雅

规范性
,最后是
可扩展性
:理想情况下,这应该适用于
所有数据类型,甚至是可比较的数据类型。 Person_ID
sql-server t-sql group-by aggregate functional-dependencies
© www.soinside.com 2019 - 2024. All rights reserved.