sql - Oracle SQL 查询的性能调优

标签 sql oracle performance

有人可以帮我调整这个查询吗?在sqldeveloper中返回数据需要1分钟时间。

SELECT
  masterid, notification_id, notification_list, typeid,
  subject, created_at, created_by, approver, sequence_no,
  productid, statusid, updated_by, updated_at, product_list,
  notification_status, template, notification_type, classification
FROM 
( 
  SELECT
    masterid, notification_id, notification_list, typeid, subject,
    approver, created_at, created_by, sequence_no, productid,
    statusid, updated_by, updated_at, product_list, notification_status,
    template, notification_type, classification,
    ROW_NUMBER() OVER(ORDER BY masterid DESC)AS r
  FROM 
  (
    SELECT DISTINCT
      a.masterid         AS masterid,
      a.maxid            AS notification_id,
      notification_list,
      typeid,
      noti.subject       AS subject,
      noti.approver      AS approver,
      noti.created_at    AS created_at,
      noti.created_by    AS created_by,
      noti.sequence_no   AS sequence_no,
      a.productid        AS productid,
      a.statusid         AS statusid,
      noti.updated_by    AS updated_by,
      noti.updated_at    AS updated_at,
      (
        SELECT LISTAGG(p.name,',') WITHIN GROUP(ORDER BY p.id) AS list_noti
        FROM product p
        INNER JOIN notification_product np ON np.product_id = p.id
        WHERE  notification_id = a.maxid
      ) AS product_list,
      (
        SELECT description
        FROM notification_status
        WHERE id = a.statusid
      ) AS notification_status,
      (
        SELECT name
        FROM template
        WHERE id = a.templateid
      ) AS template,
      (
        SELECT description
        FROM notification_type
        WHERE id = a.typeid
      ) AS notification_type,
      (
        SELECT tc.description
        FROM template_classification tc
        INNER JOIN notification nt ON tc.id = nt.classification_id
        WHERE  nt.id = a.maxid
      ) AS classification
    FROM
    (
      SELECT
        nm.id                       AS masterid,
        nm.product_id               AS productid,
        nm.notification_status_id   AS statusid,
        nm.template_id              AS templateid,
        nm.notification_type_id     AS typeid,
        (
          SELECT MAX(id)
          FROM notification
          WHERE notification_master_id = nm.id
        ) AS maxid,
        (
          SELECT LISTAGG(n.id,',') WITHIN GROUP(ORDER BY nf.id) AS list_noti
          FROM notification n
          WHERE notification_master_id = nm.id
        ) AS notification_list
      FROM notification_master nm
      INNER JOIN notification nf ON nm.id = nf.notification_master_id
      WHERE nm.disable = 'N'
      ORDER BY nm.id DESC
    ) a
    INNER JOIN notification noti 
      ON a.maxid = noti.id
      AND 
      (
        (
          (
            TO_DATE('01-jan-1970','dd-MM-YYYY') +
            numtodsinterval(created_at / 1000,'SECOND')
           ) < 
           (current_date + INTERVAL '-21' DAY)
        )
        OR (typeid exists(2,4) AND statusid = 4)
      )
  )
)
WHERE r BETWEEN 11 AND 20

最佳答案

DISTINCT 通常是查询编写错误的指示符。规范化数据库不包含重复数据,那么必须使用 DISTINCT 删除的重复数据突然从何而来?通常是您自己的查询产生这些。首先避免产生重复项,这样以后就不需要 DISTINCT

在您的情况下,您正在子查询a中加入表notification,但您没有在该子查询中使用它的行;您只能从 notification_master_id 中进行选择。

毕竟,您想要获取通知主控,获取其最新的相关通知(首先获取其 ID,然后选择行)。您不需要数百个子查询来实现此目的。

一些旁注:

  • 要从 template_classification 获取描述,您需要再次加入通知表,但这不是必需的。
  • 子查询中的
  • ORDER BY (ORDER BY nm.id DESC) 是多余的,因为子查询结果是按照标准 SQL 未排序的。 (Oracle 有时会违反此标准,以便对结果应用 ROWNUM,但您在查询中没有使用 ROWNUM。)
  • 遗憾的是,您将 created_at 存储为数字,而不是 DATETIMESTAMP。这迫使你进行计算。不过,我认为这不会对您的查询产生很大影响,因为您在 OR 条件中使用它。
  • CURRENT_DATE 为您获取客户日期。这是很少需要的,因为您从数据库中选择数据,这当然不应该与某些客户端的日期相关,而应该与它自己的日期 SYSDATE 相关。

如果我没记错的话,您的查询可以缩短为:

SELECT
  nm.id                      AS masterid,
  nf.id                      AS notification_id,
  nfagg.notification_list    AS notification_list,
  nm.notification_type_id    AS typeid,
  nf.subject                 AS subject,
  nf.approver                AS approver,
  nf.created_at              AS created_at,
  nf.created_by              AS created_by,
  nf.sequence_no             AS sequence_no,
  nm.product_id              AS productid,
  nm.notification_status_id  AS statusid,
  nf.updated_by              AS updated_by,
  nf.updated_at              AS updated_at,
  (
    SELECT LISTAGG(p.name, ',') WITHIN GROUP (ORDER BY p.id)
    FROM product p
    INNER JOIN notification_product np ON np.product_id = p.id
    WHERE np.notification_id = nf.id
  ) AS product_list,
  (
    SELECT description
    FROM notification_status
    WHERE id = nm.notification_status_id
  ) AS notification_status,
  (
    SELECT name
    FROM template
    WHERE id = nm.template_id
  ) AS template,
  (
    SELECT description
    FROM notification_type
    WHERE id = nm.notification_type_id
  ) AS notification_type,
  (
    SELECT description
    FROM template_classification
    WHERE id = nf.classification_id
  ) AS classification
FROM notification_master nm
INNER JOIN
(
  SELECT
    notification_master_id,
    MAX(id) AS maxid,
    LISTAGG(id,',') WITHIN GROUP (ORDER BY id) AS notification_list
  FROM notification
  GROUP BY notification_master_id
) nfagg ON nfagg.notification_master_id = nm.id
INNER JOIN notification nf
   ON nf.id = nfagg.maxid
  AND
  (
    (
      DATE '1970-01-01' + NUMTODSINTERVAL(nf.created_at / 1000, 'SECOND')
        < CURRENT_DATE + INTERVAL '-21' DAY
    )
    OR (nm.notification_type_id IN (2,4) AND nm.notification_status_id = 4)
  )
WHERE nm.disable = 'N'
ORDER BY nm.id DESC
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;

如上所述,您可能需要将 CURRENT_DATE 替换为 SYSDATE

我推荐使用以下索引进行查询:

CREATE INDEX idx1 ON notification_master (disable, id, notification_status_id, notification_type_id);
CREATE INDEX idx2 ON notification (notification_master_id, id, created_at);

关于分页的最后一点:为了跳过 n 行来获取下一个 n 行,必须对所有数据执行整个查询,然后对所有结果行进行排序,最后仅选取其中的 n 行。通常最好记住上次获取的 ID,然后在下次执行时仅选择具有更高 ID 的行。

关于sql - Oracle SQL 查询的性能调优,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61053212/

相关文章:

sql - 如何使用jsp将日期插入数据库?

java - hibernate 批量插入。它会使用一个插件而不是多个插件吗?

c# - Array.ForEach() 与 C# 中的标准 for 循环相比如何?

mysql - SQL 和 DateTime - 如何选择在特定时间段内存在的所有行?

sql - 在一次查询中使用 DAY()、WEEK() 和 YEAR()

oracle - SAS 错误处理 - 在数据步骤之后检查是否引发错误

java - 编译期间 Netbeans 不使用可用内存

performance - React-redux重选性能

mysql - MySQL 中的动态交叉表

mysql - 如何通过 LIKE 和部分匹配在 MySQL 中为 VARCHAR 字段使用索引?