数据库中的日期连续性(查找缺失的日期间隔)
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 之间。
这里是如何工作的:
- 创建一个名为 tbl_date
的临时 table
- 创建一个 vba 函数来生成您请求的日期范围
- 创建一个查询 (all_dates_all_users),您可以在其中 select 请求的日期和用户 ID(没有连接),这将为您提供所有日期 x 所有用户的组合
- 在您离开的地方创建另一个查询,将 all_dates_all_users 查询与您的 user_payments 查询连接起来。 (这将生成所有用户的所有日期并加入您的 user_payments table)
- 检查 user_payments 是否为空。 (如果它的 null 用户 x 那个月没有付款)
这是一个例子:
[表格]
- tbl_date : id primary (auto number), date_field (date/Time)
- tbl_user_payments: pay_id (auto number, primary), user_id (number), pay_Date (Date/Time) 这是你的 table 根据您的要求修改它。我不确定您是否有专门的用户 table,所以我也使用这笔付款 table 来获得 user_id。
[查询]
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;
排序
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;
排序
[函数]
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 月生成并检查您是否收到付款。
[屏幕]:
我有一个按月付款的用户数据库。我需要检查这些付款是否具有连续性。 例如在下面的 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 之间。
这里是如何工作的:
- 创建一个名为 tbl_date 的临时 table
- 创建一个 vba 函数来生成您请求的日期范围
- 创建一个查询 (all_dates_all_users),您可以在其中 select 请求的日期和用户 ID(没有连接),这将为您提供所有日期 x 所有用户的组合
- 在您离开的地方创建另一个查询,将 all_dates_all_users 查询与您的 user_payments 查询连接起来。 (这将生成所有用户的所有日期并加入您的 user_payments table)
- 检查 user_payments 是否为空。 (如果它的 null 用户 x 那个月没有付款)
这是一个例子:
[表格]
- tbl_date : id primary (auto number), date_field (date/Time)
- tbl_user_payments: pay_id (auto number, primary), user_id (number), pay_Date (Date/Time) 这是你的 table 根据您的要求修改它。我不确定您是否有专门的用户 table,所以我也使用这笔付款 table 来获得 user_id。
[查询]
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;
排序
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;
排序
[函数]
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 月生成并检查您是否收到付款。
[屏幕]: