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

时间:2015-09-21 07:14:22

标签: sql ms-access access-vba gaps-and-islands

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

+---------+------------+
| 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 |
+---------+------------+

直到五月一切正常,但是在6月份用户4没有付款,尽管他在下个月(7月)付款。 7月份用户1和2没有付费,但这没关系,因为他们可以从服务中退出。 因此,在这种情况下,我需要提供“用户4未在6月支付”的信息。 是否可以使用SQL做到这一点?

如果是必要的信息,我会使用MS Access。

3 个答案:

答案 0 :(得分:1)

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

这意味着您需要列出从1月到12月的所有日期,并检查每个用户是否已付款。这又需要一张表格,其中包含您要求的日期进行比较。

专用RDBMS提供临时表,SP,函数,允许您创建更高级别/复杂的查询。另一方面,ACE / JET引擎提供的可能性较小,但有一种方法可以完成这项工作。 (VBA)

在任何情况下,您都需要为数据库指定要查找空白的特定日期。您可以说当前年份或年份X和年份之间。

在这里它是如何工作的:

  1. 创建一个名为tbl_date的临时表
  2. 创建一个vba函数以生成您请求的日期范围
  3. 创建一个查询(all_dates_all_users),您可以在其中选择所请求的日期&用户ID(没有连接)这将为您提供所有日期x所有用户组合
  4. 创建另一个查询,您可以在其中使用user_payments查询加入all_dates_all_users查询。 (这将生成所有用户的所有日期并加入您的user_payments表)
  5. 执行检查user_payments是否为空。 (如果其空用户x尚未支付该月)
  6. 以下是一个例子:

    [表]

    1. tbl_date:id primary(自动编号),date_field(日期/时间)
    2. tbl_user_payments:pay_id(自动编号,主要),user_id(数字),pay_Date(日期/时间)这是您的表格根据您的要求修改它。我不确定你是否有专用的用户表,所以我也使用这个付款表来获取user_id。
    3. [查询]

      1. qry_user_payments_all_month_all_user:

        SELECT Year([date_field])AS mYear,Month([date_field])AS mMonth,qry_user_payments_user_group.user_id 来自qry_user_payments_user_group,tbl_date 按年份排序([date_field]),月份([date_field]),qry_user_payments_user_group.user_id;

      2. qry_user_payments_paid_or_not_paid

        SELECT 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,    IIf(IsNull([tbl_user_payments]。[user_id]),“未付款”,“付费”)AS [支付?] 来自qry_user_payments_all_month_all_user     LEFT JOIN 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])))) ORDER BY 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;

      3. [功能]

        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
        

        您可以从按钮或表单或调试窗口调用此公共函数:

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

        这将从jan生成到oct并检查您是否收到了付款。

        [屏幕]: user payments table

答案 1 :(得分:0)

这样的事情,寻找下个月没有付款的用户,但是付款后的那个月:

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:在ANSI SQL date中是保留字,需要以"date"分隔。

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

答案 2 :(得分:0)

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

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”栏目。如果它们不相等,则该用户存在差距。