Java 8流中的常见SQL子句及其等效项

功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅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情况下,会生成其他“默认”行。 就集合论/关系代数而言 ,可以表示为:

dd81ee1373d922122ce1b3e0da74cb28

或使用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/357785.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

混合使用Azure LB和ILB访问相同web服务(3)

接下来我们来配置Azure Load balancer&#xff0c;就是面向公网的负载均衡器&#xff1a; 1.在该测试中&#xff0c;为了保持内网访问和外网访问一样的体验&#xff0c;本地端口和public端口和ILB一样&#xff0c;同样是80&#xff1a; PS C:\> Get-AzureVM -ServiceName …

mysql日活统计函数_如何通过简化日活模型,预估一个产品的日活(DAU)?

本文笔者通过简化日活模型&#xff0c;粗略地估算出未来一段时间的产品日活规模。进而通过日活的规模&#xff0c;再去估算一些潜在收益&#xff0c;以及运营成本等等数据。这段时间&#xff0c;经常有人问到以下的问题&#xff1a;按照现在的推广和留存&#xff0c;我们在未来…

罐中研讨会:设置JBoss BPM Suite全天研讨会

是否在寻找一种简单的方法来宣传&#xff0c;展示或演示JBoss业务流程管理套件&#xff08;BPM Suite&#xff09;产品的入门难度&#xff1f; 别无所求&#xff0c;因为我们召集了这个研讨会&#xff0c;因此您可以围绕JBoss BPM Suite构建一个晚上&#xff0c;半天或全天的…

java实现影视创作论坛

导读:随着时代的发展,互联网的出现,给传统影视行业带来的最大便利就是,方便了影视从业人员以及爱好者的交流和互动,而为用户提供一个书写影评,阅读影评以及回复影评的平台,以影评为载体来使用户感受影评、解读影评的是互联网在传统影视行业下应运而生的产物。 所谓的影视…

一文教你使用java开发一款推箱子游戏

导读&#xff1a;社会在进步&#xff0c;人们生活质量也在日益提高。高强度的压力也接踵而来。社会中急需出现新的有效方式来缓解人们的压力。此次设计符合了社会需求&#xff0c;Java推箱子游戏可以让人们在闲暇之余&#xff0c;体验游戏的乐趣。具有操作简单,易于上手的特点。…

uiautomator的坑和AAPT命令方式启动一个应用程序

最近在使用UIautomator完成公司的一个主流程的自动化&#xff0c;因为不适用H5和IOS所以会放弃这个工具的使用&#xff0c;现在记录在使用uiautomator的一些问题&#xff1a; 案列1&#xff1a;使用命令去启动要运用的apk包 使用命令方式启动&#xff0c;可以使用SDK中自带的aa…

一文教你用java实现即时通讯软件的设计(附下载源码)

导读&#xff1a;即时通讯软件即所谓的聊天工具&#xff0c;其主要用途是用于文字信息的传递与文件传输。使用eclipse作为即时通讯软件的开发工具&#xff0c;使用Socket建立通讯渠道&#xff0c;多线程实现多台计算机同时进行信息的传递&#xff0c;swing技术等进行实际开发相…

Divide and conquer:Drying(POJ 3104)

烘干衣服 题目大意&#xff1a;主人公有一个烘干机&#xff0c;但是一次只能烘干一件衣服&#xff0c;每分钟失水k个单位的水量&#xff0c;自然烘干每分钟失水1个单位的水量&#xff08;在烘干机不算自然烘干的那一个单位的水量&#xff09;&#xff0c;问你最少需要多长时间烘…

java 的 AccessController.doPrivileged使用

AccessController.doPrivileged意思是这个是特别的,不用做权限检查. 在什么地方会用到呢:加入1.jar中有类可以读取一个文件,现在我们要使用1.jar去做这个事情.但是我们的类本生是没有权限去读取那个文件的,一般情况下就是眼睁睁的看着了. 但是jiava提供了doPrivileged.在1.ja…

cordova 实现网页缓存_如何解决ionic,cordova混合开发的app缓存大的问题

如何解决ionic&#xff0c;cordova混合开发的app缓存大的问题关注:143 答案:2 mip版解决时间 2021-02-05 06:24提问者旧梦已过期2021-02-04 16:54如何解决ionic&#xff0c;cordova混合开发的app缓存大的问题最佳答案二级知识专家臸釪樶初2021-02-04 17:10可以试试sd卡挂在&a…

当年课设期末大作业,班主任让我们做一个新闻发布管理系统

导读:基于web的新闻发布及管理系统的设计与实现,是动态网页和数据库结合,通过事件来处理新闻。我们对最新信息的更新和发布需要比较及时,而动态交互网页能实现这些功能,新闻发布及管理系统就是一个能够在网上实现新闻的发布及管理,让人们更好的获取更新的新闻资讯。 随着电…

疫情期间,在家使用java的SSH框架实现一个简单的任务调度系统

导读:随着科学技术的飞速发展和各行各业的分工愈发明细化,对于改革传统的人工任务调度方式的呼声越来越大。得益于快速发展的计算机技术,我们看到了改革的方向。本系统是针对企业或者事业单位甚至一个小团队的任务调度而设计的,目的是改变传统的调度方式,通过计算机来计算…

css3优惠卷上方锯齿_css3怎么实现锯齿边框?

白板的微信你这样的最好还是切图做用css3也达不到一毛一样的效果&#xff0c;如果你很想要尝试一下的话可以试试&#xff0c;不过边缘不是顺滑的感觉

改善Hibernate应用程序性能的7种方法

Hibernate是将Java Web应用程序与SQL数据库集成的好工具&#xff0c;但是当应用程序的响应时间开始显着增加时&#xff0c;我们应该怎么做&#xff1f; 当我们怀疑应用程序是否会随着客户群的增长而扩展时&#xff0c;我们该怎么办&#xff1f; 在花大价钱托管或重写代码之前&…

CSDN:2020博客之星年度总评选大赛,趣味总结!

导读&#xff1a;从2020年正式写博客&#xff0c;第一次参加活动&#xff0c;竟然落选了&#xff0c;在此记录一下这个过程吧&#xff0c;方便找帖子链接&#xff0c;毕竟靠着本人的影响力还是能捞点饭票的&#xff0c;哈哈。 2020博客之星年度总评选活动地址&#xff1a;https…

Java 日期格式

转载于:https://www.cnblogs.com/bylion/p/5141022.html

一文教你用java实现出租车计价器设计(附下载源码)

导读&#xff1a;在我国&#xff0c;出租车行业是八十年代初兴起的一项新兴行业&#xff0c;随着出租车的产生&#xff0c;计价器也就应运而生。但当时在全国没有一家企业能够生产&#xff0c;因而那个时期的计价器是由台湾引进。台湾是计价器的主要生产场地&#xff0c;目前全…

一键转发抢红包源码及搭建教程

导读:一键转发抢红包搭建教程如下:源码请在资源模块下载。 搭建教程 第一步注册bmob建立一个项目 项目包括这些表单(file表可以不建立) (应用ID在后台项目设置里面) 表DaSha这样建立 表Sha这样建立 找zi()方法第一步

android 开发

初学者学习 Android 开发&#xff0c;有什么好网站推荐&#xff1f; 前段日子一直在看安卓巴士这个网站&#xff0c;但网站突然进行改版&#xff0c;很多内容都找不到了&#xff0c;一下子怅然若失起来1 条评论 分享按投票排序按时间排序50 个回答 yava&#xff0c;半瓶水fei …

ztree java 增删改_Ztree实现增删改查

1.和另一篇文章--[TreeGrid 实现增删改查]前面的内容一致&#xff0c;只需要创建一个html页面&#xff0c;命名为ztree-01.html&#xff0c;粘贴如下网址的代码进入ztree-01.html。访问http://www.treejs.cn/v3/faq.php#_206复制这些代码html> ZTREE DEMO TITLE>HEAD>…