skip to Main Content

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

3

Answers


  1. 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;
    
    Login or Signup to reply.
  2. The error message identifies a solution: i.e., cast the expression dt + interval '1 month' to DATE:

    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')::DATE 
        from cte_0
        where dt < (select  max(to_char(date_trunc('month', day), 'yyyy-mm-dd')::date) from Transactions )
    )
    select * from cte_0;
    

    Instead of using a recursive CTE, use GENERATE_SERIES:

    SELECT
      GENERATE_SERIES(
        DATE_TRUNC('month', MIN(transactions.day)),
        DATE_TRUNC('month', MAX(transactions.day)),
        INTERVAL '1' MONTH
      )::DATE AS dt
    FROM
      transactions;
    

    This approach is usually more efficient than using a recursive CTE. If dt is to be used in other expressions that involve timestamps, then remove ::DATE to avoid unnecessary type cast and only cast to DATE when required.

    Consider renaming the column day in transactions to avoid using a SQL reserved word as a user identifier.

    Login or Signup to reply.
  3. To be perfecly honest, you would be far better just using generate_series to generate these rows, rather than messing with recursive CTEs, which are inefficient.

    Also, rather than doing min(to_char(date_trunc('month', day), 'yyyy-mm-dd')::date), it’s almost certainly better to do the min and max first, like date_trunc('month', min(day))::date.

    select
      g.dt::date as dt
    from generate_series(
        (select date_trunc('month', min(t.day)) from Transactions t),
        (select date_trunc('month', max(t.day)) from Transactions t),
        interval '1 month'
    ) g(dt);
    

    It might be faster to do the min and max in a single query, rather than two separate subqueries, but it depends on indexing.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search