在 JPQL 中修剪 table 到 500 条记录

Prune table to 500 records in JPQL

我在某种缓存上工作,有时,我们需要根据 last_access_date(只保留500 个最近访问的行)。

使用 "plain" SQL,可以通过以下方式完成:

DELETE FROM records WHERE id not in 
    (SELECT id FROM records ORDER BY last_access_date DESC LIMIT 500)

现在 JPQL 中没有 LIMIT 或类似 ROWNUM 的东西,我找到的唯一解决方案是原生 SQL,这是次优的,因为我们 运行 在多个 DBMS 上(至少是 Oracle 和 MSSQL)。

此外,setMaxResults()LIMIT 的 JPQL 版本)似乎对 DELETE 语句无效。

真的没有办法用 JPQL 解决这个问题吗?

你可以这样做:

String sql = "SELECT x.id FROM records x ORDER BY x.last_access_date DESC";
TypedQuery<Long> query = em.createQuery(sql, Long.class);

List<Long> ids = query.setMaxResults(500).getResultList();

String delete = "DELETE FROM records x where x.id not in :ids";
em.createQuery(delete).setParameter("ids", ids).executeUpdate();

我不记得删除查询的确切语法,因此您可能需要将 :ids 放在括号之间,例如:

String delete = "DELETE FROM records x where x.id not in (:ids)";

编辑: dkb 提出了一个更快的评论解决方案(取决于唯一日期以确保剩余行数的完美准确性):

String sql = "SELECT x.last_access_date FROM records x ORDER BY x.last_access_date DESC";

//If you're not using calendar, change to your specific date class
TypedQuery<Calendar> query = em.createQuery(sql, Calendar.class);

Calendar lastDate = query.setFirstResult(499).setMaxResults(1).getSingleResult();

String delete = "DELETE FROM records x where x.last_access_date < :lastDate";
em.createQuery(delete).setParameter("lastDate", lastDate, TemporalType.DATE).executeUpdate();

出于性能原因,必须不要为了加载 500 个 ID 值而进行额外的客户端往返,然后再次将它们发送到服务器。相反,我会建议以下两种方法之一:

使用特定供应商SQL

您目前仅支持 2 个 RDBMS。编写两个单独的 SQL 语句应该是易于管理的。在这种情况下,由于您仅使用 Oracle 和 SQL 服务器,因此您可以使用标准 SQL 实现这一点,事实上:

DELETE FROM records 
WHERE id NOT IN ( 
  SELECT id 
  FROM records 
  ORDER BY last_access_date DESC 
  OFFSET 0 ROWS -- SQL Server needs this
  FETCH FIRST 500 ROWS ONLY
)

如果您不经常这样做并且可以忍受暂时的不一致,您甚至可以实施更快的解决方案:

甲骨文

CREATE TABLE temp AS 
SELECT * 
FROM records 
ORDER BY last_access_date DESC
FETCH FIRST 500 ROWS ONLY;

TRUNCATE TABLE records;

INSERT INTO records 
SELECT * FROM temp;

DROP TABLE temp;

SQL 服务器

SELECT TOP 500 *
INTO temp
FROM records
ORDER BY last_access_date DESC;

TRUNCATE TABLE records;

INSERT INTO records
SELECT * FROM temp;

DROP TABLE temp;

使用 SQL 生成器

对于更复杂的供应商不可知论者 SQL,您可能需要考虑使用 SQL 构建器,例如 jOOQ。可能存在其他替代方案。

免责声明:我为供应商工作。