Search code examples
sqlpostgresql

Recursive CTE in Postgresql


When I run this request I get an error

recursive query "cte_0" column 1 has type date in non-recursive term but type timestamp without time zone overall LINE 4: select min(to_char(date_trunc('month', day), 'yyyy-mm-d...
^ HINT: Cast the output of the non-recursive term to the correct type.

In this query, the "day" column is a column with a data type datetime.

Query:

with recursive cte_0 as
(
    select  min(to_char(date_trunc('month', day), 'yyyy-mm-dd')::date) as dt
    from Transactions
    union all
    select dt + interval '1 month' 
    from cte_0
    where dt < (select  max(to_char(date_trunc('month', day), 'yyyy-mm-dd')::date) from Transactions )
)
select * from cte_0

+----------------+------------+----------+--------+---------------------+
| transaction_id | account_id | type     | amount | day                 |
+----------------+------------+----------+--------+---------------------+
| 2              | 3          | Creditor | 107100 | 2021-06-02 11:38:14 |
| 4              | 4          | Creditor | 10400  | 2021-06-20 12:39:18 |
| 11             | 4          | Debtor   | 58800  | 2021-07-23 12:41:55 |
| 1              | 4          | Creditor | 49300  | 2021-05-03 16:11:04 |
| 15             | 3          | Debtor   | 75500  | 2021-05-23 14:40:20 |
| 10             | 3          | Creditor | 102100 | 2021-06-15 10:37:16 |
| 14             | 4          | Creditor | 56300  | 2021-07-21 12:12:25 |
| 19             | 4          | Debtor   | 101100 | 2021-05-09 15:21:49 |
| 8              | 3          | Creditor | 64900  | 2021-07-26 15:09:56 |
| 7              | 3          | Creditor | 90900  | 2021-06-14 11:23:07 |
+----------------+------------+----------+--------+---------------------+

I want to get:

2021-05-01
2021-06-01
2021-07-01

I tried changing the data type, but I couldn't fix this error


Solution

  • If you add an interval to a date, the result will be a timestamp without time zone. PostgreSQL complains that these data types are different.

    I recommend that you use timestamp in the CTE and cast to date in the main query:

    WITH RECURSIVE cte_0 AS (
        SELECT min(date_trunc('month', day)) AS dt
        FROM transactions
    
        UNION ALL
    
        SELECT dt + interval '1 month' 
        from cte_0
        where dt < (SELECT max(date_trunc('month', day)) FROM transactions)
    )
    SELECT CAST (dt AS date) FROM cte_0;