【问题标题】: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 我的意思是,我想要包含两个数组中都不存在的所有值的最终数组。如果您仍然没有得到它,请告诉我,我可以在这里举个例子。

标签: snowflake-cloud-data-platform


【解决方案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,您无法确定它们以相同的顺序扩展,因此没有共同点行的框架。
      猜你喜欢
      • 2022-07-26
      • 1970-01-01
      • 1970-01-01
      • 2013-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-07
      相关资源
      最近更新 更多