Using Snowflake SQL how do you find two records and then change another one based on those records to a predefined record using a local variable?

Viewed 112

Using SQL how do you use two records to find a place, hold onto that place and use that record to replace 'Nonsense' value with that held onto place? I am going to show what I have been able to write so far, but then write out what I am still trying to figure out:

SELECT * FROM "TABLES". "ACCTS_OF_SUPERHEROS".;

DECLARE @count_rows INT = 0;
DECLARE @row_total INT = 0;
DECLARE @refAcctNum INT = 0;
DECLARE @selectedPlaceName TINYTEXT;

SET @row_total = SELECT COUNT (*)

WHILE countRows < row_total
    for each acct_num store value in refAcctNum. 
    Using refAcctNum find place: "Gotham City", "Central City", "Metropolis", "Smallville", "Star City", "Fawcett City" store that in selectedPlaceName.
    If refAccountNumber has Nonsense then replace with selectedPlaceName record
    otherwise add + 1 to countRows and repeat. 
END

Current table data; "ACCTS_OF_SUPERHEROS" table:

| row | acct_num | exact_address    | place
| --- | -------- |------------------|--------
| 1   | 049403   | 344 Clinton Str  | Metropolis 
| 2   | 049403   | 344 Clinton Str  | Nonsense
| 3   | 049206   | 1007 Mountain Dr | Gotham City
| 4   | 049206   | 1007 Mountain Dr | Gotham City
| 5   | 049206   | 1096 Show Dr.    | Fawcett City
| 6   | 049206   | 1096 Show Dr.    | Nonsense
| 7   | 049206   | NULL             | Nonsense
| 8   | 049291   | 1938 Sullivan Pl | Smallville
| 9   | 049293   | 700 Hamilton Str | Central City
| 10  | 049396   | 800 Nonsense Way | Nonsense
| 11  | 049396   | NULL             | Nonsense

Desired output:

| row | acct_num | exact_address    | place
| --- | -------- |------------------|--------
| 1   | 049403   | 344 Clinton Str  | Metropolis 
| 2   | 049403   | 344 Clinton Str  | Metropolis
| 3   | 049206   | 1007 Mountain Dr | Gotham City
| 4   | 049206   | 1007 Mountain Dr | Gotham City
| 5   | 049206   | 1096 Show Dr.    | Fawcett City
| 6   | 049206   | 1096 Show Dr.    | Fawcett City
| 7   | 049206   | NULL             | Fawcett City
| 8   | 049291   | 1938 Sullivan Pl | Smallville
| 9   | 049293   | 700 Hamilton Str | Central City
| 10  | 049396   | 800 Tidal Way    | Star City
| 11  | 049396   | NULL             | Star City
2 Answers

You can use window functions:

select t.*,
       max(case when place <> 'Nonsense' then place end) over (partition by acct_num) as imputed_place
from t;

This returns NULL if all the rows are 'Nonsense' for a given acct_num. You can use COALESCE() to replace the value with something else.

I was reading through the available list of window functions in Snowflake and think you're going to need a new window function for this. Perhaps someone can find a more built-in way, but anyway here's a user defined table function REPLACE_WITH_LKG implemented as a window function that will replace a bad value with the last known good value. As long as I was going to write it, I thought it may as well be general purpose, so it matches "bad" values using a regular expression and JavaScript RegExp options.

create or replace function REPLACE_WITH_LKG("VALUE" string, "REGEXP" string, "REGEXP_OPTIONS" string)
returns table(LKG_VALUE string)
language javascript
strict immutable
as
$$
{
    initialize: function (argumentInfo, context) {
        this.lkg = "";
    },
    processRow: function (row, rowWriter, context) {
        const rx = new RegExp(row.REGEXP, row.REGEXP_OPTIONS);
        if (!rx.test(row.VALUE)) {
            this.lkg = row.VALUE;
        }
        rowWriter.writeRow({LKG_VALUE: this.lkg});
    },
    finalize: function (rowWriter, context) {},
}
$$;

select S.*, LKG.LKG_VALUE as PLACE
from superhero S, table(REPLACE_WITH_LKG(PLACE, 'Nonsense', 'ig')
over(partition by null order by "ROW")) LKG;
;

A note on performance; the way the data shows this the're no partition other than the entire table. That's because the one obvious place to partition, by account, won't work. Row 10 is getting its value from what would be a different window if using account, so the way the sample data appears it needs to be a window that spans the entire table. This will not parallelize well and should be avoided for very large tables.

Related