A service of Daily Data, Inc.
Contact Form

User Tools

Site Tools


software:mysql:withrecursive

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
software:mysql:withrecursive [2026/02/06 21:41] – created rodolicosoftware:mysql:withrecursive [2026/02/06 22:05] (current) rodolico
Line 1: Line 1:
-====== The with recursive syntax ======+====== The WITH RECURSIVE syntax ======
  
-Using FrontAccounting (https://www.frontaccounting.com/), I needed a way to project recurring revenue for the next 12 months. I had a combination of Perl script and mysql query to do this, but wanted to just have a single query that I could plug into my report tool+Using [[https://www.frontaccounting.com/|FrontAccounting]], I needed a way to project recurring revenue for the next 12 months. I had a combination of Perl script and a MySQL query to do this, but wanted a single query that I could plug into my report tool.
  
-The basic queryafter some looking around, turned out to be:+===== Warnings ===== 
 + 
 +Just like it saysthis is recursion, generally used to retrieve data from a hierarchical configuration. 
 + 
 +It is very resource intensive if it has to recurse very deep. Be careful when implementing this in the real world, as a poorly written CTE can blow up your database server. 
 + 
 +===== Original Task ===== 
 + 
 +After some looking around, the basic query turned out to be:
  
 <code sql> <code sql>
Line 23: Line 31:
 </code> </code>
  
-I then ran the output of this through a Perl script which would calculate when invoices would come up and increment a monthly amount by the invoice amount. Some invoices recur monthly, some quarterly, some semi-annually and some annually (the integer value monthly in the query).+I then ran the output of this through a Perl script that calculated when invoices would come up and incremented a monthly amount by the invoice amount. Some invoices recur monthly, some quarterly, some semi-annuallyand some annually (the integer value ''monthly'' in the query).
  
-I was able to bypass the Perl script by learning the new CTE (Common Table Expressions) syntax for MySQL (and MariaDB). This is similar to processing queries into temp tables, then running an additional query against them.+I was able to bypass the Perl script by learning the CTE (Common Table Expressions) syntax for MySQL (and MariaDB). This is similar to processing queries into temp tables, then running an additional query against them. 
 + 
 +===== Solution =====
  
 I'll say more about it later, but here is the resulting query. I'll say more about it later, but here is the resulting query.
Line 61: Line 71:
 params AS ( params AS (
    SELECT    SELECT
-      DATE_FORMAT(MIN(nextDue), '%Y-%m-01') AS start_month, +      DATE_FORMAT(MIN(nextDue), '%Y-%m-15') AS start_month, 
-      DATE_ADD(DATE_FORMAT(MIN(nextDue), '%Y-%m-01'), INTERVAL 12 MONTH) AS end_month+      DATE_ADD(DATE_FORMAT(MIN(nextDue), '%Y-%m-15'), INTERVAL 12 MONTH) AS end_month
    FROM recurrent    FROM recurrent
 ), ),
Line 77: Line 87:
    SUM(    SUM(
       CASE       CASE
-         WHEN month_start >= DATE_FORMAT(nextDue, '%Y-%m-01')+         WHEN month_start >= DATE_FORMAT(nextDue, '%Y-%m-15')
           AND MOD(           AND MOD(
              PERIOD_DIFF(DATE_FORMAT(month_start, '%Y%m'), DATE_FORMAT(nextDue, '%Y%m')),              PERIOD_DIFF(DATE_FORMAT(month_start, '%Y%m'), DATE_FORMAT(nextDue, '%Y%m')),
Line 92: Line 102:
  
 </code> </code>
 +
 +   * This projects 12 months into the future. Since our recurrent invoices have that as a maximum (annual), it works for us. If you want 6 or 24 months, change the constant 12 near the middle of the query: //INTERVAL 12 MONTH//.
 +   * Our invoicing is done on the 15th of every month, so in three locations we hard-code the date format. Look for //'%Y-%m-15'//. Since we are working with monthly events, the day is arbitrary as long as the invoices are due on the 15th each time.
 +
 +===== Explanation =====
 +
 +
 +==== Purpose ====
 +The query aggregates recurring invoice totals by month. Each invoice recurs every ''monthly'' months starting from its computed ''nextDue'' date.
 +
 +==== Query Structure ====
 +The query uses three CTEs:
 +
 +1. **''recurrent''**
 +   * Builds a normalized recurring-invoice dataset from FrontAccounting tables.
 +   * Computes:
 +     * ''nextDue'': the next invoice date, based on ''begin'' or ''last_sent'' plus ''monthly'' months.
 +     * ''nextMonthDue'': the month number of ''nextDue'' (kept for reference).
 +
 +2. **''params''**
 +   * Calculates the reporting window:
 +     * ''start_month'': first month to report (earliest ''nextDue'').
 +     * ''end_month'': ''start_month'' plus a fixed interval (currently 12 months).
 +
 +3. **''months''** (recursive)
 +   * Generates a row per calendar month from ''start_month'' through ''end_month''.
 +
 +==== Final Aggregation ====
 +For each generated ''month_start'', the query sums ''total'' for rows in ''recurrent'' that should be billed in that month:
 +
 +* A row is billed when the difference in months between ''month_start'' and ''nextDue'' is a multiple of ''monthly''.
 +* This is evaluated with:
 +  * ''PERIOD_DIFF(DATE_FORMAT(month_start, '%Y%m'), DATE_FORMAT(nextDue, '%Y%m'))''
 +  * ''MOD(..., monthly) = 0''
 +
 +==== Output ====
 +The result returns one row per month:
 +
 +  * ''month_start'': 15th of the month (YYYY-MM-15)
 +  * ''invoiced_total'': total invoice amount expected for that month
 +
 +==== Adjusting the Reporting Range ====
 +To change the number of months produced, edit the interval in the ''params'' CTE:
 +
 +<code sql>
 +DATE_ADD(DATE_FORMAT(MIN(nextDue), '%Y-%m-15'), INTERVAL 12 MONTH)
 +</code>
 +
 +For example, replace ''12'' with ''24'' to show two years.
 +
 +==== Notes ====
 +  * The query is compatible with MariaDB 10.11.
 +  * If there are no ''nextDue'' values, the CTEs will return no rows.
 +
  
software/mysql/withrecursive.1770435673.txt.gz · Last modified: by rodolico