Есть ли более эффективный способ объединить верхнюю строку из нескольких операторов выбора в один результат? - PullRequest
1 голос
/ 01 мая 2020

Хорошо, у меня есть опыт работы с различными базами данных, но не Oracle, в частности, поэтому я все еще пытаюсь понять, как он справляется (например, отсутствие «Top» сводит меня с ума!) В любом случае, вот что мы » мы пытаемся сделать ...

Примечание. Мы используем Oracle 11. Я понимаю, что в Oracle 12 была добавлена ​​новая функция (т.е. FETCH NEXT n ROWS ONLY), но мы не можем используйте это, к сожалению.

Для начала у нас есть таблица статей (технически это представление, но я упрощаю его здесь). Вот соответствующие поля ...

  • ID (целое число, первичный ключ)
  • Заголовок (строка)
  • Тело (строка)
  • IsFavorite ('Y' или 'N')
  • IsFeatured ('Y' или 'N')
  • DatePublished (Date)

Можно отметить несколько статей как избранные и / или избранные.

То, что мы хотим вернуть, является результирующим набором, который имеет следующий порядок:

  1. Самая последняя опубликованная статья с IsFeatured = 'Y', если таковые имеются.
  2. Самая последняя опубликованная статья с IsFavorite = 'Y', которая не является строкой из # 1 (это позволяет избежать дубликатов, если самая последняя добавленная статья также является самой последней избранной, и вместо этого выбирает следующая предпочтительная строка) если есть
  3. Три последние опубликованные статьи, которые не являются № 1 или № 2, если таковые имеются

Вот что я придумала до сих пор (вырезано / вставлено / отредактировано здесь, так что могут быть некоторые опечатки), но это просто кажется мне неуклюжим ... как будто я трачу много ненужной обработки и итерации.

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;

Это не самый необычный из запросов, поэтому я не могу представить, что нет лучшего способа сделать это, но я просто еще не вижу его. Так есть ли?

Ответы [ 2 ]

2 голосов
/ 02 мая 2020

Вот возможное решение для вашего запроса. Он использует набор CTE для выбора первых n избранных статей, первых m избранных статей (которых нет в первых n избранных статьях) и в общей сложности k статей:

  • первый генерирует номера строк для избранных и избранных статей;
  • второй подсчитывает, сколько первых n избранных статей также являются первыми m избранными; и
  • третий генерирует номер строки для статей, отсортированных по первым избранным n, первым m избранным (если они не были также среди первых n избранных статей, в этом случае следующие избранные являются выбрано) и дата опубликована.

Наконец, из последнего CTE выбираются верхние k строки.

Вот запрос, где n = 2, m = 3 и k = 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

Демонстрация на dbfiddle

1 голос
/ 01 мая 2020

отсутствие 'Top' сводит меня с ума

Поскольку вы находитесь на Oracle 11g, TOP-n не поддерживается. Так что ROWNUM - единственный путь к go. См. Как работает ROWNUM в запросе на нумерацию страниц .

Например, смещение 4 и FETCH следующие 4 с использованием ROWNUM:

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 строки характерная черта. Поскольку вы не предоставили пример данных, вот простая демонстрация:

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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...