| 1 | #include "catch.hpp" |
| 2 | #include "test_helpers.hpp" |
| 3 | |
| 4 | using namespace duckdb; |
| 5 | using namespace std; |
| 6 | |
| 7 | TEST_CASE("Test HAVING clause" , "[having]" ) { |
| 8 | unique_ptr<QueryResult> result; |
| 9 | DuckDB db(nullptr); |
| 10 | Connection con(db); |
| 11 | |
| 12 | REQUIRE_NO_FAIL(con.Query("CREATE TABLE test (a INTEGER, b INTEGER);" )); |
| 13 | REQUIRE_NO_FAIL(con.Query("INSERT INTO test VALUES (11, 22), (13, 22), (12, 21)" )); |
| 14 | |
| 15 | // HAVING with condition on group |
| 16 | result = con.Query("SELECT b, SUM(a) AS sum FROM test GROUP BY b HAVING " |
| 17 | "b=21 ORDER BY b;" ); |
| 18 | REQUIRE(CHECK_COLUMN(result, 0, {21})); |
| 19 | REQUIRE(CHECK_COLUMN(result, 1, {12})); |
| 20 | |
| 21 | // HAVING with condition on sum |
| 22 | result = con.Query("SELECT b, SUM(a) FROM test GROUP BY b HAVING SUM(a) < 20 ORDER BY b;" ); |
| 23 | REQUIRE(CHECK_COLUMN(result, 0, {21})); |
| 24 | REQUIRE(CHECK_COLUMN(result, 1, {12})); |
| 25 | |
| 26 | // HAVING with condition on ALIAS |
| 27 | // CONTROVERSIAL: this DOES work in SQLite, but not in PostgreSQL |
| 28 | REQUIRE_FAIL(con.Query("SELECT b, SUM(a) AS sum FROM test GROUP BY b HAVING sum < 20 ORDER BY b;" )); |
| 29 | |
| 30 | // HAVING without alias |
| 31 | result = con.Query("SELECT b, SUM(a) AS sum FROM test GROUP BY b HAVING " |
| 32 | "SUM(a) < 20 ORDER BY b;" ); |
| 33 | REQUIRE(CHECK_COLUMN(result, 0, {21})); |
| 34 | REQUIRE(CHECK_COLUMN(result, 1, {12})); |
| 35 | REQUIRE(result->types.size() == 2); |
| 36 | |
| 37 | // HAVING on column not in aggregate |
| 38 | result = con.Query("SELECT b, SUM(a) AS sum FROM test GROUP BY b HAVING " |
| 39 | "COUNT(*) = 1 ORDER BY b;" ); |
| 40 | REQUIRE(CHECK_COLUMN(result, 0, {21})); |
| 41 | REQUIRE(CHECK_COLUMN(result, 1, {12})); |
| 42 | REQUIRE(result->types.size() == 2); |
| 43 | |
| 44 | // expression in having |
| 45 | result = con.Query("SELECT b, SUM(a) FROM test GROUP BY b HAVING SUM(a)+10>28;" ); |
| 46 | REQUIRE(CHECK_COLUMN(result, 0, {22})); |
| 47 | REQUIRE(CHECK_COLUMN(result, 1, {24})); |
| 48 | REQUIRE(result->types.size() == 2); |
| 49 | |
| 50 | // uncorrelated subquery in having |
| 51 | result = con.Query("SELECT b, SUM(a) FROM test GROUP BY b HAVING SUM(a)>(SELECT SUM(t.a)*0.5 FROM test t);" ); |
| 52 | REQUIRE(CHECK_COLUMN(result, 0, {22})); |
| 53 | REQUIRE(CHECK_COLUMN(result, 1, {24})); |
| 54 | REQUIRE(result->types.size() == 2); |
| 55 | |
| 56 | // correlated subquery in having |
| 57 | result = con.Query("SELECT test.b, SUM(a) FROM test GROUP BY test.b HAVING SUM(a)=(SELECT SUM(a) FROM test t WHERE " |
| 58 | "test.b=t.b) ORDER BY test.b;" ); |
| 59 | REQUIRE(CHECK_COLUMN(result, 0, {21, 22})); |
| 60 | REQUIRE(CHECK_COLUMN(result, 1, {12, 24})); |
| 61 | REQUIRE(result->types.size() == 2); |
| 62 | |
| 63 | // use outer aggregation in inner subquery |
| 64 | result = con.Query("SELECT test.b, SUM(a) FROM test GROUP BY test.b HAVING SUM(a)*2=(SELECT SUM(a)+SUM(t.a) FROM " |
| 65 | "test t WHERE test.b=t.b) ORDER BY test.b" ); |
| 66 | REQUIRE(CHECK_COLUMN(result, 0, {21, 22})); |
| 67 | REQUIRE(CHECK_COLUMN(result, 1, {12, 24})); |
| 68 | REQUIRE(result->types.size() == 2); |
| 69 | |
| 70 | // use outer aggregation that hasn't been used yet in subquery |
| 71 | result = con.Query("SELECT test.b, SUM(a) FROM test GROUP BY test.b HAVING SUM(a)*2+2=(SELECT " |
| 72 | "SUM(a)+SUM(t.a)+COUNT(t.a) FROM test t WHERE test.b=t.b) ORDER BY test.b" ); |
| 73 | REQUIRE(CHECK_COLUMN(result, 0, {22})); |
| 74 | REQUIRE(CHECK_COLUMN(result, 1, {24})); |
| 75 | REQUIRE(result->types.size() == 2); |
| 76 | |
| 77 | // ORDER BY subquery |
| 78 | result = con.Query( |
| 79 | "SELECT test.b, SUM(a) FROM test GROUP BY test.b ORDER BY (SELECT SUM(a) FROM test t WHERE test.b=t.b) DESC;" ); |
| 80 | REQUIRE(CHECK_COLUMN(result, 0, {22, 21})); |
| 81 | REQUIRE(CHECK_COLUMN(result, 1, {24, 12})); |
| 82 | REQUIRE(result->types.size() == 2); |
| 83 | } |
| 84 | |
| 85 | TEST_CASE("Test HAVING clause without GROUP BY" , "[having]" ) { |
| 86 | unique_ptr<QueryResult> result; |
| 87 | DuckDB db(nullptr); |
| 88 | Connection con(db); |
| 89 | |
| 90 | // CONTROVERSIAL: HAVING without GROUP BY works in PostgreSQL, but not in SQLite |
| 91 | // scalar HAVING queries |
| 92 | // constants only |
| 93 | result = con.Query("SELECT 42 HAVING 42 > 20" ); |
| 94 | REQUIRE(CHECK_COLUMN(result, 0, {42})); |
| 95 | result = con.Query("SELECT 42 HAVING 42 > 80" ); |
| 96 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 97 | // aggregates |
| 98 | result = con.Query("SELECT SUM(42) HAVING AVG(42) > MIN(20)" ); |
| 99 | REQUIRE(CHECK_COLUMN(result, 0, {42})); |
| 100 | result = con.Query("SELECT SUM(42) HAVING SUM(42) > SUM(80)" ); |
| 101 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 102 | result = con.Query("SELECT SUM(42)+COUNT(*)+COUNT(1), 3 HAVING SUM(42)+MAX(20)+AVG(30) > SUM(120)-MIN(100)" ); |
| 103 | REQUIRE(CHECK_COLUMN(result, 0, {44})); |
| 104 | REQUIRE(CHECK_COLUMN(result, 1, {3})); |
| 105 | // subqueries |
| 106 | result = con.Query("SELECT SUM(42) HAVING (SELECT SUM(42)) > SUM(80)" ); |
| 107 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 108 | |
| 109 | con.Query("CREATE TABLE test (a INTEGER, b INTEGER);" ); |
| 110 | con.Query("INSERT INTO test VALUES (11, 22), (13, 22), (12, 21)" ); |
| 111 | |
| 112 | // HAVING with column references does not work |
| 113 | // HAVING clause can only contain aggregates |
| 114 | REQUIRE_FAIL(con.Query("SELECT a FROM test WHERE a=13 HAVING a > 11" )); |
| 115 | // HAVING clause also turns the rest of the query into an aggregate |
| 116 | // thus column references in SELECT clause also produce errors |
| 117 | REQUIRE_FAIL(con.Query("SELECT a FROM test WHERE a=13 HAVING SUM(a) > 11" )); |
| 118 | // once we produce a sum this works though |
| 119 | result = con.Query("SELECT SUM(a) FROM test WHERE a=13 HAVING SUM(a) > 11" ); |
| 120 | REQUIRE(CHECK_COLUMN(result, 0, {13})); |
| 121 | result = con.Query("SELECT SUM(a) FROM test WHERE a=13 HAVING SUM(a) > 20" ); |
| 122 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 123 | |
| 124 | // HAVING with single-node aggregation does work, even without GROUP BY |
| 125 | result = con.Query("SELECT SUM(a) FROM test HAVING SUM(a)>10;" ); |
| 126 | REQUIRE(CHECK_COLUMN(result, 0, {36})); |
| 127 | result = con.Query("SELECT SUM(a) FROM test HAVING SUM(a)<10;" ); |
| 128 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 129 | result = con.Query("SELECT SUM(a) FROM test HAVING COUNT(*)>1;" ); |
| 130 | REQUIRE(CHECK_COLUMN(result, 0, {36})); |
| 131 | result = con.Query("SELECT SUM(a) FROM test HAVING COUNT(*)>10;" ); |
| 132 | REQUIRE(CHECK_COLUMN(result, 0, {})); |
| 133 | } |
| 134 | |