Self-referential SQL 中的 CASE WHEN 子句
Self-referential CASE WHEN clause in SQL
我正在尝试将一些格式不正确的数据迁移到数据库中。数据来自 CSV,首先加载到所有 varchar 列的暂存 table 中(因为我无法在这个阶段强制执行类型安全)。
数据可能看起来像
COL1 | COL2 | COL3
Name 1 | |
2/11/16 | 0 | 0
2/12/16 | 0 | 7
2/13/16 | 5 | 7
Name 2 | |
2/11/16 | 1 | 4
2/12/16 | 3 | 8
2/13/16 | 3 | 4
第一列混合了公司名称 pseudo-headers,以及与第 2 列和第 3 列数据相关的日期。我想通过创建一个 'Brand' 列开始转换数据 - 如果 Col2 为 NULL,则 'StoreBrand' 是 Col1 的值,否则是前一行的 StoreBrand。像这样的东西:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | Name 1
2/13/16 | 5 | 7 | Name 1
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | Name 2
2/13/16 | 3 | 4 | Name 2
我写了这个:
SELECT
t.*,
CASE
WHEN t.COL2 IS NULL THEN COL1
ELSE LAG(StoreBrand) OVER ()
END AS StoreBrand
FROM
(
SELECT
ROW_NUMBER() OVER () AS i,
*
FROM
Staging_Data
) t;
但是数据库(在本例中为 postgres,但我们正在考虑替代方案,因此首选最多样化的答案)在 LAG(StoreBrand) 上阻塞,因为那是派生列 I正在创造。调用 LAG(Col1) 仅填充第一行的真实数据:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | 2/11/16
2/13/16 | 5 | 7 | 2/12/16
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | 2/11/16
2/13/16 | 3 | 4 | 2/12/16
我的目标是 StoreBrand 列,它是下一个品牌名称之前所有日期值的 COL1 的第一个值:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | Name 1
2/13/16 | 5 | 7 | Name 1
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | Name 2
2/13/16 | 3 | 4 | Name 2
当 Col2 和 Col3 为空时 StoreBrand 的值无关紧要 - 该行将作为转换过程的一部分被删除。重要的是将数据行(即带有日期的行)与其品牌相关联。
有没有办法为我缺少的列引用以前的值?
我想我明白你想要什么。从技术上讲,您需要 lag()
上的 ignore nulls
选项,因此它看起来像这样:
select lag(case when col1 not like '%/%/%' then col1 end ignore nulls) over (order by linenumber) as brandname
唯一的问题? Postgres 不支持 ignore nulls
.
但是,您可以使用子查询做几乎相同的事情。这个想法是为每个组分配一个分组标识符。这是有效品牌名称的累计计数。然后一个简单的 max()
聚合工作:
select t.*,
max(case when col1 not like '%/%/%' then col1 end) over (partition by grp) as brand
from (select t.*,
sum(case when col1 not like '%/%/%' then 1 end) over
(order by linenumber) as grp
from t
);
为通过搜索引擎找到此问题的人编辑:
诀窍是使用 WITH
允许在多个地方使用临时结果 (link)。
我认为这会做你想做的,同时丢弃空行(如果你想的话)。我们基本上 select 我们当前查看的行之前的所有品牌,如果它和当前行之间不存在 "brand row",那么我们就接受它。
WITH t AS
(SELECT
ROW_NUMBER() OVER () AS i,
*
FROM
Staging_Data
)
SELECT
a.COL1,
a.COL2,
a.COL3,
(SELECT b.COL1 FROM t b WHERE b.COL2 IS NULL AND b.i <= a.i AND NOT EXISTS(
SELECT * FROM t c WHERE c.COL2 IS NULL AND c.i <= a.i AND c.i > b.i)
) StoreBrand
FROM
t a
WHERE -- I don't think you need those rows? Otherwise remove it.
a.COL2 IS NOT NULL
这可能有点令人困惑。 t
是临时 table 我们定义了 with
您的查询。 a
、b
和 c
是 t
的别名。我们也可以写成 FROM t AS a
使它更明显。
我正在尝试将一些格式不正确的数据迁移到数据库中。数据来自 CSV,首先加载到所有 varchar 列的暂存 table 中(因为我无法在这个阶段强制执行类型安全)。
数据可能看起来像
COL1 | COL2 | COL3
Name 1 | |
2/11/16 | 0 | 0
2/12/16 | 0 | 7
2/13/16 | 5 | 7
Name 2 | |
2/11/16 | 1 | 4
2/12/16 | 3 | 8
2/13/16 | 3 | 4
第一列混合了公司名称 pseudo-headers,以及与第 2 列和第 3 列数据相关的日期。我想通过创建一个 'Brand' 列开始转换数据 - 如果 Col2 为 NULL,则 'StoreBrand' 是 Col1 的值,否则是前一行的 StoreBrand。像这样的东西:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | Name 1
2/13/16 | 5 | 7 | Name 1
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | Name 2
2/13/16 | 3 | 4 | Name 2
我写了这个:
SELECT
t.*,
CASE
WHEN t.COL2 IS NULL THEN COL1
ELSE LAG(StoreBrand) OVER ()
END AS StoreBrand
FROM
(
SELECT
ROW_NUMBER() OVER () AS i,
*
FROM
Staging_Data
) t;
但是数据库(在本例中为 postgres,但我们正在考虑替代方案,因此首选最多样化的答案)在 LAG(StoreBrand) 上阻塞,因为那是派生列 I正在创造。调用 LAG(Col1) 仅填充第一行的真实数据:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | 2/11/16
2/13/16 | 5 | 7 | 2/12/16
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | 2/11/16
2/13/16 | 3 | 4 | 2/12/16
我的目标是 StoreBrand 列,它是下一个品牌名称之前所有日期值的 COL1 的第一个值:
COL1 | COL2 | COL3 | StoreBrand
Name 1 | | | Name 1
2/11/16 | 0 | 0 | Name 1
2/12/16 | 0 | 7 | Name 1
2/13/16 | 5 | 7 | Name 1
Name 2 | | | Name 2
2/11/16 | 1 | 4 | Name 2
2/12/16 | 3 | 8 | Name 2
2/13/16 | 3 | 4 | Name 2
当 Col2 和 Col3 为空时 StoreBrand 的值无关紧要 - 该行将作为转换过程的一部分被删除。重要的是将数据行(即带有日期的行)与其品牌相关联。
有没有办法为我缺少的列引用以前的值?
我想我明白你想要什么。从技术上讲,您需要 lag()
上的 ignore nulls
选项,因此它看起来像这样:
select lag(case when col1 not like '%/%/%' then col1 end ignore nulls) over (order by linenumber) as brandname
唯一的问题? Postgres 不支持 ignore nulls
.
但是,您可以使用子查询做几乎相同的事情。这个想法是为每个组分配一个分组标识符。这是有效品牌名称的累计计数。然后一个简单的 max()
聚合工作:
select t.*,
max(case when col1 not like '%/%/%' then col1 end) over (partition by grp) as brand
from (select t.*,
sum(case when col1 not like '%/%/%' then 1 end) over
(order by linenumber) as grp
from t
);
为通过搜索引擎找到此问题的人编辑:
诀窍是使用 WITH
允许在多个地方使用临时结果 (link)。
我认为这会做你想做的,同时丢弃空行(如果你想的话)。我们基本上 select 我们当前查看的行之前的所有品牌,如果它和当前行之间不存在 "brand row",那么我们就接受它。
WITH t AS
(SELECT
ROW_NUMBER() OVER () AS i,
*
FROM
Staging_Data
)
SELECT
a.COL1,
a.COL2,
a.COL3,
(SELECT b.COL1 FROM t b WHERE b.COL2 IS NULL AND b.i <= a.i AND NOT EXISTS(
SELECT * FROM t c WHERE c.COL2 IS NULL AND c.i <= a.i AND c.i > b.i)
) StoreBrand
FROM
t a
WHERE -- I don't think you need those rows? Otherwise remove it.
a.COL2 IS NOT NULL
这可能有点令人困惑。 t
是临时 table 我们定义了 with
您的查询。 a
、b
和 c
是 t
的别名。我们也可以写成 FROM t AS a
使它更明显。