功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅API(例如Java 8的Stream API )或jOOλ的顺序Stream扩展Seq或更复杂的库(例如javaslang或functionaljava) ,我们可以以一种非常简洁的方式来表示数据转换算法。 比较相同算法的Mario Fusco的命令式和功能式版本 :

势在必行–功能分离pic.twitter.com/G2cC6iBkDJ

— Mario Fusco(@mariofusco) 2015年3月1日

使用此类API,函数式编程肯定感觉就像是真正的声明式编程。

最流行的真正的声明式编程语言是SQL。 当联接两个表时,您不会告诉RDBMS如何实现该联接。 它可以自行决定在完整查询和所有可用元信息的上下文中,嵌套循环,合并联接,哈希联接或某种其他算法是否最合适。 这非常强大,因为对简单连接有效的性能假设可能对复杂的连接不再有效,在复杂的连接上,其他算法的性能要优于原始算法。 通过这种抽象,您可以轻松地在30秒内修改查询,而不必担心诸如算法或性能之类的底层细节。

当一个API允许您将两者结合起来(例如jOOQ和Streams )时,您将获得两全其美的体验,而这些世界并没有太大的不同。

在以下各节中,我们将比较常见的SQL构造与使用Streams和jOOλ用Java 8编写的等效表达式,以防Stream API无法提供足够的功能 。

元组

为了本文的方便,我们将假定SQL行/记录在Java中具有等效的表示形式。 为此,我们将使用jOOλ的Tuple类型 ,该类型实质上是:

public class Tuple2<T1, T2> {public final T1 v1;public final T2 v2;public Tuple2(T1 v1, T2 v2) {this.v1 = v1;this.v2 = v2;}
}

…加上很多有用的mm头,例如“ Comparable Tuple等。

请注意,在此示例和所有后续示例中,我们假定以下导入。

import static org.jooq.lambda.Seq.*;
import static org.jooq.lambda.tuple.Tuple.*;import java.util.*;
import java.util.function.*;
import java.util.stream.*;import org.jooq.lambda.*;

与SQL行很像,元组是“基于值”的类型 ,这意味着它实际上没有标识。 两个元组(1, 'A')(1, 'A')可以被视为完全等效。 从游戏中删除身份使具有不变数据结构的SQL和函数式编程极为优雅。

FROM = of(),stream()等

在SQL中, FROM子句在逻辑上(但不是在语法上)位于所有其他子句之前。 它用于从至少一个表(可能是多个连接的表)生成一组元组。 例如,单表FROM子句可以简单地映射到Stream.of() ,或简单地生成流的任何其他方法:

的SQL

SELECT *
FROM (VALUES(1, 1),(2, 2)
) t(v1, v2)

屈服

+----+----+
| v1 | v2 |
+----+----+
|  1 |  1 |
|  2 |  2 |
+----+----+

Java

Stream.of(tuple(1, 1),tuple(2, 2)
).forEach(System.out::println);

屈服

(1, 1)
(2, 2)

交叉联接= flatMap()

从多个表中进行选择已经更加有趣。 在SQL中合并两个表的最简单方法是通过表列表或使用CROSS JOIN生成笛卡尔积。 以下两个是等效的SQL语句:

的SQL

-- Table list syntax
SELECT *
FROM (VALUES( 1 ), ( 2 )) t1(v1), (VALUES('A'), ('B')) t2(v2)-- CROSS JOIN syntax
SELECT *
FROM       (VALUES( 1 ), ( 2 )) t1(v1)
CROSS JOIN (VALUES('A'), ('B')) t2(v2)

屈服

+----+----+
| v1 | v2 |
+----+----+
|  1 |  A |
|  1 |  B |
|  2 |  A |
|  2 |  B |
+----+----+

在交叉连接(或笛卡尔乘积)中,将t1中的每个值与t2每个值组合在一起,总共产生size(t1) * size(t2)行。

Java

在使用Java 8的Stream函数式编程中, Stream.flatMap()方法对应于SQL CROSS JOIN如以下示例所示:

List<Integer> s1 = Stream.of(1, 2);
Supplier<Stream<String>> s2 = ()->Stream.of("A", "B");s1.flatMap(v1 -> s2.get().map(v2 -> tuple(v1, v2))).forEach(System.out::println);

屈服

(1, A)
(1, B)
(2, A)
(2, B)

请注意我们必须将第二个流包装在Supplier因为流只能被使用一次 ,但是上述算法实际上实现了嵌套循环,将流s2所有元素与流s1每个元素组合在一起。 另一种选择是不使用流而是使用列表(为简单起见,我们将在随后的示例中进行此操作):

List<Integer> s1 = Arrays.asList(1, 2);
List<String> s2 = Arrays.asList("A", "B");s1.stream().flatMap(v1 -> s2.stream().map(v2 -> tuple(v1, v2))).forEach(System.out::println);

实际上, CROSS JOIN可以在SQL和Java中轻松链接:

的SQL

-- Table list syntax
SELECT *
FROM (VALUES( 1 ), ( 2 )) t1(v1), (VALUES('A'), ('B')) t2(v2), (VALUES('X'), ('Y')) t3(v3)-- CROSS JOIN syntax
SELECT *
FROM       (VALUES( 1 ), ( 2 )) t1(v1)
CROSS JOIN (VALUES('A'), ('B')) t2(v2)
CROSS JOIN (VALUES('X'), ('Y')) t3(v3)

屈服

+----+----+----+
| v1 | v2 | v3 |
+----+----+----+
|  1 |  A |  X |
|  1 |  A |  Y |
|  1 |  B |  X |
|  1 |  B |  Y |
|  2 |  A |  X |
|  2 |  A |  Y |
|  2 |  B |  X |
|  2 |  B |  Y |
+----+----+----+

Java

List<Integer> s1 = Arrays.asList(1, 2);
List<String> s2 = Arrays.asList("A", "B");
List<String> s3 = Arrays.asList("X", "Y");s1.stream().flatMap(v1 -> s2.stream().map(v2 -> tuple(v1, v2))).flatMap(v12-> s3.stream().map(v3 -> tuple(v12.v1, v12.v2, v3))).forEach(System.out::println);

屈服

(1, A, X)
(1, A, Y)
(1, B, X)
(1, B, Y)
(2, A, X)
(2, A, Y)
(2, B, X)
(2, B, Y)

请注意,我们如何从第一个CROSS JOIN操作中显式取消嵌套元组,以在第二个操作中形成“扁平”元组。 当然,这是可选的。

Java与jOOλ的crossJoin()

我们jOOQ开发人员,我们是一个非常注重SQL的人员,因此为上述用例添加一个crossJoin()便捷方法是很自然的。 因此,我们的三重交叉联接可以这样写:

Seq<Integer> s1 = Seq.of(1, 2);
Seq<String> s2 = Seq.of("A", "B");
Seq<String> s3 = Seq.of("X", "Y");s1.crossJoin(s2).crossJoin(s3).forEach(System.out::println);

屈服

((1, A), X)
((1, A), Y)
((1, B), X)
((1, B), Y)
((2, A), X)
((2, A), Y)
((2, B), X)
((2, B), Y)

在这种情况下,我们并没有嵌套第一个交叉联接中产生的元组。 仅从关系的角度来看,这都不重要。 嵌套元组与平面元组相同。 在SQL中,我们只是看不到嵌套。 当然,我们仍然可以通过添加一个附加的映射来嵌套:

Seq<Integer> s1 = Seq.of(1, 2);
Seq<String> s2 = Seq.of("A", "B");
Seq<String> s3 = Seq.of("X", "Y");s1.crossJoin(s2).crossJoin(s3).map(t -> tuple(t.v1.v1, t.v1.v2, t.v2)).forEach(System.out::println);

再次屈服

(1, A, X)
(1, A, Y)
(1, B, X)
(1, B, Y)
(2, A, X)
(2, A, Y)
(2, B, X)
(2, B, Y)

(您可能已经注意到map()对应于SELECT ,我们稍后将再次看到)

内部联接= flatMap()与filter()

SQL INNER JOIN本质上只是SQL CROSS JOIN语法糖,其谓词可减少CROSS JOIN后的元组集。 在SQL中,以下两种内部联接方式是等效的:

的SQL

-- Table list syntax
SELECT *
FROM (VALUES(1), (2)) t1(v1), (VALUES(1), (3)) t2(v2)
WHERE t1.v1 = t2.v2-- INNER JOIN syntax
SELECT *
FROM       (VALUES(1), (2)) t1(v1)
INNER JOIN (VALUES(1), (3)) t2(v2)
ON t1.v1 = t2.v2

屈服

+----+----+
| v1 | v2 |
+----+----+
|  1 |  1 |
+----+----+

(请注意,关键字INNER是可选的)。

因此,“ t1的值2和“ t2 ”中的值3被“扔掉”,因为它们会产生连接谓词为true的任何行。

可以很容易地表达相同的内容,但在Java中则更详细

Java(低效率的解决方案!)

List<Integer> s1 = Arrays.asList(1, 2);
List<Integer> s2 = Arrays.asList(1, 3);s1.stream().flatMap(v1 -> s2.stream().map(v2 -> tuple(v1, v2))).filter(t -> Objects.equals(t.v1, t.v2)).forEach(System.out::println);

以上正确产生

(1, 1)

但是要注意,在生产笛卡尔积之后,您将获得此结果,这是每个DBA的噩梦! 如本文开头所述,与声明式编程不同,在函数式编程中,您指示程序严格执行指定的操作顺序。 换一种说法:

在函数式编程中, 您可以定义查询的确切“执行计划”

在声明式编程中, 优化器可能会重组您的“程序”

没有优化器可以将上述方法转换为效率更高的方法:

Java(效率更高)

List<Integer> s1 = Arrays.asList(1, 2);
List<Integer> s2 = Arrays.asList(1, 3);s1.stream().flatMap(v1 -> s2.stream().filter(v2 -> Objects.equals(v1, v2)).map(v2 -> tuple(v1, v2))).forEach(System.out::println);

以上还产生

(1, 1)

注意,连接谓词如何从“外部”流转移到“内部”流,这是通过传递给flatMap()的函数产生的。

Java(最佳)

如前所述,函数式编程不一定允许您根据对实际数据的了解来重写算法。 上面介绍的用于联接的实现始终实现从第一个流到第二个流的嵌套循环联接。 如果您加入两个以上的流,或者第二个流非常大,则此方法效率极低。 复杂的RDBMS绝不会像这样盲目地应用嵌套循环联接,而要在实际数据上考虑约束,索引和直方图。

但是,深入探讨该主题将超出本文的范围。

Java与jOOλ的innerJoin()

同样,受jOOQ工作的启发,我们还为上述用例添加了innerJoin()便捷方法:

Seq<Integer> s1 = Seq.of(1, 2);
Seq<Integer> s2 = Seq.of(1, 3);s1.innerJoin(s2, (t, u) -> Objects.equals(t, u)).forEach(System.out::println);

屈服

(1, 1)

…因为毕竟,当连接两个流时,唯一真正有趣的操作是join Predicate 。 所有其他内容(平面映射等)都只是样板。

LEFT OUTER JOIN = flatMap(),带有filter()和“ default”

SQL的OUTER JOIN就像INNER JOIN ,除了在JOIN谓词对成对的元组产生false情况下,会生成其他“默认”行。 就集合论/关系代数而言 ,可以表示为:

或使用SQL风格的方言:

R LEFT OUTER JOIN S ::=R INNER JOIN S
UNION ((R EXCEPT (SELECT R.* FROM R INNER JOIN S))CROSS JOIN(null, null, ..., null)
)

这只是意味着左外侧接合SR会有在结果至少一行中的每一行R与可能的空值S

相反地,当右外接合 SR会有在结果中的每一行的至少一行S ,与可能的空值R

最后,当完全外部接合 SR会有在结果中的每一行的至少一行R与可能为空值S 用于在每行S具有用于可能为空值R

让我们看一下LEFT OUTER JOIN ,它是SQL中最常用的。

的SQL

-- Table list, Oracle syntax (don't use this!)
SELECT *
FROM (SELECT 1 v1 FROM DUALUNION ALL SELECT 2 v1 FROM DUAL) t1, (SELECT 1 v2 FROM DUALUNION ALLSELECT 3 v2 FROM DUAL) t2
WHERE t1.v1 = t2.v2 (+)-- OUTER JOIN syntax
SELECT *
FROM            (VALUES(1), (2)) t1(v1)
LEFT OUTER JOIN (VALUES(1), (3)) t2(v2)
ON t1.v1 = t2.v2

屈服

+----+------+
| v1 |   v2 |
+----+------+
|  1 |    1 |
|  2 | null |
+----+------+

(请注意,关键字OUTER是可选的)。

Java

不幸的是,如果流为空,JDK的Stream API不能为我们提供一种从流中产生“至少”一个值的简单方法。 我们可能正在编写实用程序函数,如Stack Overflow上的Stuart Marks所述 :

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {Iterator<T> iterator = stream.iterator();if (iterator.hasNext()) {return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);} else {return Stream.of(supplier.get());}
}

或者,我们只使用jOOλ的Seq.onEmpty()

List<Integer> s1 = Arrays.asList(1, 2);
List<Integer> s2 = Arrays.asList(1, 3);seq(s1)
.flatMap(v1 -> seq(s2).filter(v2 -> Objects.equals(v1, v2)).onEmpty(null).map(v2 -> tuple(v1, v2)))
.forEach(System.out::println);

(注意,我们在流中添加null 。这可能并不总是一个好主意。我们将在以后的博客文章中对此进行跟进)

以上还产生

(1, 1)
(2, null)

如何读取隐式左外部联接?

  • 我们将从左侧流s1获取每个值v1
  • 对于每个这样的值v1 ,我们将右流s2平面化以生成元组(v1, v2) (笛卡尔乘积,交叉联接)
  • 我们将对每个这样的元组(v1, v2)应用连接谓词
  • 如果连接谓词对任何值v2不留下任何元组,我们将生成一个包含左流v1的值和null的单个元组

带有jOOλ的Java

为了方便起见,jOOλ还支持leftOuterJoin() ,其工作原理如上所述:

Seq<Integer> s1 = Seq.of(1, 2);
Seq<Integer> s2 = Seq.of(1, 3);s1.leftOuterJoin(s2, (t, u) -> Objects.equals(t, u)).forEach(System.out::println);

屈服

(1, 1)
(2, null)

右外连接=左外连接反向

通常, RIGHT OUTER JOIN只是前一个LEFT OUTER JOIN的逆。 rightOuterJoin()的jOOλ实现如下所示:

default <U> Seq<Tuple2<T, U>> rightOuterJoin(Stream<U> other, BiPredicate<T, U> predicate) {return seq(other).leftOuterJoin(this, (u, t) -> predicate.test(t, u)).map(t -> tuple(t.v2, t.v1));
}

如您所见, RIGHT OUTER JOIN反转了LEFT OUTER JOIN的结果,就是这样。 例如:

Seq<Integer> s1 = Seq.of(1, 2);
Seq<Integer> s2 = Seq.of(1, 3);s1.rightOuterJoin(s2, (t, u) -> Objects.equals(t, u)).forEach(System.out::println);

屈服

(1, 1)
(null, 3)

在哪里= filter()

最简单的映射可能是SQL的WHERE子句在Stream API中具有完全等效的内容: Stream.filter()

的SQL

SELECT *
FROM (VALUES(1), (2), (3)) t(v)
WHERE v % 2 = 0

屈服

+---+
| v |
+---+
| 2 |
+---+

Java

Stream<Integer> s = Stream.of(1, 2, 3);s.filter(v -> v % 2 == 0).forEach(System.out::println);

屈服

2

通常,使用filter()和Stream API的有趣之处在于,该操作可以在调用链中的任何位置应用,这与WHERE子句不同, WHERE子句被限制为仅放在FROM子句之后-即使SQL的JOIN .. ONHAVING子句在语义上相似。

GROUP BY = collect()

最不直接的映射是GROUP BYStream.collect()

首先, 要完全理解 SQL的GROUP BY可能有些棘手 。 它实际上是FROM子句的一部分,它将FROM .. JOIN .. WHERE产生的元组集转换为元组组,其中每个组都有一个关联的可聚合元组集,可以在HAVINGSELECTORDER BY子句。 当您使用诸如GROUPING SETS类的OLAP功能时,事情变得更加有趣,它可以根据几种分组组合来复制元组。

在大多数不支持ARRAYMULTISET SQL实现中,可聚合元组在SELECT中不可用(即嵌套集合)。 在这里, Stream API的功能集非常出色。 另一方面, Stream API只能将值作为终端操作进行分组,其中在SQL中, GROUP BY是纯粹以声明方式(因此是惰性地)应用的。 如果不需要,执行计划者可以选择根本不执行GROUP BY 。 例如:

SELECT *
FROM some_table
WHERE EXISTS (SELECT x, sum(y)FROM other_tableGROUP BY x
)

上面的查询在语义上等效于

SELECT *
FROM some_table
WHERE EXISTS (SELECT 1FROM other_table
)

子查询中的分组是不必要的。 可能有人从其他地方将子查询复制粘贴到该子查询中,或者将查询整体进行了重构。 在Java中,使用Stream API,总是执行每个操作。

为了简单起见,我们将在这里坚持最简单的示例

没有GROUP BY的汇总

一种特殊情况是当我们不指定任何GROUP BY子句时。 在这种情况下,我们可以在FROM子句的所有列上指定聚合,从而始终只生成一条记录。 例如:

的SQL

SELECT sum(v)
FROM (VALUES(1), (2), (3)) t(v)

屈服

+-----+
| sum |
+-----+
|   6 |
+-----+

Java

Stream<Integer> s = Stream.of(1, 2, 3);int sum = s.collect(Collectors.summingInt(i -> i));
System.out.println(sum);

屈服

6

使用GROUP BY进行汇总

在SQL中,更常见的聚合情况是指定显式的GROUP BY子句,如前所述。 例如,我们可能要按偶数和奇数分组:

的SQL

SELECT v % 2, count(v), sum(v)
FROM (VALUES(1), (2), (3)) t(v)
GROUP BY v % 2

屈服

+-------+-------+-----+
| v % 2 | count | sum |
+-------+-------+-----+
|     0 |     1 |   2 |
|     1 |     2 |   4 |
+-------+-------+-----+

Java

幸运的是,对于这个简单的分组/收集用例,JDK提供了一个称为Collectors.groupingBy()的实用程序方法,该方法生成一个收集器,该收集器生成Map<K, List<V>>类型,如下所示:

Stream<Integer> s = Stream.of(1, 2, 3);Map<Integer, List<Integer>> map = s.collect(Collectors.groupingBy(v -> v % 2)
);System.out.println(map);

屈服

{0=[2], 1=[1, 3]}

这肯定会照顾到分组。 现在,我们要为每个组生成聚合。 有点尴尬的JDK方法是:

Stream<Integer> s = Stream.of(1, 2, 3);Map<Integer, IntSummaryStatistics> map = s.collect(Collectors.groupingBy(v -> v % 2,Collectors.summarizingInt(i -> i))
);System.out.println(map);

我们现在得到:

{0=IntSummaryStatistics{count=1, sum=2, min=2, average=2.000000, max=2},1=IntSummaryStatistics{count=2, sum=4, min=1, average=2.000000, max=3}}

如您所见, count()sum()值是根据上述内容计算出来的。

更复杂的GROUP BY

当使用Java 8的Stream API进行多个聚合时,您将很快被迫与自己实现复杂的收集器和累加器的低级API进行角力。 这是乏味且不必要的。 考虑以下SQL语句:

的SQL

CREATE TABLE t (w INT,x INT,y INT,z INT
);SELECTz, w, MIN(x), MAX(x), AVG(x), MIN(y), MAX(y), AVG(y)
FROM t
GROUP BY z, w;

一口气,我们想要:

  • 按几个值分组
  • 从多个值汇总

Java

在上一篇文章中,我们详细解释了如何使用jOOλ中的便捷API通过 Seq.groupBy() 来实现此目的。

class A {final int w;final int x;final int y;final int z;A(int w, int x, int y, int z) {this.w = w;this.x = x;this.y = y;this.z = z;}
}Map<Tuple2<Integer, Integer>, Tuple2<IntSummaryStatistics, IntSummaryStatistics>
> map =
Seq.of(new A(1, 1, 1, 1),new A(1, 2, 3, 1),new A(9, 8, 6, 4),new A(9, 9, 7, 4),new A(2, 3, 4, 5),new A(2, 4, 4, 5),new A(2, 5, 5, 5))// Seq.groupBy() is just short for
// Stream.collect(Collectors.groupingBy(...))
.groupBy(a -> tuple(a.z, a.w),// ... because once you have tuples, // why not add tuple-collectors?Tuple.collectors(Collectors.summarizingInt(a -> a.x),Collectors.summarizingInt(a -> a.y))
);System.out.println(map);

以上收益

{(1, 1)=(IntSummaryStatistics{count=2, sum=3, min=1, average=1.500000, max=2},IntSummaryStatistics{count=2, sum=4, min=1, average=2.000000, max=3}),(4, 9)=(IntSummaryStatistics{count=2, sum=17, min=8, average=8.500000, max=9},IntSummaryStatistics{count=2, sum=13, min=6, average=6.500000, max=7}),(5, 2)=(IntSummaryStatistics{count=3, sum=12, min=3, average=4.000000, max=5},IntSummaryStatistics{count=3, sum=13, min=4, average=4.333333, max=5})}

有关更多详细信息, 请在此处阅读全文 。

请注意,使用Stream.collect()Seq.groupBy()如何实现隐式SELECT子句,不再需要通过map()获得(见下文)。

再次= filter()

如前所述,使用Stream API应用谓词并没有真正不同的方法,只有Stream.filter() 。 在SQL中, HAVING是一个“特殊”谓词子句,在语法上位于GROUP BY子句之后。 例如:

的SQL

SELECT v % 2, count(v)
FROM (VALUES(1), (2), (3)) t(v)
GROUP BY v % 2
HAVING count(v) > 1

屈服

+-------+-------+
| v % 2 | count |
+-------+-------+
|     1 |     2 |
+-------+-------+

Java

不幸的是,正如我们之前所看到的, collect()Stream API中的终端操作,这意味着它急切地生成Map ,而不是将Stream<T>转换为Stream<K, Stream<V> ,在复杂的Stream组合更好。 这意味着我们要收集立即执行的任何操作都必须在从输出Map生成的流上实施:

Stream<Integer> s = Stream.of(1, 2, 3);s.collect(Collectors.groupingBy(v -> v % 2,Collectors.summarizingInt(i -> i))).entrySet().stream().filter(e -> e.getValue().getCount() > 1).forEach(System.out::println);

屈服

1=IntSummaryStatistics{count=2, sum=4, min=1, average=2.000000, max=3}

如您所见,应用的类型转换为:

  • Map<Integer, IntSummaryStatistics>
  • Set<Entry<Integer, IntSummaryStatistics>>
  • Stream<Entry<Integer, IntSummaryStatistics>>

SELECT = map()

SQL中的SELECT子句不过是一个元组转换函数,该函数采用FROM子句产生的元组的笛卡尔积,并将其转换为新的元组表达式,然后将其馈送到客户端或某些更高级别的查询(如果有)这是一个嵌套的SELECT。 插图:

从输出

+------+------+------+------+------+
| T1.A | T1.B | T1.C | T2.A | T2.D |
+------+------+------+------+------+
|    1 |    A |    a |    1 |    X |
|    1 |    B |    b |    1 |    Y |
|    2 |    C |    c |    2 |    X |
|    2 |    D |    d |    2 |    Y |
+------+------+------+------+------+

应用选择

SELECT t1.a, t1.c, t1.b || t1.d+------+------+--------------+
| T1.A | T1.C | T1.B || T1.D |
+------+------+--------------+
|    1 |    a |           AX |
|    1 |    b |           BY |
|    2 |    c |           CX |
|    2 |    d |           DY |
+------+------+--------------+

使用Java 8 Streams,可以使用Stream.map()非常简单地实现SELECT ,正如我们在前面的示例中已经看到的那样,其中我们使用map()取消了元组的嵌套。 以下示例在功能上等效:

的SQL

SELECT t.v1 * 3, t.v2 + 5
FROM (VALUES(1, 1),(2, 2)
) t(v1, v2)

屈服

+----+----+
| c1 | c2 |
+----+----+
|  3 |  6 |
|  6 |  7 |
+----+----+

Java

Stream.of(tuple(1, 1),tuple(2, 2)
).map(t -> tuple(t.v1 * 3, t.v2 + 5)).forEach(System.out::println);

屈服

(3, 6)
(6, 7)

DISTINCT = distinct()

DISTINCT可以与被提供的关键字SELECT从句简单地移除它们已经被产生之后立即重复元组SELECT子句。 插图:

从输出

+------+------+------+------+------+
| T1.A | T1.B | T1.C | T2.A | T2.D |
+------+------+------+------+------+
|    1 |    A |    a |    1 |    X |
|    1 |    B |    b |    1 |    Y |
|    2 |    C |    c |    2 |    X |
|    2 |    D |    d |    2 |    Y |
+------+------+------+------+------+

应用SELECT DISTINCT

SELECT DISTINCT t1.a+------+
| T1.A |
+------+
|    1 |
|    2 |
+------+

使用Java 8 Streams,可以在Stream.distinct()之后Stream.map()使用Stream.distinct()来非常简单地实现SELECT DISTINCT 。 以下示例在功能上等效:

的SQL

SELECT DISTINCT t.v1 * 3, t.v2 + 5
FROM (VALUES(1, 1),(2, 2),(2, 2)
) t(v1, v2)

屈服

+----+----+
| c1 | c2 |
+----+----+
|  3 |  6 |
|  6 |  7 |
+----+----+

Java

Stream.of(tuple(1, 1),tuple(2, 2),tuple(2, 2)
).map(t -> tuple(t.v1 * 3, t.v2 + 5)).distinct().forEach(System.out::println);

屈服

(3, 6)
(6, 7)

UNION ALL = concat()

设置操作在SQL和使用Stream API中都非常强大。 UNION ALL操作映射到Stream.concat() ,如下所示:

的SQL

SELECT *
FROM (VALUES(1), (2)) t(v)
UNION ALL
SELECT *
FROM (VALUES(1), (3)) t(v)

屈服

+---+
| v |
+---+
| 1 |
| 2 |
| 1 |
| 3 |
+---+

Java

Stream<Integer> s1 = Stream.of(1, 2);
Stream<Integer> s2 = Stream.of(1, 3);Stream.concat(s1, s2).forEach(System.out::println);

屈服

1
2
1
3

Java(使用jOOλ)

不幸的是, concat()仅作为static方法存在于Stream ,而使用Seq.concat()时, Seq.concat()也存在于实例上。

Seq<Integer> s1 = Seq.of(1, 2);
Seq<Integer> s2 = Seq.of(1, 3);s1.concat(s2).forEach(System.out::println);

UNION = concat()和distinct()

在SQL中,定义UNION以在通过UNION ALL将两个集合串联后删除重复项。 以下两个语句是等效的:

SELECT * FROM t
UNION
SELECT * FROM u;-- equivalentSELECT DISTINCT *
FROM (SELECT * FROM tUNION ALLSELECT * FROM u
);

让我们付诸行动:

的SQL

SELECT *
FROM (VALUES(1), (2)) t(v)
UNION
SELECT *
FROM (VALUES(1), (3)) t(v)

屈服

+---+
| v |
+---+
| 1 |
| 2 |
| 3 |
+---+

Java

Stream<Integer> s1 = Stream.of(1, 2);
Stream<Integer> s2 = Stream.of(1, 3);Stream.concat(s1, s2).distinct().forEach(System.out::println);

ORDER BY = sorted()

ORDER BY映射很简单

的SQL

SELECT *
FROM (VALUES(1), (4), (3)) t(v)
ORDER BY v

屈服

+---+
| v |
+---+
| 1 |
| 3 |
| 4 |
+---+

Java

Stream<Integer> s = Stream.of(1, 4, 3);s.sorted().forEach(System.out::println);

屈服

1
3
4

LIMIT = limit()

LIMIT映射更加简单

的SQL

SELECT *
FROM (VALUES(1), (4), (3)) t(v)
LIMIT 2

屈服

+---+
| v |
+---+
| 1 |
| 4 |
+---+

Java

Stream<Integer> s = Stream.of(1, 4, 3);s.limit(2).forEach(System.out::println);

屈服

1
4

偏移= skip()

OFFSET映射也很简单

的SQL

SELECT *
FROM (VALUES(1), (4), (3)) t(v)
OFFSET 1

屈服

+---+
| v |
+---+
| 4 |
| 3 |
+---+

Java

Stream<Integer> s = Stream.of(1, 4, 3);s.skip(1).forEach(System.out::println);

屈服

4
3

结论

在上面的文章中,我们已经看到了几乎所有有用的SQL SELECT查询子句,以及如何将它们映射到Java 8 Stream API或jOOλ的Seq API,以防Stream无法提供足够的功能。

本文表明,SQL的声明性世界与Java 8的功能性世界没有太大不同。 SQL子句可以组成即席查询,就像Stream方法可以用来组成功能转换管道一样。 但是有根本的区别。

尽管SQL确实是声明性的,但是函数式编程仍然很有启发性。 Stream API不会基于约束,索引,直方图和其他有关正在转换的数据的元信息来做出优化决策。 使用Stream API就像在SQL中使用所有可能的优化提示一样,以强制SQL引擎选择一个特定的执行计划而不是另一个。 但是,尽管SQL是更高级别的算法抽象,但Stream API可能允许您实现更多可自定义的算法。

翻译自: https://www.javacodegeeks.com/2015/08/common-sql-clauses-and-their-equivalents-in-java-8-streams.html

Java 8流中的常见SQL子句及其等效项相关推荐

  1. sql limit 子句_Java 8流中的常见SQL子句及其等效项

    sql limit 子句 功能编程允许使用通用语言进行准声明性编程 . 通过使用功能强大的流畅API(例如Java 8的Stream API )或jOOλ的顺序Stream扩展Seq或更复杂的库(例如 ...

  2. Java 8流中的数据库CRUD操作

    在开始使用新工具时要克服的最大障碍是让您着手处理小事情. 到目前为止,您可能对新的Java 8 Stream API的工作方式充满信心,但是您可能尚未将其用于数据库查询. 为了帮助您开始使用Strea ...

  3. vavr_使用Vavr在Java 8流中更好的异常处理

    vavr by Rajasekar Elango 由Rajasekar Elango In this post, I will provide tips for better exception ha ...

  4. java对象流读取完毕_从Java 8流中获取具有最大频率的对象

    我有一个带有city和zip字段的对象,我们称之为Record. public class Record() { private String zip; private String city; // ...

  5. java 查询sql_Java 中如何使用 SQL 查询文本

    [摘要] 使用 SQL 查询语言,你只能查询位于数据库里面的数据,但是当你面对的数据是一些 Excel 表格或者 Txt 文本格式时,有什么办法能直接对着文件进行 Select 查询呢?去乾学院看个究 ...

  6. Java IO流中 File文件对象与Properties类(四)

    File类 用来将文件或目录封装成对象 方便对文件或目录信息进行处理 File对象可以作为参数传递给流进行操作 File类常用方法 创建 boolean createNewFile():创建新文件,如 ...

  7. [转载]Java web应用中的常见字符编码问题的解决方法

    以下是 Java web应用的常见编码问题 1. html页面的编码 在web应用中,通常浏览器会根据http header: Content-type的值来决定用什么encoding, 比如遇到Co ...

  8. 【博学谷学习记录】超强总结,用心分享 | 第1周:Java基础学习中初学者常见错误(1)

    相较于之前学习过的Python而言,Java属于一种强类型的编程语言.也就是说,对于定义每个变量时,都要明确相应的类型,才可能使用. 如果是从弱类型语言转换过来,可能刚开始会不太适应,但这种强类型的定 ...

  9. 【博学谷学习记录】超强总结,用心分享 | 第2周:Java基础学习中初学者常见错误(2)

    本周继续学习JavaSE当中的高级部分,总结的常见错误如下. 1.静态vs非静态 对于类当中的成员,可以分为静态和非静态的区别,其中静态中可以调用非静态方法,但非静态无法调用静态方法. 这个对于初学者 ...

最新文章

  1. 关于button的自动刷新
  2. FFmpeg编解码处理1-转码全流程简介
  3. 将Mac OS X从Snow Leopard升级到Mountain Lion
  4. 多线程总结之旅(12):跨线程调用控件的几种方式
  5. CentOS 与 Ubuntu:哪个更适合做服务器?
  6. asp功放怎么装_客厅家庭影院该怎么摆放?
  7. FTP服务器搭建及操作(一)
  8. 高可用结合gfs2,,实现集群文件系统以及集群逻辑卷。
  9. 超级P2P搜索引擎使用教程和下载地址
  10. python提取图像像素值 行号列号,x,y坐标关系
  11. LOL英雄联盟首页以及攻略页面制作
  12. 如何评价2022年第十三届蓝桥杯吧?
  13. Vue前端框架选型论证,字节跳动高级java开发面试
  14. android 7.0下载地址,android 7.0 Downloadprovider 下载流程
  15. 软件项目管理第4版课后习题-期末复习题型分册版-练习版无答案
  16. 百度云(主机管理密码、FTP登录密码、MySQL账号密码)配置 - 入口篇
  17. 前端面试笔记-CSS篇
  18. 推荐6个微信小程序天气接口Api
  19. Matlab实现电子钢琴
  20. 代码节空白区添加代码

热门文章

  1. eclipse导入github项目
  2. 利用bladex+avue实现下拉数据源展示
  3. 不好意思,你这个加分理由不行……
  4. 2021,春节联欢会
  5. 阅读器关闭时尝试调用Read无效时的解决方法
  6. 人脸检测源码facedetection
  7. 继承类对方法的影响java_4-Java面向对象-继承(上)
  8. java synchronized 使用_Java中synchronized的使用实例
  9. 在C语言的函数定义中 如果不需要返回结果,在C语言的函数定义中,如果不需要返回结果,就可以省略return语句...
  10. matlab边算边出图命令,Matlab:不包含边境和工具栏的figure(移除保存图片的白边)...