相当于 Oracle 中 SQL 服务器的 "TOP 1"(不使用 rownum 或 row_number())

Equivalent of SQL Server's "TOP 1" in Oracle (without using rownum or row_number())

我有一个 CONTRACTINFO table 存储合同的折扣代码(该代码可能会不时更改,具体取决于合同类型或期限)。

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

每个月,我们需要根据paymentdatediscountcode计算客户需要支付的费用。

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

折扣代码是通过从CONTRACTINFO table.

中获取具有date < paymentdate的最后一条记录来确定的

我创建了一个简单的示例来显示所需的结果(黄色)。

在 SQL 服务器中,我可以使用以下相关子查询轻松实现此目的:

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 不允许我像这样将主查询列中的值传递到嵌套子查询中。 (下面的代码会产生 "column PA.PAYMENTDATE not found" 错误)

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

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;

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 关键字。此外,尽管 char 类型是为了 ANSI 完整性而提供的,但通常使用它并不是一个好主意,因为 blank-padding 是一个相当无意义的功能,它会浪费 space 并导致错误。

我可能会从这样的事情开始:

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