【问题标题】:How can I normalize the capitalization of a group-by column?如何规范分组列的大写?
【发布时间】:2017-01-30 22:24:52
【问题描述】:

在配置为不区分大小写的 SQL Server 上,当 [n][var]char 列不是第一个 group by 列时,group by 可能会产生有趣的结果。本质上,它看起来就像遇到“第一”的任何行(在没有订单的情况下“第一”是未定义的):该分组获胜。例如:

select x.[day], x.[name], count(1) as [count]
from (
    select 1 as [day], 'a' as [name]
    union all select 1, 'A'
    union all select 2, 'A'
    union all select 2, 'a'
    ) x group by x.[day], x.[name]

返回,对我来说:

day         name count
----------- ---- -----------
1           A    2
2           a    2

使用min(x.[name]) 无效,因为分组已经发生。

我不能在group by 之前添加order by 之前,因为那是非法的;并添加order by 之后 group by 只是定义分组后的输出顺序 - 它仍然给出aA

那么:有没有一种理智的方式来做到这一点,其中所有分组的大小写至少是一致的? (我将在另一天留下单独运行的一致性问题)

所需的输出,或者:

day         name count
----------- ---- -----------
1           A    2
2           A    2

或:

day         name count
----------- ---- -----------
1           a    2
2           a    2

编辑:在组间一致时破坏大写。所以没有上/下。因此,如果其中一个组的值始终为BcDeF,我希望该行的结果为BcDeF,而不是bcdefBCDEF

【问题讨论】:

  • 您希望(1, 'A'), (2, 'a') 的输出是什么?为(1, 'A'), (2, 'a'), (3, 'A')?为(1, 'A'), (1, 'a'), (2, 'A'), (2, 'a')?
  • @AndriyM 很好的问题;也许这在数据库层无法解决;我天真地想“第二列中的所有'a'或所有'A'”,但是......
  • 为了完整起见,我最终通过标准化返回的结果在调用站点“修复”了这个问题。窗口函数方法可能最接近我的需要,但在我的场景中(运行时生成的任意复杂度的报告查询)实施起来并不实际。
  • @MarcGravell 如果数据在 Andriy 的第一个示例中具有不同标题的组,那么在任何层都不可能。并非没有“破坏”某些群体的资本化。如果你想在数据库中这样做,Lamak 的答案是有效的。
  • @ypercubeᵀᴹ 这取决于;这在调用层工作得很好——关于选择哪个版本有点随意,但至少每次运行都是一致的,这对我的目的来说已经足够了:gist.github.com/mgravell/c4385cef8121e6aa10e6cb9773ff838c

标签: sql sql-server group-by case-sensitive case-insensitive


【解决方案1】:

Group by 中使用不区分大小写的排序规则,例如:

select day, name, count(*)
from tablename
group by day, name collate SQL_Latin1_General_Cp1_CI_AS_KI_WI

也许 SQL Server 在这里有问题?使用另一个 dbms 执行如下:

SQL>create table t (d int, name varchar(10));
SQL>insert into t values (1,'A');
SQL>insert into t values (2,'A');
SQL>insert into t values (2,'a');
SQL>insert into t values (3,'BcDeF');
SQL>insert into t values (3,'bCdEf');
SQL>insert into t values (4,'a');
SQL>select d, name, count(*)
SQL&from t
SQL&group by d, name collate english_1;
          d name
=========== ========== ====================
          1 A                             1
          2 A                             2
          3 BcDeF                         2
          4 a                             1

                  4 rows found

english_1 是不区分大小写的排序规则。

如预期的那样?

【讨论】:

  • 我的意思是在组间一致时不破坏大写,抱歉:我应该更清楚
  • 我得到Invalid collation 'SQL_Latin1_General_Cp1_CI_AS_KI_WI' - 但我看不出这有什么帮助:整个服务器已经不区分大小写了。我可以看到更改为 区分大小写 会如何改变结果,但随后我会得到每个不同值的分组,这也不是我想要的:)
  • 所以where name = 'A' 的结果与where name = 'a' 的结果相同?
【解决方案2】:

我会为此使用窗口函数。通过使用ROW_NUMBER 并使用不区分大小写的排序规则进行分区,但按区分大小写的排序规则,我们将始终选择一个具有原始大写的结果,但它会将它们分组为相同的:

WITH CTE AS
(
    SELECT  *,
            RN = ROW_NUMBER() OVER(PARTITION BY [day], [name]
                                   ORDER BY [name] COLLATE SQL_Latin1_General_Cp1_Cs_AS),
            N = COUNT(*) OVER(PARTITION BY [day], [name])
    FROM (  select 1 as [day], 'a' as [name]
            union all select 1, 'A'
            union all select 2, 'A'
            union all select 2, 'a'
            union all select 3, 'BcDeF'
            union all select 3, 'bCdEf') X
)
SELECT *
FROM CTE
WHERE RN = 1;

返回:

╔═════╦═══════╦════╦═══╗
║ day ║ name  ║ RN ║ N ║
╠═════╬═══════╬════╬═══╣
║   1 ║ A     ║  1 ║ 2 ║
║   2 ║ A     ║  1 ║ 2 ║
║   3 ║ BcDeF ║  1 ║ 2 ║
╚═════╩═══════╩════╩═══╝

根据@AndriyM 的评论,如果您想在整个结果集中使用相同的大小写,而不仅仅是同一天,您可以使用:

WITH CTE AS
(
    SELECT  *,
            RN = ROW_NUMBER() OVER(PARTITION BY [day], [name]
                                   ORDER BY [name] COLLATE SQL_Latin1_General_Cp1_Cs_AS),
            N = COUNT(*) OVER(PARTITION BY [day], [name])
    FROM (  select 1 as [day], 'a' as [name]
            union all select 1, 'A'
            union all select 2, 'A'
            union all select 2, 'a'
            union all select 3, 'BcDeF'
            union all select 3, 'bCdEf') X
)
SELECT  [day],
        MAX([name] COLLATE SQL_Latin1_General_Cp1_CS_AS) OVER (PARTITION BY [name]) [name],
        N
FROM CTE
WHERE RN = 1;

【讨论】:

    【解决方案3】:

    您可以在GROUP BY 子句中使用UPPER 将所有值转换为相同的大小写。

    【讨论】:

    • 我的意思是在组间一致时不破坏大写,抱歉:我应该更清楚
    【解决方案4】:

    使用upper()lower()

    select x.[day], lower(x.[name]) as name, count(1) as [count]
    from (
        select 1 as [day], 'a' as [name]
        union all select 1, 'A'
        union all select 2, 'A'
        union all select 2, 'a'
        ) x
    group by x.[day], x.[name];
    

    SQL Server 从不确定的行中选择一个值是正确的。 min()max() 无济于事,因为它们的值是相等的。最简单的解决方案是明确选择您想要的案例。

    【讨论】:

    • 你不需要group by x.[day], lower(x.[name])吗?
    • 我的意思是在组间一致时不破坏大写,抱歉:我应该更清楚
    猜你喜欢
    • 2010-11-12
    • 1970-01-01
    • 2022-12-22
    • 1970-01-01
    • 1970-01-01
    • 2016-11-02
    • 1970-01-01
    • 2015-03-03
    • 2020-01-01
    相关资源
    最近更新 更多