原始数据表是
+--------+--------+
| id | value |
+--------+--------+
| 1 | 0.1 |
| 1 | 0.2 |
| 1 | 0.3 |
| 1 | 0.2 |
| 1 | 0.2 |
| 2 | 0.4 |
| 2 | 0.5 |
| 2 | 0.1 |
| 3 | 0.5 |
| 3 | 0.5 |
+--------+--------+
对于每个id,其值总和为1.我想选择每个id的最少的行,其值和大于或等于0.7,如
+--------+--------+
| id | value |
+--------+--------+
| 1 | 0.3 |
| 1 | 0.2 |
| 1 | 0.2 |
| 2 | 0.5 |
| 2 | 0.4 |
| 3 | 0.5 |
| 3 | 0.5 |
+--------+--------+
如何解决这个问题?
答案 0 :(得分:1)
它既不漂亮也不高效,但它是我能想到的最好的。
with recursive calc (id, row_list, value_list, total_value) as (
select id, array[ctid], array[value]::numeric(6,2)[], value::numeric(6,2) as total_value
from data
union all
select c.id, p.row_list||c.ctid, (p.value_list||c.value)::numeric(6,2)[], (p.total_value + c.value)::numeric(6,2)
from data as c
join calc as p on p.id = c.id and c.ctid <> all(p.row_list)
)
select id, unnest(min(value_list)) as value
from (
select id,
value_list,
array_length(row_list,1) num_values,
min(array_length(row_list,1)) over (partition by id) as min_num_values
from calc
where total_value >= 0.7
) as result
where num_values = min_num_values
group by id
SQLFiddle示例:http://sqlfiddle.com/#!15/8966b/1
这是如何工作的?
递归CTE(thew with recursive
)部分创建表中所有可能的值组合。为了确保相同的值不被计算两次,我收集已经处理成数组的每一行的CTID
s(每行的Postgres内部唯一标识符)。然后,递归连接条件(p.id = c.id and c.ctid <> all(p.row_list)
)确保仅添加相同id
的值,并且仅添加尚未处理的值。
然后将CTE的结果缩减为总和(列total_value
)为>= 0.7
的所有行。
然后将最终外部选择(别名result
)过滤到构成总和的值的数量最小的那些。然后distinct
和不再将数组转换回适当的&#34;表&#34;。区别是必要的,因为CTE收集所有组合,以便例如id = 3 value_list
数组将包含{0.40,0.50}
和{0.50,0.40}
。如果没有明确的,那么不需要的将返回两个组合,使id=3
总共四行。
答案 1 :(得分:1)
这也不是那么漂亮,但我认为它更有效(并且在RDBMS之间可以更好地转移)
with unique_data as (
select id
, value
, row_number() over ( partition by id order by value desc ) as rn
from my_table
)
, cumulative_sum as (
select id
, value
, sum(value) over ( partition by id order by rn ) as csum
from unique_data
)
, first_over_the_mark as (
select id
, value
, csum
, lag(csum) over ( partition by id order by csum ) as prev_value
from cumulative_sum
)
select *
from first_over_the_mark
where coalesce(prev_value, 0) < 0.7
我已经用CTE完成了它,以便更容易看到发生了什么但是没有必要使用它们。
它使用累积和,第一个CTE使数据唯一,因为没有它0.2是相同的值,因此所有0.2的行总和在一起。第二个计算出运行总和。第三个然后计算出之前的值。如果以前严格小于0.7就拿起一切。这个想法是,如果先前的累积和小于0.7,则当前值更多(或等于)该数字。
值得注意的是,如果表中的任何行的值为0,则会出现故障。
答案 2 :(得分:0)
这是Ben的方法的变体,但实现起来更简单。你只需要一个累计和,按反向排序,然后取累积和小于0.7加上第一个超过该值的所有东西。
select t.*
from (select t.*,
sum(value) over (partition by id order by value desc) as csum
from t
) t
where csum - value < 0.7;
表达式csum - value
是累计总和减去当前值(您也可以使用类似rows between unbounded preceding and 1 preceding
的内容来获取此值)。您的条件是该值小于某个阈值。
编辑:
Ben的评论是关于重复值的。他的解决方案很好。这是另一种解决方案:
select t.*
from (select t.*,
sum(value) over (partition by id order by value desc, random()) as csum
from t
) t
where csum - value < 0.7;