| 1 | #include "catch.hpp" | 
|---|
| 2 | #include "test_helpers.hpp" | 
|---|
| 3 |  | 
|---|
| 4 | using namespace duckdb; | 
|---|
| 5 | using namespace std; | 
|---|
| 6 |  | 
|---|
| 7 | TEST_CASE( "Most scalar window functions", "[window]") { | 
|---|
| 8 | unique_ptr<QueryResult> result; | 
|---|
| 9 | DuckDB db(nullptr); | 
|---|
| 10 | Connection con(db); | 
|---|
| 11 | con.EnableQueryVerification(); | 
|---|
| 12 |  | 
|---|
| 13 | // test scalar window functions | 
|---|
| 14 | result = con.Query( "SELECT row_number() OVER ()"); | 
|---|
| 15 | REQUIRE(CHECK_COLUMN(result, 0, {1})); | 
|---|
| 16 |  | 
|---|
| 17 | result = con.Query( "SELECT avg(42) OVER ()"); | 
|---|
| 18 | REQUIRE(CHECK_COLUMN(result, 0, {42})); | 
|---|
| 19 |  | 
|---|
| 20 | // nested window functions are not allowed | 
|---|
| 21 | REQUIRE_FAIL(con.Query( "SELECT avg(row_number() over ()) over ()")); | 
|---|
| 22 | REQUIRE_FAIL(con.Query( "SELECT avg(42) over (partition by row_number() over ())")); | 
|---|
| 23 | REQUIRE_FAIL(con.Query( "SELECT avg(42) over (order by row_number() over ())")); | 
|---|
| 24 | // distinct aggregates not supported for window functions | 
|---|
| 25 | REQUIRE_FAIL(con.Query( "SELECT COUNT(DISTINCT 42) OVER ()")); | 
|---|
| 26 | REQUIRE_FAIL( | 
|---|
| 27 | con.Query( "WITH t AS (SELECT col0 AS a, col1 AS b FROM (VALUES(1,2),(1,1),(1,2),(2,1),(2,1),(2,2),(2,3),(2,4)) " | 
|---|
| 28 | "v) SELECT *, COUNT(b) OVER(PARTITION BY a), COUNT(DISTINCT b) OVER(PARTITION BY a) FROM t;")); | 
|---|
| 29 | } | 
|---|
| 30 |  | 
|---|
| 31 | TEST_CASE( "Most basic window function", "[window]") { | 
|---|
| 32 | unique_ptr<QueryResult> result; | 
|---|
| 33 | DuckDB db(nullptr); | 
|---|
| 34 | Connection con(db); | 
|---|
| 35 | con.EnableQueryVerification(); | 
|---|
| 36 |  | 
|---|
| 37 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE empsalary (depname varchar, empno bigint, salary int, enroll_date date)")); | 
|---|
| 38 | REQUIRE_NO_FAIL( | 
|---|
| 39 | con.Query( "INSERT INTO empsalary VALUES ('develop', 10, 5200, '2007-08-01'), ('sales', 1, 5000, '2006-10-01'), " | 
|---|
| 40 | "('personnel', 5, 3500, '2007-12-10'), ('sales', 4, 4800, '2007-08-08'), ('personnel', 2, 3900, " | 
|---|
| 41 | "'2006-12-23'), ('develop', 7, 4200, '2008-01-01'), ('develop', 9, 4500, '2008-01-01'), ('sales', 3, " | 
|---|
| 42 | "4800, '2007-08-01'), ('develop', 8, 6000, '2006-10-01'), ('develop', 11, 5200, '2007-08-15')")); | 
|---|
| 43 |  | 
|---|
| 44 | // basic example from postgres' window.sql | 
|---|
| 45 | result = con.Query( "SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname ORDER BY empno) FROM " | 
|---|
| 46 | "empsalary ORDER BY depname, empno"); | 
|---|
| 47 | REQUIRE(CHECK_COLUMN( | 
|---|
| 48 | result, 0, | 
|---|
| 49 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 50 | REQUIRE(CHECK_COLUMN(result, 1, {7, 8, 9, 10, 11, 2, 5, 1, 3, 4})); | 
|---|
| 51 | REQUIRE(CHECK_COLUMN(result, 2, {4200, 6000, 4500, 5200, 5200, 3900, 3500, 5000, 4800, 4800})); | 
|---|
| 52 | REQUIRE(CHECK_COLUMN(result, 3, {4200, 10200, 14700, 19900, 25100, 3900, 7400, 5000, 9800, 14600})); | 
|---|
| 53 |  | 
|---|
| 54 | // sum | 
|---|
| 55 | result = con.Query( | 
|---|
| 56 | "SELECT sum(salary) OVER (PARTITION BY depname ORDER BY salary) ss FROM empsalary ORDER BY depname, ss"); | 
|---|
| 57 | REQUIRE(result->types.size() == 1); | 
|---|
| 58 | REQUIRE(CHECK_COLUMN(result, 0, {4200, 8700, 19100, 19100, 25100, 3500, 7400, 9600, 9600, 14600})); | 
|---|
| 59 |  | 
|---|
| 60 | // row_number | 
|---|
| 61 | result = con.Query( | 
|---|
| 62 | "SELECT row_number() OVER (PARTITION BY depname ORDER BY salary) rn FROM empsalary ORDER BY depname, rn"); | 
|---|
| 63 | REQUIRE(result->types.size() == 1); | 
|---|
| 64 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5, 1, 2, 1, 2, 3})); | 
|---|
| 65 |  | 
|---|
| 66 | // first_value | 
|---|
| 67 | result = con.Query( "SELECT empno, first_value(empno) OVER (PARTITION BY depname ORDER BY empno) fv FROM empsalary " | 
|---|
| 68 | "ORDER BY depname, fv"); | 
|---|
| 69 | REQUIRE(result->types.size() == 2); | 
|---|
| 70 | REQUIRE(CHECK_COLUMN(result, 0, {11, 8, 7, 9, 10, 5, 2, 4, 3, 1})); | 
|---|
| 71 | REQUIRE(CHECK_COLUMN(result, 1, {7, 7, 7, 7, 7, 2, 2, 1, 1, 1})); | 
|---|
| 72 |  | 
|---|
| 73 | // rank_dense | 
|---|
| 74 | result = con.Query( "SELECT depname, salary, dense_rank() OVER (PARTITION BY depname ORDER BY salary) FROM " | 
|---|
| 75 | "empsalary order by depname, salary"); | 
|---|
| 76 | REQUIRE(result->types.size() == 3); | 
|---|
| 77 | REQUIRE(CHECK_COLUMN( | 
|---|
| 78 | result, 0, | 
|---|
| 79 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 80 | REQUIRE(CHECK_COLUMN(result, 1, {4200, 4500, 5200, 5200, 6000, 3500, 3900, 4800, 4800, 5000})); | 
|---|
| 81 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3, 3, 4, 1, 2, 1, 1, 2})); | 
|---|
| 82 |  | 
|---|
| 83 | // rank | 
|---|
| 84 | result = con.Query( "SELECT depname, salary, rank() OVER (PARTITION BY depname ORDER BY salary) FROM " | 
|---|
| 85 | "empsalary order by depname, salary"); | 
|---|
| 86 | REQUIRE(result->types.size() == 3); | 
|---|
| 87 | REQUIRE(CHECK_COLUMN( | 
|---|
| 88 | result, 0, | 
|---|
| 89 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 90 | REQUIRE(CHECK_COLUMN(result, 1, {4200, 4500, 5200, 5200, 6000, 3500, 3900, 4800, 4800, 5000})); | 
|---|
| 91 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3, 3, 5, 1, 2, 1, 1, 3})); | 
|---|
| 92 |  | 
|---|
| 93 | // min/max/avg | 
|---|
| 94 | result = con.Query( "SELECT depname, min(salary) OVER (PARTITION BY depname ORDER BY salary, empno) m1, max(salary) " | 
|---|
| 95 | "OVER (PARTITION BY depname ORDER BY salary, empno) m2, AVG(salary) OVER (PARTITION BY depname " | 
|---|
| 96 | "ORDER BY salary, empno) m3 FROM empsalary ORDER BY depname, empno"); | 
|---|
| 97 | REQUIRE(result->types.size() == 4); | 
|---|
| 98 | REQUIRE(CHECK_COLUMN( | 
|---|
| 99 | result, 0, | 
|---|
| 100 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 101 | REQUIRE(CHECK_COLUMN(result, 1, {4200, 4200, 4200, 4200, 4200, 3500, 3500, 4800, 4800, 4800})); | 
|---|
| 102 | REQUIRE(CHECK_COLUMN(result, 2, {4200, 6000, 4500, 5200, 5200, 3900, 3500, 5000, 4800, 4800})); | 
|---|
| 103 | REQUIRE(CHECK_COLUMN( | 
|---|
| 104 | result, 3, | 
|---|
| 105 | {4200.0, 5020.0, 4350.0, 4633.33333333333, 4775.0, 3700.0, 3500.0, 4866.66666666667, 4800.0, 4800.0})); | 
|---|
| 106 |  | 
|---|
| 107 | // stddev_pop | 
|---|
| 108 | result = con.Query( "SELECT depname, " | 
|---|
| 109 | "STDDEV_POP(salary) OVER (PARTITION BY depname ORDER BY salary, empno) s " | 
|---|
| 110 | "FROM empsalary ORDER BY depname, empno"); | 
|---|
| 111 | REQUIRE(result->types.size() == 2); | 
|---|
| 112 | REQUIRE(CHECK_COLUMN( | 
|---|
| 113 | result, 0, | 
|---|
| 114 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 115 | REQUIRE(CHECK_COLUMN(result, 1, {0.0, 627.375486, 150.0, 418.993503, 438.035387, 200, 0.0, 94.280904, 0.0, 0.0})); | 
|---|
| 116 |  | 
|---|
| 117 | // covar_pop | 
|---|
| 118 | result = con.Query( "SELECT depname, " | 
|---|
| 119 | "COVAR_POP(salary, empno) OVER (PARTITION BY depname ORDER BY salary, empno) c " | 
|---|
| 120 | "FROM empsalary ORDER BY depname, empno"); | 
|---|
| 121 | REQUIRE(result->types.size() == 2); | 
|---|
| 122 | REQUIRE(CHECK_COLUMN( | 
|---|
| 123 | result, 0, | 
|---|
| 124 | { "develop", "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales", "sales"})); | 
|---|
| 125 | REQUIRE(CHECK_COLUMN(result, 1, {0.0, 240.0, 150.0, 477.777778, 606.250000, -300.0, 0.0, -111.111111, 0.0, 0.0})); | 
|---|
| 126 | } | 
|---|
| 127 |  | 
|---|
| 128 | TEST_CASE( "Illegal window function", "[window]") { | 
|---|
| 129 | unique_ptr<QueryResult> result; | 
|---|
| 130 | DuckDB db(nullptr); | 
|---|
| 131 | Connection con(db); | 
|---|
| 132 | con.EnableQueryVerification(); | 
|---|
| 133 |  | 
|---|
| 134 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE empsalary (depname varchar, empno bigint, salary int, enroll_date date)")); | 
|---|
| 135 | REQUIRE_NO_FAIL( | 
|---|
| 136 | con.Query( "INSERT INTO empsalary VALUES ('develop', 10, 5200, '2007-08-01'), ('sales', 1, 5000, '2006-10-01'), " | 
|---|
| 137 | "('personnel', 5, 3500, '2007-12-10'), ('sales', 4, 4800, '2007-08-08'), ('personnel', 2, 3900, " | 
|---|
| 138 | "'2006-12-23'), ('develop', 7, 4200, '2008-01-01'), ('develop', 9, 4500, '2008-01-01'), ('sales', 3, " | 
|---|
| 139 | "4800, '2007-08-01'), ('develop', 8, 6000, '2006-10-01'), ('develop', 11, 5200, '2007-08-15')")); | 
|---|
| 140 |  | 
|---|
| 141 | // GROUP BY window function is not allowed | 
|---|
| 142 | REQUIRE_FAIL(con.Query( "SELECT depname, min(salary) OVER (PARTITION BY depname ORDER BY salary, empno) m1 FROM " | 
|---|
| 143 | "empsalary GROUP BY m1 ORDER BY depname, empno")); | 
|---|
| 144 | } | 
|---|
| 145 |  | 
|---|
| 146 | TEST_CASE( "More evil cases", "[window]") { | 
|---|
| 147 | unique_ptr<QueryResult> result; | 
|---|
| 148 | DuckDB db(nullptr); | 
|---|
| 149 | Connection con(db); | 
|---|
| 150 | con.EnableQueryVerification(); | 
|---|
| 151 |  | 
|---|
| 152 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE empsalary (depname varchar, empno bigint, salary int, enroll_date date)")); | 
|---|
| 153 | REQUIRE_NO_FAIL( | 
|---|
| 154 | con.Query( "INSERT INTO empsalary VALUES ('develop', 10, 5200, '2007-08-01'), ('sales', 1, 5000, '2006-10-01'), " | 
|---|
| 155 | "('personnel', 5, 3500, '2007-12-10'), ('sales', 4, 4800, '2007-08-08'), ('personnel', 2, 3900, " | 
|---|
| 156 | "'2006-12-23'), ('develop', 7, 4200, '2008-01-01'), ('develop', 9, 4500, '2008-01-01'), ('sales', 3, " | 
|---|
| 157 | "4800, '2007-08-01'), ('develop', 8, 6000, '2006-10-01'), ('develop', 11, 5200, '2007-08-15')")); | 
|---|
| 158 |  | 
|---|
| 159 | // aggr as input to window | 
|---|
| 160 | result = con.Query( "SELECT depname, sum(sum(salary)) over (partition by depname order by salary) FROM empsalary " | 
|---|
| 161 | "group by depname, salary order by depname, salary"); | 
|---|
| 162 | REQUIRE(result->types.size() == 2); | 
|---|
| 163 | REQUIRE(CHECK_COLUMN(result, 0, | 
|---|
| 164 | { "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales"})); | 
|---|
| 165 | REQUIRE(CHECK_COLUMN(result, 1, {4200, 8700, 19100, 25100, 3500, 7400, 9600, 14600})); | 
|---|
| 166 |  | 
|---|
| 167 | // expr in window | 
|---|
| 168 | result = con.Query( "SELECT empno, sum(salary*2) OVER (PARTITION BY depname ORDER BY empno) FROM " | 
|---|
| 169 | "empsalary ORDER BY depname, empno"); | 
|---|
| 170 | REQUIRE(result->types.size() == 2); | 
|---|
| 171 | REQUIRE(CHECK_COLUMN(result, 0, {7, 8, 9, 10, 11, 2, 5, 1, 3, 4})); | 
|---|
| 172 | REQUIRE(CHECK_COLUMN(result, 1, {8400, 20400, 29400, 39800, 50200, 7800, 14800, 10000, 19600, 29200})); | 
|---|
| 173 |  | 
|---|
| 174 | // expr ontop of window | 
|---|
| 175 | result = con.Query( "SELECT empno, 2*sum(salary) OVER (PARTITION BY depname ORDER BY empno) FROM " | 
|---|
| 176 | "empsalary ORDER BY depname, empno"); | 
|---|
| 177 | REQUIRE(result->types.size() == 2); | 
|---|
| 178 | REQUIRE(CHECK_COLUMN(result, 0, {7, 8, 9, 10, 11, 2, 5, 1, 3, 4})); | 
|---|
| 179 | REQUIRE(CHECK_COLUMN(result, 1, {8400, 20400, 29400, 39800, 50200, 7800, 14800, 10000, 19600, 29200})); | 
|---|
| 180 |  | 
|---|
| 181 | // tpcds-derived window | 
|---|
| 182 | result = | 
|---|
| 183 | con.Query( "SELECT depname, sum(salary)*100.0000/sum(sum(salary)) OVER (PARTITION BY depname ORDER BY salary) " | 
|---|
| 184 | "AS revenueratio FROM empsalary GROUP BY depname, salary ORDER BY depname, revenueratio"); | 
|---|
| 185 | REQUIRE(result->types.size() == 2); | 
|---|
| 186 | REQUIRE(CHECK_COLUMN(result, 0, | 
|---|
| 187 | { "develop", "develop", "develop", "develop", "personnel", "personnel", "sales", "sales"})); | 
|---|
| 188 | REQUIRE(CHECK_COLUMN(result, 1, | 
|---|
| 189 | {23.904382, 51.724138, 54.450262, 100.000000, 52.702703, 100.000000, 34.246575, 100.000000})); | 
|---|
| 190 | } | 
|---|
| 191 |  | 
|---|
| 192 | TEST_CASE( "Wisconsin-derived window test cases", "[window]") { | 
|---|
| 193 | unique_ptr<QueryResult> result; | 
|---|
| 194 | DuckDB db(nullptr); | 
|---|
| 195 | Connection con(db); | 
|---|
| 196 | con.EnableQueryVerification(); | 
|---|
| 197 |  | 
|---|
| 198 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE tenk1 (unique1 int4, unique2 int4, two int4, four int4, ten int4, twenty " | 
|---|
| 199 | "int4, hundred int4, thousand int4, twothousand int4, fivethous int4, tenthous int4, odd " | 
|---|
| 200 | "int4, even int4, stringu1 varchar, stringu2 varchar, string4 varchar)")); | 
|---|
| 201 | REQUIRE_NO_FAIL( | 
|---|
| 202 | con.Query( "insert into tenk1 values (8800,0,0,0,0,0,0,800,800,3800,8800,0,1,'MAAAAA','AAAAAA','AAAAxx'), " | 
|---|
| 203 | "(1891,1,1,3,1,11,91,891,1891,1891,1891,182,183,'TUAAAA','BAAAAA','HHHHxx'), " | 
|---|
| 204 | "(3420,2,0,0,0,0,20,420,1420,3420,3420,40,41,'OBAAAA','CAAAAA','OOOOxx'), " | 
|---|
| 205 | "(9850,3,0,2,0,10,50,850,1850,4850,9850,100,101,'WOAAAA','DAAAAA','VVVVxx'), " | 
|---|
| 206 | "(7164,4,0,0,4,4,64,164,1164,2164,7164,128,129,'OPAAAA','EAAAAA','AAAAxx'), " | 
|---|
| 207 | "(8009,5,1,1,9,9,9,9,9,3009,8009,18,19,'BWAAAA','FAAAAA','HHHHxx'), " | 
|---|
| 208 | "(5057,6,1,1,7,17,57,57,1057,57,5057,114,115,'NMAAAA','GAAAAA','OOOOxx'), " | 
|---|
| 209 | "(6701,7,1,1,1,1,1,701,701,1701,6701,2,3,'TXAAAA','HAAAAA','VVVVxx'), " | 
|---|
| 210 | "(4321,8,1,1,1,1,21,321,321,4321,4321,42,43,'FKAAAA','IAAAAA','AAAAxx'), " | 
|---|
| 211 | "(3043,9,1,3,3,3,43,43,1043,3043,3043,86,87,'BNAAAA','JAAAAA','HHHHxx')")); | 
|---|
| 212 |  | 
|---|
| 213 | result = con.Query( "SELECT COUNT(*) OVER () FROM tenk1"); | 
|---|
| 214 | REQUIRE(result->types.size() == 1); | 
|---|
| 215 |  | 
|---|
| 216 | // FIXME	REQUIRE(CHECK_COLUMN(result, 0, {10, 10, 10, 10, 10, 10, 10, 10, 10, 10})); | 
|---|
| 217 |  | 
|---|
| 218 | result = con.Query( "SELECT sum(four) OVER (PARTITION BY ten ORDER BY unique2) AS sum_1, ten, four FROM tenk1 WHERE " | 
|---|
| 219 | "unique2 < 10 order by ten, unique2"); | 
|---|
| 220 |  | 
|---|
| 221 | REQUIRE(result->types.size() == 3); | 
|---|
| 222 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 2, 3, 4, 5, 3, 0, 1, 1})); | 
|---|
| 223 | REQUIRE(CHECK_COLUMN(result, 1, {0, 0, 0, 1, 1, 1, 3, 4, 7, 9})); | 
|---|
| 224 | REQUIRE(CHECK_COLUMN(result, 2, {0, 0, 2, 3, 1, 1, 3, 0, 1, 1})); | 
|---|
| 225 |  | 
|---|
| 226 | result = con.Query( "SELECT row_number() OVER (ORDER BY unique2) rn FROM tenk1 WHERE unique2 < 10 ORDER BY rn"); | 
|---|
| 227 | REQUIRE(result->types.size() == 1); | 
|---|
| 228 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})); | 
|---|
| 229 |  | 
|---|
| 230 | result = con.Query( "SELECT rank() OVER (PARTITION BY four ORDER BY ten) AS rank_1, ten, four FROM tenk1 WHERE " | 
|---|
| 231 | "unique2 < 10 ORDER BY four, ten"); | 
|---|
| 232 | REQUIRE(result->types.size() == 3); | 
|---|
| 233 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 3, 1, 1, 3, 4, 1, 1, 2})); | 
|---|
| 234 | REQUIRE(CHECK_COLUMN(result, 1, {0, 0, 4, 1, 1, 7, 9, 0, 1, 3})); | 
|---|
| 235 | REQUIRE(CHECK_COLUMN(result, 2, {0, 0, 0, 1, 1, 1, 1, 2, 3, 3})); | 
|---|
| 236 |  | 
|---|
| 237 | result = con.Query( "SELECT dense_rank() OVER (PARTITION BY four ORDER BY ten) FROM tenk1 WHERE unique2 " | 
|---|
| 238 | "< 10 ORDER BY four, ten"); | 
|---|
| 239 | REQUIRE(result->types.size() == 1); | 
|---|
| 240 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 1, 1, 2, 3, 1, 1, 2})); | 
|---|
| 241 |  | 
|---|
| 242 | result = con.Query( "SELECT first_value(ten) OVER (PARTITION BY four ORDER BY ten) FROM tenk1 WHERE unique2 < 10 " | 
|---|
| 243 | "order by four, ten"); | 
|---|
| 244 | REQUIRE(result->types.size() == 1); | 
|---|
| 245 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 1, 1, 1, 1, 0, 1, 1})); | 
|---|
| 246 |  | 
|---|
| 247 | // percent_rank | 
|---|
| 248 | result = con.Query( "SELECT cast(percent_rank() OVER (PARTITION BY four ORDER BY ten)*10 as INTEGER) FROM tenk1 " | 
|---|
| 249 | "ORDER BY four, ten"); | 
|---|
| 250 | REQUIRE(result->types.size() == 1); | 
|---|
| 251 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 10, 0, 0, 6, 10, 0, 0, 10})); | 
|---|
| 252 |  | 
|---|
| 253 | // cume_dist | 
|---|
| 254 | result = con.Query( "SELECT cast(cume_dist() OVER (PARTITION BY four ORDER BY ten)*10 as integer) FROM tenk1 WHERE  " | 
|---|
| 255 | "unique2 < 10 order by four, ten"); | 
|---|
| 256 | REQUIRE(result->types.size() == 1); | 
|---|
| 257 | REQUIRE(CHECK_COLUMN(result, 0, {6, 6, 10, 5, 5, 7, 10, 10, 5, 10})); | 
|---|
| 258 |  | 
|---|
| 259 | // ntile | 
|---|
| 260 | result = con.Query( "SELECT ntile(2) OVER (ORDER BY ten, four) nn FROM tenk1 ORDER BY ten, four, nn"); | 
|---|
| 261 | REQUIRE(result->types.size() == 1); | 
|---|
| 262 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 1, 1, 2, 2, 2, 2, 2})); | 
|---|
| 263 |  | 
|---|
| 264 | result = con.Query( "SELECT ntile(3) OVER (ORDER BY ten, four) nn FROM tenk1 ORDER BY ten, four, nn"); | 
|---|
| 265 | REQUIRE(result->types.size() == 1); | 
|---|
| 266 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 1, 2, 2, 2, 3, 3, 3})); | 
|---|
| 267 |  | 
|---|
| 268 | result = con.Query( "SELECT ntile(4) OVER (ORDER BY ten, four) nn FROM tenk1 ORDER BY ten, four, nn"); | 
|---|
| 269 | REQUIRE(result->types.size() == 1); | 
|---|
| 270 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 2, 2, 2, 3, 3, 4, 4})); | 
|---|
| 271 |  | 
|---|
| 272 | result = con.Query( "SELECT ntile(5) OVER (ORDER BY ten, four) nn FROM tenk1 ORDER BY ten, four, nn"); | 
|---|
| 273 | REQUIRE(result->types.size() == 1); | 
|---|
| 274 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 2, 2, 3, 3, 4, 4, 5, 5})); | 
|---|
| 275 |  | 
|---|
| 276 | // lead/lag | 
|---|
| 277 | result = con.Query( "SELECT lag(ten) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 278 | REQUIRE(result->types.size() == 1); | 
|---|
| 279 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), 0, 0, Value(), 1, 1, 7, Value(), Value(), 1})); | 
|---|
| 280 |  | 
|---|
| 281 | result = con.Query( "SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 282 | REQUIRE(result->types.size() == 1); | 
|---|
| 283 | REQUIRE(CHECK_COLUMN(result, 0, {0, 4, Value(), 1, 7, 9, Value(), Value(), 3, Value()})); | 
|---|
| 284 |  | 
|---|
| 285 | result = | 
|---|
| 286 | con.Query( "SELECT lag(ten, four) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 287 | REQUIRE(result->types.size() == 1); | 
|---|
| 288 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 4, Value(), 1, 1, 7, Value(), Value(), Value()})); | 
|---|
| 289 |  | 
|---|
| 290 | result = con.Query( | 
|---|
| 291 | "SELECT lag(ten, four, 0) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 292 | REQUIRE(result->types.size() == 1); | 
|---|
| 293 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 4, 0, 1, 1, 7, 0, 0, 0})); | 
|---|
| 294 |  | 
|---|
| 295 | result = con.Query( "SELECT lead(ten) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 296 | REQUIRE(result->types.size() == 1); | 
|---|
| 297 | REQUIRE(CHECK_COLUMN(result, 0, {0, 4, Value(), 1, 7, 9, Value(), Value(), 3, Value()})); | 
|---|
| 298 |  | 
|---|
| 299 | result = | 
|---|
| 300 | con.Query( "SELECT lead(ten * 2, 1) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 301 | REQUIRE(result->types.size() == 1); | 
|---|
| 302 | REQUIRE(CHECK_COLUMN(result, 0, {0, 8, Value(), 2, 14, 18, Value(), Value(), 6, Value()})); | 
|---|
| 303 |  | 
|---|
| 304 | result = con.Query( | 
|---|
| 305 | "SELECT lead(ten * 2, 1, -1) OVER (PARTITION BY four ORDER BY ten) lt FROM tenk1 order by four, ten, lt"); | 
|---|
| 306 | REQUIRE(result->types.size() == 1); | 
|---|
| 307 | REQUIRE(CHECK_COLUMN(result, 0, {0, 8, -1, 2, 14, 18, -1, -1, 6, -1})); | 
|---|
| 308 |  | 
|---|
| 309 | // empty OVER clause | 
|---|
| 310 | result = | 
|---|
| 311 | con.Query( "SELECT COUNT(*) OVER w c, SUM(four) OVER w s, cast(AVG(ten) OVER w * 10 as integer) a, RANK() over " | 
|---|
| 312 | "w r, DENSE_RANK() over w dr, ROW_NUMBER() OVER w rn FROM tenk1 WINDOW w AS () ORDER BY rn"); | 
|---|
| 313 | REQUIRE(result->types.size() == 6); | 
|---|
| 314 | REQUIRE(CHECK_COLUMN(result, 0, {10, 10, 10, 10, 10, 10, 10, 10, 10, 10})); | 
|---|
| 315 | REQUIRE(CHECK_COLUMN(result, 1, {12, 12, 12, 12, 12, 12, 12, 12, 12, 12})); | 
|---|
| 316 | REQUIRE(CHECK_COLUMN(result, 2, {26, 26, 26, 26, 26, 26, 26, 26, 26, 26})); | 
|---|
| 317 | REQUIRE(CHECK_COLUMN(result, 3, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); | 
|---|
| 318 | REQUIRE(CHECK_COLUMN(result, 4, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); | 
|---|
| 319 | REQUIRE(CHECK_COLUMN(result, 5, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})); | 
|---|
| 320 |  | 
|---|
| 321 | // no ordering but still a frame spec (somewhat underdefined) | 
|---|
| 322 | result = | 
|---|
| 323 | con.Query( "SELECT COUNT(*) OVER w c, SUM(four) OVER w s, cast(AVG(ten) OVER w * 10 as integer) a, RANK() over " | 
|---|
| 324 | "w r, DENSE_RANK() over w dr, ROW_NUMBER() OVER w rn FROM tenk1 WINDOW w AS (rows between 1 " | 
|---|
| 325 | "preceding and 1 following) ORDER BY rn"); | 
|---|
| 326 | REQUIRE(result->types.size() == 6); | 
|---|
| 327 | REQUIRE(CHECK_COLUMN(result, 0, {2, 3, 3, 3, 3, 3, 3, 3, 3, 2})); | 
|---|
| 328 | REQUIRE(CHECK_COLUMN(result, 1, {3, 3, 5, 2, 3, 2, 3, 3, 5, 4})); | 
|---|
| 329 | REQUIRE(CHECK_COLUMN(result, 2, {5, 3, 3, 13, 43, 66, 56, 30, 16, 20})); | 
|---|
| 330 | REQUIRE(CHECK_COLUMN(result, 3, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); | 
|---|
| 331 | REQUIRE(CHECK_COLUMN(result, 4, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); | 
|---|
| 332 | REQUIRE(CHECK_COLUMN(result, 5, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10})); | 
|---|
| 333 | } | 
|---|
| 334 |  | 
|---|
| 335 | TEST_CASE( "Non-default window specs", "[window]") { | 
|---|
| 336 | unique_ptr<QueryResult> result; | 
|---|
| 337 | DuckDB db(nullptr); | 
|---|
| 338 | Connection con(db); | 
|---|
| 339 | con.EnableQueryVerification(); | 
|---|
| 340 |  | 
|---|
| 341 | REQUIRE_NO_FAIL(con.Query( "create table tenk1d(ten int4, four int4)")); | 
|---|
| 342 | REQUIRE_NO_FAIL(con.Query( "insert into tenk1d values (0,0), (1,1), (3,3), (2,2), (4,2), (9,1), (4,0), (7,3), " | 
|---|
| 343 | "(0,2), (2,0), (5,1), (1,3), (3,1), (6,0), (8,0), (9,3), (8,2), (6,2), (7,1), (5,3)")); | 
|---|
| 344 |  | 
|---|
| 345 | // BASIC | 
|---|
| 346 | result = con.Query( "SELECT four, ten, sum(ten) over (partition by four order by ten) st, last_value(ten) over " | 
|---|
| 347 | "(partition by four order by ten) lt FROM tenk1d ORDER BY four, ten"); | 
|---|
| 348 | REQUIRE(result->types.size() == 4); | 
|---|
| 349 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3})); | 
|---|
| 350 | REQUIRE(CHECK_COLUMN(result, 1, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9})); | 
|---|
| 351 | REQUIRE(CHECK_COLUMN(result, 2, {0, 2, 6, 12, 20, 1, 4, 9, 16, 25, 0, 2, 6, 12, 20, 1, 4, 9, 16, 25})); | 
|---|
| 352 | REQUIRE(CHECK_COLUMN(result, 3, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9})); | 
|---|
| 353 |  | 
|---|
| 354 | // same but with explicit window def | 
|---|
| 355 | result = con.Query( "SELECT four, ten, sum(ten) over (partition by four order by ten range between unbounded " | 
|---|
| 356 | "preceding and current row) st, last_value(ten) over (partition by four order by ten range " | 
|---|
| 357 | "between unbounded preceding and current row) lt FROM tenk1d order by four, ten"); | 
|---|
| 358 | REQUIRE(result->types.size() == 4); | 
|---|
| 359 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3})); | 
|---|
| 360 | REQUIRE(CHECK_COLUMN(result, 1, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9})); | 
|---|
| 361 | REQUIRE(CHECK_COLUMN(result, 2, {0, 2, 6, 12, 20, 1, 4, 9, 16, 25, 0, 2, 6, 12, 20, 1, 4, 9, 16, 25})); | 
|---|
| 362 | REQUIRE(CHECK_COLUMN(result, 3, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9})); | 
|---|
| 363 |  | 
|---|
| 364 | // unbounded following | 
|---|
| 365 | result = con.Query( "SELECT four, ten, sum(ten) over (partition by four order by ten range between unbounded " | 
|---|
| 366 | "preceding and unbounded following) st, last_value(ten) over (partition by four order by ten " | 
|---|
| 367 | "range between unbounded preceding and unbounded following) lt FROM tenk1d order by four, ten"); | 
|---|
| 368 | REQUIRE(result->types.size() == 4); | 
|---|
| 369 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3})); | 
|---|
| 370 | REQUIRE(CHECK_COLUMN(result, 1, {0, 2, 4, 6, 8, 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9})); | 
|---|
| 371 | REQUIRE(CHECK_COLUMN(result, 2, {20, 20, 20, 20, 20, 25, 25, 25, 25, 25, 20, 20, 20, 20, 20, 25, 25, 25, 25, 25})); | 
|---|
| 372 | REQUIRE(CHECK_COLUMN(result, 3, {8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9})); | 
|---|
| 373 |  | 
|---|
| 374 | // unbounded following with expressions | 
|---|
| 375 | result = con.Query( "SELECT four, ten/4 as two, 	sum(ten/4) over (partition by four order by ten/4 range between " | 
|---|
| 376 | "unbounded preceding and current row) st, last_value(ten/4) over (partition by four order by " | 
|---|
| 377 | "ten/4 range between unbounded preceding and current row) lt FROM tenk1d order by four, ten/4"); | 
|---|
| 378 | REQUIRE(result->types.size() == 4); | 
|---|
| 379 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3})); | 
|---|
| 380 | REQUIRE(CHECK_COLUMN(result, 1, {0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2})); | 
|---|
| 381 | REQUIRE(CHECK_COLUMN(result, 2, {0, 0, 2, 2, 4, 0, 0, 2, 2, 4, 0, 0, 2, 2, 4, 0, 0, 2, 2, 4})); | 
|---|
| 382 | REQUIRE(CHECK_COLUMN(result, 3, {0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2})); | 
|---|
| 383 |  | 
|---|
| 384 | // unbounded following with named windows | 
|---|
| 385 | result = con.Query( | 
|---|
| 386 | "SELECT four, ten/4 as two, sum(ten/4) OVER w st, last_value(ten/4) OVER w lt FROM tenk1d WINDOW w AS " | 
|---|
| 387 | "(partition by four order by ten/4 range between unbounded preceding and current row) order by four, ten/4 "); | 
|---|
| 388 | REQUIRE(result->types.size() == 4); | 
|---|
| 389 | REQUIRE(CHECK_COLUMN(result, 0, {0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3})); | 
|---|
| 390 | REQUIRE(CHECK_COLUMN(result, 1, {0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2})); | 
|---|
| 391 | REQUIRE(CHECK_COLUMN(result, 2, {0, 0, 2, 2, 4, 0, 0, 2, 2, 4, 0, 0, 2, 2, 4, 0, 0, 2, 2, 4})); | 
|---|
| 392 | REQUIRE(CHECK_COLUMN(result, 3, {0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2})); | 
|---|
| 393 | } | 
|---|
| 394 |  | 
|---|
| 395 | TEST_CASE( "Expressions in boundaries", "[window]") { | 
|---|
| 396 | unique_ptr<QueryResult> result; | 
|---|
| 397 | DuckDB db(nullptr); | 
|---|
| 398 | Connection con(db); | 
|---|
| 399 | con.EnableQueryVerification(); | 
|---|
| 400 |  | 
|---|
| 401 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE tenk1 ( unique1 int4, unique2 int4, two int4, four int4, ten int4, twenty " | 
|---|
| 402 | "int4, hundred int4, thousand int4, twothousand int4, fivethous int4, tenthous int4, odd " | 
|---|
| 403 | "int4, even int4, stringu1 string, stringu2 string, string4 string )")); | 
|---|
| 404 | REQUIRE_NO_FAIL( | 
|---|
| 405 | con.Query( "insert into tenk1 values (4, 1621, 0, 0, 4, 4, 4, 4, 4, 4, 4, 8 ,9 ,'EAAAAA', 'JKCAAA', 'HHHHxx'), " | 
|---|
| 406 | "(2, 2716, 0, 2, 2, 2, 2, 2, 2, 2, 2, 4 ,5 ,'CAAAAA', 'MAEAAA', 'AAAAxx'), (1, 2838, 1, 1, 1, 1, 1, " | 
|---|
| 407 | "1, 1, 1, 1, 2 ,3 ,'BAAAAA', 'EFEAAA', 'OOOOxx'), (6, 2855, 0, 2, 6, 6, 6, 6, 6, 6, 6, 12 ,13 " | 
|---|
| 408 | ",'GAAAAA', 'VFEAAA', 'VVVVxx'), (9, 4463, 1, 1, 9, 9, 9, 9, 9, 9, 9, 18 ,19 ,'JAAAAA', 'RPGAAA', " | 
|---|
| 409 | "'VVVVxx'),(8, 5435, 0, 0, 8, 8, 8, 8, 8, 8, 8, 16 ,17 ,'IAAAAA', 'BBIAAA', 'VVVVxx'), (5, 5557, 1, " | 
|---|
| 410 | "1, 5, 5, 5, 5, 5, 5, 5, 10 ,11,'FAAAAA', 'TFIAAA', 'HHHHxx'), (3, 5679, 1, 3, 3, 3, 3, 3, 3, 3, 3, " | 
|---|
| 411 | "6 ,7 ,'DAAAAA', 'LKIAAA', 'VVVVxx'), (7, 8518, 1,3, 7, 7, 7, 7, 7, 7, 7, 14 ,15 ,'HAAAAA', " | 
|---|
| 412 | "'QPMAAA', 'OOOOxx'), (0, 9998, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,1 ,'AAAAAA','OUOAAA', 'OOOOxx')")); | 
|---|
| 413 |  | 
|---|
| 414 | result = con.Query( "SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 2 following) su FROM " | 
|---|
| 415 | "tenk1 order by unique1"); | 
|---|
| 416 | REQUIRE(CHECK_COLUMN(result, 0, {3, 6, 10, 15, 20, 25, 30, 35, 30, 24})); | 
|---|
| 417 | REQUIRE(result->types.size() == 1); | 
|---|
| 418 |  | 
|---|
| 419 | result = con.Query( "SELECT sum(unique1) over (order by unique1 rows between 2 preceding and 1 preceding) su FROM " | 
|---|
| 420 | "tenk1 order by unique1"); | 
|---|
| 421 | REQUIRE(CHECK_COLUMN(result, 0, {Value(), 0, 1, 3, 5, 7, 9, 11, 13, 15})); | 
|---|
| 422 | REQUIRE(result->types.size() == 1); | 
|---|
| 423 |  | 
|---|
| 424 | result = con.Query( "SELECT sum(unique1) over (order by unique1 rows between 1 following and 3 following) su FROM " | 
|---|
| 425 | "tenk1 order by unique1"); | 
|---|
| 426 | REQUIRE(CHECK_COLUMN(result, 0, {6, 9, 12, 15, 18, 21, 24, 17, 9, Value()})); | 
|---|
| 427 | REQUIRE(result->types.size() == 1); | 
|---|
| 428 |  | 
|---|
| 429 | result = con.Query( "SELECT sum(unique1) over (order by unique1 rows between unbounded preceding and 1 following) " | 
|---|
| 430 | "su FROM tenk1 order by unique1"); | 
|---|
| 431 | REQUIRE(CHECK_COLUMN(result, 0, {1, 3, 6, 10, 15, 21, 28, 36, 45, 45})); | 
|---|
| 432 | REQUIRE(result->types.size() == 1); | 
|---|
| 433 | } | 
|---|
| 434 |  | 
|---|
| 435 | TEST_CASE( "TPC-DS inspired micro benchmarks", "[window]") { | 
|---|
| 436 | unique_ptr<QueryResult> result; | 
|---|
| 437 | DuckDB db(nullptr); | 
|---|
| 438 | Connection con(db); | 
|---|
| 439 | con.EnableQueryVerification(); | 
|---|
| 440 |  | 
|---|
| 441 | REQUIRE_NO_FAIL(con.Query( "BEGIN TRANSACTION")); | 
|---|
| 442 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE item(i_category VARCHAR, i_brand VARCHAR, i_price INTEGER)")); | 
|---|
| 443 | REQUIRE_NO_FAIL(con.Query( "INSERT INTO item VALUES ('toys', 'fisher-price', 100)")); | 
|---|
| 444 | result = | 
|---|
| 445 | con.Query( "SELECT i_category, i_brand, avg(sum(i_price)) OVER (PARTITION BY i_category), rank() OVER " | 
|---|
| 446 | "(PARTITION BY i_category ORDER BY i_category, i_brand) rn FROM item GROUP BY i_category, i_brand;"); | 
|---|
| 447 | REQUIRE(CHECK_COLUMN(result, 0, { "toys"})); | 
|---|
| 448 | REQUIRE(CHECK_COLUMN(result, 1, { "fisher-price"})); | 
|---|
| 449 | REQUIRE(CHECK_COLUMN(result, 2, {100})); | 
|---|
| 450 | REQUIRE(CHECK_COLUMN(result, 3, {1})); | 
|---|
| 451 | REQUIRE_NO_FAIL(con.Query( "ROLLBACK")); | 
|---|
| 452 |  | 
|---|
| 453 | REQUIRE_NO_FAIL(con.Query( "BEGIN TRANSACTION")); | 
|---|
| 454 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE item(i_category VARCHAR, i_brand VARCHAR, i_item_sk INTEGER);")); | 
|---|
| 455 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE store(s_store_name VARCHAR, s_company_name VARCHAR, s_store_sk INTEGER);")); | 
|---|
| 456 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE date_dim(d_year INTEGER, d_moy INTEGER, d_date_sk INTEGER);")); | 
|---|
| 457 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE store_sales(ss_sales_price DECIMAL, ss_item_sk INTEGER, ss_sold_date_sk " | 
|---|
| 458 | "INTEGER, ss_store_sk INTEGER);")); | 
|---|
| 459 | REQUIRE_NO_FAIL(con.Query( "INSERT INTO item VALUES ('Music', 'exportischolar', 1);")); | 
|---|
| 460 | REQUIRE_NO_FAIL(con.Query( "INSERT INTO store VALUES ('ought', 'Unknown', 1);")); | 
|---|
| 461 | REQUIRE_NO_FAIL(con.Query( "INSERT INTO date_dim VALUES (1999, 1, 1);")); | 
|---|
| 462 | REQUIRE_NO_FAIL(con.Query( "INSERT INTO store_sales VALUES (2.8, 1, 1, 1);")); | 
|---|
| 463 | result = con.Query( | 
|---|
| 464 | "SELECT i_category, i_brand, s_store_name, s_company_name, d_year, d_moy, sum(ss_sales_price) sum_sales, " | 
|---|
| 465 | "avg(sum(ss_sales_price)) OVER (PARTITION BY i_category, i_brand, s_store_name, s_company_name, d_year) " | 
|---|
| 466 | "avg_monthly_sales, rank() OVER (PARTITION BY i_category, i_brand, s_store_name, s_company_name ORDER BY " | 
|---|
| 467 | "d_year, d_moy) rn FROM item, store_sales, date_dim, store WHERE ss_item_sk = i_item_sk AND ss_sold_date_sk = " | 
|---|
| 468 | "d_date_sk AND ss_store_sk = s_store_sk AND (d_year = 1999 OR (d_year = 1999-1 AND d_moy =12) OR (d_year = " | 
|---|
| 469 | "1999+1 AND d_moy =1)) GROUP BY i_category, i_brand, s_store_name, s_company_name, d_year, d_moy;"); | 
|---|
| 470 | REQUIRE(CHECK_COLUMN(result, 0, { "Music"})); | 
|---|
| 471 | REQUIRE(CHECK_COLUMN(result, 1, { "exportischolar"})); | 
|---|
| 472 | REQUIRE(CHECK_COLUMN(result, 2, { "ought"})); | 
|---|
| 473 | REQUIRE(CHECK_COLUMN(result, 3, { "Unknown"})); | 
|---|
| 474 | REQUIRE(CHECK_COLUMN(result, 4, {1999})); | 
|---|
| 475 | REQUIRE(CHECK_COLUMN(result, 5, {1})); | 
|---|
| 476 | REQUIRE(CHECK_COLUMN(result, 6, {2.8})); | 
|---|
| 477 | REQUIRE(CHECK_COLUMN(result, 7, {2.8})); | 
|---|
| 478 | REQUIRE(CHECK_COLUMN(result, 8, {1})); | 
|---|
| 479 | REQUIRE_NO_FAIL(con.Query( "ROLLBACK")); | 
|---|
| 480 | } | 
|---|
| 481 |  | 
|---|
| 482 | TEST_CASE( "TPC-DS Q49 bug fix for multi-sort window functions", "[window]") { | 
|---|
| 483 | unique_ptr<QueryResult> result; | 
|---|
| 484 | DuckDB db(nullptr); | 
|---|
| 485 | Connection con(db); | 
|---|
| 486 | con.EnableQueryVerification(); | 
|---|
| 487 |  | 
|---|
| 488 | REQUIRE_NO_FAIL(con.Query( "BEGIN TRANSACTION")); | 
|---|
| 489 | REQUIRE_NO_FAIL(con.Query( "create table wintest( item integer, return_ratio numeric, currency_ratio numeric)")); | 
|---|
| 490 | REQUIRE_NO_FAIL(con.Query( | 
|---|
| 491 | "insert into wintest values  (7539  ,0.590000 , 0.590000), (3337  ,0.626506 , 0.626506), (15597 ,0.661972 , " | 
|---|
| 492 | "0.661972), (2915  ,0.698630 , 0.698630), (11933 ,0.717172 , 0.717172), (483   ,0.800000 , 0.800000), (85    " | 
|---|
| 493 | ",0.857143 , 0.857143), (97    ,0.903614 , 0.903614), (117   ,0.925000 , 0.925000), (5299  ,0.927083 , " | 
|---|
| 494 | "0.927083), (10055 ,0.945652 , 0.945652), (4231  ,0.977778 , 0.977778), (5647  ,0.987805 , 0.987805), (8679  " | 
|---|
| 495 | ",0.988764 , 0.988764), (10323 ,0.977778 , 1.111111), (3305  ,0.737500 , 1.293860)")); | 
|---|
| 496 |  | 
|---|
| 497 | result = con.Query( "SELECT item, rank() OVER (ORDER BY return_ratio) AS return_rank, rank() OVER (ORDER BY " | 
|---|
| 498 | "currency_ratio) AS currency_rank FROM wintest order by item"); | 
|---|
| 499 | REQUIRE(CHECK_COLUMN( | 
|---|
| 500 | result, 0, {85, 97, 117, 483, 2915, 3305, 3337, 4231, 5299, 5647, 7539, 8679, 10055, 10323, 11933, 15597})); | 
|---|
| 501 | REQUIRE(CHECK_COLUMN(result, 1, {8, 9, 10, 7, 4, 6, 2, 13, 11, 15, 1, 16, 12, 13, 5, 3})); | 
|---|
| 502 | REQUIRE(CHECK_COLUMN(result, 2, {7, 8, 9, 6, 4, 16, 2, 12, 10, 13, 1, 14, 11, 15, 5, 3})); | 
|---|
| 503 |  | 
|---|
| 504 | REQUIRE_NO_FAIL(con.Query( "ROLLBACK")); | 
|---|
| 505 | } | 
|---|
| 506 |  | 
|---|
| 507 | TEST_CASE( "Ensure dbplyr crash with ORDER BY under window stays fixed", "[window]") { | 
|---|
| 508 | unique_ptr<QueryResult> result; | 
|---|
| 509 | DuckDB db(nullptr); | 
|---|
| 510 | Connection con(db); | 
|---|
| 511 | con.EnableQueryVerification(); | 
|---|
| 512 |  | 
|---|
| 513 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE dbplyr_052 (x INTEGER, g DOUBLE, w int)")); | 
|---|
| 514 | REQUIRE_NO_FAIL( | 
|---|
| 515 | con.Query( "INSERT INTO dbplyr_052 VALUES (1,1, 42),(2,1, 42),(3,1, 42),(2,2, 42),(3,2, 42),(4,2, 42)")); | 
|---|
| 516 |  | 
|---|
| 517 | // this works fine because we order by the already-projected column in the innermost query | 
|---|
| 518 | result = | 
|---|
| 519 | con.Query( "SELECT x, g FROM (SELECT x, g, SUM(x) OVER (PARTITION BY g ORDER BY x ROWS UNBOUNDED PRECEDING) AS " | 
|---|
| 520 | "zzz67 FROM (SELECT x, g FROM dbplyr_052 ORDER BY x) dbplyr_053) dbplyr_054 WHERE (zzz67 > 3.0)"); | 
|---|
| 521 | REQUIRE(CHECK_COLUMN(result, 0, {3, 3, 4})); | 
|---|
| 522 | REQUIRE(CHECK_COLUMN(result, 1, {1.0, 2.0, 2.0})); | 
|---|
| 523 |  | 
|---|
| 524 | // this breaks because we add a fake projection that is not pruned | 
|---|
| 525 | result = | 
|---|
| 526 | con.Query( "SELECT x, g FROM (SELECT x, g, SUM(x) OVER (PARTITION BY g ORDER BY x ROWS UNBOUNDED PRECEDING) AS " | 
|---|
| 527 | "zzz67 FROM (SELECT x, g FROM dbplyr_052 ORDER BY w) dbplyr_053) dbplyr_054 WHERE (zzz67 > 3.0)"); | 
|---|
| 528 | REQUIRE(CHECK_COLUMN(result, 0, {3, 3, 4})); | 
|---|
| 529 | REQUIRE(CHECK_COLUMN(result, 1, {1.0, 2.0, 2.0})); | 
|---|
| 530 |  | 
|---|
| 531 | // this also breaks because we add a fake projection that is not pruned even if we already have that projection, | 
|---|
| 532 | // just with a different table name | 
|---|
| 533 | result = | 
|---|
| 534 | con.Query( "SELECT x, g FROM (SELECT x, g, SUM(x) OVER (PARTITION BY g ORDER BY x ROWS UNBOUNDED PRECEDING) AS " | 
|---|
| 535 | "zzz67 FROM (SELECT * FROM dbplyr_052 ORDER BY x) dbplyr_053) dbplyr_054 WHERE (zzz67 > 3.0)"); | 
|---|
| 536 | REQUIRE(CHECK_COLUMN(result, 0, {3, 3, 4})); | 
|---|
| 537 | REQUIRE(CHECK_COLUMN(result, 1, {1.0, 2.0, 2.0})); | 
|---|
| 538 | } | 
|---|
| 539 |  | 
|---|
| 540 | TEST_CASE( "Test errors in binding window functions", "[window]") { | 
|---|
| 541 | unique_ptr<QueryResult> result; | 
|---|
| 542 | DuckDB db(nullptr); | 
|---|
| 543 | Connection con(db); | 
|---|
| 544 | con.EnableQueryVerification(); | 
|---|
| 545 |  | 
|---|
| 546 | REQUIRE_NO_FAIL(con.Query( "CREATE TABLE integers(i INTEGER)")); | 
|---|
| 547 |  | 
|---|
| 548 | // we use columns here that are not part of the table | 
|---|
| 549 | REQUIRE_FAIL(con.Query( "SELECT MIN(a) OVER (PARTITION BY i ORDER BY i) FROM integers")); | 
|---|
| 550 | REQUIRE_FAIL(con.Query( "SELECT MIN(i) OVER (PARTITION BY a ORDER BY i) FROM integers")); | 
|---|
| 551 | REQUIRE_FAIL(con.Query( "SELECT MIN(i) OVER (PARTITION BY i ORDER BY a) FROM integers")); | 
|---|
| 552 | REQUIRE_FAIL(con.Query( "SELECT MIN(i) OVER (PARTITION BY i, a ORDER BY i) FROM integers")); | 
|---|
| 553 | REQUIRE_FAIL(con.Query( "SELECT MIN(i) OVER (PARTITION BY i ORDER BY i, a) FROM integers")); | 
|---|
| 554 |  | 
|---|
| 555 | // now we only use the "proper" columns | 
|---|
| 556 | result = con.Query( "SELECT MIN(i) OVER (PARTITION BY i ORDER BY i) FROM integers"); | 
|---|
| 557 | REQUIRE(CHECK_COLUMN(result, 0, {})); | 
|---|
| 558 | } | 
|---|
| 559 |  | 
|---|
| 560 | TEST_CASE( "Test binding of named window functions in CTEs", "[window]") { | 
|---|
| 561 | unique_ptr<QueryResult> result; | 
|---|
| 562 | DuckDB db(nullptr); | 
|---|
| 563 | Connection con(db); | 
|---|
| 564 | con.EnableQueryVerification(); | 
|---|
| 565 |  | 
|---|
| 566 | // named window clause | 
|---|
| 567 | result = con.Query( | 
|---|
| 568 | "select i, lag(i) over named_window from (values (1), (2), (3)) as t (i) window named_window as (order by i);"); | 
|---|
| 569 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); | 
|---|
| 570 | REQUIRE(CHECK_COLUMN(result, 1, {Value(), 1, 2})); | 
|---|
| 571 |  | 
|---|
| 572 | // named window clause in CTE | 
|---|
| 573 | result = con.Query( "with subquery as (select i, lag(i) over named_window from (values (1), (2), (3)) as t (i) " | 
|---|
| 574 | "window named_window as (order by i)) select * from subquery;"); | 
|---|
| 575 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); | 
|---|
| 576 | REQUIRE(CHECK_COLUMN(result, 1, {Value(), 1, 2})); | 
|---|
| 577 |  | 
|---|
| 578 | // named window clause in subquery | 
|---|
| 579 | result = con.Query( "select * from (select i, lag(i) over named_window from (values (1), (2), (3)) as t (i) window " | 
|---|
| 580 | "named_window as (order by i)) t1;"); | 
|---|
| 581 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); | 
|---|
| 582 | REQUIRE(CHECK_COLUMN(result, 1, {Value(), 1, 2})); | 
|---|
| 583 |  | 
|---|
| 584 | // named window clause in view | 
|---|
| 585 | REQUIRE_NO_FAIL(con.Query( "CREATE VIEW v1 AS select i, lag(i) over named_window from (values (1), (2), (3)) as t " | 
|---|
| 586 | "(i) window named_window as (order by i);")); | 
|---|
| 587 |  | 
|---|
| 588 | result = con.Query( "select * from v1;"); | 
|---|
| 589 | REQUIRE(CHECK_COLUMN(result, 0, {1, 2, 3})); | 
|---|
| 590 | REQUIRE(CHECK_COLUMN(result, 1, {Value(), 1, 2})); | 
|---|
| 591 |  | 
|---|
| 592 | // same window clause name multiple times but in different subqueries | 
|---|
| 593 | result = con.Query( "SELECT * FROM (SELECT i, lag(i) OVER named_window FROM ( VALUES (1), (2), (3)) AS t (i) window " | 
|---|
| 594 | "named_window AS ( ORDER BY i)) t1, (SELECT i, lag(i) OVER named_window FROM ( VALUES (1), (2), " | 
|---|
| 595 | "(3)) AS t (i) window named_window AS ( ORDER BY i)) t2 ORDER BY 1, 2, 3, 4;"); | 
|---|
| 596 | REQUIRE(CHECK_COLUMN(result, 0, {1, 1, 1, 2, 2, 2, 3, 3, 3})); | 
|---|
| 597 | REQUIRE(CHECK_COLUMN(result, 1, {Value(), Value(), Value(), 1, 1, 1, 2, 2, 2})); | 
|---|
| 598 | REQUIRE(CHECK_COLUMN(result, 2, {1, 2, 3, 1, 2, 3, 1, 2, 3})); | 
|---|
| 599 | REQUIRE(CHECK_COLUMN(result, 3, {Value(), 1, 2, Value(), 1, 2, Value(), 1, 2})); | 
|---|
| 600 |  | 
|---|
| 601 | // we cannot use named window specifications of the main query inside CTEs | 
|---|
| 602 | REQUIRE_FAIL(con.Query( "WITH subquery AS (SELECT i, lag(i) OVER named_window FROM ( VALUES (1), (2), (3)) AS t " | 
|---|
| 603 | "(i)) SELECT * FROM subquery window named_window AS ( ORDER BY i);")); | 
|---|
| 604 | // duplicate window clause name | 
|---|
| 605 | REQUIRE_FAIL(con.Query( "select i, lag(i) over named_window from (values (1), (2), (3)) as t (i) window " | 
|---|
| 606 | "named_window as (order by i), named_window as (order by j);")); | 
|---|
| 607 | } | 
|---|
| 608 |  | 
|---|