From a62e1ff7dfbd0e528599967a4146050979491a42 Mon Sep 17 00:00:00 2001 From: KhaledR57 Date: Thu, 6 Nov 2025 08:44:50 +0200 Subject: [PATCH] MDEV-24943: Implement FILTER clause support for aggregate functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aggregates lacked the SQL-standard FILTER clause, forcing CASE-based workarounds that reduced readability across (sum, avg, count, …). This update introduces the ability to specify a FILTER clause for aggregate functions, allowing for more granular control over which rows are included in the aggregation. Also, improves standards compliance and makes queries clearer and more readable. The FILTER(WHERE ...) condition may contain any expression allowed in regular WHERE clauses, except window functions, and outer references. --- .../main/aggregates-filter.combinations | 9 + mysql-test/main/aggregates-filter.result | 696 ++++++++++ mysql-test/main/aggregates-filter.test | 1235 +++++++++++++++++ mysql-test/main/join_outer_reorder.result | 12 +- .../compat/oracle/r/ora_outer_join.result | 12 +- .../compat/oracle/r/ora_outer_join_err.result | 6 +- .../suite/perfschema/r/digest_view.result | 50 +- .../start_server_low_digest_sql_length.result | 4 +- sql/item_func.cc | 28 +- sql/item_func.h | 5 + sql/item_jsonfunc.cc | 5 +- sql/item_sum.cc | 207 ++- sql/item_sum.h | 43 + sql/item_windowfunc.cc | 5 + sql/lex.h | 1 + sql/opt_range.cc | 12 + sql/opt_sum.cc | 16 +- sql/sql_select.cc | 49 + sql/sql_yacc.yy | 67 +- storage/columnstore/columnstore | 2 +- storage/sequence/sequence.cc | 5 +- storage/spider/spd_db_mysql.cc | 2 + 22 files changed, 2381 insertions(+), 90 deletions(-) create mode 100644 mysql-test/main/aggregates-filter.combinations create mode 100644 mysql-test/main/aggregates-filter.result create mode 100644 mysql-test/main/aggregates-filter.test diff --git a/mysql-test/main/aggregates-filter.combinations b/mysql-test/main/aggregates-filter.combinations new file mode 100644 index 0000000000000..ce2a60320816a --- /dev/null +++ b/mysql-test/main/aggregates-filter.combinations @@ -0,0 +1,9 @@ +[innodb] +innodb +default-storage-engine=innodb + +[myisam] +default-storage-engine=myisam + +[aria] +default-storage-engine=aria diff --git a/mysql-test/main/aggregates-filter.result b/mysql-test/main/aggregates-filter.result new file mode 100644 index 0000000000000..a606aacfba5a5 --- /dev/null +++ b/mysql-test/main/aggregates-filter.result @@ -0,0 +1,696 @@ +# +# Setup test table +# +# +# Create custom aggregate function +# +# +# Basic aggregates +# +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +137.5000 120.3056768558952 550 3 75 200 Novel 0 15 6 ["Phone","Tablet","Laptop"] {"item2":"Tablet", "item5":"Laptop"} 44.7214 54.1987 1100.0000 312.5000 MULTIPOINT(1 1,2 2,4 4,5 5,7 7,8 8) +# +# Aggregates with DISTINCT +# +avg_result sum_result count_result min_result max_result group_concat_result json_arrayagg_result collect_result +175.0000 525 1 75 200 Laptop,Phone,Tablet ["Electronics"] MULTIPOINT(2 2,4 4,8 8) +# +# Aggregates with WHERE (AND) +# +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +183.3333 156.04395604395606 250 5 75 200 Laptop,Phone,Tablet 0 15 9 ["Tablet","Laptop"] {"item2":"Tablet", "item5":"Laptop"} 41.4578 35.3553 742.1875 NULL MULTIPOINT(2 2,4 4,8 8) +# +# Aggregates with WHERE (OR) +# +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +137.5000 NULL 825 6 50 200 Guide,Novel,Pants,Phone,Shirt,Textbook 0 15 14 ["Phone","Tablet","Laptop","Shirt","Pants"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 49.4764 54.1987 2447.9167 3541.6667 MULTIPOINT(1 1,2 2,3 3,4 4,5 5,7 7,8 8) +# +# Aggregates with WHERE (LIKE) +# +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +137.5000 NULL 350 5 100 100 Guide,Novel,Textbook 6 15 5 ["Tablet","Shirt"] {"item2":"Tablet", "item6":"Shirt"} 12.5000 54.1987 2447.9167 312.5000 MULTIPOINT(1 1,2 2,3 3,4 4,5 5,7 7,8 8) +# +# Aggregates with (GROUP BY) +# +category avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 NULL 7 15 8 NULL NULL 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Clothing 75.0000 75 NULL 2 75 75 NULL 0 0 0 NULL NULL NULL NULL NULL NULL MULTIPOINT(7 7) +Electronics 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 1 ["Tablet","Laptop"] {"item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +category min_amount +Books 24.99 +Clothing 50.00 +Electronics 100.00 +category min_category +Books Books +Clothing Clothing +Electronics Electronics +category avg_value_0e0 +Books 31.666666666666668 +Clothing 45 +Electronics 27.5 +category sum_value +Books 95.00 +Clothing 90.00 +Electronics 55.00 +# +# Aggregates with multiple GROUP BY columns +# +category status avg_result +Books active 200.0000 +Books inactive NULL +Clothing NULL 50.0000 +Clothing active 75.0000 +Electronics active 116.6667 +category status weighted_avg_result +Books active 200 +Books inactive NULL +Clothing NULL 50 +Clothing active 75 +Electronics active 120 +category status sum_result +Books active 400 +Books inactive NULL +Clothing NULL 50 +Clothing active 75 +Electronics active 350 +category status count_result +Books active 2 +Books inactive 1 +Clothing NULL 1 +Clothing active 1 +Electronics active 3 +category status min_result +Books active 200 +Books inactive NULL +Clothing NULL NULL +Clothing active 75 +Electronics active 100 +category status max_result +Books active 200 +Books inactive NULL +Clothing NULL 50 +Clothing active 75 +Electronics active 150 +category status group_concat_result +Books active Guide,Textbook +Books inactive Novel +Clothing NULL Shirt +Clothing active Pants +Electronics active Laptop,Phone,Tablet +category status bit_and_result +Books active 7 +Books inactive 18446744073709551615 +Clothing NULL 3 +Clothing active 12 +Electronics active 0 +category status bit_or_result +Books active 15 +Books inactive 0 +Clothing NULL 0 +Clothing active 12 +Electronics active 15 +category status bit_xor_result +Books active 8 +Books inactive 0 +Clothing NULL 3 +Clothing active 12 +Electronics active 9 +category status json_arrayagg_result +Books active ["Textbook","Guide"] +Books inactive ["Novel"] +Clothing NULL ["Shirt"] +Clothing active ["Pants"] +Electronics active ["Phone","Tablet","Laptop"] +category status json_objectagg_result +Books active {"item4":"Textbook", "item8":"Guide"} +Books inactive {"item3":"Novel"} +Clothing NULL {"item6":"Shirt"} +Clothing active {"item7":"Pants"} +Electronics active {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} +category status std_result +Books active 0.0000 +Books inactive NULL +Clothing NULL 0.0000 +Clothing active 0.0000 +Electronics active 23.5702 +category status stddev_samp_result +Books active 0.0000 +Books inactive NULL +Clothing NULL NULL +Clothing active NULL +Electronics active 28.8675 +category status variance_result +Books active 0.0000 +Books inactive NULL +Clothing NULL 0.0000 +Clothing active 0.0000 +Electronics active 555.5556 +category status var_samp_result +Books active NULL +Books inactive NULL +Clothing NULL NULL +Clothing active NULL +Electronics active NULL +category status collect_result +Books active MULTIPOINT(4 4,8 8) +Books inactive NULL +Clothing NULL NULL +Clothing active MULTIPOINT(7 7) +Electronics active MULTIPOINT(1 1,2 2,5 5) +# +# GROUP BY with HAVING on aggregates +# +category avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 Guide,Novel,Textbook 7 15 8 ["Novel","Textbook","Guide"] {"item3":"Novel", "item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +# +# HAVING with alias +# +category avg_value weighted_avg_value sum_value count_value min_value max_value +Books 200.0000 200 400 2 NULL 200 +# +# WHERE + GROUP BY + HAVING +# +category avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 Guide,Novel,Textbook 7 15 8 ["Novel","Textbook","Guide"] {"item3":"Novel", "item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Electronics 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +# +# WHERE + multiple aggregates with FILTER +# +category total_avg total_weighted_avg active_avg active_weighted_avg total_sum active_sum total_count active_count total_min active_min total_max active_max +Books 200.0000 NULL 200.0000 200 400 NULL 2 2 NULL NULL 200 200 +Clothing 62.5000 66.07142857142857 75.0000 75 NULL NULL 2 2 NULL NULL 75 75 +Electronics 116.6667 120 116.6667 120 150 350 3 1 100 100 150 150 +# +# WHERE + FILTER + HAVING +# +category active_avg active_weighted_avg active_sum active_count group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 0 Guide,Textbook 7 15 8 ["Textbook","Guide"] {"item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Clothing 75.0000 75 NULL 0 Pants,Shirt 0 12 15 ["Shirt","Pants"] {"item6":"Shirt", "item7":"Pants"} 12.5000 NULL 156.2500 NULL MULTIPOINT(7 7) +Electronics 116.6667 120 150 3 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +# +# WHERE + window function (OVER) +# +category cat_avg cat_sum cat_count cat_min cat_max bit_and_result bit_or_result bit_xor_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 400 2 NULL 200 7 15 8 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Books 200.0000 400 2 NULL 200 7 15 8 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Books 200.0000 400 2 NULL 200 7 15 8 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Clothing 75.0000 NULL 2 75 75 0 12 15 12.5000 NULL 156.2500 NULL MULTIPOINT(7 7) +Clothing 75.0000 NULL 2 75 75 0 12 15 12.5000 NULL 156.2500 NULL MULTIPOINT(7 7) +Electronics 116.6667 150 3 100 100 0 15 9 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +Electronics 116.6667 150 3 100 100 0 15 9 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +Electronics 116.6667 150 3 100 100 0 15 9 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +# +# WHERE + window function with ORDER BY +# +id running_avg running_sum running_count running_min running_max bit_and_result bit_or_result bit_xor_result std_result stddev_samp_result variance_result var_samp_result collect_result +1 100.0000 100 1 100 100 6 6 0 0.0000 NULL 0.0000 NULL MULTIPOINT(1 1) +2 125.0000 250 2 100 150 6 14 0 25.0000 35.3553 625.0000 NULL MULTIPOINT(1 1,2 2) +3 125.0000 250 3 100 150 6 14 0 25.0000 35.3553 625.0000 NULL MULTIPOINT(1 1,2 2) +4 150.0000 450 4 100 200 6 15 7 40.8248 50.0000 1666.6667 NULL MULTIPOINT(1 1,2 2,4 4) +5 137.5000 550 5 100 200 0 15 14 41.4578 47.8714 1718.7500 NULL MULTIPOINT(1 1,2 2,4 4,5 5) +6 120.0000 550 6 100 200 0 15 13 50.9902 47.8714 2600.0000 NULL MULTIPOINT(1 1,2 2,4 4,5 5) +7 112.5000 625 7 75 200 0 15 1 49.4764 50.0000 2447.9167 NULL MULTIPOINT(1 1,2 2,4 4,5 5,7 7) +8 125.0000 825 8 75 200 0 15 14 55.0973 54.1987 3035.7143 NULL MULTIPOINT(1 1,2 2,4 4,5 5,7 7,8 8) +# +# WHERE + window function with frame specification +# +id windowed_avg windowed_sum windowed_count windowed_min windowed_max bit_and_result bit_or_result bit_xor_result std_result stddev_samp_result variance_result var_samp_result collect_result +1 125.0000 150 2 100 NULL 6 14 0 25.0000 35.3553 625.0000 NULL MULTIPOINT(1 1,2 2) +2 125.0000 150 2 100 NULL 6 14 0 25.0000 35.3553 625.0000 NULL MULTIPOINT(1 1,2 2) +3 175.0000 350 2 150 200 6 15 7 25.0000 35.3553 625.0000 NULL MULTIPOINT(2 2,4 4) +4 150.0000 200 2 100 200 1 15 14 50.0000 70.7107 2500.0000 NULL MULTIPOINT(4 4,5 5) +5 150.0000 200 3 100 200 1 15 13 62.3610 70.7107 3888.8889 NULL MULTIPOINT(4 4,5 5) +6 87.5000 NULL 3 75 100 0 13 6 20.4124 17.6777 416.6667 NULL MULTIPOINT(5 5,7 7) +7 137.5000 200 3 75 200 0 15 0 65.6167 88.3883 4305.5556 NULL MULTIPOINT(7 7,8 8) +8 137.5000 200 2 75 200 12 15 3 62.5000 88.3883 3906.2500 NULL MULTIPOINT(7 7,8 8) +# +# Subquery with aggregates in WHERE +# +category status value price amount name key_name value_col bit_value geom +Books inactive NULL 19.99 19.99 Novel item3 Novel NULL POINT(3 3) +Books active 200 29.99 29.99 Textbook item4 Textbook 7 POINT(4 4) +Books active 200 24.99 24.99 Guide item8 Guide 15 POINT(8 8) +# +# Correlated subquery with FILTER in SELECT list +# +category filtered_sum filtered_count +Books NULL 2 +Clothing 75 1 +Electronics 350 3 +# +# Derived table (FROM subquery) with FILTER +# +category filtered_avg filtered_sum +Books 200.0000 400 +Clothing 75.0000 NULL +Electronics 116.6667 150 +# +# Scalar subquery with FILTER +# +category value global_active_avg diff_from_avg +Electronics 100 137.5000 -37.5000 +Electronics 150 137.5000 12.5000 +Books NULL 137.5000 NULL +Books 200 137.5000 62.5000 +Electronics 100 137.5000 -37.5000 +Clothing 50 137.5000 -87.5000 +Clothing 75 137.5000 -62.5000 +Books 200 137.5000 62.5000 +avg_result +183.3333 +# +# IN subquery in FILTER WHERE +# +count_result +8 +# +# AVG with STD in subquery +# +AVG(x) +12.02340868 +# +# Aggregates with GROUP BY and ORDER BY +# +category avg_val weighted_avg_val sum_val count_val min_val max_val group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 Guide,Novel,Textbook 7 15 8 ["Novel","Textbook","Guide"] {"item3":"Novel", "item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Electronics 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +Clothing 75.0000 75 NULL 2 75 75 Pants,Shirt 0 12 15 ["Shirt","Pants"] {"item6":"Shirt", "item7":"Pants"} 12.5000 NULL 156.2500 NULL MULTIPOINT(7 7) +# +# Complete query with WHERE, GROUP BY, HAVING, ORDER BY +# +category avg_val weighted_avg_val sum_val count_val min_val max_val group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 Guide,Textbook 7 15 8 ["Textbook","Guide"] {"item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Electronics 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +# +# Aggregates with GROUP BY, ORDER BY, and LIMIT +# +category avg_val weighted_avg_val sum_val count_val min_val max_val group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books 200.0000 200 400 2 NULL 200 Guide,Novel,Textbook 7 15 8 ["Novel","Textbook","Guide"] {"item3":"Novel", "item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Electronics 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +# +# Aggregates with ROLLUP +# +category status avg_val weighted_avg_val sum_val count_val min_val max_val group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +Books active 200.0000 200 400 2 NULL 200 Guide,Textbook 7 15 8 ["Textbook","Guide"] {"item4":"Textbook", "item8":"Guide"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Books inactive NULL NULL NULL 0 NULL NULL Novel 18446744073709551615 0 0 ["Novel"] {"item3":"Novel"} NULL NULL NULL NULL NULL +Books NULL 200.0000 200 400 2 NULL 200 Guide,Novel,Textbook 7 15 8 ["Textbook","Guide","Novel"] {"item4":"Textbook", "item8":"Guide", "item3":"Novel"} 0.0000 0.0000 0.0000 NULL MULTIPOINT(4 4,8 8) +Clothing NULL NULL NULL NULL 1 NULL 50 Shirt 3 0 3 ["Shirt"] {"item6":"Shirt"} 0.0000 NULL 0.0000 NULL NULL +Clothing active 75.0000 75 NULL 1 75 75 Pants 12 12 12 ["Pants"] {"item7":"Pants"} 0.0000 NULL 0.0000 NULL MULTIPOINT(7 7) +Clothing NULL 75.0000 75 NULL 2 75 75 Pants,Shirt 0 12 15 ["Shirt","Pants"] {"item6":"Shirt", "item7":"Pants"} 12.5000 NULL 156.2500 NULL MULTIPOINT(7 7) +Electronics active 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +Electronics NULL 116.6667 120 150 3 100 100 Laptop,Phone,Tablet 0 15 9 ["Phone","Tablet","Laptop"] {"item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 23.5702 28.8675 555.5556 NULL MULTIPOINT(1 1,2 2,5 5) +NULL NULL 137.5000 120.3056768558952 550 7 75 200 Guide,Laptop,Novel,Pants,Phone,Shirt,Tablet,Textbook 0 15 14 ["Textbook","Guide","Novel","Shirt","Pants","Phone","Tablet","Laptop"] {"item4":"Textbook", "item8":"Guide", "item3":"Novel", "item6":"Shirt", "item7":"Pants", "item1":"Phone", "item2":"Tablet", "item5":"Laptop"} 55.0973 54.1987 3035.7143 NULL MULTIPOINT(4 4,8 8,7 7,1 1,2 2,5 5) +# +# COUNT with column names (not just *) +# +count_value count_category count_name +6 3 3 +# +# COUNT DISTINCT with multiple columns +# +count_distinct +3 +# +# FILTER on indexed column +# +# Create index on status column +avg_result weighted_avg_result sum_result count_result min_result max_result bit_and_result std_result stddev_samp_result variance_result var_samp_result collect_result +137.5000 120.3056768558952 350 3 75 150 0 49.4764 54.1987 3035.7143 NULL MULTIPOINT(1 1,2 2,5 5) +# +# Empty result set (all rows filtered out) +# +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +NULL NULL NULL 0 NULL NULL NULL 18446744073709551615 0 0 NULL NULL NULL NULL NULL NULL NULL +# +# FILTER with NULL conditions +# +avg_result weighted_avg_result count_result sum_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +NULL NULL 0 NULL NULL NULL NULL 18446744073709551615 0 0 NULL NULL NULL NULL NULL NULL NULL +# +# FILTER with JOINs +# +# Create second table for JOIN test +category avg_result weighted_avg_result sum_result count_result +Books 200.0000 NULL 200 2 +Electronics 150.0000 150 NULL 0 +category avg_result weighted_avg_result sum_result +Books 201.0000 NULL 201 +Electronics 151.0000 151 NULL +category avg_result weighted_avg_result sum_result +Books 200.0000 NULL 200 +Electronics 125.0000 140 250 +# +# FILTER with window functions - different partitions +# +category status cat_avg status_avg cat_count status_count +Books active 200.0000 183.3333 2 4 +Books active 200.0000 183.3333 2 4 +Books inactive 200.0000 NULL 2 0 +Clothing NULL 75.0000 NULL 2 0 +Clothing active 75.0000 183.3333 2 4 +Electronics active 116.6667 183.3333 3 4 +Electronics active 116.6667 183.3333 3 4 +Electronics active 116.6667 183.3333 3 4 +# +# FILTER with empty table +# +# Create empty table +avg_result weighted_avg_result sum_result count_result min_result max_result group_concat_result bit_and_result bit_or_result bit_xor_result json_arrayagg_result json_objectagg_result std_result stddev_samp_result variance_result var_samp_result collect_result +NULL NULL NULL 0 NULL NULL NULL 18446744073709551615 0 0 NULL NULL NULL NULL NULL NULL NULL +# +# FILTER with CASE in WHERE clause +# +avg_result weighted_avg_result sum_result count_result +137.5000 120.3056768558952 550 3 +# +# ORDER BY with FILTER aggregates +# +category avg_val weighted_avg_val sum_val +Books 200.0000 200 400 +Electronics 116.6667 120 150 +Clothing 75.0000 75 NULL +# +# Aggregates on id column itself with FILTER +# +avg_id weighted_avg_id sum_id count_id min_id max_id distinct_count_id +4.5000 3.7991266375545854 14 3 1 8 5 +# +# Aggregates on id with GROUP BY and FILTER +# +category avg_id weighted_avg_id sum_id count_id min_id max_id +Books 6.0000 5.818181818181818 12 2 NULL 8 +Clothing 6.5000 6.642857142857143 7 0 7 7 +Electronics 5.0000 5 8 1 1 5 +# +# STORED AGGREGATE FUNCTION TESTS +# +# Multiple stored aggregates with different FILTER conditions +category active_weighted inactive_weighted high_value_weighted low_value_weighted electronics_weighted +Books 200 NULL 200 NULL NULL +Clothing 75 NULL NULL 66.07142857142857 NULL +Electronics 120 NULL 150 100 120 +# Stored aggregate with subquery correlation and FILTER +category correlated_weighted_avg +Books NULL +Books NULL +Clothing 75 +Clothing NULL +Electronics 150 +Electronics NULL +# Stored aggregate with all rows filtered out +category no_match_weighted +Books NULL +Clothing NULL +Electronics NULL +# Stored aggregate with complex nested conditions in FILTER +category complex_filter_weighted +Books 200 +Clothing 75 +Electronics 120 +# +# SEQUENCE ENGINE TESTS +# +# COUNT with FILTER on sequence +COUNT(*) FILTER (WHERE seq < 5) +4 +# Verify FILTER and CASE produce same results +filtered_count case_count +4 4 +# SUM with FILTER on sequence +SUM(seq) FILTER (WHERE seq < 5) +10 +# Verify FILTER and CASE produce same results +filtered_sum case_sum +10 10 +# Multiple FILTER conditions on sequence +count_le_3 count_gt_7 count_between sum_even sum_odd +3 3 3 30 25 +# Mixed filtered and unfiltered aggregates on sequence +unfiltered_count filtered_count unfiltered_sum filtered_sum +10 4 55 45 +# FILTER with different sequence ranges +COUNT(*) FILTER (WHERE seq < 50) +49 +SUM(seq) FILTER (WHERE seq BETWEEN 10 AND 20) +165 +# AVG, MIN, MAX with FILTER on sequence +total_avg filtered_avg filtered_min filtered_max +5.5000 3.0000 4 7 +# FILTER that matches no rows +COUNT(*) FILTER (WHERE seq > 100) +0 +SUM(seq) FILTER (WHERE seq < 0) +NULL +# FILTER that matches all rows +COUNT(*) FILTER (WHERE seq > 0) +10 +SUM(seq) FILTER (WHERE seq <= 10) +55 +# Sequence with window functions and FILTER +seq filtered_total even_count +1 1 0 +2 3 1 +3 6 1 +4 6 2 +5 6 2 +# +# FILTER with loose index scan (QUICK_GROUP_MIN_MAX_SELECT) +# +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +# t2 is the same as t1, but with some NULLs in the MIN/MAX column +# add rows with NULL's in the MIN/MAX column +Table Op Msg_type Msg_text +test.t2 analyze status Engine-independent statistics collected +test.t2 analyze status OK +# Queries without WHERE clause - basic MIN/MAX with FILTER +a1 MIN(a2) FILTER (WHERE a2 > 'a') +a b +b b +c b +d b +a1 MAX(a2) FILTER (WHERE a2 < 'b') +a a +b a +c a +d a +a1 a2 b MIN(c) FILTER (WHERE c > 'b') MAX(c) FILTER (WHERE c < 'n') +a a a b111 d111 +a a b e112 h112 +a b a i121 l121 +a b b m122 m122 +b a a b211 d211 +b a b e212 h212 +b b a i221 l221 +b b b m222 m222 +c a a b311 d311 +c a b e312 h312 +c b a i321 l321 +c b b m322 m322 +d a a b411 d411 +d a b e412 h412 +d b a i421 l421 +d b b m422 m422 +# Queries with predicates over GROUP BY attributes +a1 MAX(c) FILTER (WHERE c LIKE '%11%') +a d111 +a h112 +a NULL +a NULL +b d211 +b NULL +b NULL +b NULL +d d411 +d NULL +d NULL +d NULL +a1 a2 b MIN(c) FILTER (WHERE c > 'c') MAX(c) FILTER (WHERE c < 'm') +a a a c111 d111 +a a b e112 h112 +a b a i121 l121 +a b b m122 NULL +b a a c211 d211 +b a b e212 h212 +b b a i221 l221 +b b b m222 NULL +c a a c311 d311 +c a b e312 h312 +c b a i321 l321 +c b b m322 NULL +a1 a2 b MIN(c) FILTER (WHERE d != 'xy2') MAX(c) FILTER (WHERE d != 'xy3') +a a a c111 d111 +a a b e112 f112 +b a a c211 d211 +b a b e212 f212 +c a a c311 d311 +c a b e312 f312 +d a a c411 d411 +d a b e412 f412 +# IS NULL predicates +a1 a2 b MIN(c) FILTER (WHERE c > 'a5') +a a NULL a777 +c a NULL c777 +# Mixed predicates +a1 a2 b MIN(c) FILTER (WHERE d LIKE 'xy%') MAX(c) FILTER (WHERE d LIKE 'xy%') +a a b e112 h112 +b a b e212 h212 +c a b e312 h312 +c b b m322 p322 +d a b e412 h412 +d b b m422 p422 +# Test with NULLs in MIN/MAX column +a1 a2 b MIN(c) FILTER (WHERE c > 'a5') MAX(c) FILTER (WHERE c < 'z') +a a NULL a777 a999 +a a a b111 d111 +a a b e112 h112 +a b a i121 l121 +a b b m122 p122 +b a a b211 d211 +b a b e212 h212 +b b a i221 l221 +b b b m222 p222 +c a NULL c777 c999 +c a a b311 d311 +c a b e312 h312 +c b a i321 l321 +c b b m322 p322 +d a a b411 d411 +d a b e412 h412 +d b a i421 l421 +d b b m422 p422 +# Test FILTER with HAVING clause +a1 min_c +a b111 +b b211 +c b311 +d b411 +# +# Test large integer with FILTER (stolen from func_group.test :) +# +cast_min +9223372036854775807 +9223372036854775807 +NULL +# +# ERROR CASES +# +# FILTER with RANK (should error - FILTER only works with aggregates) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'FILTER (WHERE status = 'active') OVER (ORDER BY value) FROM test_aggregates' at line 1 +# FILTER with LAG (should error - FILTER only works with aggregates) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'FILTER (WHERE status = 'active') OVER (ORDER BY id) FROM test_aggregates' at line 1 +# OVER clause before FILTER clause (should error - correct order is FILTER then OVER) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '(WHERE status = 'active') FROM test_aggregates' at line 1 +# Regular scalar function (should error - FILTER only for aggregates) +ERROR HY000: Incorrect usage of FILTER and non-aggregate function +# FILTER on plain column (should error - not a function) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '(WHERE status = 'active') FROM test_aggregates' at line 1 +# Empty FILTER clause (should error - WHERE condition required) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') FROM test_aggregates' at line 1 +# Nested FILTER clauses (should error - cannot nest FILTER) +ERROR HY000: Incorrect usage of aggregate function and FILTER +# Multiple WHERE keywords (should error) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'WHERE value > 100) FROM test_aggregates' at line 1 +# Using column alias in FILTER WHERE (should error - alias not available) +ERROR 42S22: Unknown column 'val' in 'SELECT' +# Window function in FILTER WHERE condition (should error - not allowed) +ERROR HY000: Incorrect usage of window function and FILTER +ERROR HY000: Incorrect usage of window function and FILTER +# This should error - aggregate in WHERE clause +ERROR HY000: Incorrect usage of aggregate function and FILTER +ERROR HY000: Incorrect usage of aggregate function and FILTER +# Missing WHERE keyword (should error) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'status = 'active') FROM test_aggregates' at line 1 +# Missing parentheses (should error) +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'WHERE status = 'active' FROM test_aggregates' at line 1 +# +# Illegal use of FILTER with non-aggregate stored function +# +# FILTER clause with non-aggregate stored function should fail +ERROR HY000: Incorrect usage of FILTER and non-aggregate function +# SUM() FILTER on DOUBLE must exclude filtered rows (streaming path) +SUM(d) FILTER (WHERE i=1) +104.5 +# SUM() FILTER on DOUBLE must exclude filtered rows (grouped path) +g SUM(d) FILTER (WHERE d > 10) +1 55 +2 NULL +# JSON_ARRAYAGG() FILTER must drop excluded rows, not keep them as JSON null +JSON_ARRAYAGG(c) FILTER (WHERE b=1) +["x","z"] +# FILTER on a UDF aggregate must not crash and must actually filter +avgcost(qty, price) FILTER (WHERE g = 1) +2.3333 +# A volatile FILTER predicate must be evaluated once per row, so AVG's sum and count agree +AVG(i) FILTER (WHERE (@n:=@n+1) MOD 2 = 0) +4.0000 +# A volatile FILTER predicate must give the same SUM for FLOAT and DOUBLE under GROUP BY +g SUM(f) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) +1 4 +g SUM(d) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) +1 4 +# A volatile FILTER predicate must not let update_field() discard the running DECIMAL sum +g SUM(n) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) +1 1003 +# FILTER must be accepted on a schema-qualified stored aggregate function call +weighted_avg_result +120.3056768558952 +# A view over a cross-schema stored aggregate with FILTER must keep the clause +View Create View character_set_client collation_connection +v_filter_view CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v_filter_view` AS select `agg_filter_view`(`test`.`test_aggregates`.`value`) FILTER(WHERE `test`.`test_aggregates`.`status` = 'active') AS `wavg` from `test_aggregates` latin1 latin1_swedish_ci +Warnings: +Warning 1356 View 'test.v_filter_view' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them +# print() must show the original predicate, not the substituted temporary table field +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE test_aggregates ALL NULL NULL NULL NULL 8 100.00 Using temporary +Warnings: +Note 1003 select sql_buffer_result sum(`test`.`test_aggregates`.`value`) FILTER(WHERE `test`.`test_aggregates`.`status` = 'active') AS `SUM(value) FILTER (WHERE status = 'active')` from `test`.`test_aggregates` +Level Code Message +Note 1003 select sql_buffer_result sum(`test`.`test_aggregates`.`value`) FILTER(WHERE `test`.`test_aggregates`.`status` = 'active') AS `SUM(value) FILTER (WHERE status = 'active')` from `test`.`test_aggregates` +# filter_passed() must use boolean truth (val_bool), not val_int() truncation +COUNT(*) FILTER (WHERE a) +2 +COUNT(*) +2 +# EXISTS subquery in FILTER WHERE, correlated to the outer row +category count_result +Books 3 +Clothing 2 +Electronics 3 +# Correlated scalar subquery inside FILTER WHERE +category sum_result +Books NULL +Clothing 75 +Electronics 150 +# LEFT JOIN: FILTER on the nullable side must exclude NULL-extended rows +category count_result +Books 1 +Clothing 0 +Electronics 1 +# RIGHT JOIN: FILTER on the nullable side must exclude NULL-extended rows +ref_id count_result +1 1 +3 0 +99 0 +# filter is unreserved: works unquoted as a column, table, and database name +filter +10 +id filter +2 20 +1 99 +a +1 +a +1 +# filter is unreserved, but a bare (no AS) alias right after a function +# call is intentionally rejected: it is ambiguous with FILTER(WHERE ...). +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'FROM test_aggregates' at line 1 +filter +875 +# +# CLEANUP +# +# +# End of aggregates FILTER test +# diff --git a/mysql-test/main/aggregates-filter.test b/mysql-test/main/aggregates-filter.test new file mode 100644 index 0000000000000..17039cec556d3 --- /dev/null +++ b/mysql-test/main/aggregates-filter.test @@ -0,0 +1,1235 @@ +# +# MDEV-24943: Add FILTER clause +# +# Test for aggregate functions with FILTER clause. +# The FILTER clause extends aggregate functions with a WHERE condition. +# +# Test coverage includes: +# - Basic aggregate functions (AVG, SUM, COUNT, MIN, MAX) +# - Extended aggregates (GROUP_CONCAT, BIT_*, JSON_*, STD, VARIANCE) +# - DISTINCT and column/star variations +# - GROUP BY, HAVING, ORDER BY, LIMIT +# - Window functions with FILTER +# - Subqueries, JOINs, and edge cases +# - Stored aggregate functions +# - Sequence engine tables +# - MIN/MAX with FILTER and loose index scan (QUICK_GROUP_MIN_MAX_SELECT) +# - Error cases for invalid FILTER usage +# + +--source include/have_sequence.inc +--source include/have_udf.inc + +--disable_query_log +--echo # +--echo # Setup test table +--echo # +CREATE TABLE test_aggregates ( + id INT PRIMARY KEY, + category VARCHAR(50), + status VARCHAR(20), + value INT, + price DECIMAL(10,2), + amount DECIMAL(10,2) unique NOT NULL, + name VARCHAR(50), + key_name VARCHAR(50), + value_col VARCHAR(50), + bit_value INT, + extra_value float(10,2), + geom GEOMETRY +); + +INSERT INTO test_aggregates VALUES +(1, 'Electronics', 'active', 100, 299.99, 100.00, 'Phone', 'item1', 'Phone', 6, 10.00, ST_GeomFromText('POINT(1 1)')), +(2, 'Electronics', 'active', 150, 399.99, 399.99, 'Tablet', 'item2', 'Tablet', 14, 25.00, ST_GeomFromText('POINT(2 2)')), +(3, 'Books', 'inactive', NULL, 19.99, 19.99, 'Novel', 'item3', 'Novel', NULL, 15.00, ST_GeomFromText('POINT(3 3)')), +(4, 'Books', 'active', 200, 29.99, 29.99, 'Textbook', 'item4', 'Textbook', 7, 20.00, ST_GeomFromText('POINT(4 4)')), +(5, 'Electronics', 'active', 100, 499.99, 499.99, 'Laptop', 'item5', 'Laptop', 9, 30.00, ST_GeomFromText('POINT(5 5)')), +(6, 'Clothing', NULL, 50, NULL, 50.00, 'Shirt', 'item6', 'Shirt', 3, 40.00, ST_GeomFromText('POINT(6 6)')), +(7, 'Clothing', 'active', 75, 89.99, 89.99, 'Pants', 'item7', 'Pants', 12, 50.00, ST_GeomFromText('POINT(7 7)')), +(8, 'Books', 'active', 200, 24.99, 24.99, 'Guide', 'item8', 'Guide', 15, 60.00, ST_GeomFromText('POINT(8 8)')); + +--echo # +--echo # Create custom aggregate function +--echo # +delimiter |; +CREATE AGGREGATE FUNCTION weighted_avg(val INT, weight INT) RETURNS DOUBLE +BEGIN + DECLARE sum_val_weight DOUBLE DEFAULT 0; + DECLARE sum_weight DOUBLE DEFAULT 0; + DECLARE CONTINUE HANDLER FOR NOT FOUND + RETURN IF(sum_weight > 0, sum_val_weight / sum_weight, NULL); + LOOP + FETCH GROUP NEXT ROW; + SET sum_val_weight = sum_val_weight + val * weight; + SET sum_weight = sum_weight + weight; + END LOOP; +END| +delimiter ;| + + +--echo # +--echo # Basic aggregates +--echo # + +SELECT + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE value > 100) as sum_result, + COUNT(*) FILTER (WHERE category = 'Electronics') as count_result, + MIN(value) FILTER (WHERE amount > 50) as min_result, + MAX(value) FILTER (WHERE value IS NOT NULL) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE status = 'inactive') as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE value BETWEEN 100 AND 200) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE category IN ('Books', 'Electronics')) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE bit_value IS NOT NULL) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE price > 100) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE amount > 200) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 100) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE value < 200) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing') as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates; + +--echo # +--echo # Aggregates with DISTINCT +--echo # + +SELECT + AVG(DISTINCT value) FILTER (WHERE value > 100) as avg_result, + SUM(DISTINCT value) FILTER (WHERE status = 'active') as sum_result, + COUNT(DISTINCT category) FILTER (WHERE category = 'Electronics') as count_result, + MIN(DISTINCT value) FILTER (WHERE amount > 50) as min_result, + MAX(DISTINCT value) FILTER (WHERE id > 3) as max_result, + GROUP_CONCAT(DISTINCT name ORDER BY name SEPARATOR ',') FILTER (WHERE price > 100) as group_concat_result, + JSON_ARRAYAGG(DISTINCT category) FILTER (WHERE price > 100) as json_arrayagg_result, + ST_AsText(ST_COLLECT(DISTINCT geom) FILTER (WHERE value > 100)) as collect_result +FROM test_aggregates; + +--echo # +--echo # Aggregates with WHERE (AND) +--echo # + +SELECT + AVG(value) FILTER (WHERE status = 'active' AND value > 100) as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active' AND value > 100) as weighted_avg_result, + SUM(value) FILTER (WHERE category = 'Electronics' AND amount > 200) as sum_result, + COUNT(*) FILTER (WHERE value IS NOT NULL AND id > 3) as count_result, + MIN(value) FILTER (WHERE status = 'active' AND amount > 50) as min_result, + MAX(value) FILTER (WHERE category IN ('Books', 'Electronics') AND value > 0) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE price > 100 AND status = 'active') as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value IS NOT NULL AND value > 50) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE category = 'Books' AND amount > 20) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE value BETWEEN 100 AND 200 AND status IS NOT NULL) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE amount > 100 AND category = 'Electronics') as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE amount > 200 AND price > 100) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 100 AND id <= 5) as std_result, + STDDEV_SAMP(value) FILTER (WHERE value > 50 AND amount > 100) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE status = 'active' AND value < 200) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active' AND value > 100)) as collect_result +FROM test_aggregates; + +--echo # +--echo # Aggregates with WHERE (OR) +--echo # + +SELECT + AVG(value) FILTER (WHERE category IN ('Books', 'Electronics') OR amount > 50) as avg_result, + weighted_avg(value, amount) FILTER (WHERE category IN ('Books', 'Electronics') OR amount > 50) as weighted_avg_result, + SUM(value) FILTER (WHERE status = 'active' OR value > 100) as sum_result, + COUNT(*) FILTER (WHERE category = 'Electronics' OR id > 5) as count_result, + MIN(value) FILTER (WHERE value IS NOT NULL OR amount > 200) as min_result, + MAX(value) FILTER (WHERE price > 100 OR status IS NOT NULL) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE category = 'Books' OR amount BETWEEN 20 AND 100) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value IS NOT NULL OR value >= 100) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE category IN ('Clothing', 'Electronics') OR amount < 100) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE status = 'inactive' OR value < 200) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE amount > 50 OR category = 'Clothing') as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE amount > 200 OR category = 'Electronics') as json_objectagg_result, + STD(value) FILTER (WHERE value > 50 OR id <= 4) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL OR amount > 100) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE category = 'Electronics' OR price IS NOT NULL) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' OR status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE category IN ('Books', 'Electronics') OR amount > 50)) as collect_result +FROM test_aggregates; + +--echo # +--echo # Aggregates with WHERE (LIKE) +--echo # + +SELECT + AVG(value) FILTER (WHERE status LIKE '%active%') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status LIKE '%active%') as weighted_avg_result, + SUM(value) FILTER (WHERE category LIKE 'Elect%') as sum_result, + COUNT(category) FILTER (WHERE name LIKE '%t%') as count_result, + MIN(value) FILTER (WHERE category LIKE '%s') as min_result, + MAX(value) FILTER (WHERE name LIKE 'P%') as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE category LIKE 'B%') as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE name LIKE '%one') as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE category LIKE '%ics') as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE status LIKE 'a%') as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name LIKE '%t') as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name LIKE '%t') as json_objectagg_result, + STD(value) FILTER (WHERE category LIKE 'C%') as std_result, + STDDEV_SAMP(value) FILTER (WHERE status LIKE '%e') as stddev_samp_result, + VARIANCE(value) FILTER (WHERE status LIKE '%e') as variance_result, + VAR_SAMP(value) FILTER (WHERE category LIKE 'C%') as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status LIKE '%active%')) as collect_result +FROM test_aggregates; + +--echo # +--echo # Aggregates with (GROUP BY) +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE value > 100) as sum_result, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_result, + MIN(value) FILTER (WHERE amount > 50) as min_result, + MAX(value) FILTER (WHERE id > 3) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE price > 100) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value IS NOT NULL) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value BETWEEN 100 AND 200) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE category IN ('Books', 'Electronics')) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE amount > 200) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE amount > 200) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 100) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL OR amount > 100) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE id <= 5) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates GROUP BY category; + +SELECT category, MIN(amount) FILTER (WHERE id > 0 AND value IS NOT NULL) as min_amount FROM test_aggregates GROUP BY category; +SELECT category, MIN(category) FILTER (WHERE id > 0) as min_category FROM test_aggregates GROUP BY category; +SELECT category, AVG(extra_value+0.0e0) FILTER (WHERE extra_value > 10) as avg_value_0e0 FROM test_aggregates GROUP BY category; +SELECT category, SUM(extra_value) FILTER (WHERE extra_value > 10) as sum_value FROM test_aggregates GROUP BY category; + +--echo # +--echo # Aggregates with multiple GROUP BY columns +--echo # + +SELECT category, status, AVG(value) FILTER (WHERE value > 0) as avg_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, weighted_avg(value, amount) FILTER (WHERE value > 0) as weighted_avg_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, SUM(value) FILTER (WHERE amount > 0) as sum_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, COUNT(*) FILTER (WHERE id > 0) as count_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, MIN(value) FILTER (WHERE price > 0) as min_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, MAX(value) FILTER (WHERE value IS NOT NULL) as max_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE category IS NOT NULL) as group_concat_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, STD(value) FILTER (WHERE value >= 0) as std_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result FROM test_aggregates GROUP BY category, status; +SELECT category, status, ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result FROM test_aggregates GROUP BY category, status; + +--echo # +--echo # GROUP BY with HAVING on aggregates +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE value > 100) as sum_result, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_result, + MIN(value) FILTER (WHERE amount > 50) as min_result, + MAX(value) FILTER (WHERE id > 3) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates GROUP BY category +HAVING AVG(value) FILTER (WHERE status = 'active') > 120 AND SUM(value) FILTER (WHERE value > 100) > 200; + +--echo # +--echo # HAVING with alias +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_value, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_value, + SUM(value) FILTER (WHERE value > 100) as sum_value, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_value, + MIN(value) FILTER (WHERE amount > 50) as min_value, + MAX(value) FILTER (WHERE id > 3) as max_value +FROM test_aggregates GROUP BY category HAVING avg_value > 100 AND sum_value > 200; + +--echo # +--echo # WHERE + GROUP BY + HAVING +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE value > 100) as sum_result, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_result, + MIN(value) FILTER (WHERE amount > 50) as min_result, + MAX(value) FILTER (WHERE id > 3) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +GROUP BY category +HAVING AVG(value) FILTER (WHERE status = 'active') > 0 AND SUM(value) FILTER (WHERE value > 100) > 0; + +--echo # +--echo # WHERE + multiple aggregates with FILTER +--echo # + +SELECT category, + AVG(value) FILTER (WHERE amount > 0) as total_avg, + weighted_avg(value, amount) FILTER (WHERE amount > 0) as total_weighted_avg, + AVG(value) FILTER (WHERE status = 'active') as active_avg, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as active_weighted_avg, + SUM(value) FILTER (WHERE value > 100) as total_sum, + SUM(value) FILTER (WHERE category = 'Electronics') as active_sum, + COUNT(*) FILTER (WHERE value IS NOT NULL) as total_count, + COUNT(*) FILTER (WHERE id > 3) as active_count, + MIN(value) FILTER (WHERE price > 100) as total_min, + MIN(value) FILTER (WHERE amount > 200) as active_min, + MAX(value) FILTER (WHERE value BETWEEN 50 AND 200) as total_max, + MAX(value) FILTER (WHERE bit_value IS NOT NULL) as active_max +FROM test_aggregates +GROUP BY category; + +--echo # +--echo # WHERE + FILTER + HAVING +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as active_avg, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as active_weighted_avg, + SUM(value) FILTER (WHERE value > 100) as active_sum, + COUNT(*) FILTER (WHERE category = 'Electronics') as active_count, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +WHERE value IS NOT NULL +GROUP BY category +HAVING AVG(value) FILTER (WHERE value IS NOT NULL) > 1 AND SUM(value) FILTER (WHERE amount > 0) > 1; + +--echo # +--echo # WHERE + window function (OVER) +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') OVER (PARTITION BY category) as cat_avg, + SUM(value) FILTER (WHERE value > 100) OVER (PARTITION BY category) as cat_sum, + COUNT(*) FILTER (WHERE value IS NOT NULL) OVER (PARTITION BY category) as cat_count, + MIN(value) FILTER (WHERE amount > 50) OVER (PARTITION BY category) as cat_min, + MAX(value) FILTER (WHERE id > 3) OVER (PARTITION BY category) as cat_max, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) OVER (PARTITION BY category) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) OVER (PARTITION BY category) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) OVER (PARTITION BY category) as bit_xor_result, + STD(value) FILTER (WHERE value >= 0) OVER (PARTITION BY category) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) OVER (PARTITION BY category) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) OVER (PARTITION BY category) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) OVER (PARTITION BY category) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active') OVER (PARTITION BY category)) as collect_result +FROM test_aggregates; + +--echo # +--echo # WHERE + window function with ORDER BY +--echo # + +SELECT id, + AVG(value) FILTER (WHERE value IS NOT NULL) OVER (ORDER BY id) as running_avg, + SUM(value) FILTER (WHERE status = 'active') OVER (ORDER BY id) as running_sum, + COUNT(*) FILTER (WHERE amount > 0) OVER (ORDER BY id) as running_count, + MIN(value) FILTER (WHERE value > 50) OVER (ORDER BY id) as running_min, + MAX(value) FILTER (WHERE id > 0) OVER (ORDER BY id) as running_max, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) OVER (ORDER BY id) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) OVER (ORDER BY id) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) OVER (ORDER BY id) as bit_xor_result, + STD(value) FILTER (WHERE value >= 0) OVER (ORDER BY id) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) OVER (ORDER BY id) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) OVER (ORDER BY id) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) OVER (ORDER BY id) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active') OVER (ORDER BY id)) as collect_result +FROM test_aggregates; + +--echo # +--echo # WHERE + window function with frame specification +--echo # + +SELECT id, + AVG(value) FILTER (WHERE status = 'active') OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_avg, + SUM(value) FILTER (WHERE value > 100) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_sum, + COUNT(*) FILTER (WHERE value IS NOT NULL) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_count, + MIN(value) FILTER (WHERE amount > 50) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_min, + MAX(value) FILTER (WHERE id > 3) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as windowed_max, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as bit_xor_result, + STD(value) FILTER (WHERE value >= 0) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active') OVER (ORDER BY id ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)) as collect_result +FROM test_aggregates; + +--echo # +--echo # Subquery with aggregates in WHERE +--echo # + +SELECT category, status, value, price, amount, name, key_name, value_col, bit_value, ST_AsText(geom) as geom FROM test_aggregates +WHERE category IN ( + SELECT category FROM test_aggregates GROUP BY category + HAVING AVG(value) FILTER (WHERE status = 'active') > 120 AND SUM(value) FILTER (WHERE value > 100) > 200 +); + +--echo # +--echo # Correlated subquery with FILTER in SELECT list +--echo # + +SELECT + t1.category, + ( + SELECT SUM(t2.value) FILTER (WHERE t2.amount > 50) + FROM test_aggregates t2 + WHERE t2.category = t1.category + ) AS filtered_sum, + ( + SELECT COUNT(*) FILTER (WHERE t2.status = 'active') + FROM test_aggregates t2 + WHERE t2.category = t1.category + ) AS filtered_count +FROM test_aggregates t1 +GROUP BY t1.category; + +--echo # +--echo # Derived table (FROM subquery) with FILTER +--echo # + +SELECT category, filtered_avg, filtered_sum +FROM ( + SELECT + category, + AVG(value) FILTER (WHERE status = 'active') AS filtered_avg, + SUM(value) FILTER (WHERE value > 100) AS filtered_sum + FROM test_aggregates + GROUP BY category +) AS derived_table +WHERE filtered_avg IS NOT NULL; + +--echo # +--echo # Scalar subquery with FILTER +--echo # + +SELECT + category, + value, + (SELECT AVG(value) FILTER (WHERE status = 'active') FROM test_aggregates) AS global_active_avg, + value - (SELECT AVG(value) FILTER (WHERE status = 'active') FROM test_aggregates) AS diff_from_avg +FROM test_aggregates; + +SELECT + AVG(value) FILTER (WHERE value > (SELECT AVG(value) FROM test_aggregates)) as avg_result +FROM test_aggregates; + +--echo # +--echo # IN subquery in FILTER WHERE +--echo # + +SELECT + COUNT(*) FILTER (WHERE category IN (SELECT DISTINCT category FROM test_aggregates WHERE status = 'active')) as count_result +FROM test_aggregates; + +--echo # +--echo # AVG with STD in subquery +--echo # + +SELECT AVG(x) FROM (SELECT STD(value) FILTER (WHERE 1=1) as x FROM test_aggregates GROUP BY category) t; + +--echo # +--echo # Aggregates with GROUP BY and ORDER BY +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_val, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_val, + SUM(value) FILTER (WHERE value > 100) as sum_val, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_val, + MIN(value) FILTER (WHERE amount > 50) as min_val, + MAX(value) FILTER (WHERE id > 3) as max_val, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +GROUP BY category +ORDER BY avg_val DESC, weighted_avg_val DESC, sum_val DESC; + +--echo # +--echo # Complete query with WHERE, GROUP BY, HAVING, ORDER BY +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_val, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_val, + SUM(value) FILTER (WHERE value > 100) as sum_val, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_val, + MIN(value) FILTER (WHERE amount > 50) as min_val, + MAX(value) FILTER (WHERE id > 3) as max_val, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +WHERE status = 'active' +GROUP BY category +HAVING AVG(value) FILTER (WHERE status = 'active') > 0 AND SUM(value) FILTER (WHERE value > 100) > 0 +ORDER BY avg_val DESC, sum_val DESC; + +--echo # +--echo # Aggregates with GROUP BY, ORDER BY, and LIMIT +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_val, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_val, + SUM(value) FILTER (WHERE value > 100) as sum_val, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_val, + MIN(value) FILTER (WHERE amount > 50) as min_val, + MAX(value) FILTER (WHERE id > 3) as max_val, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +GROUP BY category +ORDER BY AVG(value) FILTER (WHERE status = 'active') DESC, weighted_avg(value, amount) FILTER (WHERE status = 'active') DESC +LIMIT 2; + +--echo # +--echo # Aggregates with ROLLUP +--echo # + +SELECT category, status, + AVG(value) FILTER (WHERE status = 'active') as avg_val, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_val, + SUM(value) FILTER (WHERE value > 100) as sum_val, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_val, + MIN(value) FILTER (WHERE amount > 50) as min_val, + MAX(value) FILTER (WHERE id > 3) as max_val, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE name IS NOT NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE name IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE name IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE status = 'active')) as collect_result +FROM test_aggregates +GROUP BY category, status WITH ROLLUP; + +--echo # +--echo # COUNT with column names (not just *) +--echo # + +SELECT + COUNT(value) FILTER (WHERE status = 'active') as count_value, + COUNT(category) FILTER (WHERE value > 100) as count_category, + COUNT(name) FILTER (WHERE category = 'Electronics') as count_name +FROM test_aggregates; + +--echo # +--echo # COUNT DISTINCT with multiple columns +--echo # + +SELECT + COUNT(DISTINCT category, status) FILTER (WHERE status = 'active' OR value > 100) as count_distinct +FROM test_aggregates; + +--echo # +--echo # FILTER on indexed column +--echo # + +--echo # Create index on status column +CREATE INDEX idx_status ON test_aggregates(status); +CREATE INDEX idx_category ON test_aggregates(category); + +SELECT + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE category = 'Electronics') as sum_result, + COUNT(*) FILTER (WHERE category = 'Electronics') as count_result, + MIN(value) FILTER (WHERE status = 'active') as min_result, + MAX(value) FILTER (WHERE category = 'Electronics') as max_result, + BIT_AND(bit_value) FILTER (WHERE bit_value > 0) as bit_and_result, + STD(value) FILTER (WHERE status = 'active') as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE amount >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE category = 'Clothing' AND status IS NOT NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE category = 'Electronics')) as collect_result +FROM test_aggregates; + +--echo # +--echo # Empty result set (all rows filtered out) +--echo # + +SELECT + AVG(value) FILTER (WHERE 1=0) as avg_result, + weighted_avg(value, amount) FILTER (WHERE 1=0) as weighted_avg_result, + SUM(value) FILTER (WHERE 2=1) as sum_result, + COUNT(*) FILTER (WHERE 3=2) as count_result, + MIN(value) FILTER (WHERE 4=3) as min_result, + MAX(value) FILTER (WHERE 5=4) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE 0) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE bit_value < 0) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE bit_value < 0) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE bit_value < 0) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE 0) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE 0) as json_objectagg_result, + STD(value) FILTER (WHERE 5=4) as std_result, + STDDEV_SAMP(value) FILTER (WHERE 5=4) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE 5=4) as variance_result, + VAR_SAMP(value) FILTER (WHERE 5=4) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE 5=4)) as collect_result +FROM test_aggregates; + +--echo # +--echo # FILTER with NULL conditions +--echo # + +SELECT + AVG(value) FILTER (WHERE NULL) as avg_result, + weighted_avg(value, amount) FILTER (WHERE NULL) as weighted_avg_result, + COUNT(*) FILTER (WHERE NULL) as count_result, + SUM(value) FILTER (WHERE NULL) as sum_result, + MIN(value) FILTER (WHERE NULL) as min_result, + MAX(value) FILTER (WHERE NULL) as max_result, + GROUP_CONCAT(name ORDER BY name SEPARATOR ',') FILTER (WHERE NULL) as group_concat_result, + BIT_AND(bit_value) FILTER (WHERE NULL) as bit_and_result, + BIT_OR(bit_value) FILTER (WHERE NULL) as bit_or_result, + BIT_XOR(bit_value) FILTER (WHERE NULL) as bit_xor_result, + JSON_ARRAYAGG(name) FILTER (WHERE NULL) as json_arrayagg_result, + JSON_OBJECTAGG(key_name, value_col) FILTER (WHERE NULL) as json_objectagg_result, + STD(value) FILTER (WHERE NULL) as std_result, + STDDEV_SAMP(value) FILTER (WHERE NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE NULL) as variance_result, + VAR_SAMP(value) FILTER (WHERE NULL) as var_samp_result, + ST_AsText(ST_COLLECT(geom) FILTER (WHERE NULL)) as collect_result +FROM test_aggregates; + +--echo # +--echo # FILTER with JOINs +--echo # + +--echo # Create second table for JOIN test +CREATE TABLE test_aggregates2 ( + id INT PRIMARY KEY, + ref_id INT, + extra_value INT +); + +INSERT INTO test_aggregates2 VALUES +(1, 1, 10), +(2, 2, 20), +(3, 3, 30), +(4, 4, 40); + +SELECT + t1.category, + AVG(t1.value) FILTER (WHERE t2.extra_value > 15) as avg_result, + weighted_avg(t1.value, t1.amount) FILTER (WHERE t2.extra_value > 15) as weighted_avg_result, + SUM(t1.value) FILTER (WHERE t2.extra_value > 20) as sum_result, + COUNT(*) FILTER (WHERE t2.extra_value > 25) as count_result +FROM test_aggregates t1 +JOIN test_aggregates2 t2 ON t1.id = t2.ref_id +GROUP BY t1.category; + +SELECT + t1.category, + AVG(t1.value + 1) FILTER (WHERE t2.extra_value > 15) as avg_result, + weighted_avg(t1.value + 1, t1.amount + 1) FILTER (WHERE t2.extra_value > 15) as weighted_avg_result, + SUM(t1.value + 1) FILTER (WHERE t2.extra_value > 20) as sum_result +FROM test_aggregates t1 +JOIN test_aggregates2 t2 ON t1.id = t2.ref_id +GROUP BY t1.category; + +SELECT + t1.category, + AVG(t1.value) FILTER (WHERE t2.extra_value) as avg_result, + weighted_avg(t1.value, t1.amount) FILTER (WHERE t2.extra_value) as weighted_avg_result, + SUM(t1.value) FILTER (WHERE t2.extra_value) as sum_result +FROM test_aggregates t1 +JOIN test_aggregates2 t2 ON t1.id = t2.ref_id +GROUP BY t1.category; + +DROP TABLE test_aggregates2; + +--echo # +--echo # FILTER with window functions - different partitions +--echo # + +SELECT category, status, + AVG(value) FILTER (WHERE status = 'active') OVER (PARTITION BY category) as cat_avg, + AVG(value) FILTER (WHERE value > 100) OVER (PARTITION BY status) as status_avg, + COUNT(*) FILTER (WHERE value IS NOT NULL) OVER (PARTITION BY category) as cat_count, + COUNT(*) FILTER (WHERE amount > 50) OVER (PARTITION BY status) as status_count +FROM test_aggregates +ORDER BY category, status; + +--echo # +--echo # FILTER with empty table +--echo # + +--echo # Create empty table +CREATE TABLE empty_test ( + id INT, + value INT, + status VARCHAR(20) +); + +SELECT + AVG(value) FILTER (WHERE status = 'active') as avg_result, + weighted_avg(value, id) FILTER (WHERE status = 'active') as weighted_avg_result, + SUM(value) FILTER (WHERE value > 100) as sum_result, + COUNT(*) FILTER (WHERE value IS NOT NULL) as count_result, + MIN(value) FILTER (WHERE id > 2) as min_result, + MAX(value) FILTER (WHERE id > 3) as max_result, + GROUP_CONCAT(status ORDER BY status SEPARATOR ',') FILTER (WHERE status IS NOT NULL) as group_concat_result, + BIT_AND(value) FILTER (WHERE value > 0) as bit_and_result, + BIT_OR(value) FILTER (WHERE value > 50) as bit_or_result, + BIT_XOR(value) FILTER (WHERE id > 2) as bit_xor_result, + JSON_ARRAYAGG(status) FILTER (WHERE status IS NOT NULL) as json_arrayagg_result, + JSON_OBJECTAGG(status, value) FILTER (WHERE status IS NOT NULL) as json_objectagg_result, + STD(value) FILTER (WHERE value >= 0) as std_result, + STDDEV_SAMP(value) FILTER (WHERE status IS NOT NULL) as stddev_samp_result, + VARIANCE(value) FILTER (WHERE value >= 0) as variance_result, + VAR_SAMP(value) FILTER (WHERE value >= 0) as var_samp_result, + ST_AsText(ST_COLLECT(value) FILTER (WHERE value >= 0)) as collect_result +FROM empty_test; + +DROP TABLE empty_test; + +--echo # +--echo # FILTER with CASE in WHERE clause +--echo # + +SELECT + AVG(value) FILTER (WHERE CASE WHEN status = 'active' THEN 1 ELSE 0 END = 1) as avg_result, + weighted_avg(value, amount) FILTER (WHERE CASE WHEN status = 'active' THEN 1 ELSE 0 END = 1) as weighted_avg_result, + SUM(value) FILTER (WHERE CASE WHEN value > 100 THEN 1 ELSE 0 END = 1) as sum_result, + COUNT(*) FILTER (WHERE CASE WHEN category = 'Electronics' THEN 1 ELSE 0 END = 1) as count_result +FROM test_aggregates; + +--echo # +--echo # ORDER BY with FILTER aggregates +--echo # + +SELECT category, + AVG(value) FILTER (WHERE status = 'active') as avg_val, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as weighted_avg_val, + SUM(value) FILTER (WHERE value > 100) as sum_val +FROM test_aggregates +GROUP BY category +ORDER BY AVG(value) FILTER (WHERE status = 'active') DESC, weighted_avg(value, amount) FILTER (WHERE status = 'active') DESC, SUM(value) FILTER (WHERE value > 100) DESC; + +--echo # +--echo # Aggregates on id column itself with FILTER +--echo # + +SELECT + AVG(id) FILTER (WHERE status = 'active') as avg_id, + weighted_avg(id, amount) FILTER (WHERE status = 'active') as weighted_avg_id, + SUM(id) FILTER (WHERE value > 100) as sum_id, + COUNT(id) FILTER (WHERE category = 'Electronics') as count_id, + MIN(id) FILTER (WHERE amount > 50) as min_id, + MAX(id) FILTER (WHERE value IS NOT NULL) as max_id, + COUNT(DISTINCT id) FILTER (WHERE id > 3) as distinct_count_id +FROM test_aggregates; + +--echo # +--echo # Aggregates on id with GROUP BY and FILTER +--echo # + +SELECT category, + AVG(id) FILTER (WHERE id > 3) as avg_id, + weighted_avg(id, amount) FILTER (WHERE id > 3) as weighted_avg_id, + SUM(id) FILTER (WHERE status = 'active') as sum_id, + COUNT(id) FILTER (WHERE value > 100) as count_id, + MIN(id) FILTER (WHERE amount > 50) as min_id, + MAX(id) FILTER (WHERE value IS NOT NULL) as max_id +FROM test_aggregates +GROUP BY category; + +--echo # +--echo # STORED AGGREGATE FUNCTION TESTS +--echo # + +--echo # Multiple stored aggregates with different FILTER conditions +SELECT + category, + weighted_avg(value, amount) FILTER (WHERE status = 'active') as active_weighted, + weighted_avg(value, amount) FILTER (WHERE status = 'inactive') as inactive_weighted, + weighted_avg(value, amount) FILTER (WHERE value > 100) as high_value_weighted, + weighted_avg(value, amount) FILTER (WHERE value <= 100) as low_value_weighted, + weighted_avg(value, amount) FILTER (WHERE category = 'Electronics') as electronics_weighted +FROM test_aggregates +GROUP BY category; + +--echo # Stored aggregate with subquery correlation and FILTER +SELECT + t1.category, + ( + SELECT weighted_avg(t2.value, t2.amount) FILTER (WHERE t2.value > t1.value) + FROM test_aggregates t2 + WHERE t2.category = t1.category + ) as correlated_weighted_avg +FROM test_aggregates t1 +GROUP BY t1.category, t1.value +ORDER BY t1.category, t1.value; + +--echo # Stored aggregate with all rows filtered out +SELECT + category, + weighted_avg(value, amount) FILTER (WHERE 1 = 0) as no_match_weighted +FROM test_aggregates +GROUP BY category; + +--echo # Stored aggregate with complex nested conditions in FILTER +SELECT + category, + weighted_avg(value, amount) FILTER ( + WHERE (status = 'active' AND value > 50) + OR (category = 'Electronics' AND amount > 200) + OR (value BETWEEN 100 AND 200 AND bit_value IS NOT NULL) + ) as complex_filter_weighted +FROM test_aggregates +GROUP BY category; + +--echo # +--echo # SEQUENCE ENGINE TESTS +--echo # + +--echo # COUNT with FILTER on sequence +SELECT COUNT(*) FILTER (WHERE seq < 5) FROM seq_1_to_10; + +--echo # Verify FILTER and CASE produce same results +SELECT + COUNT(*) FILTER (WHERE seq < 5) AS filtered_count, + SUM(CASE WHEN seq < 5 THEN 1 ELSE 0 END) AS case_count +FROM seq_1_to_10; + +--echo # SUM with FILTER on sequence +SELECT SUM(seq) FILTER (WHERE seq < 5) FROM seq_1_to_10; + +--echo # Verify FILTER and CASE produce same results +SELECT + SUM(seq) FILTER (WHERE seq < 5) AS filtered_sum, + SUM(CASE WHEN seq < 5 THEN seq ELSE 0 END) AS case_sum +FROM seq_1_to_10; + +--echo # Multiple FILTER conditions on sequence +SELECT + COUNT(*) FILTER (WHERE seq <= 3) AS count_le_3, + COUNT(*) FILTER (WHERE seq > 7) AS count_gt_7, + COUNT(*) FILTER (WHERE seq BETWEEN 4 AND 6) AS count_between, + SUM(seq) FILTER (WHERE seq % 2 = 0) AS sum_even, + SUM(seq) FILTER (WHERE seq % 2 = 1) AS sum_odd +FROM seq_1_to_10; + +--echo # Mixed filtered and unfiltered aggregates on sequence +SELECT + COUNT(*) AS unfiltered_count, + COUNT(*) FILTER (WHERE seq < 5) AS filtered_count, + SUM(seq) AS unfiltered_sum, + SUM(seq) FILTER (WHERE seq >= 5) AS filtered_sum +FROM seq_1_to_10; + +--echo # FILTER with different sequence ranges +SELECT COUNT(*) FILTER (WHERE seq < 50) FROM seq_1_to_100; +SELECT SUM(seq) FILTER (WHERE seq BETWEEN 10 AND 20) FROM seq_1_to_100; + +--echo # AVG, MIN, MAX with FILTER on sequence +SELECT + AVG(seq) AS total_avg, + AVG(seq) FILTER (WHERE seq <= 5) AS filtered_avg, + MIN(seq) FILTER (WHERE seq > 3) AS filtered_min, + MAX(seq) FILTER (WHERE seq < 8) AS filtered_max +FROM seq_1_to_10; + +--echo # FILTER that matches no rows +SELECT COUNT(*) FILTER (WHERE seq > 100) FROM seq_1_to_10; +SELECT SUM(seq) FILTER (WHERE seq < 0) FROM seq_1_to_10; + +--echo # FILTER that matches all rows +SELECT COUNT(*) FILTER (WHERE seq > 0) FROM seq_1_to_10; +SELECT SUM(seq) FILTER (WHERE seq <= 10) FROM seq_1_to_10; + +--echo # Sequence with window functions and FILTER +SELECT + seq, + SUM(seq) FILTER (WHERE seq <= 3) OVER (ORDER BY seq) AS filtered_total, + COUNT(*) FILTER (WHERE seq % 2 = 0) OVER (ORDER BY seq) AS even_count +FROM seq_1_to_5; + + +--echo # +--echo # FILTER with loose index scan (QUICK_GROUP_MIN_MAX_SELECT) +--echo # + +CREATE TABLE t1 ( + a1 CHAR(64), a2 CHAR(64), b CHAR(16), c CHAR(16) NOT NULL, d CHAR(16), dummy CHAR(64) DEFAULT ' ' +) charset=latin1; + +INSERT INTO t1 (a1, a2, b, c, d) VALUES +('a','a','a','a111','xy1'),('a','a','a','b111','xy2'),('a','a','a','c111','xy3'),('a','a','a','d111','xy4'), +('a','a','b','e112','xy1'),('a','a','b','f112','xy2'),('a','a','b','g112','xy3'),('a','a','b','h112','xy4'), +('a','b','a','i121','xy1'),('a','b','a','j121','xy2'),('a','b','a','k121','xy3'),('a','b','a','l121','xy4'), +('a','b','b','m122','xy1'),('a','b','b','n122','xy2'),('a','b','b','o122','xy3'),('a','b','b','p122','xy4'), +('b','a','a','a211','xy1'),('b','a','a','b211','xy2'),('b','a','a','c211','xy3'),('b','a','a','d211','xy4'), +('b','a','b','e212','xy1'),('b','a','b','f212','xy2'),('b','a','b','g212','xy3'),('b','a','b','h212','xy4'), +('b','b','a','i221','xy1'),('b','b','a','j221','xy2'),('b','b','a','k221','xy3'),('b','b','a','l221','xy4'), +('b','b','b','m222','xy1'),('b','b','b','n222','xy2'),('b','b','b','o222','xy3'),('b','b','b','p222','xy4'), +('c','a','a','a311','xy1'),('c','a','a','b311','xy2'),('c','a','a','c311','xy3'),('c','a','a','d311','xy4'), +('c','a','b','e312','xy1'),('c','a','b','f312','xy2'),('c','a','b','g312','xy3'),('c','a','b','h312','xy4'), +('c','b','a','i321','xy1'),('c','b','a','j321','xy2'),('c','b','a','k321','xy3'),('c','b','a','l321','xy4'), +('c','b','b','m322','xy1'),('c','b','b','n322','xy2'),('c','b','b','o322','xy3'),('c','b','b','p322','xy4'), +('d','a','a','a411','xy1'),('d','a','a','b411','xy2'),('d','a','a','c411','xy3'),('d','a','a','d411','xy4'), +('d','a','b','e412','xy1'),('d','a','b','f412','xy2'),('d','a','b','g412','xy3'),('d','a','b','h412','xy4'), +('d','b','a','i421','xy1'),('d','b','a','j421','xy2'),('d','b','a','k421','xy3'),('d','b','a','l421','xy4'), +('d','b','b','m422','xy1'),('d','b','b','n422','xy2'),('d','b','b','o422','xy3'),('d','b','b','p422','xy4'); + +CREATE INDEX idx_t1_0 ON t1 (a1); +CREATE INDEX idx_t1_1 ON t1 (a1,a2,b,c); +CREATE INDEX idx_t1_2 ON t1 (a1,a2,b); +ANALYZE TABLE t1; + +--echo # t2 is the same as t1, but with some NULLs in the MIN/MAX column +CREATE TABLE t2 ( + a1 CHAR(64), a2 CHAR(64) NOT NULL, b CHAR(16), c CHAR(16), d CHAR(16), dummy CHAR(64) DEFAULT ' ' +) charset=latin1; + +INSERT INTO t2 SELECT * FROM t1; + +--echo # add rows with NULL's in the MIN/MAX column +INSERT INTO t2 (a1, a2, b, c, d) VALUES +('a','a',NULL,'a777','xyz'),('a','a',NULL,'a888','xyz'),('a','a',NULL,'a999','xyz'), +('a','a','a',NULL,'xyz'), +('a','a','b',NULL,'xyz'), +('a','b','a',NULL,'xyz'), +('c','a',NULL,'c777','xyz'),('c','a',NULL,'c888','xyz'),('c','a',NULL,'c999','xyz'), +('d','b','b',NULL,'xyz'); + +CREATE INDEX idx_t2_0 ON t2 (a1); +CREATE INDEX idx_t2_1 ON t2 (a1,a2,b,c); +CREATE INDEX idx_t2_2 ON t2 (a1,a2,b); +ANALYZE TABLE t2; + +--echo # Queries without WHERE clause - basic MIN/MAX with FILTER +SELECT a1, MIN(a2) FILTER (WHERE a2 > 'a') FROM t1 GROUP BY a1; +SELECT a1, MAX(a2) FILTER (WHERE a2 < 'b') FROM t1 GROUP BY a1; +SELECT a1, a2, b, MIN(c) FILTER (WHERE c > 'b'), MAX(c) FILTER (WHERE c < 'n') FROM t1 GROUP BY a1,a2,b; + +--echo # Queries with predicates over GROUP BY attributes +SELECT a1, MAX(c) FILTER (WHERE c LIKE '%11%') FROM t1 WHERE a1 IN ('a','b','d') GROUP BY a1,a2,b; +SELECT a1,a2,b,MIN(c) FILTER (WHERE c > 'c'),MAX(c) FILTER (WHERE c < 'm') FROM t1 WHERE a1 < 'd' GROUP BY a1,a2,b; +SELECT a1,a2,b,MIN(c) FILTER (WHERE d != 'xy2'),MAX(c) FILTER (WHERE d != 'xy3') FROM t1 WHERE (c > 'b111') AND (c <= 'g112') GROUP BY a1,a2,b; + +--echo # IS NULL predicates +SELECT a1,a2,b,MIN(c) FILTER (WHERE c > 'a5') FROM t2 WHERE (a2 = 'a') AND b IS NULL GROUP BY a1; + +--echo # Mixed predicates +SELECT a1,a2,b,MIN(c) FILTER (WHERE d LIKE 'xy%'),MAX(c) FILTER (WHERE d LIKE 'xy%') FROM t1 WHERE (a1 >= 'c' OR a2 < 'b') AND (b > 'a') GROUP BY a1,a2,b; + +--echo # Test with NULLs in MIN/MAX column +SELECT a1,a2,b,MIN(c) FILTER (WHERE c > 'a5'),MAX(c) FILTER (WHERE c < 'z') FROM t2 GROUP BY a1,a2,b; + +--echo # Test FILTER with HAVING clause +SELECT a1, MIN(c) FILTER (WHERE d > 'xy1') AS min_c FROM t1 GROUP BY a1 HAVING MIN(c) FILTER (WHERE d > 'xy1') > 'b1'; + +--echo # +--echo # Test large integer with FILTER (stolen from func_group.test :) +--echo # + +CREATE TABLE t1_min (id INT, a DOUBLE); +INSERT INTO t1_min VALUES (1, 0x7FFFFFFFFFFFFFFF),(2, 0x7FFFFFFFFFFFFFFF), (3, NULL); +SELECT + CAST(MIN(a) FILTER (WHERE a IS NOT NULL) AS SIGNED) AS cast_min +FROM t1_min GROUP BY id; +DROP TABLE t1_min; + +--echo # +--echo # ERROR CASES +--echo # + +--echo # FILTER with RANK (should error - FILTER only works with aggregates) +--error ER_PARSE_ERROR +SELECT RANK() FILTER (WHERE status = 'active') OVER (ORDER BY value) FROM test_aggregates; + +--echo # FILTER with LAG (should error - FILTER only works with aggregates) +--error ER_PARSE_ERROR +SELECT LAG(value) FILTER (WHERE status = 'active') OVER (ORDER BY id) FROM test_aggregates; + +--echo # OVER clause before FILTER clause (should error - correct order is FILTER then OVER) +--error ER_PARSE_ERROR +SELECT AVG(value) OVER (PARTITION BY category) FILTER (WHERE status = 'active') FROM test_aggregates; + +--echo # Regular scalar function (should error - FILTER only for aggregates) +--error ER_WRONG_USAGE +SELECT UPPER(name) FILTER (WHERE status = 'active') FROM test_aggregates; + +--echo # FILTER on plain column (should error - not a function) +--error ER_PARSE_ERROR +SELECT value FILTER (WHERE status = 'active') FROM test_aggregates; + +--echo # Empty FILTER clause (should error - WHERE condition required) +--error ER_PARSE_ERROR +SELECT AVG(value) FILTER () FROM test_aggregates; + +--echo # Nested FILTER clauses (should error - cannot nest FILTER) +--error ER_WRONG_USAGE +SELECT AVG(value) FILTER (WHERE COUNT(*) FILTER (WHERE status = 'active') > 5) FROM test_aggregates; + +--echo # Multiple WHERE keywords (should error) +--error ER_PARSE_ERROR +SELECT AVG(value) FILTER (WHERE status = 'active' WHERE value > 100) FROM test_aggregates; + +--echo # Using column alias in FILTER WHERE (should error - alias not available) +--error ER_BAD_FIELD_ERROR +SELECT value as val, AVG(value) FILTER (WHERE val > 100) FROM test_aggregates GROUP BY value; + +--echo # Window function in FILTER WHERE condition (should error - not allowed) +--error ER_WRONG_USAGE +SELECT AVG(value) FILTER (WHERE ROW_NUMBER() OVER (ORDER BY id) > 2) FROM test_aggregates; + +--error ER_WRONG_USAGE +SELECT AVG(value) FILTER (WHERE ROW_NUMBER() OVER (ORDER BY id)) FROM test_aggregates; + +--echo # This should error - aggregate in WHERE clause +--error ER_WRONG_USAGE +SELECT category, AVG(value) FILTER (WHERE value > AVG(value)) FROM test_aggregates GROUP BY category; + +--error ER_WRONG_USAGE +SELECT AVG(value) FILTER (WHERE COUNT(*)) FROM test_aggregates; + +--echo # Missing WHERE keyword (should error) +--error ER_PARSE_ERROR +SELECT AVG(value) FILTER (status = 'active') FROM test_aggregates; + +--echo # Missing parentheses (should error) +--error ER_PARSE_ERROR +SELECT AVG(value) FILTER WHERE status = 'active' FROM test_aggregates; + +--echo # +--echo # Illegal use of FILTER with non-aggregate stored function +--echo # + +delimiter |; +CREATE FUNCTION simple_double(x INT) RETURNS INT +DETERMINISTIC +BEGIN + RETURN x * 2; +END| +delimiter ;| + +--echo # FILTER clause with non-aggregate stored function should fail +--error ER_WRONG_USAGE +SELECT simple_double(value) FILTER (WHERE status = 'active') FROM test_aggregates; + +--echo # SUM() FILTER on DOUBLE must exclude filtered rows (streaming path) +CREATE TABLE t3 (i INT, d DOUBLE); +INSERT INTO t3 VALUES (1,100.5),(1,4),(2,31); +SELECT SUM(d) FILTER (WHERE i=1) FROM t3; + +--echo # SUM() FILTER on DOUBLE must exclude filtered rows (grouped path) +CREATE TABLE t4 (g INT, d DOUBLE); +INSERT INTO t4 VALUES (1,10.0),(1,25.0),(1,30.0),(2,5.0); +SELECT g, SUM(d) FILTER (WHERE d > 10) FROM t4 GROUP BY g; + +--echo # JSON_ARRAYAGG() FILTER must drop excluded rows, not keep them as JSON null +CREATE TABLE t5 (b INT, c VARCHAR(10)); +INSERT INTO t5 VALUES (1,'x'),(0,'y'),(1,'z'); +SELECT JSON_ARRAYAGG(c) FILTER (WHERE b=1) FROM t5; + +--echo # FILTER on a UDF aggregate must not crash and must actually filter +--replace_result $UDF_EXAMPLE_SO UDF_EXAMPLE_LIB +eval CREATE AGGREGATE FUNCTION avgcost RETURNS REAL SONAME "$UDF_EXAMPLE_SO"; +CREATE TABLE t6 (g INT, qty INT, price DOUBLE); +INSERT INTO t6 VALUES (1,10,2.0),(1,5,3.0),(2,7,4.0); +SELECT avgcost(qty, price) FILTER (WHERE g = 1) FROM t6; +DROP FUNCTION avgcost; + +--echo # A volatile FILTER predicate must be evaluated once per row, so AVG's sum and count agree +CREATE TABLE t7 (i INT); +INSERT INTO t7 VALUES (1),(2),(3),(4),(5),(6); +SET @n=0; +SELECT AVG(i) FILTER (WHERE (@n:=@n+1) MOD 2 = 0) FROM t7; + +--echo # A volatile FILTER predicate must give the same SUM for FLOAT and DOUBLE under GROUP BY +CREATE TABLE t8 (g INT, f FLOAT, d DOUBLE); +INSERT INTO t8 VALUES (1,1,1),(1,2,2),(1,3,3),(1,4,4); +SET @c=0; +SELECT g, SUM(f) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) FROM t8 GROUP BY g; +SET @c=0; +SELECT g, SUM(d) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) FROM t8 GROUP BY g; + +--echo # A volatile FILTER predicate must not let update_field() discard the running DECIMAL sum +CREATE TABLE t10 (g INT, n INT); +INSERT INTO t10 VALUES (1,1000),(1,2),(1,3),(1,4); +SET @c=0; +SELECT g, SUM(n) FILTER (WHERE (@c:=@c+1) MOD 2 = 1) FROM t10 GROUP BY g; + +--echo # FILTER must be accepted on a schema-qualified stored aggregate function call +SELECT test.weighted_avg(value, amount) FILTER (WHERE status = 'active') AS weighted_avg_result FROM test_aggregates; + +--echo # A view over a cross-schema stored aggregate with FILTER must keep the clause +CREATE DATABASE d_filter_view; +USE d_filter_view; +delimiter |; +CREATE AGGREGATE FUNCTION agg_filter_view(x INT) RETURNS INT +BEGIN + DECLARE s INT DEFAULT 0; + DECLARE CONTINUE HANDLER FOR NOT FOUND RETURN s; + LOOP + FETCH GROUP NEXT ROW; + SET s = s + x; + END LOOP; +END| +delimiter ;| +CREATE VIEW test.v_filter_view AS + SELECT agg_filter_view(value) FILTER (WHERE status = 'active') AS wavg + FROM test.test_aggregates; +USE test; +SHOW CREATE VIEW test.v_filter_view; +DROP VIEW test.v_filter_view; +DROP DATABASE d_filter_view; + +--echo # print() must show the original predicate, not the substituted temporary table field +EXPLAIN EXTENDED SELECT SQL_BUFFER_RESULT SUM(value) FILTER (WHERE status = 'active') + FROM test_aggregates; +SHOW WARNINGS; + +--echo # filter_passed() must use boolean truth (val_bool), not val_int() truncation +CREATE TABLE t9 (a DOUBLE); +INSERT INTO t9 VALUES (0.4),(2.0); +SELECT COUNT(*) FILTER (WHERE a) FROM t9; +SELECT COUNT(*) FROM t9 WHERE a; + +--echo # EXISTS subquery in FILTER WHERE, correlated to the outer row +SELECT category, + COUNT(*) FILTER (WHERE EXISTS (SELECT 1 FROM test_aggregates t2 + WHERE t2.category = test_aggregates.category + AND t2.status = 'active')) AS count_result +FROM test_aggregates GROUP BY category; + +--echo # Correlated scalar subquery inside FILTER WHERE +SELECT category, + SUM(value) FILTER (WHERE value > (SELECT AVG(t2.value) FROM test_aggregates t2 + WHERE t2.category = test_aggregates.category)) AS sum_result +FROM test_aggregates GROUP BY category; + +--echo # LEFT JOIN: FILTER on the nullable side must exclude NULL-extended rows +CREATE TABLE t11 (id INT PRIMARY KEY, ref_id INT, extra_value INT); +INSERT INTO t11 VALUES (1, 1, 10), (2, 3, 30), (3, 99, 999); + +SELECT t1.category, + COUNT(*) FILTER (WHERE t2.extra_value > 5) AS count_result +FROM test_aggregates t1 +LEFT JOIN t11 t2 ON t1.id = t2.ref_id +GROUP BY t1.category ORDER BY t1.category; + +--echo # RIGHT JOIN: FILTER on the nullable side must exclude NULL-extended rows +SELECT t2.ref_id, + COUNT(*) FILTER (WHERE t1.status = 'active') AS count_result +FROM test_aggregates t1 +RIGHT JOIN t11 t2 ON t1.id = t2.ref_id +GROUP BY t2.ref_id ORDER BY t2.ref_id; + +DROP TABLE t11; + +--echo # filter is unreserved: works unquoted as a column, table, and database name +CREATE TABLE t12 (id INT, filter INT); +INSERT INTO t12 VALUES (1, 10), (2, 20); +SELECT filter FROM t12 WHERE filter = 10; +UPDATE t12 SET filter = 99 WHERE id = 1; +SELECT id, filter FROM t12 ORDER BY filter; +DROP TABLE t12; + +CREATE TABLE filter (a INT); +INSERT INTO filter VALUES (1), (2); +SELECT a FROM filter WHERE a = 1; +DROP TABLE filter; + +CREATE DATABASE filter; +CREATE TABLE filter.t13 (a INT); +INSERT INTO filter.t13 VALUES (1); +SELECT * FROM filter.t13; +DROP DATABASE filter; + +--echo # filter is unreserved, but a bare (no AS) alias right after a function +--echo # call is intentionally rejected: it is ambiguous with FILTER(WHERE ...). +--error ER_PARSE_ERROR +SELECT SUM(value) filter FROM test_aggregates; +SELECT SUM(value) AS filter FROM test_aggregates; + +--echo # +--echo # CLEANUP +--echo # + +DROP FUNCTION weighted_avg; +DROP FUNCTION simple_double; +DROP TABLE test_aggregates, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10; +--enable_query_log + +--echo # +--echo # End of aggregates FILTER test +--echo # diff --git a/mysql-test/main/join_outer_reorder.result b/mysql-test/main/join_outer_reorder.result index 5827aabed8bd7..d44a04c930770 100644 --- a/mysql-test/main/join_outer_reorder.result +++ b/mysql-test/main/join_outer_reorder.result @@ -5595,13 +5595,13 @@ a b c d # Oracle outer join not in WHERE clause # SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c GROUP BY tj2.c(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c GROUP BY tj2.c HAVING tj2.c(+) > 1; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') > 1' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '> 1' at line 1 SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c ORDER BY tj2.c(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 SELECT tj2.c(+) FROM tj2; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') FROM tj2' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'FROM tj2' at line 1 # # Mix ANSI and Oracle outer join # ORA-25156 @@ -5643,7 +5643,7 @@ BEGIN SELECT * FROM tj1 WHERE tj1.a = v1.a(+); END $$ -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '; END' at line 4 # # Unspecified table used in WHERE clause that contains (+) @@ -5926,7 +5926,7 @@ a b 1 1 3 NULL select t1.a, t2.b from t1, t2 where t1.a = t2.b(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 set SQL_MODE= oracle; drop view v1; drop table t1,t2; diff --git a/mysql-test/suite/compat/oracle/r/ora_outer_join.result b/mysql-test/suite/compat/oracle/r/ora_outer_join.result index 8118df2f04daa..5905953e58184 100644 --- a/mysql-test/suite/compat/oracle/r/ora_outer_join.result +++ b/mysql-test/suite/compat/oracle/r/ora_outer_join.result @@ -314,13 +314,13 @@ a b c d # Oracle outer join not in WHERE clause # SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c GROUP BY tj2.c(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c GROUP BY tj2.c HAVING tj2.c(+) > 1; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') > 1' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '> 1' at line 1 SELECT * FROM tj1, tj2 WHERE tj1.a = tj2.c ORDER BY tj2.c(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 SELECT tj2.c(+) FROM tj2; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') FROM tj2' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'FROM tj2' at line 1 # # Mix ANSI and Oracle outer join # ORA-25156 @@ -362,7 +362,7 @@ BEGIN SELECT * FROM tj1 WHERE tj1.a = v1.a(+); END $$ -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '; END' at line 4 # # Unspecified table used in WHERE clause that contains (+) @@ -645,7 +645,7 @@ a b 1 1 3 NULL select t1.a, t2.b from t1, t2 where t1.a = t2.b(+); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ')' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1 set SQL_MODE= oracle; drop view v1; drop table t1,t2; diff --git a/mysql-test/suite/compat/oracle/r/ora_outer_join_err.result b/mysql-test/suite/compat/oracle/r/ora_outer_join_err.result index f54ee14bbf436..58fbcda838d18 100644 --- a/mysql-test/suite/compat/oracle/r/ora_outer_join_err.result +++ b/mysql-test/suite/compat/oracle/r/ora_outer_join_err.result @@ -68,11 +68,11 @@ DROP TABLE t1,t2,t3; create table t1 (a int); create table t2 (a int); select t1.a(+), t2.a from t1,t2; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '), t2.a from t1,t2' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ' t2.a from t1,t2' at line 1 select t1.a, t2.a from t1,t2 HAVING t1.a(+) = t2.a; -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') = t2.a' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '= t2.a' at line 1 select t1.a, t2.a from t1 join t2 on (t1.a(+) = t2.a); -ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') = t2.a)' at line 1 +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '= t2.a)' at line 1 DROP TABLE t1,t2; # # outer reference diff --git a/mysql-test/suite/perfschema/r/digest_view.result b/mysql-test/suite/perfschema/r/digest_view.result index 346b5e0c44e60..b7954b50a12ba 100644 --- a/mysql-test/suite/perfschema/r/digest_view.result +++ b/mysql-test/suite/perfschema/r/digest_view.result @@ -191,17 +191,17 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest ORDER BY DIGEST_TEXT; SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR -test cc5e38c5a702f49627052e59a2603818 EXPLAIN SELECT * FROM `test` . `v1` 1 -test 264b69debfd30bbfe374cdca018fa4f9 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1 -test 75f31bf60b75a4f851ff9c9ee4e19d96 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1 -test a8f8a85697afacda9f2f3d3b023f9ed0 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1 -test 512598992d37826136f3f1292bb32e1e EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1 -test 54342cc19df16ce54b3ba43d1fd7552d SELECT * FROM `test` . `v1` 1 -test 3750b7d8c33e040b90a2cdccb4c642a3 SELECT * FROM `test` . `v1` WHERE `a` = ? 1 -test 94af70ef76a31364845f534863d99da8 SELECT * FROM `test` . `v1` WHERE `b` > ? 1 -test 0c87d86b62e664a81a23d4f176fbb377 SELECT `a` , `b` FROM `test` . `v1` 1 -test de7e6f1350ff14a952b97b373d76c1a4 SELECT `b` , `a` FROM `test` . `v1` 1 -test 3468091e6d6ce474aded20beaec36b53 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 +test 983c118738efc2378bb2a15495c7e6c9 EXPLAIN SELECT * FROM `test` . `v1` 1 +test f34ab36bba7121e7953b5c6ca8f58e11 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 1 +test ee2356b394246ac8b7fc5faa97b0c1ad EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 1 +test 3e70e03c91932264badade3cb1f2e66e EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 1 +test 021f3cff6701828a7f017af34d29ce1c EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 1 +test d2e11df922514a25243fc04ebf839825 SELECT * FROM `test` . `v1` 1 +test c967f871a7c1bc86a7667b83a17ca6e1 SELECT * FROM `test` . `v1` WHERE `a` = ? 1 +test 7c87fc30bf915208e69006449a702b7a SELECT * FROM `test` . `v1` WHERE `b` > ? 1 +test 258f5bff705a10fe1c43fd25c29c61fd SELECT `a` , `b` FROM `test` . `v1` 1 +test 518f87edc2c96f2b022d6ab3c767df7b SELECT `b` , `a` FROM `test` . `v1` 1 +test d972cde0f9bbec28c84e4dcca8ef3281 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 DROP TABLE test.v1; CREATE VIEW test.v1 AS SELECT * FROM test.t1; EXPLAIN SELECT * from test.v1; @@ -248,19 +248,19 @@ SELECT SCHEMA_NAME, DIGEST, DIGEST_TEXT, COUNT_STAR FROM performance_schema.events_statements_summary_by_digest ORDER BY DIGEST_TEXT; SCHEMA_NAME DIGEST DIGEST_TEXT COUNT_STAR -test ef8b10f452e117fa96af49687a93a10f CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1 -test 6b99f4d2ad410f9fa4ee1d501b0db571 DROP TABLE `test` . `v1` 1 -test cc5e38c5a702f49627052e59a2603818 EXPLAIN SELECT * FROM `test` . `v1` 2 -test 264b69debfd30bbfe374cdca018fa4f9 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2 -test 75f31bf60b75a4f851ff9c9ee4e19d96 EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2 -test a8f8a85697afacda9f2f3d3b023f9ed0 EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2 -test 512598992d37826136f3f1292bb32e1e EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2 -test 54342cc19df16ce54b3ba43d1fd7552d SELECT * FROM `test` . `v1` 2 -test 3750b7d8c33e040b90a2cdccb4c642a3 SELECT * FROM `test` . `v1` WHERE `a` = ? 2 -test 94af70ef76a31364845f534863d99da8 SELECT * FROM `test` . `v1` WHERE `b` > ? 2 -test 187a846fafe04b746eddaaab80b6a766 SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1 -test 0c87d86b62e664a81a23d4f176fbb377 SELECT `a` , `b` FROM `test` . `v1` 2 -test de7e6f1350ff14a952b97b373d76c1a4 SELECT `b` , `a` FROM `test` . `v1` 2 -test 3468091e6d6ce474aded20beaec36b53 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 +test bd4716657502ce7922d2ed7e20043b76 CREATE VIEW `test` . `v1` AS SELECT * FROM `test` . `t1` 1 +test 2d14a4845ee825e3a1a21bd5799cba3c DROP TABLE `test` . `v1` 1 +test 983c118738efc2378bb2a15495c7e6c9 EXPLAIN SELECT * FROM `test` . `v1` 2 +test f34ab36bba7121e7953b5c6ca8f58e11 EXPLAIN SELECT * FROM `test` . `v1` WHERE `a` = ? 2 +test ee2356b394246ac8b7fc5faa97b0c1ad EXPLAIN SELECT * FROM `test` . `v1` WHERE `b` > ? 2 +test 3e70e03c91932264badade3cb1f2e66e EXPLAIN SELECT `a` , `b` FROM `test` . `v1` 2 +test 021f3cff6701828a7f017af34d29ce1c EXPLAIN SELECT `b` , `a` FROM `test` . `v1` 2 +test d2e11df922514a25243fc04ebf839825 SELECT * FROM `test` . `v1` 2 +test c967f871a7c1bc86a7667b83a17ca6e1 SELECT * FROM `test` . `v1` WHERE `a` = ? 2 +test 7c87fc30bf915208e69006449a702b7a SELECT * FROM `test` . `v1` WHERE `b` > ? 2 +test aa329bcb29afc39d9793e4fd6e108722 SELECT SCHEMA_NAME , `DIGEST` , `DIGEST_TEXT` , `COUNT_STAR` FROM `performance_schema` . `events_statements_summary_by_digest` ORDER BY `DIGEST_TEXT` 1 +test 258f5bff705a10fe1c43fd25c29c61fd SELECT `a` , `b` FROM `test` . `v1` 2 +test 518f87edc2c96f2b022d6ab3c767df7b SELECT `b` , `a` FROM `test` . `v1` 2 +test d972cde0f9bbec28c84e4dcca8ef3281 TRUNCATE TABLE `performance_schema` . `events_statements_summary_by_digest` 1 DROP VIEW test.v1; DROP TABLE test.t1; diff --git a/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result b/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result index e4e2c8b294a3f..e78e21e19a9ca 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result +++ b/mysql-test/suite/perfschema/r/start_server_low_digest_sql_length.result @@ -8,5 +8,5 @@ SELECT 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 #################################### SELECT event_name, digest, digest_text, sql_text FROM events_statements_history_long; event_name digest digest_text sql_text -statement/sql/select c9e142fe40c43498607ca5310e11d2ce SELECT ? + ? + SELECT ... -statement/sql/truncate 506e3496d92689cd2367329dc7725165 TRUNCATE TABLE truncat... +statement/sql/select 3e19ece73d286977122b52fb027c180c SELECT ? + ? + SELECT ... +statement/sql/truncate 7ab36a94986dd98b93370b025166a946 TRUNCATE TABLE truncat... diff --git a/sql/item_func.cc b/sql/item_func.cc index 2b0bbbba70991..19f89ae586f3b 100644 --- a/sql/item_func.cc +++ b/sql/item_func.cc @@ -6700,7 +6700,8 @@ longlong Item_func_row_count::val_int() Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name, const Sp_handler *sph): - Item_func(thd), Item_sp(thd, context_arg, name), m_handler(sph) + Item_func(thd), Item_sp(thd, context_arg, name), m_handler(sph), + m_filter(NULL) { set_maybe_null(); } @@ -6709,7 +6710,8 @@ Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, Item_func_sp::Item_func_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name_arg, const Sp_handler *sph, List &list): - Item_func(thd, list), Item_sp(thd, context_arg, name_arg), m_handler(sph) + Item_func(thd, list), Item_sp(thd, context_arg, name_arg), m_handler(sph), + m_filter(NULL) { set_maybe_null(); } @@ -6729,6 +6731,18 @@ Item_func_sp::func_name_cstring() const m_handler == &sp_handler_package_function); } +void +Item_func_sp::print(String *str, enum_query_type query_type) +{ + Item_func::print(str, query_type); + if (m_filter) + { + str->append(STRING_WITH_LEN(" FILTER(WHERE ")); + m_filter->print(str, query_type); + str->append(')'); + } +} + void my_missing_function_error(const LEX_CSTRING &token, const char *func_name) { @@ -6921,6 +6935,12 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) if (res) DBUG_RETURN(TRUE); + if (m_filter && m_sp->agg_type() != GROUP_AGGREGATE) + { + my_error(ER_WRONG_USAGE, MYF(0), "FILTER", "non-aggregate function"); + DBUG_RETURN(TRUE); + } + if (m_sp->agg_type() == GROUP_AGGREGATE) { Item_sum_sp *item_sp; @@ -6943,6 +6963,10 @@ Item_func_sp::fix_fields(THD *thd, Item **ref) DBUG_RETURN(TRUE); *ref= item_sp; item_sp->name= name; + if (m_filter) + { + item_sp->set_filter(m_filter); + } bool err= item_sp->fix_fields(thd, ref); if (err) DBUG_RETURN(TRUE); diff --git a/sql/item_func.h b/sql/item_func.h index cc0985c8d53b7..e0a0d6e7591d5 100644 --- a/sql/item_func.h +++ b/sql/item_func.h @@ -4193,6 +4193,7 @@ class Item_func_sp :public Item_func, { private: const Sp_handler *m_handler; + Item *m_filter; bool execute(); @@ -4215,6 +4216,10 @@ class Item_func_sp :public Item_func, virtual ~Item_func_sp() = default; + void set_filter(Item *filter) { m_filter= filter; } + + void print(String *str, enum_query_type query_type) override; + void update_used_tables() override; void cleanup() override; diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index 491c6fd3bdcf9..28f4c60bff73b 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -4910,6 +4910,9 @@ Item_func_json_objectagg::fix_fields(THD *thd, Item **ref) * collation.collation->mbmaxlen, UINT_MAX32); + if (fix_filter(thd)) + return TRUE; + if (check_sum_func(thd, ref)) return TRUE; @@ -4947,7 +4950,7 @@ bool Item_func_json_objectagg::add() String *key; key= args[0]->val_str(&buf); - if (args[0]->is_null()) + if (args[0]->is_null() || !filter_passed()) return 0; null_value= 0; diff --git a/sql/item_sum.cc b/sql/item_sum.cc index cc07a6de608eb..8efc0f5310b19 100644 --- a/sql/item_sum.cc +++ b/sql/item_sum.cc @@ -510,6 +510,11 @@ Item_sum::Item_sum(THD *thd, Item_sum *item): with_distinct= item->with_distinct; if (item->aggr) set_aggregator(thd, item->aggr->Aggrtype()); + if (item->has_filter()) + { + set_filter(item->filter_expr); + orig_filter= item->orig_filter; + } } @@ -543,6 +548,12 @@ void Item_sum::print(String *str, enum_query_type query_type) pargs[i]->print(str, query_type); } str->append(')'); + if (has_filter()) + { + str->append(STRING_WITH_LEN(" FILTER(WHERE ")); + (fixed() ? orig_filter : filter_expr)->print(str, query_type); + str->append(')'); + } } void Item_sum::fix_num_length_and_dec() @@ -579,6 +590,50 @@ void Item_sum::update_used_tables () item->used_tables() == 0 && !item->const_item() */ } + if (has_filter()) + { + filter_expr->update_used_tables(); + used_tables_cache|= filter_expr->used_tables(); + } +} + + +bool Item_sum::filter_passed() +{ + if (!has_filter()) + return true; + /* + Skip filter check if we're in endup phase (processing distinct values + that already passed the filter during collection phase) + */ + if (aggr && aggr->is_in_endup_phase()) + return true; + return filter_expr->val_bool(); +} + + +bool Item_sum::fix_filter(THD *thd) +{ + if (!has_filter()) + return false; + + if (filter_expr->fix_fields_if_needed_for_scalar(thd, &filter_expr)) + return true; + + if (filter_expr->with_sum_func()) + { + my_error(ER_WRONG_USAGE, MYF(0), "aggregate function", "FILTER"); + return true; + } + + if (filter_expr->with_window_func()) + { + my_error(ER_WRONG_USAGE, MYF(0), "window function", "FILTER"); + return true; + } + + orig_filter= filter_expr; + return false; } @@ -589,6 +644,12 @@ Item *Item_sum::set_arg(uint i, THD *thd, Item *new_val) } +void Item_sum::set_filter(THD *thd, Item *new_filter_expr) +{ + thd->change_item_tree(&filter_expr, new_filter_expr); +} + + int Item_sum::set_aggregator(THD *thd, Aggregator::Aggregator_type aggregator) { /* @@ -995,6 +1056,9 @@ bool Aggregator_distinct::add() if (copy_funcs(tmp_table_param->items_to_copy, table->in_use)) return TRUE; + if (!item_sum->filter_passed()) + return 0; + for (Field **field=table->field ; *field ; field++) if ((*field)->is_real_null(0)) return 0; // Don't count NULL @@ -1020,7 +1084,7 @@ bool Aggregator_distinct::add() else { item_sum->get_arg(0)->save_in_field(table->field[0], FALSE); - if (table->field[0]->is_null()) + if (table->field[0]->is_null() || !item_sum->filter_passed()) return 0; DBUG_ASSERT(tree); item_sum->null_value= 0; @@ -1136,6 +1200,10 @@ Item_sum_num::fix_fields(THD *thd, Item **ref) /* We should ignore FIELD's in arguments to sum functions */ with_flags|= (args[i]->with_flags & ~item_with_t::FIELD); } + + if (fix_filter(thd)) + return TRUE; + result_field=0; max_length=float_length(decimals); null_value=1; @@ -1168,6 +1236,9 @@ Item_sum_min_max::fix_fields(THD *thd, Item **ref) if (fix_length_and_dec(thd)) DBUG_RETURN(TRUE); + if (fix_filter(thd)) + DBUG_RETURN(TRUE); + if (!is_window_func_sum_expr()) setup_hybrid(thd, args[0], NULL); result_field=0; @@ -1341,6 +1412,7 @@ Item_sum_sp::Item_sum_sp(THD *thd, Name_resolution_context *context_arg, set_maybe_null(); quick_group= 0; m_sp= sp; + m_started= false; } Item_sum_sp::Item_sum_sp(THD *thd, Name_resolution_context *context_arg, @@ -1350,6 +1422,7 @@ Item_sum_sp::Item_sum_sp(THD *thd, Name_resolution_context *context_arg, set_maybe_null(); quick_group= 0; m_sp= sp; + m_started= false; } Item_sum_sp::Item_sum_sp(THD *thd, Item_sum_sp *item): @@ -1357,6 +1430,7 @@ Item_sum_sp::Item_sum_sp(THD *thd, Item_sum_sp *item): { base_flags|= (item->base_flags & item_base_t::MAYBE_NULL); quick_group= item->quick_group; + m_started= false; } bool @@ -1395,6 +1469,10 @@ Item_sum_sp::fix_fields(THD *thd, Item **ref) /* We should ignore FIELD's in arguments to sum functions */ with_flags|= (args[i]->with_flags & ~item_with_t::FIELD); } + + if (fix_filter(thd)) + return TRUE; + result_field= NULL; max_length= float_length(decimals); null_value= 1; @@ -1423,6 +1501,12 @@ Item_sum_sp::fix_fields(THD *thd, Item **ref) bool Item_sum_sp::execute() { + if (!func_ctx && !m_started) + { + null_value= true; + return true; + } + THD *thd= current_thd; bool res; uint old_server_status= thd->server_status; @@ -1449,6 +1533,9 @@ Item_sum_sp::execute() bool Item_sum_sp::add() { + if (!filter_passed()) + return false; + m_started= true; return execute_impl(current_thd, args, arg_count); } @@ -1456,6 +1543,9 @@ Item_sum_sp::add() void Item_sum_sp::clear() { + m_started= false; + if (!func_ctx) + return; delete func_ctx; func_ctx= NULL; sp_query_arena->free_items(); @@ -1652,6 +1742,7 @@ void Item_sum_sum::add_helper(bool perform_removal) DBUG_ASSERT(!perform_removal); direct_added= FALSE; + m_row_included= !direct_sum_is_null; if (likely(!direct_sum_is_null)) { my_decimal_add(E_DEC_FATAL_ERROR, dec_buffs + (curr_dec_buff^1), @@ -1665,7 +1756,8 @@ void Item_sum_sum::add_helper(bool perform_removal) direct_reseted_field= FALSE; my_decimal value; const my_decimal *val= aggr->arg_val_decimal(&value); - if (!aggr->arg_is_null(true)) + m_row_included= !aggr->arg_is_null(true) && filter_passed(); + if (m_row_included) { if (perform_removal) { @@ -1697,6 +1789,7 @@ void Item_sum_sum::add_helper(bool perform_removal) DBUG_ASSERT(!perform_removal); direct_added= FALSE; + m_row_included= !direct_sum_is_null; if (!direct_sum_is_null) { sum+= direct_sum_real; @@ -1706,21 +1799,23 @@ void Item_sum_sum::add_helper(bool perform_removal) else { direct_reseted_field= FALSE; - if (perform_removal && count > 0) - sum-= aggr->arg_val_real(); - else - sum+= aggr->arg_val_real(); - if (!aggr->arg_is_null(true)) + double val= aggr->arg_val_real(); + m_row_included= !aggr->arg_is_null(true) && filter_passed(); + if (m_row_included) { if (perform_removal) { if (count > 0) { + sum-= val; count--; } } else + { + sum+= val; count++; + } null_value= (count > 0) ? 0 : 1; } @@ -1940,7 +2035,7 @@ bool Item_sum_count::add() else { direct_reseted_field= FALSE; - if (aggr->arg_is_null(false)) + if (aggr->arg_is_null(false) || !filter_passed()) DBUG_RETURN(0); count++; } @@ -1955,7 +2050,7 @@ bool Item_sum_count::add() void Item_sum_count::remove() { DBUG_ASSERT(aggr->Aggrtype() == Aggregator::SIMPLE_AGGREGATOR); - if (aggr->arg_is_null(false)) + if (aggr->arg_is_null(false) || !filter_passed()) return; if (count > 0) count--; @@ -2063,7 +2158,7 @@ bool Item_sum_avg::add() { if (Item_sum_sum::add()) return TRUE; - if (!aggr->arg_is_null(true)) + if (m_row_included) count++; return FALSE; } @@ -2071,7 +2166,7 @@ bool Item_sum_avg::add() void Item_sum_avg::remove() { Item_sum_sum::remove(); - if (!aggr->arg_is_null(true)) + if (m_row_included) { if (count > 0) count--; @@ -2311,7 +2406,7 @@ bool Item_sum_variance::add() */ double nr= args[0]->val_real(); - if (!args[0]->null_value) + if (!args[0]->null_value && filter_passed()) m_stddev.recurrence_next(nr); return 0; } @@ -2348,7 +2443,7 @@ void Item_sum_variance::reset_field() nr= args[0]->val_real(); /* sets null_value as side-effect */ - if (args[0]->null_value) + if (args[0]->null_value || !filter_passed()) bzero(res,Stddev::binary_size()); else Stddev(nr).to_binary(res); @@ -2379,7 +2474,7 @@ void Item_sum_variance::update_field() double nr= args[0]->val_real(); /* sets null_value as side-effect */ - if (args[0]->null_value) + if (args[0]->null_value || !filter_passed()) return; /* Serialize format is (double)m, (double)s, (longlong)count */ @@ -2564,7 +2659,7 @@ bool Item_sum_min::add() DBUG_PRINT("info", ("null_value: %s", null_value ? "TRUE" : "FALSE")); /* args[0] < value */ arg_cache->cache_value(); - if (!arg_cache->null_value && + if (!arg_cache->null_value && filter_passed() && (null_value || cmp->compare() < 0)) { value->store(arg_cache); @@ -2604,7 +2699,7 @@ bool Item_sum_max::add() /* args[0] > value */ arg_cache->cache_value(); DBUG_PRINT("info", ("null_value: %s", null_value ? "TRUE" : "FALSE")); - if (!arg_cache->null_value && + if (!arg_cache->null_value && filter_passed() && (null_value || cmp->compare() > 0)) { value->store(arg_cache); @@ -2653,7 +2748,7 @@ bool Item_sum_bit::clear_as_window() bool Item_sum_bit::remove_as_window(ulonglong value) { DBUG_ASSERT(as_window_function); - if (num_values_added == 0) + if (num_values_added == 0 || args[0]->null_value || !filter_passed()) return 0; // Nothing to remove. for (int i= 0; i < NUM_BIT_COUNTERS; i++) @@ -2699,7 +2794,7 @@ void Item_sum_or::set_bits_from_counters() bool Item_sum_or::add() { ulonglong value= (ulonglong) args[0]->val_int(); - if (!args[0]->null_value) + if (!args[0]->null_value && filter_passed()) { if (as_window_function) return add_as_window(value); @@ -2727,7 +2822,7 @@ Item *Item_sum_xor::copy_or_same(THD* thd) bool Item_sum_xor::add() { ulonglong value= (ulonglong) args[0]->val_int(); - if (!args[0]->null_value) + if (!args[0]->null_value && filter_passed()) { if (as_window_function) return add_as_window(value); @@ -2762,7 +2857,7 @@ Item *Item_sum_and::copy_or_same(THD* thd) bool Item_sum_and::add() { ulonglong value= (ulonglong) args[0]->val_int(); - if (!args[0]->null_value) + if (!args[0]->null_value && filter_passed()) { if (as_window_function) return add_as_window(value); @@ -2796,7 +2891,7 @@ void Item_sum_min_max::reset_field() String tmp(buff,sizeof(buff),result_field->charset()),*res; res= arg0->val_str(&tmp); - if (arg0->null_value) + if (arg0->null_value || !filter_passed()) { result_field->set_null(); result_field->reset(); @@ -2814,7 +2909,7 @@ void Item_sum_min_max::reset_field() if (maybe_null()) { - if (arg0->null_value) + if (arg0->null_value || !filter_passed()) { nr=0; result_field->set_null(); @@ -2832,7 +2927,7 @@ void Item_sum_min_max::reset_field() if (maybe_null()) { - if (arg0->null_value) + if (arg0->null_value || !filter_passed()) { nr=0.0; result_field->set_null(); @@ -2849,7 +2944,7 @@ void Item_sum_min_max::reset_field() if (maybe_null()) { - if (arg_dec.is_null()) + if (arg_dec.is_null() || !filter_passed()) result_field->set_null(); else result_field->set_notnull(); @@ -2900,7 +2995,7 @@ void Item_sum_sum::reset_field() null_flag= direct_sum_is_null; } else - null_flag= args[0]->null_value; + null_flag= args[0]->null_value || !filter_passed(); if (null_flag) result_field->set_null(); @@ -2922,7 +3017,7 @@ void Item_sum_count::reset_field() direct_counted= FALSE; direct_reseted_field= TRUE; } - else if (!args[0]->maybe_null() || !args[0]->is_null()) + else if ((!args[0]->maybe_null() || !args[0]->is_null()) && filter_passed()) nr= 1; DBUG_PRINT("info", ("nr: %lld", nr)); int8store(res,nr); @@ -2938,8 +3033,17 @@ void Item_sum_avg::reset_field() { longlong tmp; VDec value(args[0]); - tmp= value.is_null() ? 0 : 1; - value.to_binary(res, f_precision, f_scale); + if (value.is_null() || !filter_passed()) + { + tmp= 0; + my_decimal_set_zero(dec_buffs); + dec_buffs[0].to_binary(res, f_precision, f_scale); + } + else + { + tmp= 1; + value.to_binary(res, f_precision, f_scale); + } res+= dec_bin_size; int8store(res, tmp); } @@ -2947,7 +3051,7 @@ void Item_sum_avg::reset_field() { double nr= args[0]->val_real(); - if (args[0]->null_value) + if (args[0]->null_value || !filter_passed()) bzero(res,sizeof(double)+sizeof(longlong)); else { @@ -2999,7 +3103,7 @@ void Item_sum_sum::update_field() else { arg_val= args[0]->val_decimal(&value); - null_flag= args[0]->null_value; + null_flag= args[0]->null_value || !filter_passed(); } if (!null_flag) @@ -3033,11 +3137,14 @@ void Item_sum_sum::update_field() else { nr= args[0]->val_real(); - null_flag= args[0]->null_value; + null_flag= args[0]->null_value || !filter_passed(); } if (!null_flag) { - old_nr+=nr; + if (!result_field->is_null()) + old_nr+= nr; + else + old_nr= nr; result_field->set_notnull(); } float8store(res,old_nr); @@ -3057,7 +3164,7 @@ void Item_sum_count::update_field() direct_counted= direct_reseted_field= FALSE; nr+= direct_count; } - else if (!args[0]->maybe_null() || !args[0]->is_null()) + else if ((!args[0]->maybe_null() || !args[0]->is_null()) && filter_passed()) nr++; DBUG_PRINT("info", ("nr: %lld", nr)); int8store(res,nr); @@ -3075,7 +3182,7 @@ void Item_sum_avg::update_field() if (result_type() == DECIMAL_RESULT) { VDec tmp(args[0]); - if (!tmp.is_null()) + if (!tmp.is_null() && filter_passed()) { binary2my_decimal(E_DEC_FATAL_ERROR, res, dec_buffs + 1, f_precision, f_scale); @@ -3092,7 +3199,7 @@ void Item_sum_avg::update_field() double nr; nr= args[0]->val_real(); - if (!args[0]->null_value) + if (!args[0]->null_value && filter_passed()) { double old_nr; float8get(old_nr, res); @@ -3119,6 +3226,10 @@ Item *Item_sum_avg::result_item(THD *thd, Field *field) void Item_sum_min_max::update_field() { DBUG_ENTER("Item_sum_min_max::update_field"); + if (!filter_passed()) + { + DBUG_VOID_RETURN; + } Item *UNINIT_VAR(tmp_item); if (unlikely(direct_added)) { @@ -3376,6 +3487,8 @@ bool Item_udf_sum::add() { my_bool tmp_null_value; DBUG_ENTER("Item_udf_sum::add"); + if (!filter_passed()) + DBUG_RETURN(0); udf.add(&tmp_null_value); null_value= tmp_null_value; DBUG_RETURN(0); @@ -3394,6 +3507,8 @@ void Item_udf_sum::remove() { my_bool tmp_null_value; DBUG_ENTER("Item_udf_sum::remove"); + if (!filter_passed()) + DBUG_VOID_RETURN; udf.remove(&tmp_null_value); null_value= tmp_null_value; DBUG_VOID_RETURN; @@ -3422,6 +3537,12 @@ void Item_udf_sum::print(String *str, enum_query_type query_type) args[i]->print(str, query_type); } str->append(')'); + if (has_filter()) + { + str->append(STRING_WITH_LEN(" FILTER(WHERE ")); + (fixed() ? get_orig_filter() : *get_filter())->print(str, query_type); + str->append(')'); + } } @@ -3567,6 +3688,9 @@ Item_sum_str::fix_fields(THD *thd, Item **ref) return true; } + if (fix_filter(thd)) + return TRUE; + if (fix_fields_impl(thd, ref)) return TRUE; @@ -4214,6 +4338,8 @@ bool Item_func_group_concat::repack_tree(THD *thd) bool Item_func_group_concat::add(bool exclude_nulls) { + if (!filter_passed()) + return 0; if (always_null && exclude_nulls) return 0; copy_fields(tmp_table_param); @@ -4621,6 +4747,13 @@ void Item_func_group_concat::print(String *str, enum_query_type query_type) row_limit->print(str, query_type); } str->append(STRING_WITH_LEN(")")); + + if (has_filter()) + { + str->append(STRING_WITH_LEN(" FILTER(WHERE ")); + (fixed() ? get_orig_filter() : *get_filter())->print(str, query_type); + str->append(')'); + } } @@ -4650,7 +4783,7 @@ bool Item_func_collect::add() { uint current_geometry_srid; has_cached_result= false; - if (tmp_arg[0]->null_value) + if (tmp_arg[0]->null_value || !filter_passed()) return 0; /* @@ -4681,7 +4814,7 @@ void Item_func_collect::remove() { String *wkb= args[0]->val_str(&value); has_cached_result= false; - if (args[0]->null_value) return; + if (args[0]->null_value || !filter_passed()) return; List_iterator geometries_iterator(geometries); String* temp_geometry; diff --git a/sql/item_sum.h b/sql/item_sum.h index 39ed79e7c0203..d9d68e4df6c51 100644 --- a/sql/item_sum.h +++ b/sql/item_sum.h @@ -103,6 +103,13 @@ class Aggregator : public Sql_alloc (updated by arg_val*()). */ virtual bool arg_is_null(bool use_null_value) = 0; + + /** + Check if we're currently in the endup() phase processing distinct values. + This is used to skip filter checks for DISTINCT aggregates since the filter + was already applied during the collection phase. + */ + virtual bool is_in_endup_phase() const { return false; } }; @@ -341,11 +348,38 @@ class Item_sum :public Item_func_or_sum /* TRUE if this is aggregate function of a window function */ bool window_func_sum_expr_flag; + /* Optional filter clause for the aggregate function */ + Item *filter_expr; + /* filter_expr before the optimizer may rewrite it in place, for print() */ + Item *orig_filter; + public: bool has_force_copy_fields() const { return force_copy_fields; } bool has_with_distinct() const { return with_distinct; } + /* Filter expression helpers */ + void set_filter(Item *filter_expr) { this->filter_expr= filter_expr; } + void set_filter(THD *thd, Item *new_filter_expr); + bool has_filter() const { return filter_expr != NULL; } + Item **get_filter() { return &filter_expr; } + Item *get_orig_filter() const { return orig_filter; } + bool filter_passed(); + bool fix_filter(THD *thd); + + bool walk(Item_processor processor, void *arg, + item_walk_flags flags) override + { + if (walk_args(processor, arg, flags)) + return true; + if (has_filter()) + { + if (filter_expr->walk(processor, arg, flags)) + return true; + } + return (this->*processor)(arg); + } + enum Sumfunctype { COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC, AVG_DISTINCT_FUNC, MIN_FUNC, MAX_FUNC, STD_FUNC, @@ -548,6 +582,8 @@ class Item_sum :public Item_func_or_sum aggr= NULL; with_distinct= FALSE; force_copy_fields= FALSE; + filter_expr= NULL; + orig_filter= NULL; } /** @@ -711,6 +747,7 @@ class Aggregator_distinct : public Aggregator my_decimal *arg_val_decimal(my_decimal * value) override; double arg_val_real() override; bool arg_is_null(bool use_null_value) override; + bool is_in_endup_phase() const override { return use_distinct_values; } bool unique_walk_function(void *element); bool unique_walk_function_for_count(void *element); @@ -825,6 +862,7 @@ class Item_sum_sum :public Item_sum_num, my_decimal direct_sum_decimal; my_decimal dec_buffs[2]; uint curr_dec_buff; + bool m_row_included; bool fix_length_and_dec(THD *thd) override; public: @@ -1456,6 +1494,9 @@ class Item_sum_sp :public Item_sum, { private: bool execute(); + /* Lets execute() tell "group had zero passing rows" apart from "routine + already finished", so it returns NULL only for the former. */ + bool m_started; public: Item_sum_sp(THD *thd, Name_resolution_context *context_arg, sp_name *name, @@ -1745,6 +1786,8 @@ class Item_udf_sum : public Item_sum "false" here. */ const_item_cache= false; + if (fix_filter(thd)) + return TRUE; memcpy (orig_args, args, sizeof (Item *) * arg_count); return check_sum_func(thd, ref); } diff --git a/sql/item_windowfunc.cc b/sql/item_windowfunc.cc index b7cf5ed07a560..51a1af5d91017 100644 --- a/sql/item_windowfunc.cc +++ b/sql/item_windowfunc.cc @@ -171,6 +171,11 @@ void Item_window_func::split_sum_func(THD *thd, Ref_ptr_array ref_pointer_array, Item **p_item= &window_func()->arguments()[i]; (*p_item)->split_sum_func2(thd, ref_pointer_array, fields, p_item, flags); } + if (window_func()->has_filter()) + { + Item **p_filter= window_func()->get_filter(); + (*p_filter)->split_sum_func2(thd, ref_pointer_array, fields, p_filter, flags); + } window_func()->setup_caches(thd); } diff --git a/sql/lex.h b/sql/lex.h index 61774df019f0d..fdc6cabe5bf40 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -248,6 +248,7 @@ SYMBOL symbols[] = { { "FETCH", SYM(FETCH_SYM)}, { "FIELDS", SYM(COLUMNS)}, { "FILE", SYM(FILE_SYM)}, + { "FILTER", SYM(FILTER_SYM)}, { "FIRST", SYM(FIRST_SYM)}, { "FIXED", SYM(FIXED_SYM)}, { "FLOAT", SYM(FLOAT_SYM)}, diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 63fd9333c9803..3acbfcbe87266 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -14729,6 +14729,18 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) Item_sum **func_ptr= join->sum_funcs; while ((min_max_item= *(func_ptr++))) { + /* + FILTER clause requires per-row evaluation, cannot use loose index scan. + */ + if (min_max_item->has_filter()) + { + if (unlikely(trace_group.trace_started())) + trace_group. + add("chosen", false). + add("cause", "aggregate has FILTER clause"); + DBUG_RETURN(NULL); + } + if (min_max_item->sum_func() == Item_sum::MIN_FUNC) have_min= TRUE; else if (min_max_item->sum_func() == Item_sum::MAX_FUNC) diff --git a/sql/opt_sum.cc b/sql/opt_sum.cc index 6d5bf8c6f487f..fc99d1116ef82 100644 --- a/sql/opt_sum.cc +++ b/sql/opt_sum.cc @@ -357,11 +357,12 @@ int opt_sum_query(THD *thd, /* If the expr in COUNT(expr) can never be null we can change this to the number of rows in the tables if this number is exact and - there are no outer joins. + there are no outer joins and there is no filter clause. */ if (!conds && !((Item_sum_count*) item)->get_arg(0)->maybe_null() && !outer_tables && maybe_exact_count && - ((item->used_tables() & OUTER_REF_TABLE_BIT) == 0)) + ((item->used_tables() & OUTER_REF_TABLE_BIT) == 0) && + !((Item_sum_count*) item)->has_filter()) { if (!is_exact_count) { @@ -382,6 +383,17 @@ int opt_sum_query(THD *thd, case Item_sum::MIN_FUNC: case Item_sum::MAX_FUNC: { + /* + Do not attempt MIN/MAX constant replacement if a FILTER clause + is present on the aggregate. FILTER must be evaluated per-row and + cannot be folded by the index-based shortcut here. + */ + if (item_sum->has_filter()) + { + const_result= 0; + break; + } + int is_max= MY_TEST(item_sum->sum_func() == Item_sum::MAX_FUNC); /* If MIN/MAX(expr) is the first part of a key or if all previous diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 8e78bc9f994cf..7ec6c4d1f5ddf 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22349,6 +22349,42 @@ bool Create_tmp_table::add_fields(THD *thd, new_field->flags|= FIELD_PART_OF_TMP_UNIQUE; } } + + /* + If the aggregate has FILTER, materialize the predicate into the tmp table + and rewrite it to read from the tmp row (we compute aggregates later). + */ + if (sum_item->has_filter()) + { + Item *fexpr= *sum_item->get_filter(); + if (!fexpr->const_item()) + { + Item *tmp_item; + Field *new_field= + create_tmp_field(table, fexpr, ©_func, + tmp_from_field, &m_default_field[fieldnr], + m_group != 0, not_all_columns, + distinct_record_structure, false); + if (!new_field) + goto err; + tmp_from_field++; + + thd->mem_root= mem_root_save; + if (!(tmp_item= new (thd->mem_root) Item_field(thd, new_field))) + goto err; + static_cast(tmp_item)->set_refers_to_temp_table(); + sum_item->set_filter(thd, tmp_item); + thd->mem_root= &table->mem_root; + + uneven_delta= m_uneven_bit_length; + add_field(table, new_field, fieldnr++, param->force_not_null_cols); + m_field_count[current_counter]++; + m_uneven_bit[current_counter]+= (m_uneven_bit_length - uneven_delta); + + if (!(new_field->flags & NOT_NULL_FLAG)) + tmp_item->set_maybe_null(); + } + } } else { @@ -29373,6 +29409,19 @@ count_field_types(SELECT_LEX *select_lex, TMP_TABLE_PARAM *param, else param->func_count++; } + + // Count FILTER so it can be stored in the GROUP BY temp table and read later + if (sum_item->has_filter()) + { + Item *fexpr= *sum_item->get_filter(); + if (!fexpr->const_item()) + { + if (fexpr->real_item()->type() == Item::FIELD_ITEM) + param->field_count++; + else + param->func_count++; + } + } } param->func_count++; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 65d9378d5a221..79f7f937e91eb 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -134,6 +134,32 @@ static Item* escape(THD *thd) return new (thd->mem_root) Item_string_ascii(thd, esc, MY_TEST(esc[0])); } +/** + @brief Attach a FILTER(WHERE ...) clause to a just-parsed function call. + + @return true if a parse error was raised (caller must MYSQL_YYABORT), + false otherwise (including when filter_expr is NULL, i.e. there + was no FILTER clause). +*/ +static bool attach_func_filter(Item *item, Item *filter_expr) +{ + if (!filter_expr) + return false; + if (item->type() == Item::SUM_FUNC_ITEM) + { + ((Item_sum *) item)->set_filter(filter_expr); + return false; + } + if (item->type() == Item::FUNC_ITEM && + static_cast(item)->functype() == Item_func::FUNC_SP) + { + static_cast(item)->set_filter(filter_expr); + return false; + } + my_error(ER_WRONG_USAGE, MYF(0), "FILTER", "non-aggregate function"); + return true; +} + /** @brief Bison callback to report a syntax/OOM error @@ -912,6 +938,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token FAULTS_SYM %token FEDERATED_SYM /* MariaDB privilege */ %token FILE_SYM +%token FILTER_SYM %token FIRST_SYM /* SQL-2003-N */ %token FIXED_SYM %token FLUSH_SYM @@ -1266,6 +1293,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %nonassoc NEG '~' NOT2_SYM BINARY %nonassoc COLLATE_SYM %nonassoc SUBQUERY_AS_EXPR +%nonassoc FILTER_SYM /* Tokens that can change their meaning from identifier to something else @@ -1629,6 +1657,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); opt_versioning_interval_start json_default_literal set_expr_misc + unfiltered_sum_expr + opt_filter_expr %type sql_statement_name @@ -11413,7 +11443,7 @@ function_call_generic: #endif } - opt_udf_expr_list_or_join_operator ')' opt_object_member_access + opt_udf_expr_list_or_join_operator ')' opt_object_member_access opt_filter_expr { const Type_handler *h; Create_func *builder; @@ -11503,6 +11533,9 @@ function_call_generic: } } + if (item && attach_func_filter(item, $7)) + MYSQL_YYABORT; + if ($6.str && !allow_field_accessor) { Lex_ident_sys field_sys(thd, &$6); @@ -11531,13 +11564,15 @@ function_call_generic: $1, $3))) MYSQL_YYABORT; } - | ident_cli '.' ident_cli '(' opt_expr_list_or_join_operator ')' + | ident_cli '.' ident_cli '(' opt_expr_list_or_join_operator ')' opt_filter_expr { const Lex_ident_cli pos($1.pos(), $6.pos() - $1.pos() + 1); if (unlikely(!($$= Lex->make_item_func_or_method_call(thd, $1, $3, $5, pos)))) MYSQL_YYABORT; + if (attach_func_filter($$, $7)) + MYSQL_YYABORT; } | '.' ident_cli '.' ident_cli '(' '+' ')' { @@ -11553,10 +11588,12 @@ function_call_generic: !($$= Lex->make_item_func_call_generic(thd, &empty, &$2, &$4, list)))) MYSQL_YYABORT; } - | ident_cli '.' ident_cli '.' ident_cli '(' opt_expr_list_or_join_operator ')' + | ident_cli '.' ident_cli '.' ident_cli '(' opt_expr_list_or_join_operator ')' opt_filter_expr { if (unlikely(!($$= Lex->make_item_func_call_generic(thd, &$1, &$3, &$5, $7)))) MYSQL_YYABORT; + if (attach_func_filter($$, $9)) + MYSQL_YYABORT; } | ident_cli '.' REPLACE '(' opt_expr_list ')' { @@ -11682,7 +11719,30 @@ udf_expr: } ; +opt_filter_expr: + /* empty */ { $$= NULL; } %prec SUBQUERY_AS_EXPR + | FILTER_SYM '(' WHERE + { Select->in_sum_expr++; } + expr + { Select->in_sum_expr--; } + ')' + { + $$= $5; + } + ; + sum_expr: + unfiltered_sum_expr opt_filter_expr + { + if ($2) + { + ((Item_sum *)$1)->set_filter($2); + } + $$= $1; + } + ; + +unfiltered_sum_expr: AVG_SYM '(' in_sum_expr ')' { $$= new (thd->mem_root) Item_sum_avg(thd, $3, FALSE); @@ -17135,6 +17195,7 @@ keyword_func_sp_var_and_label: | FAST_SYM | FEDERATED_SYM | FILE_SYM + | FILTER_SYM | FIRST_SYM | FOUND_SYM | FULL diff --git a/storage/columnstore/columnstore b/storage/columnstore/columnstore index 962e861c6a11e..746293c977f57 160000 --- a/storage/columnstore/columnstore +++ b/storage/columnstore/columnstore @@ -1 +1 @@ -Subproject commit 962e861c6a11e375cd95c78a517ab2b5d50e0076 +Subproject commit 746293c977f577a7d5a098a2c0770372ee880347 diff --git a/storage/sequence/sequence.cc b/storage/sequence/sequence.cc index 2f34d7c1a2fb6..a7a2942a639e5 100644 --- a/storage/sequence/sequence.cc +++ b/storage/sequence/sequence.cc @@ -419,9 +419,10 @@ create_group_by_handler(THD *thd, Query *query) Field *field; if (item->type() != Item::SUM_FUNC_ITEM || (((Item_sum*) item)->sum_func() != Item_sum::SUM_FUNC && - ((Item_sum*) item)->sum_func() != Item_sum::COUNT_FUNC)) + ((Item_sum*) item)->sum_func() != Item_sum::COUNT_FUNC) || + ((Item_sum*) item)->has_filter()) - return 0; // Not a SUM() function + return 0; // Not a SUM() function or has FILTER arg0= ((Item_sum*) item)->get_arg(0); if (arg0->type() != Item::FIELD_ITEM) { diff --git a/storage/spider/spd_db_mysql.cc b/storage/spider/spd_db_mysql.cc index c485b3f6b81fe..39e9681f78bce 100644 --- a/storage/spider/spd_db_mysql.cc +++ b/storage/spider/spd_db_mysql.cc @@ -6102,6 +6102,8 @@ int spider_db_mbase_util::open_item_sum_func( int error_num; DBUG_ENTER("spider_db_mbase_util::open_item_sum_func"); DBUG_PRINT("info",("spider Sumfunctype = %d", item_sum->sum_func())); + if (item_sum->has_filter()) + DBUG_RETURN(ER_SPIDER_COND_SKIP_NUM); switch (item_sum->sum_func()) { case Item_sum::COUNT_FUNC: