【问题标题】:Sorting twice on same column在同一列上排序两次
【发布时间】:2012-08-10 21:34:09
【问题描述】:

我有一个客户向我提出的奇怪问题。

他有一个数据列表,括号之间的日期如下:

Foo (14/08/2012)
Bar (15/08/2012)
Bar (16/09/2012)
Xyz (20/10/2012)

但是,他希望列表显示如下:

Foo (14/08/2012)
Bar (16/09/2012)
Bar (15/08/2012)
Foot (20/10/2012)

(请注意,第二个 Bar 已上移一位)

因此,其背后的逻辑是,列表必须按日期升序排序,除非两行具有相同的名称('Bar')。如果它们具有相同的名称,则必须以 LATEST 日期在顶部进行排序,同时保持其他排序顺序。

这甚至是遥不可及的吗?我已经尝试了很多ORDER BY 子句,但找不到合适的子句。有人有想法吗?

我应该指定此数据来自 sql server 数据库中的表(名称和日期位于两个不同的列中)。所以我正在寻找可以进行我想要的排序的 SQL 查询。

(我已经把这个例子简化了很多,所以如果你需要更多的上下文,请不要犹豫)

【问题讨论】:

  • 如果Bars 之一的日期是 2012 年 8 月 13 日而不是 15 日(所以 Foo 行应该在它们之间,没有其他规则) - 那么会发生什么?
  • 更新了我的问题。我正在使用 SQL 服务器。
  • 它们只有在相邻时才需要按指定顺序排列。因此,如果它们之间还有另一条记录,那没关系,它们应该按日期升序排序。只有当两个同名记录相邻时,才应该降序排列。

标签: sql sql-server sorting


【解决方案1】:

我认为这行得通

declare @t table (data varchar(50), date datetime)

insert @t 
values
('Foo','2012-08-14'),
('Bar','2012-08-15'),
('Bar','2012-09-16'), 
('Xyz','2012-10-20')

select t.*
from @t t
    inner join (select data, COUNT(*) cg, MAX(date) as mg from @t group by data) tc
        on t.data = tc.data
order by case when cg>1 then mg else date end, date desc

产生

data       date
---------- -----------------------
Foo        2012-08-14 00:00:00.000
Bar        2012-09-16 00:00:00.000
Bar        2012-08-15 00:00:00.000
Xyz        2012-10-20 00:00:00.000

【讨论】:

  • @Steven - 除非这不能满足仅对“相邻”记录进行排序的要求 - 它会将具有相同数据的所有记录一起移动。
【解决方案2】:

比其他发布的任何答案都具有更好性能的方法是完全使用ORDER BY 而不是JOIN 或使用CTE

DECLARE @t TABLE (myData varchar(50), myDate datetime)

INSERT INTO @t VALUES 
('Foo','2012-08-14'),
('Bar','2012-08-15'),
('Bar','2012-09-16'), 
('Xyz','2012-10-20')

SELECT *
FROM @t t1
ORDER BY (SELECT MIN(t2.myDate) FROM @t t2 WHERE t2.myData = t1.myData), T1.myDate DESC

这完全符合您的要求,并且可以与任何索引一起使用,并且比其他任何答案都更好地处理大量数据。

此外,您在此处实际尝试执行的操作更加清楚,而不是使用连接的复杂性来掩盖真实逻辑并检查连接项的数量。

【讨论】:

    【解决方案3】:

    这个使用analytic functions 来执行排序,它只需要从你的表中选择一个。

    内部查询查找名称更改的间隙。这些间隙用于识别下一个查询中的组,外部查询按这些组进行最终排序。

    我已经用扩展的测试数据尝试了here (SQL Fiddle)

    SELECT name, dat
    FROM (
      SELECT name, dat, SUM(gap) over(ORDER BY dat, name) AS grp
      FROM (
        SELECT name, dat,
              CASE WHEN LAG(name) OVER (ORDER BY dat, name) = name THEN 0 ELSE 1 END AS gap
        FROM t
      ) x
    ) y
    ORDER BY grp, dat DESC
    

    扩展测试数据

    ('Bar','2012-08-12'),
    ('Bar','2012-08-11'),
    ('Foo','2012-08-14'),
    ('Bar','2012-08-15'),
    ('Bar','2012-08-16'),
    ('Bar','2012-09-17'),
    ('Xyz','2012-10-20')
    

    结果

    Bar     2012-08-12
    Bar     2012-08-11
    Foo     2012-08-14
    Bar     2012-09-17
    Bar     2012-08-16
    Bar     2012-08-15
    Xyz     2012-10-20
    

    【讨论】:

      【解决方案4】:

      我认为这行得通,包括我在 cmets 中询问的案例:

      declare @t table (data varchar(50), [date] datetime)
      
      insert @t 
      values
      ('Foo','20120814'),
      ('Bar','20120815'),
      ('Bar','20120916'), 
      ('Xyz','20121020')
      
      ; With OuterSort as (
          select *,ROW_NUMBER() OVER (ORDER BY [date] asc) as rn from @t
      )
      --Now we need to find contiguous ranges of the same data value, and the min and max row number for such a range
      , Islands as (
          select data,rn as rnMin,rn as rnMax from OuterSort os where not exists (select * from OuterSort os2 where os2.data = os.data and os2.rn = os.rn - 1)
          union all
          select i.data,rnMin,os.rn
          from
              Islands i
                  inner join
              OuterSort os
                  on
                      i.data = os.data and
                      i.rnMax = os.rn-1
      ), FullIslands as (
          select
              data,rnMin,MAX(rnMax) as rnMax
          from Islands
          group by data,rnMin
      )
      select
          *
      from
          OuterSort os
              inner join
          FullIslands fi
              on
                  os.rn between fi.rnMin and fi.rnMax
      order by
          fi.rnMin asc,os.rn desc
      

      它首先计算OuterSort CTE 中的初始排序。然后,使用两个 CTE(IslandsFullIslands),我们计算该排序中相同数据值出现在相邻行中的部分。完成后,我们可以通过所有相邻值将具有的任何值(例如它们所属的“岛”的最低行号)计算最终排序,然后在“岛”内,我们使用相反的最初计算的排序顺序。

      请注意,这对于大型数据集可能不太有效。在示例数据中,它显示需要对基表进行 4 次表扫描,以及一个假脱机。

      【讨论】:

      • 如果你愿意,检查my answer,它会产生与你相同的结果,但只执行 1 次表扫描...
      【解决方案5】:

      尝试类似...

      ORDER BY CASE date
          WHEN '14/08/2012' THEN 1
          WHEN '16/09/2012' THEN 2
          WHEN '15/08/2012' THEN 3
          WHEN '20/10/2012' THEN 4
      END
      

      在 MySQL 中,你可以这样做:

      ORDER BY FIELD(date, '14/08/2012', '16/09/2012', '15/08/2012', '20/10/2012')
      

      在 Postgres 中,您可以创建一个函数 FIELD 并执行以下操作:

      CREATE OR REPLACE FUNCTION field(anyelement, anyarray) RETURNS numeric AS $$
        SELECT
          COALESCE((SELECT i
                    FROM generate_series(1, array_upper($2, 1)) gs(i)
                    WHERE $2[i] = $1),
                   0);
      $$ LANGUAGE SQL STABLE
      

      如果你不想使用 CASE,你可以尝试寻找一个 FIELD 函数到 SQL Server 的实现。

      【讨论】:

      • NO - 因为 OP 不太可能知道 all 需要排序的日期,这实际上是基于出现在序列。
      猜你喜欢
      • 1970-01-01
      • 2012-05-12
      • 2018-05-03
      • 2014-07-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-07
      • 2012-05-29
      • 1970-01-01
      相关资源
      最近更新 更多