如何引用列中的第一个非空字符串 - Cloudera Impala / Apache Hive / Spark SQL
How to Reference First Non-null String in a Column - Cloudera Impala / Apache Hive / Spark SQL
我正在使用 Impala SQL。我目前有一个包含 3 列的数据库:Account
、Date
、Type
。
Type
下有各种描述关联类型的数据串,但有的等于'UNKNOWN'
,有的等于null
。
我想再创建一个专栏 Fixed_Type
。 Fixed_Type
中的值应来自 Type
列。
- 如果
Type
中的值是 null
或 'UNKNOWN'
,它应该获取 Type
列中的最后一个有效值,按帐户分区并按日期排序.
- 如果分区以
null
或 'UNKNOWN'
开头,则 Fixed_Type
中的值应该是 Type
. 中的第一个有效值
例如:
Account | Date | Type | Fixed_Type
1 Jan data1 data1
1 Feb 'UNKNOWN' data1
1 Mar null data1
2 Apr data2 data2
2 May null data2
2 Jun null data2
2 Jul data3 data3
3 Feb 'UNKNOWN' data4
3 Mar 'UNKNOWN' data4
3 Apr data4 data4
我开始在 Oracle 中执行此操作,但后来意识到没有类似于 Impala 中实现的 IGNORE NULLS
的功能。
这就是我想在 Oracle 中做的事情(我意识到这只处理空值的前向填充):
select account, date, type,
case when type is null
then last_value(type ignore nulls)
over (partition by account order by date)
else type
end as fixed_type
我使用 postgresql 来测试查询,所以不能 100% 确定它是否可以在您的系统中运行。 WITH
可以用子查询替换。还必须将您的日期更改为数字,以便 ORDER BY
按预期工作。
- enumerateWords: 为有效单词创建枚举列表。
- createFlag:设置一个标志,以便您可以验证下一组何时开始。
- createGrp :使用标志和
SUM()
创建组。
- 最后你加入了枚举列表的组来分配
Fixed_Type
- 当第一行是
NULL
或 'UNKNOWN'
时,JOIN c.grp = 0 and e.rn =1
中的特殊条件
WITH enumerateWords as (
SELECT "Account", "Date", "Type",
row_number() over (partition by "Account"
order by "Date") rn
FROM Days
WHERE "Type" <> '''UNKNOWN''' AND "Type" IS NOT NULL
), createFlag as (
SELECT *, CASE WHEN "Type" = '''UNKNOWN''' OR "Type" IS NULL
THEN 0
ELSE 1
END as FLAG
FROM Days
), createGrp as (
SELECT *,
SUM(FLAG) OVER (PARTITION BY "Account"
ORDER BY "Date") as grp
FROM createFlag
)
SELECT c.*, e."Account", e."Date", e."Type" as "Fixed_Type"
FROM createGrp c
JOIN enumerateWords e
ON c."Account" = e."Account"
AND ( c.grp = e.rn
OR (c.grp = 0 and e.rn = 1)
)
输出
如您所见,createGrp 显示 Fixed_Type
从数据库中的值类型,但 enumerateWords 从 Type
.
创建它
您可以看到 flag 和 grp 如何协同工作来检测变化。
| createGrp || enumerateWords |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| Account | Date | Type | Fixed_Type | flag | grp || Account | rn | Fixed_Type |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 1 | 1 | data1 | data1 | 1 | 1 || 1 | 1 | data1 |
| 1 | 2 | 'UNKNOWN' | data1 | 0 | 1 || 1 | 1 | data1 |
| 1 | 3 | (null) | data1 | 0 | 1 || 1 | 1 | data1 |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 2 | 4 | data2 | data2 | 1 | 1 || 2 | 1 | data2 |
| 2 | 5 | (null) | data2 | 0 | 1 || 2 | 1 | data2 |
| 2 | 6 | (null) | data2 | 0 | 1 || 2 | 1 | data2 |
| 2 | 7 | data3 | data3 | 1 | 2 || 2 | 2 | data3 |
| 2 | 8 | (null) | data3 | 0 | 2 || 2 | 2 | data3 |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 3 | 9 | 'UNKNOWN' | data4 | 0 | 0 || 3 | 1 | data4 | <=
| 3 | 10 | 'UNKNOWN' | data4 | 0 | 0 || 3 | 1 | data4 | <=
| 3 | 11 | data4 | data4 | 1 | 1 || 3 | 1 | data4 |
^^^ special case 0 = 1
Oracle 设置:
CREATE TABLE Table_Name ( Acct, Dt, Type ) AS
SELECT 1, DATE '2016-01-01', 'Data1' FROM DUAL UNION ALL
SELECT 1, DATE '2016-02-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 1, DATE '2016-03-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-04-01', 'Data2' FROM DUAL UNION ALL
SELECT 2, DATE '2016-05-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-06-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-07-01', 'Data3' FROM DUAL UNION ALL
SELECT 3, DATE '2016-02-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 3, DATE '2016-03-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 3, DATE '2016-04-01', 'Data4' FROM DUAL;
查询:
SELECT Acct,
Dt,
Type,
Fixed_Type
FROM (
SELECT r.Acct,
r.Dt,
r.Type,
t.type AS fixed_type,
ROW_NUMBER() OVER ( PARTITION BY r.Acct, r.dt
ORDER BY SIGN( ABS( t.dt - r.dt ) ),
SIGN( t.dt - r.dt ),
ABS( t.dt - r.dt ) ) AS rn
FROM table_name r
LEFT OUTER JOIN
table_name t
ON ( r.acct = t.acct
AND t.type IS NOT NULL
AND t.type <> 'UNKNOWN' )
)
WHERE rn = 1
ORDER BY acct, dt;
解释:
如果您将 table 连接到自身,因此两个 table 具有相同的帐号,那么您可以将每个帐户的每一行与同一帐户中的所有其他行进行比较。但是,我们对与所有行进行比较不感兴趣,而只是与不是 NULL
或 'UNKNOWN'
的行进行比较,因此我们得到连接条件:
ON ( r.acct = t.acct
AND t.type IS NOT NULL
AND t.type <> 'UNKNOWN' )
A LEFT OUTER JOIN
用于万一有一个帐号具有其类型的所有 NULL
或 'UNKNOWN'
值,这样行就不会被排除。
然后就是找到最新的行。在 Oracle 中,如果您从一个日期减去另一个日期,则会得到天数(或天数的一部分)差异 - 所以:
如果两个日期相同,SIGN( ABS( t.dt - r.dt ) )
将给出 0
,如果不同,将给出 1
。首先排序意味着如果有一个值具有相同的日期,那么它将优先于非相同的日期;
SIGN( t.dt - r.dt )
将 return 0
如果两个日期相同(但已在前面的语句中过滤)或 -1
如果比较日期是在当前行之前或 +1
如果它在之后 - 这用于优先选择之前的日期而不是之后的日期。
ABS( t.dt - r.dt )
将按最接近的日期排序。
因此 ORDER BY
子句有效地声明:首先是 ORDER BY
相同的日期,然后是之前的日期(首先最接近 r.dt
)最后是之后的日期(最接近 r.dt
首先)。
然后将所有内容放在一个在线视图中并进行过滤以获得每一行的最佳匹配 (WHERE rn = 1
)。
输出:
ACCT DT TYPE FIXED_TYPE
---------- ------------------- ------- ----------
1 2016-01-01 00:00:00 Data1 Data1
1 2016-02-01 00:00:00 UNKNOWN Data1
1 2016-03-01 00:00:00 Data1
2 2016-04-01 00:00:00 Data2 Data2
2 2016-05-01 00:00:00 Data2
2 2016-06-01 00:00:00 Data2
2 2016-07-01 00:00:00 Data3 Data3
3 2016-02-01 00:00:00 UNKNOWN Data4
3 2016-03-01 00:00:00 UNKNOWN Data4
3 2016-04-01 00:00:00 Data4 Data4
这是一个类似于 Juan Carlos 的解决方案,使用解析函数 count
和 case
表达式一次性创建组。
我创建了更多输入数据来测试,例如,当一个帐户只有 null
and/or 'UNKNOWN'
作为类型时会发生什么(确保左外连接按预期工作).
create table table_name ( acct, dt, type ) as
select 1, date '2016-01-01', 'Data1' from dual union all
select 1, date '2016-02-01', 'UNKNOWN' from dual union all
select 1, date '2016-03-01', null from dual union all
select 2, date '2016-04-01', 'Data2' from dual union all
select 2, date '2016-05-01', null from dual union all
select 2, date '2016-06-01', null from dual union all
select 2, date '2016-07-01', 'Data3' from dual union all
select 3, date '2016-02-01', 'UNKNOWN' from dual union all
select 3, date '2016-03-01', 'UNKNOWN' from dual union all
select 3, date '2016-04-01', 'Data4' from dual union all
select 3, date '2016-05-01', 'UNKNOWN' from dual union all
select 3, date '2016-06-01', 'Data5' from dual union all
select 4, date '2016-02-01', null from dual union all
select 4, date '2016-03-01', 'UNKNOWN' from dual;
SQL> select * from table_name;
ACCT DT TYPE
---------- ---------- -------
1 2016-01-01 Data1
1 2016-02-01 UNKNOWN
1 2016-03-01
2 2016-04-01 Data2
2 2016-05-01
2 2016-06-01
2 2016-07-01 Data3
3 2016-02-01 UNKNOWN
3 2016-03-01 UNKNOWN
3 2016-04-01 Data4
3 2016-05-01 UNKNOWN
3 2016-06-01 Data5
4 2016-02-01
4 2016-03-01 UNKNOWN
14 rows selected.
查询:
with
prep(acct, dt, type, gp) as (
select acct, dt, type,
count(case when type != 'UNKNOWN' then 1 end)
over (partition by acct order by dt)
from table_name
),
no_nulls(acct, type, gp) as (
select acct, type, gp
from prep
where type != 'UNKNOWN'
)
select p.acct, p.dt, p.type, n.type as fixed_type
from prep p left outer join no_nulls n
on p.acct = n.acct and (p.gp = n.gp or p.gp = 0 and n.gp = 1)
order by acct, dt;
输出:
ACCT DT TYPE FIXED_TYPE
---------- ---------- ------- ----------
1 2016-01-01 Data1 Data1
1 2016-02-01 UNKNOWN Data1
1 2016-03-01 Data1
2 2016-04-01 Data2 Data2
2 2016-05-01 Data2
2 2016-06-01 Data2
2 2016-07-01 Data3 Data3
3 2016-02-01 UNKNOWN Data4
3 2016-03-01 UNKNOWN Data4
3 2016-04-01 Data4 Data4
3 2016-05-01 UNKNOWN Data4
3 2016-06-01 Data5 Data5
4 2016-02-01
4 2016-03-01 UNKNOWN
14 rows selected.
我正在使用 Impala SQL。我目前有一个包含 3 列的数据库:Account
、Date
、Type
。
Type
下有各种描述关联类型的数据串,但有的等于'UNKNOWN'
,有的等于null
。
我想再创建一个专栏 Fixed_Type
。 Fixed_Type
中的值应来自 Type
列。
- 如果
Type
中的值是null
或'UNKNOWN'
,它应该获取Type
列中的最后一个有效值,按帐户分区并按日期排序. - 如果分区以
null
或'UNKNOWN'
开头,则Fixed_Type
中的值应该是Type
. 中的第一个有效值
例如:
Account | Date | Type | Fixed_Type
1 Jan data1 data1
1 Feb 'UNKNOWN' data1
1 Mar null data1
2 Apr data2 data2
2 May null data2
2 Jun null data2
2 Jul data3 data3
3 Feb 'UNKNOWN' data4
3 Mar 'UNKNOWN' data4
3 Apr data4 data4
我开始在 Oracle 中执行此操作,但后来意识到没有类似于 Impala 中实现的 IGNORE NULLS
的功能。
这就是我想在 Oracle 中做的事情(我意识到这只处理空值的前向填充):
select account, date, type,
case when type is null
then last_value(type ignore nulls)
over (partition by account order by date)
else type
end as fixed_type
我使用 postgresql 来测试查询,所以不能 100% 确定它是否可以在您的系统中运行。 WITH
可以用子查询替换。还必须将您的日期更改为数字,以便 ORDER BY
按预期工作。
- enumerateWords: 为有效单词创建枚举列表。
- createFlag:设置一个标志,以便您可以验证下一组何时开始。
- createGrp :使用标志和
SUM()
创建组。 - 最后你加入了枚举列表的组来分配
Fixed_Type
- 当第一行是
NULL
或'UNKNOWN'
时,JOIN
c.grp = 0 and e.rn =1
中的特殊条件 - 当第一行是
WITH enumerateWords as (
SELECT "Account", "Date", "Type",
row_number() over (partition by "Account"
order by "Date") rn
FROM Days
WHERE "Type" <> '''UNKNOWN''' AND "Type" IS NOT NULL
), createFlag as (
SELECT *, CASE WHEN "Type" = '''UNKNOWN''' OR "Type" IS NULL
THEN 0
ELSE 1
END as FLAG
FROM Days
), createGrp as (
SELECT *,
SUM(FLAG) OVER (PARTITION BY "Account"
ORDER BY "Date") as grp
FROM createFlag
)
SELECT c.*, e."Account", e."Date", e."Type" as "Fixed_Type"
FROM createGrp c
JOIN enumerateWords e
ON c."Account" = e."Account"
AND ( c.grp = e.rn
OR (c.grp = 0 and e.rn = 1)
)
输出
如您所见,createGrp 显示 Fixed_Type
从数据库中的值类型,但 enumerateWords 从 Type
.
您可以看到 flag 和 grp 如何协同工作来检测变化。
| createGrp || enumerateWords |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| Account | Date | Type | Fixed_Type | flag | grp || Account | rn | Fixed_Type |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 1 | 1 | data1 | data1 | 1 | 1 || 1 | 1 | data1 |
| 1 | 2 | 'UNKNOWN' | data1 | 0 | 1 || 1 | 1 | data1 |
| 1 | 3 | (null) | data1 | 0 | 1 || 1 | 1 | data1 |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 2 | 4 | data2 | data2 | 1 | 1 || 2 | 1 | data2 |
| 2 | 5 | (null) | data2 | 0 | 1 || 2 | 1 | data2 |
| 2 | 6 | (null) | data2 | 0 | 1 || 2 | 1 | data2 |
| 2 | 7 | data3 | data3 | 1 | 2 || 2 | 2 | data3 |
| 2 | 8 | (null) | data3 | 0 | 2 || 2 | 2 | data3 |
|---------|------|-----------|------------|------|-----||---------|----|------------|
| 3 | 9 | 'UNKNOWN' | data4 | 0 | 0 || 3 | 1 | data4 | <=
| 3 | 10 | 'UNKNOWN' | data4 | 0 | 0 || 3 | 1 | data4 | <=
| 3 | 11 | data4 | data4 | 1 | 1 || 3 | 1 | data4 |
^^^ special case 0 = 1
Oracle 设置:
CREATE TABLE Table_Name ( Acct, Dt, Type ) AS
SELECT 1, DATE '2016-01-01', 'Data1' FROM DUAL UNION ALL
SELECT 1, DATE '2016-02-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 1, DATE '2016-03-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-04-01', 'Data2' FROM DUAL UNION ALL
SELECT 2, DATE '2016-05-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-06-01', NULL FROM DUAL UNION ALL
SELECT 2, DATE '2016-07-01', 'Data3' FROM DUAL UNION ALL
SELECT 3, DATE '2016-02-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 3, DATE '2016-03-01', 'UNKNOWN' FROM DUAL UNION ALL
SELECT 3, DATE '2016-04-01', 'Data4' FROM DUAL;
查询:
SELECT Acct,
Dt,
Type,
Fixed_Type
FROM (
SELECT r.Acct,
r.Dt,
r.Type,
t.type AS fixed_type,
ROW_NUMBER() OVER ( PARTITION BY r.Acct, r.dt
ORDER BY SIGN( ABS( t.dt - r.dt ) ),
SIGN( t.dt - r.dt ),
ABS( t.dt - r.dt ) ) AS rn
FROM table_name r
LEFT OUTER JOIN
table_name t
ON ( r.acct = t.acct
AND t.type IS NOT NULL
AND t.type <> 'UNKNOWN' )
)
WHERE rn = 1
ORDER BY acct, dt;
解释:
如果您将 table 连接到自身,因此两个 table 具有相同的帐号,那么您可以将每个帐户的每一行与同一帐户中的所有其他行进行比较。但是,我们对与所有行进行比较不感兴趣,而只是与不是 NULL
或 'UNKNOWN'
的行进行比较,因此我们得到连接条件:
ON ( r.acct = t.acct
AND t.type IS NOT NULL
AND t.type <> 'UNKNOWN' )
A LEFT OUTER JOIN
用于万一有一个帐号具有其类型的所有 NULL
或 'UNKNOWN'
值,这样行就不会被排除。
然后就是找到最新的行。在 Oracle 中,如果您从一个日期减去另一个日期,则会得到天数(或天数的一部分)差异 - 所以:
-
如果两个日期相同,
SIGN( ABS( t.dt - r.dt ) )
将给出0
,如果不同,将给出1
。首先排序意味着如果有一个值具有相同的日期,那么它将优先于非相同的日期;SIGN( t.dt - r.dt )
将 return0
如果两个日期相同(但已在前面的语句中过滤)或-1
如果比较日期是在当前行之前或+1
如果它在之后 - 这用于优先选择之前的日期而不是之后的日期。ABS( t.dt - r.dt )
将按最接近的日期排序。
因此 ORDER BY
子句有效地声明:首先是 ORDER BY
相同的日期,然后是之前的日期(首先最接近 r.dt
)最后是之后的日期(最接近 r.dt
首先)。
然后将所有内容放在一个在线视图中并进行过滤以获得每一行的最佳匹配 (WHERE rn = 1
)。
输出:
ACCT DT TYPE FIXED_TYPE
---------- ------------------- ------- ----------
1 2016-01-01 00:00:00 Data1 Data1
1 2016-02-01 00:00:00 UNKNOWN Data1
1 2016-03-01 00:00:00 Data1
2 2016-04-01 00:00:00 Data2 Data2
2 2016-05-01 00:00:00 Data2
2 2016-06-01 00:00:00 Data2
2 2016-07-01 00:00:00 Data3 Data3
3 2016-02-01 00:00:00 UNKNOWN Data4
3 2016-03-01 00:00:00 UNKNOWN Data4
3 2016-04-01 00:00:00 Data4 Data4
这是一个类似于 Juan Carlos 的解决方案,使用解析函数 count
和 case
表达式一次性创建组。
我创建了更多输入数据来测试,例如,当一个帐户只有 null
and/or 'UNKNOWN'
作为类型时会发生什么(确保左外连接按预期工作).
create table table_name ( acct, dt, type ) as
select 1, date '2016-01-01', 'Data1' from dual union all
select 1, date '2016-02-01', 'UNKNOWN' from dual union all
select 1, date '2016-03-01', null from dual union all
select 2, date '2016-04-01', 'Data2' from dual union all
select 2, date '2016-05-01', null from dual union all
select 2, date '2016-06-01', null from dual union all
select 2, date '2016-07-01', 'Data3' from dual union all
select 3, date '2016-02-01', 'UNKNOWN' from dual union all
select 3, date '2016-03-01', 'UNKNOWN' from dual union all
select 3, date '2016-04-01', 'Data4' from dual union all
select 3, date '2016-05-01', 'UNKNOWN' from dual union all
select 3, date '2016-06-01', 'Data5' from dual union all
select 4, date '2016-02-01', null from dual union all
select 4, date '2016-03-01', 'UNKNOWN' from dual;
SQL> select * from table_name;
ACCT DT TYPE
---------- ---------- -------
1 2016-01-01 Data1
1 2016-02-01 UNKNOWN
1 2016-03-01
2 2016-04-01 Data2
2 2016-05-01
2 2016-06-01
2 2016-07-01 Data3
3 2016-02-01 UNKNOWN
3 2016-03-01 UNKNOWN
3 2016-04-01 Data4
3 2016-05-01 UNKNOWN
3 2016-06-01 Data5
4 2016-02-01
4 2016-03-01 UNKNOWN
14 rows selected.
查询:
with
prep(acct, dt, type, gp) as (
select acct, dt, type,
count(case when type != 'UNKNOWN' then 1 end)
over (partition by acct order by dt)
from table_name
),
no_nulls(acct, type, gp) as (
select acct, type, gp
from prep
where type != 'UNKNOWN'
)
select p.acct, p.dt, p.type, n.type as fixed_type
from prep p left outer join no_nulls n
on p.acct = n.acct and (p.gp = n.gp or p.gp = 0 and n.gp = 1)
order by acct, dt;
输出:
ACCT DT TYPE FIXED_TYPE
---------- ---------- ------- ----------
1 2016-01-01 Data1 Data1
1 2016-02-01 UNKNOWN Data1
1 2016-03-01 Data1
2 2016-04-01 Data2 Data2
2 2016-05-01 Data2
2 2016-06-01 Data2
2 2016-07-01 Data3 Data3
3 2016-02-01 UNKNOWN Data4
3 2016-03-01 UNKNOWN Data4
3 2016-04-01 Data4 Data4
3 2016-05-01 UNKNOWN Data4
3 2016-06-01 Data5 Data5
4 2016-02-01
4 2016-03-01 UNKNOWN
14 rows selected.