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

sql limit 子句

功能编程允许使用通用语言进行准声明性编程 。 通过使用功能强大的流畅API(例如Java 8的Stream API )或jOOλ的顺序Stream扩展Seq或更复杂的库(例如javaslangfunctionaljava) ,我们可以以一种非常简洁的方式来表示数据转换算法。 比较相同算法的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;
    }
}

…以及很多有用的头,例如“ 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 DUAL
      UNION ALL 
      SELECT 2 v1 FROM DUAL) t1, 
     (SELECT 1 v2 FROM DUAL
      UNION ALL
      SELECT 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)应用连接谓词
  • 如果join谓词对任何值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_table
    GROUP BY x
)

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

SELECT *
FROM some_table
WHERE EXISTS (
    SELECT 1
    FROM 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
);

SELECT
    z, 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>>

选择= 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;

-- equivalent

SELECT DISTINCT *
FROM (
  SELECT * FROM t
  UNION ALL
  SELECT * 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

sql limit 子句

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值