有没有更有效的方法将多个 select 语句的第一行聚合成一个结果?

Is there a more efficient way to aggregate the top row from multiple select statements into a single result?

好吧,我在各种数据库方面都有经验,但对 Oracle 不是特别了解,所以我仍在努力思考它如何处理事情(即缺少 'Top' 让我发疯!)无论如何,这就是我们正在努力做...

注意:我们使用的是 Oracle 11。我知道 Oracle 12 中添加了一项新功能(即 FETCH NEXT n ROWS ONLY),但不幸的是我们不能使用它。

首先,我们有一篇文章 table(从技术上讲,它是一个视图,但我在这里对其进行了简化)。以下是相关字段...

多篇文章可以标记为收藏夹and/or精选。

我们想要 return 的结果集具有以下顺序...

  1. most-recently 发表的文章IsFeatured = 'Y',如果有的话。
  2. most-recently 发表的文章 IsFavorite = 'Y' 不是来自 #1 的行(如果最近的精选也是最近的收藏,这可以避免重复,而是选择 下一个最喜欢的行)如果有的话
  3. 三篇most-recently发表的非#1或#2的文章,如果有的话

这是我到目前为止的想法(cut/pasted/edited 这里可能有一些拼写错误),但这对我来说 'clunky' 感觉太...就像我在浪费许多不必要的处理和迭代。

WITH mostRecentlyFeatured as (
    Select * from (
        Select 2 as MAJOR_SORT, A.*
        From Articles A
        where IsFeatured = 'Y'
        order by DatePublished DESC
    )
    where ROWNUM = 1
),
mostRecentlyFavorited as (
    Select * from (
        Select 1 as MAJOR_SORT, A.*
        From Articles A
        minus Select * From mostRecentlyFeatured
        where IsFavorite = 'Y'
        order by DatePublished DESC
    )
    where ROWNUM = 1
),
topThreeOthers as (
    Select * from (
        select 0 as MAJOR_SORT, A.*
        from Articles
        minus
        SELECT * from mostRecentlyFeatured
        minus
        SELECT * from mostRecentlyFavorited
        order by DatePublished desc
    )
    where ROWNUM <= 3
),
finalRows as (
    Select * from mostRecentlyFeatured
    union all
    Select * from mostRecentlyFavorited
    union all
    select * from topThreeOthers
)
Select * from finalRows
Order By MAJOR_SORT    DESC,
         DatePublished DESC;

这不是最不常见的查询,所以我无法想象没有更好的方法来执行此操作,但我还没有看到它。那么有吗?

the lack of 'Top' drives me mad

由于您使用的是 Oracle 11g,因此不支持 TOP-n。所以 ROWNUM 是唯一的出路。参见

例如,偏移量为 4 并使用 ROWNUM 获取下一个 4:

SQL> SELECT val
     FROM   (SELECT val, rownum AS rnum
            FROM   (SELECT val
                    FROM   order_test
                    ORDER BY val)
            WHERE rownum <= 8)
     WHERE  rnum >= 5;

       VAL
----------
         3
         3
         4
         4

Oracle 12c 开始,您可以使用 Top-n row limiting 功能。由于您没有提供任何示例数据,这里有一个简单的演示:

SQL> select * from order_test order by val;

       VAL
----------
         1
         1
         2
         2
         3
         3
         4
         4
         5
         5
         6
         6
         7
         7
         8
         8
         9
         9
        10
        10

20 rows selected.

前 4 行:

SQL> SELECT val
  2  FROM   order_test
  3  ORDER BY VAL
  4  FETCH FIRST 4 ROWS ONLY;

       VAL
----------
         1
         1
         2
         2

下 4 行(查看 OFFSET) :

SQL> SELECT val
  2  FROM   order_test
  3  ORDER BY VAL
  4  OFFSET 4 ROWS FETCH NEXT 4 ROWS ONLY;

       VAL
----------
         3
         3
         4
         4

最后,接下来的 4 行 OFFSET 8 行:

SQL> SELECT val
  2  FROM   order_test
  3  ORDER BY VAL
  4  OFFSET 8 ROWS FETCH NEXT 4 ROWS ONLY;

       VAL
----------
         5
         5
         6
         6

使用上面的方法,你可以得到标记为'IsFeatured'的最新发表的文章如下:

SELECT * FROM articles
WHERE isfeatured = true
ORDER BY datepublished DESC
FETCH FIRST ONE ROW ONLY;

这是您的查询的可能解决方案。它使用一组 CTE 来 select 第 n 篇精选文章、第 m 篇最喜欢的文章(不在第 n 篇精选文章中)以及总共k 篇文章:

  • 第一个生成特色文章和收藏文章的行号;
  • 第二个计算前 n 篇精选文章中有多少篇也是前 m 篇收藏;和
  • 第三个为根据第一个 n 个精选文章、第一个 m 个收藏夹排序的文章生成行号(除非它们也属于第一个 n 个精选文章,在这种情况下选择下一个收藏夹)和发布日期。

最后 k 行是 select 从最后一个 CTE 编辑的。

这是一个查询,其中 n = 2m = 3k = 8:

-- first 2 (n) featured, first 3 (m) favourites, then 3 others (8 (k) total)
WITH favfeatrank AS (
  -- rank articles by date and whether they are favourite or featured
  SELECT ID, Title, IsFavorite, IsFeatured, DatePublished,
         ROW_NUMBER() OVER (ORDER BY IsFeatured DESC, DatePublished DESC) feat,
         ROW_NUMBER() OVER (ORDER BY IsFavorite DESC, DatePublished DESC) fav
  FROM Articles
),
numfavfeat AS (
  -- number of favourites that are also in the first n featured
  -- use m in the next line
  SELECT COUNT(CASE WHEN fav <= 3 THEN 1 END) AS featfav
  FROM favfeatrank
  WHERE feat <= 2 -- use n here
), 
articlerank AS (
  -- articles ranked according to featured, favourite and date published
  SELECT ID, Title, IsFavorite, IsFeatured, DatePublished,
         ROW_NUMBER() OVER (ORDER BY CASE WHEN feat <= 2 THEN 0 -- use n here
                                          WHEN fav <= 3 + featfav THEN 1 -- use m here
                                          ELSE 2
                                          END,
                                      DatePublished DESC) AS rn

  FROM favfeatrank
  CROSS JOIN numfavfeat
)
SELECT ID, Title, IsFavorite, IsFeatured, DatePublished
FROM articlerank 
WHERE rn <= 8 -- use k here

Demo on dbfiddle