【问题标题】:Checking for overlapping appointments in database not working?检查数据库中的重叠约会不起作用?
【发布时间】:2021-12-25 07:00:30
【问题描述】:

我正在将约会保存到数据库中,并且有一个方法可以检查时间是否重叠。

public static boolean checkForOverlappingAppointment(Timestamp start, Timestamp end, int 
customerID) {    
try {        
String sql = "SELECT * FROM appointments WHERE Customer_ID = ?";        
PreparedStatement ps = JDBC.getConnection().prepareStatement(sql);
ps.setString(1, String.valueOf(customerID));
ResultSet rs = ps.executeQuery();       
while (rs.next()) { 
    Timestamp startOtherAppts = rs.getTimestamp("Start");
    Timestamp endOtherAppts = rs.getTimestamp("End");   
     if ((start.before(startOtherAppts) && end.after(endOtherAppts)) ||                    
     (start.before(startOtherAppts) && end.after(startOtherAppts) && 
     end.before(endOtherAppts)) || (start.after(startOtherAppts) && 
     start.before(endOtherAppts) && end.after(endOtherAppts)) ||                    
     (start.after(startOtherAppts) && end.before(endOtherAppts))) 
     {    
     return true;
        }
    }
} 
catch (SQLException e) {
    e.printStackTrace();    }
   return false;
}

我正在调用该方法

if (DBAppointments.checkForOverlappingAppointment(Timestamp.valueOf(startDateTimeZDT.toLocalDateTime()), Timestamp.valueOf(endDateTimeZDT.toLocalDateTime()), customerIDint)) {
Alert alert3 = new Alert(Alert.AlertType.ERROR);
alert3.setHeaderText("APPOINTMENT OVERLAP");
alert3.setContentText("Appointment overlaps with an existing appointment.");    
alert3.showAndWait();}

我在保存约会时没有收到任何错误,尽管它们可以是相同的确切时间。
我正在使用Timestamp.valueOf(startDateTimeZDT.toLocalDateTime() Timestamp.valueOf(endDateTimeZDT.toLocalDateTime()LocalTime 转换为ZonedDateTime 以保存在数据库中

我似乎无法找出哪里出了问题。任何帮助都会很棒!

【问题讨论】:

  • 您正在使用糟糕的日期时间类,这些类在几年前被 JSR 310 中定义的现代 java.time 类所取代。根本不需要使用 Timestamp 类.
  • 这有用吗? Determine Whether Two Date Ranges Overlap。我也建议你不要使用Timestamp。它的设计很糟糕,是对已经设计很糟糕的“日期”类的真正破解,而且已经过时了。
  • @BasilBourque 我不知道它们已经过时了,哈哈。我只是一个试图遵循规则的学生。当我试图弄清楚这个项目时,我已经转移到 ZonedDateTime 和 LocalDateTimes 。下面的信息帮助我朝着正确的方向前进。
  • 为什么它们已经过时了? Official answer here.

标签: java sql


【解决方案1】:

棘手的事情

处理约会实际上是一件复杂的事情。

我会假设您的约会就像牙医或汽车修理工一样,您打算保持一天中的时间不变,并根据需要调整时间以遵守政治造成的异常情况,例如 Daylight Saving Time (DST)。这与由自然时间驱动的事件(例如火箭发射)形成鲜明对比。

要存储此类约会,您的数据库中需要 三个 列:

  • 预期时区。如果您的数据库缺少这方面的数据类型,请使用文本列以Continent/Region 的格式存储real time zone names,例如America/New_YorkAfrica/Casablanca
  • TIMESTAMP WITHOUT TIME ZONE,用于存储约会的日期和时间,但没有 time zoneoffset-from-UTC 的上下文。
  • 持续时间,约会将持续多长时间。如果您的数据库缺少此数据类型,请以标准ISO 8601 durations 格式存储文本:PnYnMnDTnHnMnS。例如,一个半小时将是PT1H30M

时区变化

我们无法为约会存储时间线上的特定时间点,因为我们不知道政治家何时会更改其管辖范围内的时区所使用的偏移量。世界各地的政客都表现出改变时区规则的偏好。他们经常出人意料地这样做,而且几乎没有甚至没有警告。因此,我们程序员必须始终假设我们的时区规则将来会发生变化。

在确定日程安排时,例如您想要的查询,我们必须动态确定每次约会的时间。我们必须制作查询以将每个约会行的存储值转换为动态计算的时刻。

  • 在约会开始时,我们采用TIMESTAMP WITHOUT TIME ZONE 值,应用行的时区,得到一个TIMESTAMP WITH TIME ZONE 值。
  • 在约会结束时,我们将生成的 TIMESTAMP WITH TIME ZONE 值添加到该行的持续时间值中。

SQL

如果您的数据库提供强大的日期时间处理工具,您或许可以制作这样的查询。

Java

如果您的数据库缺乏强大的日期时间处理工具,请将数据导入 Java 进行处理和过滤。

定义一个类来表示每一行。这里我们使用record

record Appointment ( UUID uuid , ZoneId zoneId , LocalDateTime start , Duration duration ) {}

检索数据并收集。

List< Appointment > appointments = new ArrayList<>() ;
…
UUID uuid = myResultSet.getObject( … , UUID.class ) ;
ZoneID zoneId = ZoneId.of( myResultSet.getString( … ) ) ;
LocalDateTime start = myResultSet.getObject( … , LocalDateTime.class ) ;
Duration duration = Duration.parse( myResultSet.getString( … ) ) ;
appointments.add( new Appointment( uuid , zoneId , start , duration ) ) ;

定义搜索的目标开始和结束。通常最好使用半开方法来定义时间跨度。在 Half-Open 中,开头是 inclusive 而结尾是 exclusive。这提供了整齐的邻接跨度和其他好处。

ZonedDateTime zdtStart = ZonedDateTime.of( 2022 , Month.JANUARY , 23 , 15 , 30 , 0 , 0 , ZoneId.of( "America/Los_Angeles" ) ) ;
ZonedDateTime zdtEnd = zdtStart.plusHours( 2 ) ;

通过提取 Instant 将它们调整为 UTC(零时分秒的偏移量)。

Instant targetStart = zdtStart.toInstant() ;
Instant targetEnd = zdtEnd.toInstant() ;

您可以自己编写逻辑来询问约会是否与目标时间跨度重叠。但我发现使用ThreeTen-Extra 库中的Interval 类更容易。

Interval target = Interval.of( targetStart , targetEnd ) ;

现在我们可以循环我们的Appointment 对象列表,将每个对象与我们的目标进行比较。

List< Appointment > hits = new ArrayList<>() ;
for( Appointment appointment : appointments ) 
{
    ZonedDateTime start = appointment.start().atZone( appointment.zoneId() ) ;
    ZonedDateTime stop = start.plus( appointment.duration() ) ;
    Interval appointmentInterval = Interval.of( start , stop ) ;
    if( appointmentInterval.overlaps( target ) )
    {
        hits.add( appointment ) ;
    }
}
return List.copyOf( hits ) ;  // Return an unmodifiable list, as a generally good habit. 

提醒:您的主机操作系统、数据库引擎和 Java 实现可能都有自己的时区规则副本(通常是tzdata)。如果您关心的任何时区的规则正在更改,请务必更新所有这些。

避免使用旧的日期时间类

永远不要使用糟糕的日期时间类,例如 TimestampDateCalendarSimpleDateFormat。这些是由不了解日期时间处理的微妙和复杂性的人设计的。

这些遗留类在几年前被JSR 310 中定义的现代java.time 类所取代。 JDBC 4.2 及更高版本需要 JDBC drivers 支持 java.time 类型。

【讨论】:

    【解决方案2】:

    您的数据库可能会为您完成这项工作。

    public static boolean checkForOverlappingAppointment(
            LocalDateTime start, LocalDateTime end, int customerID) {
        try {
            String sql = "SELECT *"
                    + " FROM appointments"
                    + " WHERE Customer_ID = ? and Start < ? and end > ?";
            PreparedStatement ps = JDBC.getConnection().prepareStatement(sql);
            ps.setInt(1, customerID);
            ps.setObject(2, end);
            ps.setObject(3, start);
            ResultSet rs = ps.executeQuery();
            // If there is a result row, we know it’s an overlapping appointment
            return rs.next();
        }
        catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }
    

    java.time:我建议您使用 java.time 作为日期和时间。假设您的开始和结束列的类型为datetimetimestamp(没有时区),LocalDateTime 是在Java 中使用的正确对应类型。从 JDBC 4.2 开始,您可以使用 setObject 方法直接将 LocalDateTime 和其他 java.time 类传递给您准备好的语句。

    检查重叠:您的逻辑对于此检查过于复杂。这个论点可能有点棘手,但正如您所见,我的代码很简单。对于数据库中已经存在的每个约会,存在三种可能性:要么更早,要么重叠,要么更晚。如果它的开始是在这次约会结束之前,我们知道它不会更晚。如果它的结束是在此约会开始之后,则不能更早。如果两个条件都满足,那么一定是重叠的。

    你的逻辑有错误吗?首先我发现你的if 条件不可读。我的代码格式化程序以这种方式对其进行了格式化,这很有帮助:

            if ((start.before(startOtherAppts) && end.after(endOtherAppts))
                    || (start.before(startOtherAppts)
                            && end.after(startOtherAppts)
                            && end.before(endOtherAppts))
                    || (start.after(startOtherAppts)
                            && start.before(endOtherAppts)
                            && end.after(endOtherAppts))
                    || (start.after(startOtherAppts) && end.before(endOtherAppts))) {
                return true;
            }
    

    我认为您没有考虑两个约会的开始时间完全相同或结束时间相同的情况。您总是只检查一个时间是严格在另一个时间之前还是严格在另一个时间之后。

    错误处理:我认为您不会只想在 SQL 错误的情况下返回 false

    教程链接: Oracle Tutorial: Date Time

    【讨论】:

      猜你喜欢
      • 2021-02-22
      • 2018-09-26
      • 2015-08-18
      • 1970-01-01
      • 2020-08-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多