选择给定条件的行

Posted

技术标签:

【中文标题】选择给定条件的行【英文标题】:selecting rows given criteria 【发布时间】:2016-03-16 21:05:04 【问题描述】:

名为“TEST”的 Oracle sql 表

id  amount  date
1   0   1/2/2015
2   100 1/2/2015
3   2   5/10/2016
4   55  5/10/2016

我想选择有两行具有相同日期的行,其中一个金额为0,另一个金额大于0,并且金额较低的行也具有最低的id。

这是我目前所拥有的,但我不确定如何确保金额为 0 的行也具有两行中较低的 id。

select * from TEST
where id in(    
select id   
(select id,
       created_dt,
       sum(case when amount=0 then 1 else case when amount>0 then 2 end end) 
       over (partition by d.created_dt) as checkAmount
from TEST)
where checkAmount = 3)

【问题讨论】:

您是在寻找给定日期所有可能的 id 对,还是只寻找给定日期正好两行的组? 【参考方案1】:

Oracle 设置

CREATE TABLE test ( id,  amount, dt ) AS
SELECT  1,   0, DATE '2016-02-01' FROM DUAL UNION ALL -- Valid
SELECT  2, 100, DATE '2016-02-01' FROM DUAL UNION ALL -- Valid
SELECT  3,   2, DATE '2016-10-05' FROM DUAL UNION ALL -- Non-zero group
SELECT  4,  55, DATE '2016-10-05' FROM DUAL UNION ALL -- Non-zero group
SELECT  5,   5, DATE '2016-01-01' FROM DUAL UNION ALL -- +ve before zero
SELECT  6,   0, DATE '2016-01-01' FROM DUAL UNION ALL -- +ve before zero
SELECT  7,  -5, DATE '2016-01-01' FROM DUAL UNION ALL -- -ve
SELECT  8,   0, DATE '2016-01-02' FROM DUAL UNION ALL -- More than 2 rows
SELECT  9,   1, DATE '2016-01-02' FROM DUAL UNION ALL -- More than 2 rows
SELECT 10,   2, DATE '2016-01-02' FROM DUAL UNION ALL -- More than 2 rows
SELECT 11,   0, DATE '2016-01-03' FROM DUAL UNION ALL -- Valid
SELECT 12,  -1, DATE '2016-01-03' FROM DUAL UNION ALL -- -ve
SELECT 13,   2, DATE '2016-01-03' FROM DUAL;          -- Valid

查询

SELECT id,amount,dt
FROM   (
  SELECT t.*,
         CASE
           WHEN amount = 0 THEN LEAD( CASE WHEN amount > 0 THEN id END ) IGNORE NULLS OVER ( PARTITION BY dt ORDER BY id )
           WHEN amount > 0 THEN LAG( CASE WHEN amount = 0 THEN id END ) IGNORE NULLS OVER ( PARTITION BY dt ORDER BY id )
           END AS is_valid,
         COUNT( CASE amount WHEN 0 THEN 1 END ) OVER ( PARTITION BY dt ) AS num_zeros,
         COUNT( CASE WHEN amount > 0 THEN 1 END ) OVER ( PARTITION BY dt ) AS num_positive
  FROM   test t
)
WHERE is_valid IS NOT NULL
AND   num_zeros    = 1
AND   num_positive = 1;

输出

        ID     AMOUNT DT                
---------- ---------- -------------------
        11          0 2016-01-03 00:00:00 
        13          2 2016-01-03 00:00:00 
         1          0 2016-02-01 00:00:00 
         2        100 2016-02-01 00:00:00 

【讨论】:

【参考方案2】:

我会做一个简单的 UNION ALL 查询:获取数量=0 的记录,其在同一日期存在较高 ID 且金额为正数,并获取金额>0 记录存在较低 ID 且金额为零的记录同一天。

select * 
from test
where amount = 0
and exists
(
  select *
  from test other
  where other.created_dt = test.created_dt
  and other.id > test.id
  and amount > 0
)
union all
select * 
from test
where amount > 0
and exists
(
  select *
  from test other
  where other.created_dt = test.created_dt
  and other.id < test.id
  and amount = 0
);

【讨论】:

您只是在检查存在,但除非我忽略了某些内容,否则不会将“组”限制为每种类型的一行。 嗯,你认为一个团体是什么?假设一个日期有四条记录,前两条的金额为零,后两条的金额为正。我会全部展示它们,因为它们都符合标准(在同一日期,ID 较小的零金额然后是正金额)。但你是对的;到目前为止,OP 只展示了一个非常简单的示例,其中有两条记录,我们不知道当有更多行时是否还有更多需要考虑。 是的,但是如果还有更多呢?根本不给他们看?回到我的四行示例,当我查看第 1 行和第 3 行时,我发现有两行符合给定条件。 #1 + #4、#2 + #3 和 #2 + #4 也是如此。你是对的,虽然 OP 谈到了两行。这有点模糊。一个日期可能只有两行吗?然后我的解决方案仍然有效。我的回答是有效的,至少只要 OP 没有添加关于如何处理日期为零或正数的多条记录的进一步规则。 我可以看到你的逻辑。我想我自己可能希望看到配对的行 ID,如果这是意图的话。你可能是对的。【参考方案3】:

我将您的意图解释为仅保留具有两行符合您已经尝试过的查询的日期组。如果您的问题是关于满足条件的行对,那么这种方法不是您需要的。

根据您的金额范围,您可以使用比例因子将 id 和金额组合成一个值:

select created_dt, min(id), max(id), max(amount)
from TEST
group by created_dt
having
        count(*) = 2 and min(amount) = 0 and max(amount) > 0
    and min(10000 * id + amount) = min(10000 * id)

同样,您可以尝试这个(作为最终条件的替代品),尽管它可能会导致溢出而不加小心。这个想法应该仍然是合理的:

    and max(id) * max(amount) = max(id * amount)

另一种可能的聪明方法:

    and min(id) = max(case when amount = 0 then id else -id end)

或者可能是最安全和最简单的,这两个选项之一:

    and min(id) + min(amount) = min(id + amount)
    and max(id) + max(amount) = max(id + amount)

那么完整的查询是:

select created_dt, min(id), max(id), max(amount)
from TEST
group by created_dt
having
        count(*) = 2 and min(amount) = 0 and max(amount) > 0
    and min(id) + min(amount) = min(id + amount)

编辑: 将所有数据放在一行中可能确实是一种优势,但我意识到您可能希望将其保留在两行中。如果您只是返回这些值而不是其他列,则仍然很容易得到它。 请注意,如果需要,您仍然可以将 ID 与连接一起使用以获取它们。

with data as (
    select
        created_dt,
        min(id) as min_id, max(id) as max_id, max(amount) as max_amount
    from TEST
    group by created_dt
    having
            count(*) = 2 and min(amount) = 0 and max(amount) > 0
        and min(id) + min(amount) = min(id + amount)
)
select split.*
from data cross apply (
    values
        (created_dt, min_id, 0),
        (created_dt, max_id, max_amount)
    ) split(created_dt, id, amount)

此查询可以在没有 withcross applyvalues 的情况下编写,如果这些不是您可以随意使用的工具的话。

【讨论】:

以上是关于选择给定条件的行的主要内容,如果未能解决你的问题,请参考以下文章

在给定条件下转置/重塑列中的行

跨数据库表中的行验证给定条件

根据列中的条件对数据框中的行进行子集/过滤

如果所有孩子都符合条件,则选择父母

SQL - 我需要制定一个条件来限制选择只显示与这两个条件相对应的行

选择符合多个条件的行