数据库中的日期连续性(查找缺失的日期间隔)

Date continuity in a database (find missing date gaps)

我有一个按月付款的用户数据库。我需要检查这些付款是否具有连续性。 例如在下面的 table 中:

+---------+------------+
| user_id |    date    |
+---------+------------+
|       1 | 2015-02-01 |
|       2 | 2015-02-01 |
|       3 | 2015-02-01 |
|       1 | 2015-03-01 |
|       2 | 2015-03-01 |
|       3 | 2015-03-01 |
|       4 | 2015-03-01 |
|       1 | 2015-04-01 |
|       2 | 2015-04-01 |
|       3 | 2015-04-01 |
|       4 | 2015-04-01 |
|       5 | 2015-04-01 |
|       1 | 2015-05-01 |
|       2 | 2015-05-01 |
|       3 | 2015-05-01 |
|       4 | 2015-05-01 |
|       5 | 2015-05-01 |
|       1 | 2015-06-01 |
|       2 | 2015-06-01 |
|       3 | 2015-06-01 |
|       5 | 2015-06-01 |
|       3 | 2015-07-01 |
|       4 | 2015-07-01 |
|       5 | 2015-07-01 |
+---------+------------+

5月份之前一切正常,但是6月份用户4没有付款,虽然他在下个月(7月份)付款了。 7 月,用户 1 和 2 没有付款,但这没关系,因为他们可以退出该服务。 所以在这种情况下,我需要有信息 "User 4 didn't pay in June"。 是否可以使用 SQL 来做到这一点?

如果需要信息,我会使用 MS Access。

类似这样,查找下个月未付款但下个月付款的用户:

select user_id, month(date) + 1
from tablename t1
where not exists (select 1 from tablename t2
                  where t2.user_id = t1.user_id
                    and month(t2.date) = month(t1.date) + 1)
  and exists     (select 1 from tablename t3
                  where t3.user_id = t1.user_id
                    and month(t3.date) > month(t1.date) + 2)

注1:没有指定dbms,所以month()函数只是伪代码。 ANSI SQL 有 extract(month from column_name).

注2: in ANSI SQL date是保留字,需要分隔为"date".

注3:刚刚意识到这个答案只会return第一个月,即使有几个(连续)缺失...

我为此编写了一个简单的查询,但我意识到这不是最佳解决方案。仍然欢迎其他解决方案。

SELECT user_id,
       MIN(date) AS min_date,
       MAX(date) AS max_date,
       COUNT(*) AS no_of_records,
       round((MAX(date)-MIN(date))/30.4+1,0) AS months,
       (months-no_of_records) AS diff
FROM test
GROUP BY user_id
HAVING (round((MAX(date)-MIN(date))/30.4+1,0)-COUNT(*)) > 0
ORDER BY 6 DESC;

现在我们可以查看 "no_of_records" 和 "months" 列。如果它们不相等,则表示该用户存在差距。

根据我的经验,您不能只使用 table 中的付费来填补空白。如果您的所有用户都没有支付特定月份的费用,则您的查询可能会忽略整个月份。

这意味着您需要列出从 1 月到 12 月的所有日期,并检查每个用户是否已付款。这又需要 table 与您请求的日期进行比较。

专用 RDBMS 提供临时 tables、SP、函数,允许您创建更高级别的 level/complex 查询。另一方面,ACE/JET 引擎提供的可能性较少,但有一种方法可以做到这一点。 (VBA)

在任何情况下,您都需要为数据库提供您要查找间隔的特定日期时间段。您可以说当前年份或 yearX 和 yearY 之间。

这里是如何工作的:

  1. 创建一个名为 tbl_date
  2. 的临时 table
  3. 创建一个 vba 函数来生成您请求的日期范围
  4. 创建一个查询 (all_dates_all_users),您可以在其中 select 请求的日期和用户 ID(没有连接),这将为您提供所有日期 x 所有用户的组合
  5. 在您离开的地方创建另一个查询,将 all_dates_all_users 查询与您的 user_payments 查询连接起来。 (这将生成所有用户的所有日期并加入您的 user_payments table)
  6. 检查 user_payments 是否为空。 (如果它的 null 用户 x 那个月没有付款)

这是一个例子:

[表格]

  1. tbl_date : id primary (auto number), date_field (date/Time)
  2. tbl_user_payments: pay_id (auto number, primary), user_id (number), pay_Date (Date/Time) 这是你的 table 根据您的要求修改它。我不确定您是否有专门的用户 table,所以我也使用这笔付款 table 来获得 user_id。

[查询]

  1. qry_user_payments_all_month_all_user:

    SELECT 年([date_field]) AS mYear, Month([date_field]) AS mMonth, qry_user_payments_user_group.user_id FROM qry_user_payments_user_group, tbl_date 按年([date_field])、月([date_field])、qry_user_payments_user_group.user_id;

  2. 排序
  3. qry_user_payments_paid_or_not_paid

    SELECTqry_user_payments_all_month_all_user.mYear, qry_user_payments_all_month_all_user.mMonth, qry_user_payments_all_month_all_user.user_id, IIf(IsNull([tbl_user_payments].[user_id]),"Not paid","Paid") AS [付费?] FROM qry_user_payments_all_month_all_user 左连接 tbl_user_payments ON (qry_user_payments_all_month_all_user.user_id = tbl_user_payments.user_id) AND ((qry_user_payments_all_month_all_user.mMonth = month(tbl_user_payments.[pay_date]) AND (qry_user_payments_all_month_all_user.mYear = year(tbl_user_payments.[pay_date] )))) 按 qry_user_payments_all_month_all_user.mYear、qry_user_payments_all_month_all_user.mMonth、qry_user_payments_all_month_all_user.user_id;

  4. 排序

[函数]

Public Function FN_CRETAE_DATE_TABLE(iDate_From As Date, Optional iDate_To As Date)
'---------------------------------------------------------------------------------------
' Procedure : FN_CRETAE_DATE_TABLE
' Author    : KRISH KM
' Date      : 22/09/2015
' Purpose   : will generate date period and check whether payments are received. A query will be opened with results
' CopyRights: You are more than welcome to edit and reuse this code. i'll be happy to receive courtesy reference:
' Contact   : krishkm@outlook.com
'---------------------------------------------------------------------------------------
'

    Dim From_month, To_Month As Integer
    Dim From_Year, To_Year As Long
    Dim I, J As Integer
    Dim SQL_SET As String
    Dim strDoc As String
    strDoc = "tbl_date"

    DoCmd.SetWarnings (False)

    SQL_SET = "DELETE * FROM " & strDoc
    DoCmd.RunSQL SQL_SET


    If (IsMissing(iDate_To)) Or (iDate_To <= iDate_From) Then
        'just current year
        From_month = VBA.Month(iDate_From)
        From_Year = VBA.Year(iDate_From)

        For I = From_month To 12
            SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & From_Year & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
            DoCmd.RunSQL SQL_SET
        Next I
    Else
        From_month = VBA.Month(iDate_From)
        To_Month = VBA.Month(iDate_To)
        From_Year = VBA.Year(iDate_From)
        To_Year = VBA.Year(iDate_To)

        For J = From_Year To To_Year
            For I = From_month To To_Month
                SQL_SET = "INSERT INTO " & strDoc & "(date_field) values ('" & J & "-" & VBA.Format(I, "00") & "-01 00:00:00')"
                DoCmd.RunSQL SQL_SET
            Next I
        Next J

    End If

    DoCmd.SetWarnings (True)

    On Error Resume Next
    strDoc = "qry_user_payments_paid_or_not_paid"
    DoCmd.Close acQuery, strDoc
    DoCmd.OpenQuery strDoc, acViewNormal
End Function

您可以从按钮或表单或调试中调用此 public 函数 window:

?FN_CRETAE_DATE_TABLE("2015-01-01","2015-10-01")

这将从 1 月到 10 月生成并检查您是否收到付款。

[屏幕]: