功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅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)
)
这只是意味着左外侧接合时S
到R
会有在结果至少一行中的每一行R
与可能的空值S
。
相反地,当右外接合 S
到R
会有在结果中的每一行的至少一行S
,与可能的空值R
最后,当完全外部接合 S
到R
会有在结果中的每一行的至少一行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 .. ON
或HAVING
子句在语义上相似。
GROUP BY = collect()
最不直接的映射是GROUP BY
与Stream.collect()
。
首先, 要完全理解 SQL的GROUP BY
可能有些棘手 。 它实际上是FROM
子句的一部分,它将FROM .. JOIN .. WHERE
产生的元组集转换为元组组,其中每个组都有一个关联的可聚合元组集,可以在HAVING
, SELECT
和ORDER BY
子句。 当您使用诸如GROUPING SETS
类的OLAP功能时,事情变得更加有趣,它可以根据几种分组组合来复制元组。
在大多数不支持ARRAY
或MULTISET
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