Эквивалент SQL Server «TOP 1» в Oracle (без использования rownum или row_number ()) - PullRequest
0 голосов
/ 27 июня 2019

У меня есть таблица CONTRACTINFO, в которой хранится код скидки по контракту (код может изменяться время от времени в зависимости от типа контракта или периода).

CREATE TABLE CONTRACTINFO
(
    ID CHAR(8),
    BASERECORD CHAR(1),
    DATE CHAR(8),
    DISCOUNTCODE CHAR(1)
)

Каждый месяц мынеобходимо рассчитать комиссию, которую клиенты должны платить, исходя из paymentdate и discountcode.

CREATE TABLE PAYMENT
(
    CONTRACTID CHAR(8),
    TIME NUMBER(12),
    PAYMENTDATE CHAR(8)
)

Код скидки определяется путем получения последней записи, которая имеет date <<code>paymentdate из таблицы CONTRACTINFO.

Я создал простой пример для отображения желаемогорезультат (желтым цветом).

enter image description here

В SQL Server я могу легко добиться этого, используя ниже коррелированный подзапрос:

SELECT 
    PA.*,
    (SELECT TOP 1 DISCOUNTCODE 
     FROM CONTRACTINFO 
     WHERE ID = PA.CONTRACTID 
       AND DATE < PA.PAYMENTDATE 
     ORDER BY DATE DESC) AS DISCOUNTCODE 
FROM 
    PAYMENT PA
INNER JOIN 
    CONTRACTINFO CI ON PA.CONTRACTID  = CI.ID 
WHERE 
    CI.BASERECORD = 1 'ALWAYS GET INFORMATION FROM THE BASE RECORD

Нов Oracle SQL я не могу, потому что у него нет функции top 1.

Я не могу использовать rownum или row_number также потому, что Oracle не позволяет мне передавать значение из столбца основного запроса во вложенный подзапрос, подобный этому.(приведенный ниже код сгенерирует ошибку «столбец PA.PAYMENTDATE не найден»)

SELECT 
PA.*,
(
SELECT DISCOUNTCODE FROM 
   (SELECT * FROM CONTRACTINFO WHERE ID = PA.CONTRACTID AND DATE < PA.PAYMENTDATE ORDER BY DATE DESC) 
WHERE ROWNUM = 1
) 
AS DISCOUNTCODE 
FROM PAYMENT PA
INNER JOIN CONTRACTINFO CI 
ON PA.CONTRACTID  = CI.ID 
WHERE CI.BASERECORD = 1 'ALWAYS GET INFORMATION FROM THE BASE RECORD

Ответы [ 2 ]

3 голосов
/ 29 июня 2019

Oracle имеет fetch first вместо top n, поэтому эквивалент будет:

select pa.*
     , ( select discountcode
         from   contractinfo
         where  id = pa.contractid
         and    contractdate < pa.paymentdate
         order  by contractdate desc fetch first row only ) as discountcode
from   payment pa
       join contractinfo ci
            on  pa.contractid = ci.id
where  ci.baserecord = 1;

Мне пришлось переименовать DATE в CONTRACTDATE, потому что DATE является ключевым словом SQL. Кроме того, хотя для полноты ANSI предусмотрен тип char, обычно его не рекомендуется использовать, поскольку заполнение пробелами - это довольно бессмысленная функция, которая тратит пространство и приводит к ошибкам.

Я бы, наверное, начал что-то вроде этого:

create table contracts
( id            integer constraint contract_pk primary key
, baserecord    integer not null
, contractdate  date not null
, discountcode  varchar2(1) );

create table payments
( contractid    references contracts
, paymentseq    number(12)
, paymentdate   date default on null sysdate );
3 голосов
/ 27 июня 2019

Oracle не поддерживает TOP 1, как вы указали.Возможно, вы сможете переписать в Oracle, поддерживая коррелированный подзапрос, но лучше всего будет удалить этот подзапрос, а вместо этого просто использовать соединение, которое вы уже делаете, для обработки логики:

WITH cte AS (
    SELECT
        PA.*,
        COALESCE(CI.DISCOUNTCODE, 'NA') AS DISCOUNTCODE,
        ROW_NUMBER() OVER (PARTITION BY CI.ID ORDER BY CI.DATE DESC) rn
    FROM PAYMENT PA
    LEFT JOIN CONTRACTINFO CI
        ON PA.CONTRACTID = CI.ID AND
           CI.DATE < PA.PAYMENTDATE 
    WHERE
        CI.BASERECORD = 1
)

SELECT CONTRACTID, TIME, PAYMENTDATE, DISCOUNTCODE
FROM cte
WHERE rn = 1;
...