0

I'm trying to create a way of inserting aggregated data into a table by using a loop with a moving cursor. I want to count records in a 1h timeframe and compare it to the average number in corresponding timeframes from the last 7 days. I would set a start of a timeframe as a variable and move it 15 minutes with each loop.

Here is what I've tried so far:

I created the table:

CREATE OR REPLACE TABLE WHILE_LOOP_count_TEST__RESULTS (
  ID int NULL,
  START_TIME TIMESTAMP_NTZ NULL,
  END_TIME TIMESTAMP_NTZ NULL,
  ID_COUNT int NULL,
  PREV_7D_AVG int NULL,
  
  DT DATETIME NULL,
  "CREATE_DT" TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP(),
  "CREATE_USER" VARCHAR2(50) NOT NULL DEFAULT CURRENT_USER()
);
-- START VALUE
INSERT INTO WHILE_LOOP_count_TEST__RESULTS (ID, ID_Count, DT) VALUES (1, 1, '2025-02-15 12:00');

Then, I tried the following query:

EXECUTE IMMEDIATE $$
DECLARE
  counter INTEGER := 1;
  start_time TIMESTAMP_NTZ := '2025-02-19 14:30:20.706000000Z'::TIMESTAMP_NTZ;
  end_time TIMESTAMP_NTZ := DATEADD(hour, 1, start_time);
BEGIN
  WHILE (counter < 5) DO
    -- INSERT 
    INSERT INTO WHILE_LOOP_count_TEST__RESULTS (ID, ID_COUNT, DT, PREV_7D_AVG)
    SELECT ID, ID_COUNT, DT, PREV_7D_AVG
    FROM (
      ((SELECT MAX(ID) + 1 AS ID FROM WHILE_LOOP_count_TEST__RESULTS),
          COUNT(DB1.TRANS_ID) AS ID_COUNT,
          DATEADD(minute, +15, (SELECT MAX(DT) 
        FROM WHILE_LOOP_count_TEST__RESULTS)) AS DT,
      Merchant
      
     FROM
      DB1 
      LEFT JOIN DB2 ON DB2.EXTERNAL_IDENTIFIER = DB1.TRANS_ID
      LEFT JOIN DB3 ON DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
    WHERE
      DB2.TS > :start_time
      AND DB2.TS < :end_time
      AND DB2.TYPE = 'INI'
    GROUP BY
      merchant ,
      DB3.COUNTRY_REGION) as t1
      LEFT JOIN
      (SELECT avg(number) as PREV_7D_AVG, merchant1 from (
                        (
                            (
                                select
                                            count(TRANS_ID) as number,
                                            COUNTRY_REGION as CONTINENT,
                                            merchant1
                                        from
                                            DB1
                                            left join DB2 on external_identifier = trans_id
                                            left join DB3 on DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
                                        where
                                            ts > dateadd(hour, -2, :start_time)
                                            and ts < dateadd(hour, -1, :start_time)
                                            
                                        group by
                                            CONTINENT,
                                            merchant1
                                        having
                                            number > 30
                                    
                            )
                            union all
                            (
                                select
                                            count(TRANS_ID) as number,
                                            COUNTRY_REGION as CONTINENT,
                                            merchant1
                                        from
                                            DB1
                                            left join DB2 on external_identifier = trans_id
                                            left join DB3 on DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
                                        where
                                            ts > dateadd(hour, -26, :start_time)
                                            and ts < dateadd(hour, -25, :start_time)
                                            
                                        group by
                                            CONTINENT,
                                            merchant1
                                        having
                                            number > 30
                            )) 
                            -- then six more unions, every one one day earlier
                        group by
                            merchant1::varchar,
                            CONTINENT

                        
                    )  as t2 ON t1.Merchant = t2.merchant1);

    -- Update the start time for the next iteration
    start_time := DATEADD(minute, 15, start_time);
    end_time := DATEADD(hour, 1, start_time);
    counter := counter + 1;
  END WHILE;
  RETURN counter;
END;
$$;

I feel like I'm close to fixing this problem, but I'm getting either I'm getting either "syntax error line 7 at position 17 unexpected '<'. " or "ncaught exception of type 'STATEMENT_ERROR' on line 9 at position 4 : Single-row subquery returns more than one row.", the second one if I'm not joining the t2 table but just put the subquery in the main select statement. Any suggestions?

6
  • 1
    Using loops in SQL usually indicates you are doing it the wrong way -- there should never be a need to use a loop. What platform are you using? Please indicate with the flags. I will try and understand to create a loop free solution. Commented Feb 26 at 15:21
  • It's snowflake. What I'm trying to do is to populate a table with counts and averages for the same timeframe in the last 7 days. for example: the merchant had 7 transactions between 1 PM and 2 PM, and I want to also have an average for the corresponding time for the previous 7 days. Commented Feb 26 at 15:26
  • What drives the "timeframes" is that based the earlies and lates transactions on the current day? Commented Feb 26 at 15:30
  • Also you should note -- the only time a set of () will change how an SQL statement runs is when they are used in arithmetic expressions. There is no order of operations in the query part of SQL -- all of the in your code can be removed -- they have no effect. Commented Feb 26 at 15:33
  • The one exception is when they signal a sub-query -- but as far as I can tell you are not using that way because you would need an alias. Commented Feb 26 at 15:34

1 Answer 1

0

I couldn't post this as a comment because it's too long, but I think you need to look at your parentheses and your selects. Looks like you are missing a Select from your t1 table, and also looks like you're missing a parentheses near your t2 table. I've tried to take a crack at it, but without knowing your underlying data, this is as close as I could get:

SELECT ID, ID_COUNT, DT, PREV_7D_AVG
FROM (
     (SELECT (SELECT MAX(ID) + 1 AS ID FROM WHILE_LOOP_count_TEST__RESULTS)             AS ID,
             COUNT(DB1.TRANS_ID)                                                        AS ID_COUNT,
             DATEADD(minute, +15, (SELECT MAX(DT) FROM WHILE_LOOP_count_TEST__RESULTS)) AS DT,
             Merchant
      FROM DB1
               LEFT JOIN DB2 ON DB2.EXTERNAL_IDENTIFIER = DB1.TRANS_ID
               LEFT JOIN DB3 ON DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
      WHERE DB2.TS > :start_time
        AND DB2.TS < :end_time
        AND DB2.TYPE = 'INI'
      GROUP BY merchant,
               DB3.COUNTRY_REGION) as t1
         LEFT JOIN
         (SELECT avg(number) as PREV_7D_AVG, merchant1
          from (
                   ((select count(TRANS_ID) as number,
                            COUNTRY_REGION  as CONTINENT,
                            merchant1
                     from DB1
                              left join DB2 on external_identifier = trans_id
                              left join DB3 on DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
                     where ts > dateadd(hour, -2, :start_time)
                       and ts < dateadd(hour, -1, :start_time)

                     group by CONTINENT,
                              merchant1
                     having number > 30)
                    union all
                    (select count(TRANS_ID) as number,
                            COUNTRY_REGION  as CONTINENT,
                            merchant1
                     from DB1
                              left join DB2 on external_identifier = trans_id
                              left join DB3 on DB2.COUNTRY_CODE = DB3.COUNTRY_CODE
                     where ts > dateadd(hour, -26, :start_time)
                       and ts < dateadd(hour, -25, :start_time)

                     group by CONTINENT,
                              merchant1
                     having number > 30))
                       -- then six more unions, every one one day earlier
                       group by
                       merchant1::varchar,
                       CONTINENT)) as t2 ON t1.Merchant = t2.merchant1);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.