在同一列上排序两次

时间:2012-08-14 14:46:26

标签: sql sql-server sorting

我有一个奇怪的问题,由客户给我。

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

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')。如果它们具有相同的名称,则必须使用最顶层的最新日期进行排序,同时保持其他排序顺序。

这甚至可以远程实现吗?我已经尝试了很多ORDER BY条款,但找不到合适的条款。有没有人有想法?

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

(我已经把这个例子愚蠢了,所以如果你需要更多的背景,请不要犹豫)

5 个答案:

答案 0 :(得分:4)

这很有效,我想

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

答案 1 :(得分:1)

与其他任何已发布的答案相比,性能更佳的方法是完全使用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

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

另外,你在这里尝试做的事情要清楚得多,而不是用连接的复杂性掩盖真实逻辑并检查连接项目的数量。

答案 2 :(得分:1)

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

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

我已尝试使用扩展测试数据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

答案 3 :(得分:0)

我认为这是有效的,包括我在评论中询问的案例:

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次表扫描,以及一个假脱机。

答案 4 :(得分:-2)

尝试类似......

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')

在potgres中,可以创建函数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,可以尝试在SQL Server中找到FIELD函数的实现。