问题描述
有时,SNowflake sql编译器出于自身利益而试图变得过于聪明。这是previous question here的后续工作,在{{3}}中,为我的给定用例提供了一个聪明的解决方案,但该解决方案遇到了一些限制。
简要背景;我有一个JS-UDTF,它需要3个float参数来返回表示一系列<div class="none" id="search">Example</div>
<button class="show-btn fas" id="show">Show</button>
的行,还有一个sql-UDTF GENERATE_SERIES(FLOAT,FLOAT,FLOAT)
,它将参数转换为floats,然后调用JS-UDTF,然后返回结果回到整数。此包装UDTF的原始版本为:
GENERATE_SERIES(INT,INT,INT)
在输入不是常量的大多数情况下会失败,例如:
CREATE OR REPLACE FUNCTION generate_series(FirsT_VALUE INTEGER,LAST_VALUE INTEGER,STEP_VALUE INTEGER)
RETURNS TABLE (GS_VALUE INTEGER)
AS
$$
SELECT GS_VALUE::INTEGER AS GS_VALUE FROM table(generate_series(FirsT_VALUE::DOUBLE,LAST_VALUE::DOUBLE,STEP_VALUE::DOUBLE))
$$;
会返回错误:
WITH report_params AS (
SELECT
1::integer as first_value,3::integer as last_value,1::integer AS step_value
)
SELECT
*
FROM
report_params,table(
generate_series(
first_value,last_value,step_value
)
)
提供的欺骗sql编译器行为的解决方案是将函数参数封装到VALUES表中并交叉联接内部UDTF:
sql compilation error: Unsupported subquery type cannot be evaluated
对于大多数调用而言,这很不错,但是我发现sql编译器再次出现这种情况。这是一个重现该问题的简化示例:
CREATE OR REPLACE FUNCTION generate_series_int(FirsT_VALUE INTEGER,STEP_VALUE INTEGER)
RETURNS TABLE (GS_VALUE INTEGER)
AS
$$
SELECT GS_VALUE::INTEGER AS GS_VALUE
FROM (VALUES (first_value,step_value)),table(generate_series(first_value::double,last_value::double,step_value::double))
$$;
这会导致错误:
WITH report_params AS (
SELECT
1::integer AS first_value,DATEDIFF('DAY','2020-01-01'::date,'2020-02-01'::date)::integer AS last_value,1::integer AS step_value
)
SELECT
*
FROM
report_params,table(
COMMON.FN.generate_series(
first_value,step_value
)
);
错误似乎很明显(我认为),编译器正在尝试将函数代码嵌入到外部查询中,这些外部查询在运行时将函数像宏一样对待。
这时的答案可能只是我对SNowflake的当前功能要求不高,但是出于学习和继续构建我认为非常有用的UDF库的兴趣,我想知道是否有一个解决方案,我很想念。
解决方法
主要问题是您编写了一个相关的子查询。
WITH report_params AS (
SELECT * FROM VALUES
(1,30,1)
v(first_value,last_value,step_value)
)
SELECT
*
FROM
report_params,table(
COMMON.FN.generate_series(
first_value,step_value
)
);
就像您在CTE中添加第二行一样
WITH report_params AS (
SELECT * FROM VALUES
(1,1),(2,40,2)
v(first_value,step_value
)
);
这很明显是相互关联的,不是很明显雪花应该由谁来执行。
对于上述数据,理想的外观是(如果它是有效的SQL)
WITH report_params AS (
SELECT *,mod(v.first_value,v.step_value) as mod_offset
FROM VALUES
(0,5,20,(1,3,15,3),4,(3,3)
v(id,first_value,step_value)
),report_ranges AS (
SELECT min(first_value) as mmin,max(last_value) as mmax
FROM report_params
WHERE first_value <= last_value AND step_value > 0
),all_range AS (
SELECT
row_number() over (order by seq8()) + rr.mmin - 1 as seq
FROM report_ranges rr,TABLE(GENERATOR( ROWCOUNT => (rr.mmax - rr.mmin) + 1 ))
)
SELECT
ar.seq,rp.id,rp.first_value,rp.last_value,rp.step_value,rp.mod_offset
FROM all_range as ar
JOIN report_params as rp ON ar.seq BETWEEN rp.first_value AND rp.last_value AND mod(ar.seq,rp.step_value) = rp.mod_offset
ORDER BY 2,1;
但是如果您在存储过程(或外部)中生成它,则可以替换为
WITH report_params AS (
SELECT *,all_range AS (
SELECT
row_number() over (order by seq8()) + 3 /*min*/ - 1 as seq
FROM TABLE(GENERATOR( ROWCOUNT => (20/*max*/ - 3/*min*/) + 1 ))
)
SELECT
ar.seq,1;
给予:
SEQ ID FIRST_VALUE LAST_VALUE STEP_VALUE MOD_OFFSET
5 0 5 20 1 0
6 0 5 20 1 0
7 0 5 20 1 0
8 0 5 20 1 0
9 0 5 20 1 0
10 0 5 20 1 0
11 0 5 20 1 0
12 0 5 20 1 0
13 0 5 20 1 0
14 0 5 20 1 0
15 0 5 20 1 0
16 0 5 20 1 0
17 0 5 20 1 0
18 0 5 20 1 0
19 0 5 20 1 0
20 0 5 20 1 0
3 1 3 15 3 0
6 1 3 15 3 0
9 1 3 15 3 0
12 1 3 15 3 0
15 1 3 15 3 0
4 2 4 15 3 1
7 2 4 15 3 1
10 2 4 15 3 1
13 2 4 15 3 1
5 3 5 15 3 2
8 3 5 15 3 2
11 3 5 15 3 2
14 3 5 15 3 2
我无法猜测的问题,是感觉像您以太试图隐藏表函数JS函数背后的某些复杂性,还是出于未知原因使事情变得复杂。
[编辑1-9评论]
generate_series
和GENERATOR
之间的主要区别在于前者几乎是UDF或CTE,而在雪花中,您必须在自己的子选择中使用GENERATOR,否则结果会很混乱。 >
with s1 as (
SELECT
row_number() over (order by seq8()) -1 as seq
FROM
TABLE(GENERATOR( ROWCOUNT => 3 ))
),s2 as (
SELECT
row_number() over (order by seq8()) -1 as seq
FROM
TABLE(GENERATOR( ROWCOUNT => 3 ))
)
select s1.seq as a,s2.seq as b
from s1,s2
order by 1,2;
将两个数据的9行混合在一起,就像您不想那样。 在哪里
with s1 as (
SELECT
row_number() over (order by seq8()) -1 as seq
FROM
TABLE(GENERATOR( ROWCOUNT => 3 ))
)
SELECT
row_number() over (order by seq8()) -1 as a,s1.seq as b
FROM
TABLE(GENERATOR( ROWCOUNT => 3 )),s1;
给出1-9,因为在序列代码运行之前,GENERATOR(行的创建者)已与其他数据交叉。
提供的原始解决方案的另一个版本是
WITH report_params AS (
SELECT *,trunc(div0((last_value-first_value),step_value)) as steps
FROM VALUES
(0,large_range AS (
SELECT
row_number() over (order by seq8()) -1 as seq
FROM
TABLE(GENERATOR( ROWCOUNT => 1000 ))
)
select rp.id,rp.first_value + (lr.seq*rp.step_value) as val
from report_params as rp
join large_range as lr on lr.seq <= rp.steps
order by 1,2;
我更喜欢它,因为混合的性质更加清晰。但这仍然说明了雪花和其他RDB之间的思维差异。在postgress中,进行单行操作没有任何成本,因为它是一个完全按行进行操作的时代,但是雪花没有按行的选择,并且因为它不能在每一行上执行任何操作,所以它可以独立执行许多行。这意味着每行的所有表达式都需要移到最前面,然后再加入。因此,上面试图显示的内容。