雪花 - 在两个数组之间返回不同(不相似)的值

Posted

技术标签:

【中文标题】雪花 - 在两个数组之间返回不同(不相似)的值【英文标题】:Snowflake - return different (not similar) values between two arrays 【发布时间】:2019-10-30 22:42:21 【问题描述】:

查看 Snowflake 文档后,我找到了名为 array_intersection(array_1, array_2) 的函数,它将返回两个数组之间的公共值,但我需要显示数组中的任何一个数组中都不存在的值。

示例 1:

假设我的表中有以下两个数组

array_1 = ['a', 'b', 'c', 'd', 'e']
array_2 = ['a', 'f', 'c', 'g', 'e']

我的查询:

select
  array_intersection(array_1, array_2)
from myTable

电流输出:

['a', 'c', 'e']

但我期望输出为:

['f', 'g']

示例 2:

假设我的表中有以下两个数组

array_1 = ['u', 'v', 'w', 'x', 'y']
array_2 = ['u', 'v', 'i', 'x', 'k']

我的查询:

select
  array_intersection(array_1, array_2)
from myTable

电流输出:

['u', 'v', 'x']

但我期望输出为:

['w', 'y', 'i', 'k']

如何在 Snowflake 中做到这一点?有什么建议吗?

【问题讨论】:

根据您预期的上述输出语句,您似乎希望第二个数组中的值不在第一个数组中?不过,我不认为这是你在介绍中所说的。你能澄清一下想要的结果吗? @MikeWalton 我的意思是,我想要包含两个数组中都不存在的所有值的最终数组。如果您仍然没有得到它,请告诉我,我可以在这里举个例子。 【参考方案1】:

本题中的集合运算就是数学家所说的disjunctive union。 这是一个 ARRAY 螺丝可能没有被 SQL 锤子最佳处理的情况。

一件事是让一个孤立的查询与准系统案例一起工作,另一件事是创建一个可维护的现实生活中的查询,其中也包含其他复杂性。

javascript 非常适合处理集合计算,并且非常适合此任务:

CREATE OR REPLACE FUNCTION ARRAY_DISJUNCTIVE_UNION(A1 ARRAY, A2 ARRAY)
RETURNS ARRAY LANGUAGE JAVASCRIPT AS
'return [...A1.filter(e => !A2.includes(e)),...A2.filter(e => !A1.includes(e))]';

【讨论】:

我喜欢这个答案,因为在创建一个包含 100 万行的表后,我的 sql 用了 18 秒,而 Hans 用了 6 秒。 @SimeonPilgrim 我最初打算使用 SQL 解决方案,但后来我看到了你的解决方案,并认为我没有比这更好的了。但是,最精简的 SQL 仍然是一个怪物。当涉及到数组和对象时,最新的 JavaScript 确实比 SQL 具有符号优势——这不应该让人感到意外,因为它是语言的核心。也感谢您分享时间信息。 出于速度原因,我倾向于避免使用 js 创建或替换临时表 myTable AS 与循环 AS (SELECT SEQ8() as seq, 5 + (abs(random())%5) as len from table(generator(rowcount => 1000000))), rowset AS (SELECT row_number()over(order by true) as rn from table (generator(rowcount => 10))), chars AS (select l.seq, l. len, r.rn, CHAR(UNIFORM(65,90, random())) as cha, CHAR(UNIFORM(65,90, random()+1)) as chb from loops l join rowset r on r.rn 出于同样的原因,我也倾向于避免使用非 SQL 构造 - 而且我是数据仓库专业人士,而不是应用程序程序员,所以这很自然:O。但我猜在这种情况下,js 字节码编译器可以将紧凑的符号转换为更高效的代码,甚至包括函数调用开销......【参考方案2】:

您可以通过横向展平将数组转换为结果集,然后使用 SQL 集合操作比较元素,然后将结果转换回数组。

CTE(WITH 子句)的概念允许您在一个语句中完成所有这些操作。

create or replace table arrays as (select split('1,2,3',',') a, split('4,9,1,3',',') b); //some test data

with a_elements as
(select value from arrays,   
lateral flatten(input => arrays.a) f ) // array to result set
, b_elements as 
(select value from arrays,   
lateral flatten(input => arrays.b) f )
select array_agg(value) // reassemble the array
from (select * from a_elements minus select * from b_elements);

【讨论】:

【参考方案3】:
with myTable as (
select array_construct('a', 'b', 'c', 'd', 'e') as a1
    ,array_construct('a', 'f', 'c', 'g', 'e') as a2
)
select a1, a2, array_intersection(a1, a2)
from myTable;

表明我们正在使用相同的数据。

with myTable as (
    SELECT array_construct('a', 'b', 'c', 'd', 'e') as a1
    ,array_construct('a', 'f', 'c', 'g', 'e') as a2
), seq_myTable as (
    SELECT seq8() as seq
    ,t.*
  from myTable t
), expanded_a1 as (
   select a.seq
    ,f.value as val
  from seq_myTable a, 
    lateral flatten(input => a.a1) f
), expanded_a2 as (
   select a.seq
    ,f.value as val
  from seq_myTable a, 
    lateral flatten(input => a.a2) f
)
select coalesce(a.seq,b.seq) as seq, array_agg(coalesce(a.val,b.val)) as vals
from expanded_a1 a
full outer join expanded_a2 b 
    on a.seq = b.seq and a.val = b.val
where (a.seq is null OR b.seq is null)
group by 1;

这给出了答案,但它们没有排序,您需要:

with myTable as (
    SELECT array_construct('a', 'b', 'c', 'd', 'e') as a1
    ,array_construct('a', 'f', 'c', 'g', 'e') as a2
), seq_myTable as (
    SELECT seq8() as seq
    ,t.*
  from myTable t
), expanded_a1 as (
   select a.seq
    ,f.value as val
  from seq_myTable a, 
    lateral flatten(input => a.a1) f
), expanded_a2 as (
   select a.seq
    ,f.value as val
  from seq_myTable a, 
    lateral flatten(input => a.a2) f
)
select array_agg(val) WITHIN GROUP ( order by val) as vals 
from (
  select coalesce(a.seq,b.seq) as seq, coalesce(a.val,b.val) as val
  from expanded_a1 a
  full outer join expanded_a2 b 
      on a.seq = b.seq and a.val = b.val
  where (a.seq is null OR b.seq is null)
)
group by seq;

给出输出[ "b", "d", "f", "g" ]

【讨论】:

【参考方案4】:

我不确定您是否需要像其他答案一样的序列。这很干净:

with myTable as (
select array_construct('a', 'b', 'c', 'd', 'e') as a1
    ,array_construct('a', 'f', 'c', 'g', 'e') as a2
)
SELECT array_agg(coalesce(a1.value,a2.value)) WITHIN GROUP (ORDER BY coalesce(a1.value,a2.value)) as newarray
FROM (
  SELECT *
  FROM myTable,
  lateral flatten(input => a1) a1
  ) a1
FULL OUTER JOIN (
  SELECT *
  FROM myTable,
  lateral flatten(input => a2) a2
  ) a2
ON a1.value::varchar = a2.value::varchar
WHERE a1.value IS NULL
   OR a2.value IS NULL
;

【讨论】:

公平点,这些年来我在 SF 中的 flatten 遇到了很多不幸,随着事情变得复杂,它过去随机爆炸,所以我只是推出了比需要的 CTE 更长的时间。 啊,但是如果有不止一行对,您的代码将失败,这就是我包含 seq 的原因。 不需要分析问题中的多行,但我明白你的意思。但是,您是否有理由不使用在使用 FLATTEN 时自动显示的 SEQ 字段? 老实说没有想到这一点.. 但鉴于它不是无间隙的,并且您正在独立扩展 A1 和 A2,您无法确定它们以相同的顺序扩展,因此没有共同点行的框架。

以上是关于雪花 - 在两个数组之间返回不同(不相似)的值的主要内容,如果未能解决你的问题,请参考以下文章

如何分离从表行中获取的值并将其存储在雪花中的数组中

雪花,获取两个表之间不匹配列的列表(SQL)

雪花中两个日期之间的工作时间

如何在雪花的左外连接期间提取所有值(即使是那些不匹配的值)?

雪花任务多个计划

雪花存储在使用抛出错误而不是返回错误时给出不同的错误