相当于 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)
)
每个月,我们需要根据paymentdate
和discountcode
计算客户需要支付的费用。
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 );
我有一个 CONTRACTINFO
table 存储合同的折扣代码(该代码可能会不时更改,具体取决于合同类型或期限)。
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)
)
折扣代码是通过从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 );