Search code examples
sqlitedelphifiremonkeyfiredac

Delphi & SQLite: Get the records count per month for the last 12 months


I'm trying to get the number of record grouped by month for the last 12 months including the current month. Not from the beginning of the current year.

I have a table EVENTS and a field WODATE. I can do this

  aFDQuery.Connection := TrackingDBConnection;
  aFDQuery.SQL.Clear;
  with aFDQuery.SQL do
    begin
      BeginUpdate;
      try
        Add('Select MONTH(MAX(WODate)) AS month, COUNT(*) AS count ');
        Add('from events where YEAR(WODATE) = 2020 ');
        Add('GROUP BY YEAR(WODate), MONTH(WODate) ORDER BY YEAR(WODate), MONTH(WODate) ASC');
      finally
        EndUpdate;
      end;

With that query, I get a result only for months where there are values other than 0 and obviously only for 2020

But I would like to get the last 12 months from now even with 0 like this

[01]    11/2019     12 
[02]    12/2019     24 
[03]    01/2020      0 
[04]    02/2020     12
[05]    03/2020     44
[06]    04/2020      0
[07]    05/2020     21
[08]    06/2020     37
[09]    07/2020      0
[10]    08/2020     15 
[11]    09/2020     45 
[12]    10/2020      6

I have tried this but I get a Syntax error

Add('from EVENTS where WODATE >= DATE(NOW, -12 month) ');

ESQLiteNativeException: [FireDAC][Phys][SQLite] ERROR: near "month": syntax error

Is it possible ? Thank you in advance for any suggestion

With the answer from Peter (See below), I get this

2019-11-01 - 2019-11-30    0
2019-12-01 - 2019-12-31    0
2020-01-01 - 2020-01-31    0
2020-02-01 - 2020-02-29    0
2020-03-01 - 2020-03-31    0
2020-04-01 - 2020-04-30    0
2020-05-01 - 2020-05-31    0
2020-06-01 - 2020-06-30    0
2020-07-01 - 2020-07-31    0
2020-08-01 - 2020-08-31    0
2020-09-01 - 2020-09-30    0
2020-10-01 - 2020-10-31   13

Which is perfect Thanks a lot Peter. You deserve a belgian beer


Solution

  • This isn't much a Delphi question but rather SQLite question. Since version 3.8.3 you can use common table expressions (CTE) and the VALUES clause. Below I'm going to explain how.

    First you can construct static date ranges table with 12 entries, where each entry represents a single month from past 12 months including the current:

    VALUES
      (date('now', 'start of month', '-11 month'), date('now', 'start of month', '-10 month', '-1 day')),
      (date('now', 'start of month', '-10 month'), date('now', 'start of month', '-9 month', '-1 day')),
      (date('now', 'start of month', '-9 month'),  date('now', 'start of month', '-8 month', '-1 day')),
      (date('now', 'start of month', '-8 month'),  date('now', 'start of month', '-7 month', '-1 day')),
      (date('now', 'start of month', '-7 month'),  date('now', 'start of month', '-6 month', '-1 day')),
      (date('now', 'start of month', '-6 month'),  date('now', 'start of month', '-5 month', '-1 day')),
      (date('now', 'start of month', '-5 month'),  date('now', 'start of month', '-4 month', '-1 day')),
      (date('now', 'start of month', '-4 month'),  date('now', 'start of month', '-3 month', '-1 day')),
      (date('now', 'start of month', '-3 month'),  date('now', 'start of month', '-2 month', '-1 day')),
      (date('now', 'start of month', '-2 month'),  date('now', 'start of month', '-1 month', '-1 day')),
      (date('now', 'start of month', '-1 month'),  date('now', 'start of month', '-1 day')),
      (date('now', 'start of month'),              date('now', 'start of month', '+1 month', '-1 day'))
    

    This gives (as of current date):

    StartDate EndDate
    2019-11-01 2019-11-30
    2019-12-01 2019-12-31
    2020-01-01 2020-01-31
    2020-02-01 2020-02-29
    2020-03-01 2020-03-31
    2020-04-01 2020-04-30
    2020-05-01 2020-05-31
    2020-06-01 2020-06-30
    2020-07-01 2020-07-31
    2020-08-01 2020-08-31
    2020-09-01 2020-09-30
    2020-10-01 2020-10-31

    Using a CTE you can join your events table to the above list:

    WITH DateRanges(StartDate, EndDate) AS (VALUES
      (date('now', 'start of month', '-11 month'), date('now', 'start of month', '-10 month', '-1 day')),
      (date('now', 'start of month', '-10 month'), date('now', 'start of month', '-9 month', '-1 day')),
      (date('now', 'start of month', '-9 month'),  date('now', 'start of month', '-8 month', '-1 day')),
      (date('now', 'start of month', '-8 month'),  date('now', 'start of month', '-7 month', '-1 day')),
      (date('now', 'start of month', '-7 month'),  date('now', 'start of month', '-6 month', '-1 day')),
      (date('now', 'start of month', '-6 month'),  date('now', 'start of month', '-5 month', '-1 day')),
      (date('now', 'start of month', '-5 month'),  date('now', 'start of month', '-4 month', '-1 day')),
      (date('now', 'start of month', '-4 month'),  date('now', 'start of month', '-3 month', '-1 day')),
      (date('now', 'start of month', '-3 month'),  date('now', 'start of month', '-2 month', '-1 day')),
      (date('now', 'start of month', '-2 month'),  date('now', 'start of month', '-1 month', '-1 day')),
      (date('now', 'start of month', '-1 month'),  date('now', 'start of month', '-1 day')),
      (date('now', 'start of month'),              date('now', 'start of month', '+1 month', '-1 day'))
    )
    SELECT
      DateRanges.StartDate,
      DateRanges.EndDate,
      COUNT(Events.ROWID) AS Count
    FROM DateRanges
      LEFT OUTER JOIN Events ON (DateRanges.StartDate <= Events.WODate) AND (Events.WODate <= DateRanges.EndDate)
    GROUP BY
      DateRanges.StartDate, DateRanges.EndDate
    ORDER BY
      DateRanges.StartDate
    

    See sample SQLFiddle.

    Edit

    I see that you're struggling to execute the SQL using TFDQuery, so I did it for you:

    with aFDQuery.SQL do
    begin
      BeginUpdate;
      try
        Add('WITH DateRanges(StartDate, EndDate) AS (VALUES');
        Add('(date(''now'', ''start of month'', ''-11 month''), date(''now'', ''start of month'', ''-10 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-10 month''), date(''now'', ''start of month'', ''-9 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-9 month''),  date(''now'', ''start of month'', ''-8 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-8 month''),  date(''now'', ''start of month'', ''-7 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-7 month''),  date(''now'', ''start of month'', ''-6 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-6 month''),  date(''now'', ''start of month'', ''-5 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-5 month''),  date(''now'', ''start of month'', ''-4 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-4 month''),  date(''now'', ''start of month'', ''-3 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-3 month''),  date(''now'', ''start of month'', ''-2 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-2 month''),  date(''now'', ''start of month'', ''-1 month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month'', ''-1 month''),  date(''now'', ''start of month'', ''-1 day'')),');
        Add('(date(''now'', ''start of month''),              date(''now'', ''start of month'', ''+1 month'', ''-1 day'')))');
        Add('SELECT DateRanges.StartDate, DateRanges.EndDate, COUNT(Events.ROWID) AS Count');
        Add('FROM DateRanges LEFT OUTER JOIN Events ON (DateRanges.StartDate <= Events.WODate) AND (Events.WODate <= DateRanges.EndDate)');
        Add('GROUP BY DateRanges.StartDate, DateRanges.EndDate');
        Add('ORDER BY DateRanges.StartDate');
      finally
        EndUpdate;
      end;
    end;