像Java 8流收集器一样用SQL编写自定义聚合函数

所有SQL数据库都支持标准聚合函数:COUNT()SUM()AVG()MIN()MAX().

一些数据库支持其他聚合功能,如:

  • EVERY()
  • STDDEV_POP()
  • STDDEV_SAMP()
  • VAR_POP()
  • VAR_SAMP()
  • ARRAY_AGG()
  • STRING_AGG()

但如果你想自己玩呢?

Java 8流收集器

当使用Java 8流时,我们可以轻松地提交自己的聚合函数(即a)。让我们假设我们希望在流中找到第二个最高值。可以获得这样的最高值:

 
System.out.println(
 
    Stream.of(1, 2, 3, 4)
 
          .collect(Collectors.maxBy(Integer::compareTo))
 
) ;
 

屈服:

 
Optional[4]
 

那么,第二高的价值呢?我们可以编写以下收集器:

 
System.out.println(
 
    Stream.of(1, 6, 2, 3, 4, 4, 5).parallel()
 
          .collect(Collector.of(
 
              () -> new int[] { 
 
                  Integer.MIN_VALUE, 
 
                  Integer.MIN_VALUE 
 
              },
 
              (a, i) -> {
 
                  if (a[0] < i) {
 
                      a[1] = a[0];
 
                      a[0] = i;
 
                  }
 
                  else if (a[1] < i)
 
                      a[1] = i;
 
              },
 
              (a1, a2) -> {
 
                  if (a2[0] > a1[0]) {
 
                      a1[1] = a1[0];
 
                      a1[0] = a2[0];
 
 
                      if (a2[1] > a1[1])
 
                          a1[1] = a2[1];
 
                  }
 
                  else if (a2[0] > a1[1])
 
                      a1[1] = a2[0];
 
 
                  return a1;
 
              },
 
              a -> a[1]
 
          ))
 
) ;
 

不会有什么花哨的。它具有以下四项职能:

  • 供应商<int[]>*a供货商它提供了一个中介int[]长度为2,初始化为Integer.MIN_VALUE,每个。此数组将记住MAX()值位于0位置的流中,而SECOND_MAX()值在位置1处的流中。
  • BiConsumer<int[],integer>*a累加器它将新值从流积累到中间数据结构中。
  • BinaryOperator<int[]>*a组合器它结合了两个中间数据结构。这仅用于并行流。
  • 函数<int[],整数>*修整机函数提取SECOND_MAX()函数从中间数组中的第二个位置开始。

现在的产出是:

 
5
 

如何使用SQL做同样的事情?

Many SQL databases offer a very similar way of calculating custom aggregate functions. Here's how to do the exact same thing with...

甲骨文

With the usual syntactic ceremony...

 
CREATE TYPE u_second_max AS OBJECT (
 
 
  -- Intermediary data structure
 
  MAX NUMBER,
 
  SECMAX NUMBER,
 
 
  -- Corresponds to the Collector.supplier() function
 
  STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT u_second_max) RETURN NUMBER,
 
 
  -- Corresponds to the Collector.accumulate() function
 
  MEMBER FUNCTION ODCIAggregateIterate(self IN OUT u_second_max, value IN NUMBER) RETURN NUMBER,
 
 
  -- Corresponds to the Collector.combineer() function
 
  MEMBER FUNCTION ODCIAggregateMerge(self IN OUT u_second_max, ctx2 IN u_second_max) RETURN NUMBER,
 
 
  -- Correspodns to the Collector.finisher() function
 
  MEMBER FUNCTION ODCIAggregateTerminate(self IN u_second_max, returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER
 
)
 
/
 
 
-- This is our "colletor" implementation
 
CREATE OR REPLACE TYPE BODY u_second_max IS
 
  STATIC FUNCTION ODCIAggregateInitialize(sctx IN OUT u_second_max)
 
  RETURN NUMBER IS
 
  BEGIN
 
    SCTX := U_SECOND_MAX(0, 0);
 
    RETURN ODCIConst.Success;
 
  END;
 
 
  MEMBER FUNCTION ODCIAggregateIterate(self IN OUT u_second_max, value IN NUMBER) RETURN NUMBER IS
 
  BEGIN
 
    IF VALUE > SELF.MAX THEN
 
      SELF.SECMAX := SELF.MAX;
 
      SELF.MAX := VALUE;
 
    ELSIF VALUE > SELF.SECMAX THEN
 
      SELF.SECMAX := VALUE;
 
    END IF;
 
    RETURN ODCIConst.Success;
 
  END;
 
 
  MEMBER FUNCTION ODCIAggregateTerminate(self IN u_second_max, returnValue OUT NUMBER, flags IN NUMBER) RETURN NUMBER IS
 
  BEGIN
 
    RETURNVALUE := SELF.SECMAX;
 
    RETURN ODCIConst.Success;
 
  END;
 
 
  MEMBER FUNCTION ODCIAggregateMerge(self IN OUT u_second_max, ctx2 IN u_second_max) RETURN NUMBER IS
 
  BEGIN
 
    IF CTX2.MAX > SELF.MAX THEN
 
      SELF.SECMAX := SELF.MAX;
 
      SELF.MAX := CTX2.MAX;
 
 
      IF CTX2.SECMAX > SELF.SECMAX THEN
 
        SELF.SECMAX := CTX2.SECMAX;
 
      END IF;
 
    ELSIF CTX2.MAX > SELF.SECMAX THEN
 
      SELF.SECMAX := CTX2.MAX;
 
    END IF;
 
 
    RETURN ODCIConst.Success;
 
  END;
 
END;
 
/
 
 
-- Finally, we have to give this aggregate function a name
 
CREATE FUNCTION SECOND_MAX (input NUMBER) RETURN NUMBER
 
PARALLEL_ENABLE AGGREGATE USING u_second_max;
 
/
 

我们现在可以在Sakila数据库:

 
SELECT 
 
  max(film_id), 
 
  second_max(film_id) 
 
FROM film;
 

得到:

 
MAX     SECOND_MAX
 
------------------
 
1000    999
 

更好的是,我们可以免费使用聚合函数作为窗口函数!

 
SELECT 
 
  film_id,
 
  length,
 
  max(film_id) OVER (PARTITION BY length), 
 
  second_max(film_id) OVER (PARTITION BY length)
 
FROM film
 
ORDER BY length, film_id;
 

上述收益率:

 
FILM_ID  LENGTH  MAX   SECOND_MAX
 
---------------------------------
 
15       46      730   505
 
469      46      730   505
 
504      46      730   505
 
505      46      730   505
 
730      46      730   505
 
237      47      869   784
 
247      47      869   784
 
393      47      869   784
 
398      47      869   784
 
407      47      869   784
 
784      47      869   784
 
869      47      869   784
 
2        48      931   866
 
410      48      931   866
 
575      48      931   866
 
630      48      931   866
 
634      48      931   866
 
657      48      931   866
 
670      48      931   866
 
753      48      931   866
 
845      48      931   866
 
866      48      931   866
 
931      48      931   866
 

很漂亮对吧?

PostgreSQL

类中,PostgreSQL支持稍微简洁的语法。CREATE AGGREGATE声明。如果我们不允许并行性,我们可以编写这个最小的实现:

 
CREATE FUNCTION second_max_sfunc (
 
  state INTEGER[], data INTEGER
 
) RETURNS INTEGER[] AS
 
$
 
BEGIN
 
  IF state IS NULL THEN
 
    RETURN ARRAY[data, NULL];
 
  ELSE
 
    RETURN CASE 
 
      WHEN state[1] > data
 
      THEN CASE 
 
        WHEN state[2] > data
 
        THEN state
 
        ELSE ARRAY[state[1], data]
 
      END
 
      ELSE ARRAY[data, state[1]]
 
    END;
 
  END IF;
 
END;
 
$ LANGUAGE plpgsql;
 
/
 
 
CREATE FUNCTION second_max_ffunc (
 
  state INTEGER[]
 
) RETURNS INTEGER AS
 
$
 
BEGIN
 
  RETURN state[2];
 
END;
 
$ LANGUAGE plpgsql;
 
 
CREATE AGGREGATE second_max (INTEGER) (
 
  SFUNC     = second_max_sfunc,
 
  STYPE     = INTEGER[],
 
  FINALFUNC = second_max_ffunc
 
);
 

在这里,我们使用STYPE ( Collector.supplier()),SFUNC ( Collector.accumulator()),以及FINALFUNC ( Collector.finisher())规格。

其他数据库

许多其他数据库允许指定用户定义的聚合函数。查找您的数据库手册的详细信息,以了解更多。它们总是以与Java 8相同的方式工作Collector.

猜你喜欢

转载自www.cnblogs.com/ybyqi/p/9778805.html