从组中的每个列中检索最常见的值

时间:2014-11-21 14:11:20

标签: sql-server sql-server-2012

对于使用字段GRP分组的每个组,我想检索A列中最常出现的值和B列中最常出现的值,并且可能对许多其他列执行此操作。

示例数据:

GRP | A | B
-----------
Cat | 1 | 1
Cat | 2 | 1
Cat | 3 | 2
Cat | 3 | 3
Dog | 5 | 6
Dog | 5 | 7
Dog | 6 | 7

预期产出:

GRP | A | B
-----------
Cat | 3 | 1
Dog | 5 | 7

此查询实现了该结果:

SELECT 
    freq1.GRP,
    freq1.A,
    freq2.B
FROM (
        SELECT
            GRP,
            A,
            ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY COUNT(*) DESC) AS F_RANK
        FROM MyTable
        GROUP BY GRP, A
) AS freq1
INNER JOIN (
        SELECT
            GRP,
            B,
            ROW_NUMBER() OVER(PARTITION BY GRP ORDER BY COUNT(*) DESC) AS F_RANK
        FROM MyTable
        GROUP BY GRP, B
) AS freq2 ON freq2.GRP = freq1.GRP
WHERE freq1.F_RANK = 1 AND freq2.F_RANK = 1

它看起来效率不高,如果我要添加C,D等列,那就更不用了......

有更好的方法吗?

4 个答案:

答案 0 :(得分:2)

我不会说这种方法更好"因为它会生成完全相同的执行计划。但是,随着列数的增加,我发现这种方法更易于维护。对我来说,这更容易阅读。

with GroupA as
(
    select Grp
        , A
        , ROW_NUMBER() over(partition by grp order by count(*) desc) as RowNum
    from MyTable
    group by Grp, A
)
, GroupB as
(
    select Grp
        , B
        , ROW_NUMBER() over(partition by grp order by count(*) desc) as RowNum
    from MyTable
    group by Grp, B
)

select a.Grp
    , a.A
    , b.B
from GroupA a
inner join GroupB b on a.Grp = b.Grp and b.RowNum = 1
where a.RowNum = 1;

答案 1 :(得分:0)

使用临时表中排名结果的替代方法:

SELECT  GRP, A, B,
    ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) ARank, 
    ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) BRank
INTO #TMP
FROM MyTable

SELECT t1.GRP, 
    (SELECT TOP 1 A FROM #TMP WHERE GRP = t1.Grp ORDER BY ARank DESC) A, 
    (SELECT TOP 1 B FROM #TMP WHERE GRP = t1.Grp ORDER BY BRank DESC) B 
FROM MyTable t1
GROUP BY T1.GRP 

DROP TABLE #TMP

Full Solution on SQL Fiddle

架构设置

CREATE TABLE MyTable
    ([GRP] varchar(3), [A] int, [B] int)
;

INSERT INTO MyTable
    ([GRP], [A], [B])
VALUES
    ('Cat', 1, 1),
    ('Cat', 2, 1),
    ('Cat', 3, 2),
    ('Cat', 3, 3),
    ('Dog', 5, 6),
    ('Dog', 5, 7),
    ('Dog', 6, 7)
;

查询1

SELECT  GRP, A, B,
    ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) ARank, 
    ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) BRank
INTO #TMP
FROM MyTable

SELECT t1.GRP, 
    (SELECT TOP 1 A FROM #TMP WHERE GRP = t1.Grp ORDER BY ARank DESC) A, 
    (SELECT TOP 1 B FROM #TMP WHERE GRP = t1.Grp ORDER BY BRank DESC) B 
FROM MyTable t1
GROUP BY T1.GRP 

DROP TABLE #TMP

<强> Results

| GRP | A | B |
|-----|---|---|
| Cat | 3 | 1 |
| Dog | 5 | 7 |

答案 2 :(得分:0)

我会从这个答案开始,说 NOT 会更有效地运行 - 它应该更容易添加/减去列。要做到这一点,您只需将它们添加到两个地方的代码中。

您可以使用动态SQL来构建结果集,如下所示:

CREATE TABLE ##fields (id INT IDENTITY(1,1),fieldname VARCHAR(255))
INSERT INTO ##fields
        ( fieldname )
VALUES  ('A'),('B') --Add field names here
DECLARE @maxid INT
SELECT @maxid = MAX(id) FROM ##fields

CREATE TABLE ##Output (GRP VARCHAR(3), A INT, B INT) --Add field names here

INSERT INTO ##Output
        ( GRP )
SELECT DISTINCT GRP FROM MyTable

DECLARE @SQL NVARCHAR(MAX)
DECLARE @i INT = 1

WHILE @i <=@maxid
    BEGIN
        SELECT @SQL = 'with cte as (SELECT  GRP , ' + fieldname + ' ,
                                            ROW_NUMBER() OVER ( PARTITION BY GRP ORDER BY COUNT(*) DESC ) AS F_RANK
                                    FROM    MyTable
                                    GROUP BY GRP , ' + fieldname + ')
                        UPDATE O
                        SET O.' + fieldname + ' = cte.' + fieldname + '
                        FROM ##Output O
                        INNER JOIN cte ON O.GRP = cte.GRP AND cte.F_Rank = 1' FROM ##fields WHERE id = @i
        EXEC sp_executesql @sql
        SET @i = @i + 1
    END


SELECT *
FROM ##Output

DROP TABLE ##fields
DROP TABLE ##Output

使用上面的简单示例,我收到了以下效果统计信息:

动态SQL CPU = 31 读取= 504 持续时间= 39

您的SQL CPU = 0 读数= 6 持续时间= 1

显然,这种方式不是一种更有效的方法。无论如何,我确实想把它扔出去作为你现有方法的替代品。

答案 3 :(得分:0)

首先我们创建测试数据:

DECLARE @MyTable TABLE
(
    GRP varchar(10),
    A int,
    B int
)

INSERT INTO @MyTable
( GRP, A, B)
VALUES
('Cat', 1, 1),
('Cat', 2, 1),
('Cat', 3, 2),
('Cat', 3, 3),
('Dog', 5, 6),
('Dog', 5, 7),
('Dog', 6, 7);

现在我们使用subselect中的first_value(或者你想要的cte)并抓住顶部的cat and dog列

SELECT DISTINCT
    GRP,
    FIRST_VALUE(A) OVER(PARTITION BY GRP ORDER BY d.A_CNT DESC) AS A_RANK,
    FIRST_VALUE(B) OVER(PARTITION BY GRP ORDER BY d.B_CNT DESC) AS B_RANK
FROM
(
    SELECT 
        GRP, 
        A, 
        ROW_NUMBER() OVER (PARTITION BY A ORDER BY GRP, A) AS A_CNT,
        B, 
        ROW_NUMBER() OVER (PARTITION BY B ORDER BY GRP, B) AS B_CNT
    FROM @MyTable
) d

输出:

GRP A_RANK  B_RANK
Cat 3   1
Dog 5   7