Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/86.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 根据Oracle中的行数对结果进行分组_Sql_Oracle_Plsql - Fatal编程技术网

Sql 根据Oracle中的行数对结果进行分组

Sql 根据Oracle中的行数对结果进行分组,sql,oracle,plsql,Sql,Oracle,Plsql,我有一个需求,在这个需求中,我需要根据行数对结果进行分组 以下是我从SQL中获得的结果集: ID Date Count 1 10/01/2013 50 1 10/02/2013 25 1 10/03/2013 100 1 10/04/2013 200 1 10/05/2013 175 1 10/06/2013 45 2 10/01/2013 85 2 10/02

我有一个需求,在这个需求中,我需要根据行数对结果进行分组

以下是我从SQL中获得的结果集:

ID      Date        Count
1     10/01/2013    50
1     10/02/2013    25
1     10/03/2013    100
1     10/04/2013    200 
1     10/05/2013    175
1     10/06/2013     45
2     10/01/2013     85
2     10/02/2013    100 
我可以把它们当礼物吗

    id        date    Count
    1     10/03/2013    175
    1     10/04/2013    200
    1     10/05/2013    175
    1     10/06/2013     45
    2     10/02/2013    185
我需要通过将它们的计数分组200来减少结果集,所以让它们不分组

在Oracle 11g中,是否可以使用PLSQL或SQL分析函数来解决此问题

请求的新结果集 有没有一种方法可以返回一个有附加列的结果?每行的StartD列必须采用该行的前一个结束日期

ID      StartD      EndDate     Count
1       10/01/2013  10/03/2013   175
1       10/03/2013  10/04/2013   200
1       10/04/2013  10/05/2013   250
1       10/05/2013  10/06/2013   190
1       10/06/2013  10/08/2013    45
2       10/01/2013  10/01/2013   185

您可以在Oracle 12c中使用模式匹配技术来实现这一点

设置(添加了几行,包括一些计数超过200的行,用于测试):

问题是:

select id, first_stamp, last_stamp, partial_sum
from stuff
match_recognize (
    partition by id order by stamp
    measures
      first(a.stamp) as first_stamp
    , last(a.stamp)  as last_stamp
    , sum(a.num)     as partial_sum
    pattern (A+)
    define A as (sum(a.num) <= 200 or (count(*) = 1 and a.num > 200))
);
工作原理:

  • 模式匹配在整个表上完成,按
    id
    分区,并按时间戳排序
  • 模式
    A+
    表示我们需要满足条件
    A
    的连续行组(根据分区和order by子句)
  • 条件
    A
    是集合满足:
    • 集合中num的总和为200或更少
    • 或者集合中有一行num大于200(否则这些行永远不会匹配,并且不会输出)
  • 度量
    子句指示匹配返回的内容(在分区键的顶部):
    • 每个组的第一个和最后一个时间戳
    • 每组的num之和

这里有一种使用表值函数的方法,应该可以在11g(我认为是10g)中使用。很不雅观,但能胜任。按顺序遍历表,在组“满”时输出组

您也可以为组大小添加一个参数

create or replace 
type my_row is object (id int, stamp date, num int);

create or replace 
type my_tab as table of my_row;

create or replace
  function custom_stuff_groups
    return my_tab pipelined
  as
    cur_sum number;
    cur_id  number;
    cur_dt  date;
  begin
    cur_sum := null;
    cur_id  := null;
    cur_dt  := null;
    for x in (select id, stamp, num from stuff order by id, stamp)
    loop
      if (cur_sum is null) then
        -- very first row
        cur_id      := x.id;
        cur_sum     := x.num;
      elsif (cur_id != x.id) then
        -- changed ID, so output last line for previous id and reset
        pipe row(my_row(cur_id, cur_dt, cur_sum));
        cur_id              := x.id;
        cur_sum             := x.num;
      elsif (cur_sum + x.num > 200) then
        -- same id, sum overflows.
        pipe row(my_row(cur_id, cur_dt, cur_sum));
        cur_sum := x.num;
      else
        -- same id, sum still below 200
        cur_sum := cur_sum + x.num;
      end if;
      cur_dt := x.stamp;
    end loop;
    if (cur_sum is not null) then
      -- output the last line, if any
      pipe row(my_row(cur_id, cur_dt, cur_sum));
    end if;
  end;
用作:

select * from table(custom_stuff_groups());

这将返回基于示例数据的预期结果。但我不能100%确定它是否在所有情况下都能工作(而且可能不会非常有效):


SQLFIDLE示例:

对于此类任务,可以使用生成所需结果

定义一些附加类型需要一些“管道”,但函数本身是光标上的一个简单循环,在更改
id
或累计总数超过限制时累积值并生成行

您可以通过多种方式实现这一点。在这里,使用一个普通的旧循环,而不是for in游标,我得到了一些不那么不雅观的东西:

使用与its中Mat相同的测试数据,这将产生:

ID  STAMP       LAST_STAMP  NUM
1   10/01/2013  10/03/2013  175
1   10/04/2013  10/04/2013  200
1   10/05/2013  10/05/2013  250
1   10/06/2013  10/07/2013  190
1   10/08/2013  10/08/2013  45
2   10/01/2013  10/02/2013  185

谢谢你。这为我提供了match_Recognite如何工作的详细信息。感谢你的努力。它是否与Oracle 11g不兼容?不,仅限12c。我相信你可以用“常规”分析/窗口功能和子查询/CTE来做所有的
match\u recognize
,但我不是SQL专家。如果我想到了什么,我会更新这篇文章。为什么你想要一个存储过程(或函数)?如果使用Mat指出的分析函数是可行的,那么不一定是storeProc,这也是可以理解的。由于我使用的是oracle 11g,因此match_Recognite函数似乎不受支持。您特别要求使用“in PL/SQL”,这意味着存储过程或函数,谢谢您指出。我已经更新了问题以反映这一点。添加
插入到stuff(id、计数日期、cnt)值中(1,日期'2013-09-30',200)顶部,不幸停止工作。感谢您的建议;有没有办法向结果集中添加一个新列;我已经用更新的结果更新了我的问题。@balaji通常是这样。尽管如此,由于这里的变化很小,我花了时间更新了我的答案。我让您比较两个版本,以确定几个差异。
select * from table(custom_stuff_groups());
with summed_values as (
  select stuff.*,
         case 
             when sum(cnt) over (partition by id order by count_date) >= 200 then 1
             else 0
         end as sum_group
  from stuff
), totals as (
  select id,
         max(count_date) as last_count,
         sum(cnt) as total_count
  from summed_values
  where sum_group = 0
  group by id
  union all
  select id,
         count_date as last_count,
         sum(cnt) as total_count
  from summed_values
  where sum_group = 1
  group by id, count_date
)
select *
from totals
order by id, last_count
;
CREATE OR REPLACE TYPE stuff_row AS OBJECT (
  id          int,
  stamp       date,
  last_stamp  date,
  num         int
);
CREATE OR REPLACE TYPE stuff_tbl AS TABLE OF stuff_row;
CREATE OR REPLACE FUNCTION partition_by_200
RETURN stuff_tbl PIPELINED
AS
  CURSOR data IS SELECT id, stamp, num FROM stuff ORDER BY id, stamp;
  curr data%ROWTYPE;
  acc  stuff_row := stuff_row(NULL,NULL,NULL,NULL);
BEGIN
  OPEN data;
  FETCH data INTO acc.id,acc.stamp,acc.num;
  acc.last_stamp := acc.stamp;


  IF data%FOUND THEN
  LOOP
    FETCH data INTO curr;

    IF data%NOTFOUND OR curr.id <> acc.id OR acc.num+curr.num > 200
    THEN
      PIPE ROW(stuff_row(acc.id,acc.stamp,acc.last_stamp,acc.num));
      EXIT WHEN data%NOTFOUND;

      -- reset the accumulator
      acc := stuff_row(curr.id, curr.stamp, curr.stamp, curr.num);
    ELSE
      -- accumulate value
      acc.num := acc.num + curr.num;
      acc.last_stamp := curr.stamp;
    END IF;
  END LOOP;
  END IF;

  CLOSE data;
END;
SELECT * FROM TABLE(partition_by_200());
ID  STAMP       LAST_STAMP  NUM
1   10/01/2013  10/03/2013  175
1   10/04/2013  10/04/2013  200
1   10/05/2013  10/05/2013  250
1   10/06/2013  10/07/2013  190
1   10/08/2013  10/08/2013  45
2   10/01/2013  10/02/2013  185