0

0

使用Java 8和SQL高效检索每日最新时间戳数据教程

花韻仙語

花韻仙語

发布时间:2025-09-30 15:37:01

|

678人浏览过

|

来源于php中文网

原创

使用Java 8和SQL高效检索每日最新时间戳数据教程

本教程旨在指导您如何利用Java 8 Stream API或原生SQL查询,从包含多条时间戳记录的数据集中,针对每个实体和特定日期,精确筛选出具有最新时间戳的唯一记录。我们将详细讲解如何通过分组、比较和聚合操作实现这一常见的数据处理需求,提供通用及特定场景的解决方案,并辅以代码示例、性能考量和注意事项。

1. 问题描述

在数据处理场景中,我们经常会遇到需要从一系列记录中,根据某些条件(如实体名称、日期)筛选出最新的那条记录。例如,给定一个 currency 实体列表,其中包含货名称(name)和接收时间(lastreceived),我们的目标是针对每种货币每个日期,找出在该日期内接收时间最晚(最新)的那条记录。

考虑以下 Currency 类定义:

import java.time.LocalDateTime;

class Currency {
    private Integer id;
    private String name;
    private LocalDateTime lastReceived;

    // 构造函数
    public Currency(Integer id, String name, LocalDateTime lastReceived) {
        this.id = id;
        this.name = name;
        this.lastReceived = lastReceived;
    }

    // Getter方法
    public Integer getId() { return id; }
    public String getName() { return name; }
    public LocalDateTime getLastReceived() { return lastReceived; }

    @Override
    public String toString() {
        return "Currency{" +
               "id=" + id +
               ", name='" + name + '\'' +
               ", lastReceived=" + lastReceived +
               '}';
    }
}

假设我们有如下数据库记录示例:

ID NAME LAST_RECEIVED
1 USD 2022-05-18 09:04:01.545
2 USD 2022-05-18 08:04:01.545
3 USD 2022-05-19 08:04:01.545
4 USD 2022-05-20 08:04:01.545
5 USD 2022-05-20 11:04:01.545
6 BUSD 2022-05-18 08:04:01.545

我们期望的结果是针对每种货币的每个日期,只保留时间戳最新的记录。例如,对于 "USD" 货币:

ID NAME LAST_RECEIVED
1 USD 2022-05-18 09:04:01.545
3 USD 2022-05-19 08:04:01.545
5 USD 2022-05-20 11:04:01.545

2. Java 8 Stream API 解决方案

Java 8引入的Stream API为处理集合数据提供了强大而灵活的工具。我们可以利用其分组、映射和聚合功能来实现上述需求。

立即学习Java免费学习笔记(深入)”;

2.1 通用场景:按货币和日期分组获取最新记录

此方案适用于从包含多种货币的数据集中,为每种货币的每个日期获取最新记录。核心思路是创建一个复合键,包含货币名称和日期,然后根据这个复合键进行分组,并在每个组内找出 lastReceived 时间戳最大的记录。

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class CurrencyProcessor {

    public static void main(String[] args) {
        List allCurrencies = Arrays.asList(
            new Currency(1, "USD", LocalDateTime.parse("2022-05-18T09:04:01.545")),
            new Currency(2, "USD", LocalDateTime.parse("2022-05-18T08:04:01.545")),
            new Currency(3, "USD", LocalDateTime.parse("2022-05-19T08:04:01.545")),
            new Currency(4, "USD", LocalDateTime.parse("2022-05-20T08:04:01.545")),
            new Currency(5, "USD", LocalDateTime.parse("2022-05-20T11:04:01.545")),
            new Currency(6, "BUSD", LocalDateTime.parse("2022-05-18T08:04:01.545")),
            new Currency(7, "EUR", LocalDateTime.parse("2022-05-18T10:00:00.000")),
            new Currency(8, "EUR", LocalDateTime.parse("2022-05-18T09:30:00.000"))
        );

        List lastByDateForAllCurrencies = new ArrayList<>(allCurrencies
                .stream()
                .collect(Collectors.groupingBy(
                    // 复合键:货币名称 + 日期
                    curr -> Arrays.asList(curr.getName(), curr.getLastReceived().toLocalDate()),
                    // 在每个组内,找出lastReceived最大的Currency对象
                    Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparing(Currency::getLastReceived)),
                        Optional::get // 假设每个组至少有一个元素,因此Optional::get是安全的
                    )
                ))
                .values()); // 获取Map中所有值(即筛选出的最新Currency对象)

        System.out.println("--- 所有货币按日期筛选的最新记录 (未排序) ---");
        lastByDateForAllCurrencies.forEach(System.out::println);

        // 如果需要排序,可以对结果列表进行排序
        lastByDateForAllCurrencies.sort(
            Comparator.comparing(Currency::getName)
                      .thenComparing(Currency::getLastReceived)
        );
        System.out.println("\n--- 所有货币按日期筛选的最新记录 (已排序) ---");
        lastByDateForAllCurrencies.forEach(System.out::println);
    }
}

代码解析:

Meku
Meku

AI应用和网页开发工具

下载
  1. Collectors.groupingBy(): 这是核心操作,用于将流中的元素按照指定的键进行分组。
    • 键 (curr -> Arrays.asList(curr.getName(), curr.getLastReceived().toLocalDate())): 我们创建了一个包含货币名称和日期的 List 作为复合键。toLocalDate() 方法将 LocalDateTime 转换为 LocalDate,从而忽略时间部分,只关注日期。
    • 下游收集器 (Collectors.collectingAndThen(...)): 在每个分组内部,我们希望找到 lastReceived 最大的那个 Currency 对象。
      • Collectors.maxBy(Comparator.comparing(Currency::getLastReceived)): 这个收集器会返回一个 Optional,表示组内 lastReceived 最大的元素。
      • Optional::get: 由于 groupingBy 的特性,每个分组至少会有一个元素,所以 maxBy 总是会找到一个值,因此 Optional::get 在这里是安全的。如果存在分组为空的可能,则需要更安全的 Optional 处理,例如 orElse(null) 或 orElseThrow()。
  2. .values(): groupingBy 返回一个 Map,其值是每个分组筛选出的最新 Currency 对象。我们通过 .values() 获取这些对象。
  3. new ArrayList(...): 将 Map 的值集合转换为 ArrayList,以便后续进行排序。
  4. .sort(...): 如果需要按特定顺序(例如先按货币名称,再按接收时间)排列结果,可以对 ArrayList 进行排序。

2.2 特定货币场景:筛选后按日期分组获取最新记录

如果我们的需求是只针对某一种特定货币(例如 "USD")进行处理,可以在分组前先进行过滤,这样可以简化分组键。

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

public class SpecificCurrencyProcessor {

    public static void main(String[] args) {
        List allCurrencies = Arrays.asList(
            new Currency(1, "USD", LocalDateTime.parse("2022-05-18T09:04:01.545")),
            new Currency(2, "USD", LocalDateTime.parse("2022-05-18T08:04:01.545")),
            new Currency(3, "USD", LocalDateTime.parse("2022-05-19T08:04:01.545")),
            new Currency(4, "USD", LocalDateTime.parse("2022-05-20T08:04:01.545")),
            new Currency(5, "USD", LocalDateTime.parse("2022-05-20T11:04:01.545")),
            new Currency(6, "BUSD", LocalDateTime.parse("2022-05-18T08:04:01.545"))
        );

        String targetCurrencyName = "USD";
        List lastUSDByDate = new ArrayList<>(allCurrencies
                .stream()
                .filter(curr -> targetCurrencyName.equalsIgnoreCase(curr.getName())) // 过滤出特定货币
                .collect(Collectors.groupingBy(
                    curr -> curr.getLastReceived().toLocalDate(), // 简化分组键,只需日期
                    Collectors.collectingAndThen(
                        Collectors.maxBy(Comparator.comparing(Currency::getLastReceived)),
                        Optional::get
                    )
                ))
                .values());

        // 对结果进行排序
        lastUSDByDate.sort(Comparator.comparing(Currency::getLastReceived));

        System.out.println("--- USD货币按日期筛选的最新记录 ---");
        lastUSDByDate.forEach(System.out::println);
    }
}

代码解析:

  1. filter(curr -> targetCurrencyName.equalsIgnoreCase(curr.getName())): 在 groupingBy 之前,先通过 filter 操作筛选出目标货币(例如 "USD")的所有记录。
  2. groupingBy(curr -> curr.getLastReceived().toLocalDate(), ...): 由于已经过滤了货币,分组键只需包含日期即可。
  3. 排序:同样,可以通过 sort 方法对结果进行排序,通常按 lastReceived 时间升序排列。

3. 原生SQL查询方案

对于数据库中的大量数据,使用原生SQL查询通常是更高效的选择,尤其是在数据库层面支持窗口函数的情况下。窗口函数可以在不改变查询结果集行数的情况下,对分组内的数据进行计算。

这里以 PostgreSQL 为例,展示如何使用 ROW_NUMBER() 窗口函数实现相同的功能。

SELECT id, name, last_received
FROM (
    SELECT
        c.*,
        -- 使用 ROW_NUMBER() 窗口函数为每个 (name, date) 分组内的记录编号
        -- 按照 last_received 降序排列,最新记录的 rr 值为 1
        ROW_NUMBER() OVER (
            PARTITION BY name, to_char(last_received, 'yyyy-MM-dd')
            ORDER BY last_received DESC
        ) AS rr
    FROM Currency c
    WHERE c.name = :currName -- 可选:如果只需要特定货币
) tbl
WHERE rr = 1 -- 筛选出每个分组中编号为 1 的记录(即最新记录)
ORDER BY last_received; -- 对最终结果按接收时间排序

SQL查询解析:

  1. *内层查询 (`SELECT c., ROW_NUMBER() OVER (...) AS rr FROM Currency c WHERE c.name = :currName`)**:
    • ROW_NUMBER() OVER (PARTITION BY name, to_char(last_received, 'yyyy-MM-dd') ORDER BY last_received DESC): 这是核心部分。
      • PARTITION BY name, to_char(last_received, 'yyyy-MM-dd'): 这定义了窗口(分组)的边界。它会为每种货币(name)的每个日期(to_char(last_received, 'yyyy-MM-dd') 将时间戳转换为日期字符串)创建一个独立的组。
      • ORDER BY last_received DESC: 在每个窗口(分组)内部,记录会按照 last_received 时间戳降序排列。
      • ROW_NUMBER(): 为每个窗口内的记录分配一个从 1 开始的序列号。因此,每个分组中 last_received 最新的记录将获得 rr = 1。
    • WHERE c.name = :currName: 这是一个可选的过滤条件,用于只查询特定货币的数据。如果需要查询所有货币,可以移除此条件。
  2. 外层查询 (SELECT id, name, last_received FROM (...) tbl WHERE rr = 1 ORDER BY last_received):
    • WHERE rr = 1: 从内层查询的结果中,我们只选择那些 rr 值为 1 的记录,这正是每个分组中 lastReceived 最新的那条记录。
    • ORDER BY last_received: 对最终结果进行排序,通常按 lastReceived 升序。

JPA集成注意事项:

目前,JPA(Java Persistence API)标准本身对SQL窗口函数的支持并不完善。因此,如果需要使用窗口函数,通常需要通过 @Query(nativeQuery = true, value = "...") 注解来执行原生SQL查询。

// 假设这是你的JPA Repository接口
public interface CurrencyRepository extends JpaRepository {

    @Query(nativeQuery = true, value = """
        SELECT id, name, last_received
        FROM (
            SELECT c.*,
            ROW_NUMBER() OVER (
                PARTITION BY name, to_char(last_received, 'yyyy-MM-dd')
                ORDER BY last_received DESC
            ) AS rr
            FROM Currency c
            WHERE c.name = :currName
        ) tbl
        WHERE rr = 1
        ORDER BY last_received
    """)
    List findLastByDateByCurrencyName(@Param("currName") String currName);

    // 如果需要查询所有货币,可以移除 WHERE c.name = :currName
    @Query(nativeQuery = true, value = """
        SELECT id, name, last_received
        FROM (
            SELECT c.*,
            ROW_NUMBER() OVER (
                PARTITION BY name, to_char(last_received, 'yyyy-MM-dd')
                ORDER BY last_received DESC
            ) AS rr
            FROM Currency c
        ) tbl
        WHERE rr = 1
        ORDER BY name, last_received
    """)
    List findAllLastByDate();
}

4. 总结与注意事项

  • Java 8 Stream API

    • 优点:代码简洁、声明式,适用于内存中的小型到中型数据集。易于理解和维护,充分利用了函数式编程的优势。
    • 缺点:对于非常大的数据集,如果数据需要从数据库加载到内存中,可能会消耗大量内存并影响性能。
    • 注意事项:Optional::get 的使用需要谨慎,确保分组不会为空。toLocalDate() 方法用于精确到日期,如果需要精确到更小的时间单位(如小时),则需要调整分组键。
  • 原生SQL查询(窗口函数)

    • 优点:在数据库层面执行,通常对大数据集具有更高的性能,减少了数据传输到应用层后的处理负担。尤其适用于数据库是数据源的场景。
    • 缺点:SQL语法可能因数据库类型而异(例如 to_char 函数在不同数据库中可能写法不同)。与ORM框架(如JPA)的集成可能需要使用原生查询,失去部分ORM的抽象优势。
    • 注意事项:确保 PARTITION BY 和 ORDER BY 子句正确定义了分组和排序逻辑,以准确识别最新记录。

在实际应用中,选择哪种方案取决于具体的数据量、性能要求以及应用架构。对于数据量较小且已在内存中的数据,Java Stream API是简洁高效的选择;而对于大规模数据库数据,原生SQL查询通常能提供更好的性能。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

707

2023.10.12

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

327

2023.10.27

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

349

2024.02.23

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

1201

2024.03.06

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

360

2024.03.06

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

798

2024.04.07

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

581

2024.04.29

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

422

2024.04.29

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.9万人学习

C# 教程
C# 教程

共94课时 | 7.7万人学习

Java 教程
Java 教程

共578课时 | 51.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号