diff --git a/tests/cases/standalone/common/join/cross_join_advanced.result b/tests/cases/standalone/common/join/cross_join_advanced.result new file mode 100644 index 0000000000..19a238ce5c --- /dev/null +++ b/tests/cases/standalone/common/join/cross_join_advanced.result @@ -0,0 +1,162 @@ +-- Migrated from DuckDB test: test/sql/join/cross_product/ advanced tests +-- Tests advanced cross join scenarios +CREATE TABLE products(prod_id INTEGER, prod_name VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE stores(store_id INTEGER, store_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE categories(cat_id INTEGER, cat_name VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO products VALUES +(1, 'Laptop', 999.99, 1000), (2, 'Mouse', 29.99, 2000), (3, 'Monitor', 299.99, 3000); + +Affected Rows: 3 + +INSERT INTO stores VALUES +(1, 'TechStore', 'NYC', 1000), (2, 'GadgetShop', 'LA', 2000); + +Affected Rows: 2 + +INSERT INTO categories VALUES +(1, 'Electronics', 1000), (2, 'Accessories', 2000); + +Affected Rows: 2 + +-- Basic cross join +SELECT + p.prod_name, s.store_name, s.city +FROM products p +CROSS JOIN stores s +ORDER BY p.prod_id, s.store_id; + ++-----------+------------+------+ +| prod_name | store_name | city | ++-----------+------------+------+ +| Laptop | TechStore | NYC | +| Laptop | GadgetShop | LA | +| Mouse | TechStore | NYC | +| Mouse | GadgetShop | LA | +| Monitor | TechStore | NYC | +| Monitor | GadgetShop | LA | ++-----------+------------+------+ + +-- Cross join with filtering +SELECT + p.prod_name, s.store_name, p.price +FROM products p +CROSS JOIN stores s +WHERE p.price > 100.00 +ORDER BY p.price DESC, s.store_name; + ++-----------+------------+--------+ +| prod_name | store_name | price | ++-----------+------------+--------+ +| Laptop | GadgetShop | 999.99 | +| Laptop | TechStore | 999.99 | +| Monitor | GadgetShop | 299.99 | +| Monitor | TechStore | 299.99 | ++-----------+------------+--------+ + +-- Triple cross join +SELECT + p.prod_name, s.store_name, c.cat_name, + CASE WHEN p.price > 500 THEN 'Premium' ELSE 'Standard' END as tier +FROM products p +CROSS JOIN stores s +CROSS JOIN categories c +ORDER BY p.prod_id, s.store_id, c.cat_id; + ++-----------+------------+-------------+----------+ +| prod_name | store_name | cat_name | tier | ++-----------+------------+-------------+----------+ +| Laptop | TechStore | Electronics | Premium | +| Laptop | TechStore | Accessories | Premium | +| Laptop | GadgetShop | Electronics | Premium | +| Laptop | GadgetShop | Accessories | Premium | +| Mouse | TechStore | Electronics | Standard | +| Mouse | TechStore | Accessories | Standard | +| Mouse | GadgetShop | Electronics | Standard | +| Mouse | GadgetShop | Accessories | Standard | +| Monitor | TechStore | Electronics | Standard | +| Monitor | TechStore | Accessories | Standard | +| Monitor | GadgetShop | Electronics | Standard | +| Monitor | GadgetShop | Accessories | Standard | ++-----------+------------+-------------+----------+ + +-- Cross join with aggregation +SELECT + s.city, + COUNT(*) as product_store_combinations, + AVG(p.price) as avg_price, + SUM(p.price) as total_inventory_value +FROM products p +CROSS JOIN stores s +GROUP BY s.city +ORDER BY s.city; + ++------+----------------------------+-------------------+-----------------------+ +| city | product_store_combinations | avg_price | total_inventory_value | ++------+----------------------------+-------------------+-----------------------+ +| LA | 3 | 443.3233333333333 | 1329.97 | +| NYC | 3 | 443.3233333333333 | 1329.97 | ++------+----------------------------+-------------------+-----------------------+ + +-- Cross join for inventory matrix +SELECT + p.prod_name, + SUM(CASE WHEN s.city = 'NYC' THEN 1 ELSE 0 END) as nyc_availability, + SUM(CASE WHEN s.city = 'LA' THEN 1 ELSE 0 END) as la_availability, + COUNT(s.store_id) as total_store_availability +FROM products p +CROSS JOIN stores s +GROUP BY p.prod_name, p.prod_id +ORDER BY p.prod_id; + ++-----------+------------------+-----------------+--------------------------+ +| prod_name | nyc_availability | la_availability | total_store_availability | ++-----------+------------------+-----------------+--------------------------+ +| Laptop | 1 | 1 | 2 | +| Mouse | 1 | 1 | 2 | +| Monitor | 1 | 1 | 2 | ++-----------+------------------+-----------------+--------------------------+ + +-- Cross join with conditions and calculations +SELECT + p.prod_name, + s.store_name, + p.price, + p.price * 0.1 as store_commission, + p.price * 1.08 as price_with_tax +FROM products p +CROSS JOIN stores s +WHERE p.price BETWEEN 25.00 AND 1000.00 +ORDER BY p.price DESC, s.store_name; + ++-----------+------------+--------+--------------------+--------------------+ +| prod_name | store_name | price | store_commission | price_with_tax | ++-----------+------------+--------+--------------------+--------------------+ +| Laptop | GadgetShop | 999.99 | 99.99900000000001 | 1079.9892 | +| Laptop | TechStore | 999.99 | 99.99900000000001 | 1079.9892 | +| Monitor | GadgetShop | 299.99 | 29.999000000000002 | 323.98920000000004 | +| Monitor | TechStore | 299.99 | 29.999000000000002 | 323.98920000000004 | +| Mouse | GadgetShop | 29.99 | 2.999 | 32.3892 | +| Mouse | TechStore | 29.99 | 2.999 | 32.3892 | ++-----------+------------+--------+--------------------+--------------------+ + +DROP TABLE products; + +Affected Rows: 0 + +DROP TABLE stores; + +Affected Rows: 0 + +DROP TABLE categories; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/cross_join_advanced.sql b/tests/cases/standalone/common/join/cross_join_advanced.sql new file mode 100644 index 0000000000..9788341d53 --- /dev/null +++ b/tests/cases/standalone/common/join/cross_join_advanced.sql @@ -0,0 +1,81 @@ +-- Migrated from DuckDB test: test/sql/join/cross_product/ advanced tests +-- Tests advanced cross join scenarios + +CREATE TABLE products(prod_id INTEGER, prod_name VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE stores(store_id INTEGER, store_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE categories(cat_id INTEGER, cat_name VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO products VALUES +(1, 'Laptop', 999.99, 1000), (2, 'Mouse', 29.99, 2000), (3, 'Monitor', 299.99, 3000); + +INSERT INTO stores VALUES +(1, 'TechStore', 'NYC', 1000), (2, 'GadgetShop', 'LA', 2000); + +INSERT INTO categories VALUES +(1, 'Electronics', 1000), (2, 'Accessories', 2000); + +-- Basic cross join +SELECT + p.prod_name, s.store_name, s.city +FROM products p +CROSS JOIN stores s +ORDER BY p.prod_id, s.store_id; + +-- Cross join with filtering +SELECT + p.prod_name, s.store_name, p.price +FROM products p +CROSS JOIN stores s +WHERE p.price > 100.00 +ORDER BY p.price DESC, s.store_name; + +-- Triple cross join +SELECT + p.prod_name, s.store_name, c.cat_name, + CASE WHEN p.price > 500 THEN 'Premium' ELSE 'Standard' END as tier +FROM products p +CROSS JOIN stores s +CROSS JOIN categories c +ORDER BY p.prod_id, s.store_id, c.cat_id; + +-- Cross join with aggregation +SELECT + s.city, + COUNT(*) as product_store_combinations, + AVG(p.price) as avg_price, + SUM(p.price) as total_inventory_value +FROM products p +CROSS JOIN stores s +GROUP BY s.city +ORDER BY s.city; + +-- Cross join for inventory matrix +SELECT + p.prod_name, + SUM(CASE WHEN s.city = 'NYC' THEN 1 ELSE 0 END) as nyc_availability, + SUM(CASE WHEN s.city = 'LA' THEN 1 ELSE 0 END) as la_availability, + COUNT(s.store_id) as total_store_availability +FROM products p +CROSS JOIN stores s +GROUP BY p.prod_name, p.prod_id +ORDER BY p.prod_id; + +-- Cross join with conditions and calculations +SELECT + p.prod_name, + s.store_name, + p.price, + p.price * 0.1 as store_commission, + p.price * 1.08 as price_with_tax +FROM products p +CROSS JOIN stores s +WHERE p.price BETWEEN 25.00 AND 1000.00 +ORDER BY p.price DESC, s.store_name; + +DROP TABLE products; + +DROP TABLE stores; + +DROP TABLE categories; diff --git a/tests/cases/standalone/common/join/cross_product.result b/tests/cases/standalone/common/join/cross_product.result new file mode 100644 index 0000000000..781d41ea2a --- /dev/null +++ b/tests/cases/standalone/common/join/cross_product.result @@ -0,0 +1,63 @@ +-- Migrated from DuckDB test: test/sql/join/cross_product/test_cross_product.test +-- Tests CROSS JOIN functionality +CREATE TABLE small_table (a INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE another_table (b INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO small_table VALUES (1, 1000), (2, 2000); + +Affected Rows: 2 + +INSERT INTO another_table VALUES (10, 3000), (20, 4000), (30, 5000); + +Affected Rows: 3 + +-- Basic CROSS JOIN +SELECT * FROM small_table CROSS JOIN another_table ORDER BY a, b; + ++---+---------------------+----+---------------------+ +| a | ts | b | ts | ++---+---------------------+----+---------------------+ +| 1 | 1970-01-01T00:00:01 | 10 | 1970-01-01T00:00:03 | +| 1 | 1970-01-01T00:00:01 | 20 | 1970-01-01T00:00:04 | +| 1 | 1970-01-01T00:00:01 | 30 | 1970-01-01T00:00:05 | +| 2 | 1970-01-01T00:00:02 | 10 | 1970-01-01T00:00:03 | +| 2 | 1970-01-01T00:00:02 | 20 | 1970-01-01T00:00:04 | +| 2 | 1970-01-01T00:00:02 | 30 | 1970-01-01T00:00:05 | ++---+---------------------+----+---------------------+ + +-- CROSS JOIN with WHERE filter +SELECT * FROM small_table CROSS JOIN another_table WHERE a + b < 25 ORDER BY a, b; + ++---+---------------------+----+---------------------+ +| a | ts | b | ts | ++---+---------------------+----+---------------------+ +| 1 | 1970-01-01T00:00:01 | 10 | 1970-01-01T00:00:03 | +| 1 | 1970-01-01T00:00:01 | 20 | 1970-01-01T00:00:04 | +| 2 | 1970-01-01T00:00:02 | 10 | 1970-01-01T00:00:03 | +| 2 | 1970-01-01T00:00:02 | 20 | 1970-01-01T00:00:04 | ++---+---------------------+----+---------------------+ + +-- CROSS JOIN with aliases +SELECT s.a, t.b FROM small_table s CROSS JOIN another_table t WHERE s.a = 1 ORDER BY b; + ++---+----+ +| a | b | ++---+----+ +| 1 | 10 | +| 1 | 20 | +| 1 | 30 | ++---+----+ + +DROP TABLE another_table; + +Affected Rows: 0 + +DROP TABLE small_table; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/cross_product.sql b/tests/cases/standalone/common/join/cross_product.sql new file mode 100644 index 0000000000..7bc9c7500a --- /dev/null +++ b/tests/cases/standalone/common/join/cross_product.sql @@ -0,0 +1,23 @@ +-- Migrated from DuckDB test: test/sql/join/cross_product/test_cross_product.test +-- Tests CROSS JOIN functionality + +CREATE TABLE small_table (a INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE another_table (b INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO small_table VALUES (1, 1000), (2, 2000); + +INSERT INTO another_table VALUES (10, 3000), (20, 4000), (30, 5000); + +-- Basic CROSS JOIN +SELECT * FROM small_table CROSS JOIN another_table ORDER BY a, b; + +-- CROSS JOIN with WHERE filter +SELECT * FROM small_table CROSS JOIN another_table WHERE a + b < 25 ORDER BY a, b; + +-- CROSS JOIN with aliases +SELECT s.a, t.b FROM small_table s CROSS JOIN another_table t WHERE s.a = 1 ORDER BY b; + +DROP TABLE another_table; + +DROP TABLE small_table; diff --git a/tests/cases/standalone/common/join/full_outer_join.result b/tests/cases/standalone/common/join/full_outer_join.result new file mode 100644 index 0000000000..b9dca96dbf --- /dev/null +++ b/tests/cases/standalone/common/join/full_outer_join.result @@ -0,0 +1,61 @@ +-- Migrated from DuckDB test: test/sql/join/full_outer/test_full_outer_join.test +-- Tests FULL OUTER JOIN scenarios +CREATE TABLE left_full("id" INTEGER, "name" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE right_full("id" INTEGER, "value" INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO left_full VALUES (1, 'Alice', 1000), (2, 'Bob', 2000), (3, 'Carol', 3000); + +Affected Rows: 3 + +INSERT INTO right_full VALUES (2, 200, 4000), (3, 300, 5000), (4, 400, 6000); + +Affected Rows: 3 + +-- Basic FULL OUTER JOIN +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + ++----+-------+---------------------+----+-------+---------------------+ +| id | name | ts | id | value | ts | ++----+-------+---------------------+----+-------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | | | | +| 2 | Bob | 1970-01-01T00:00:02 | 2 | 200 | 1970-01-01T00:00:04 | +| 3 | Carol | 1970-01-01T00:00:03 | 3 | 300 | 1970-01-01T00:00:05 | +| | | | 4 | 400 | 1970-01-01T00:00:06 | ++----+-------+---------------------+----+-------+---------------------+ + +-- FULL OUTER JOIN with WHERE on result +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" WHERE l."name" IS NULL OR r."value" IS NULL ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + ++----+-------+---------------------+----+-------+---------------------+ +| id | name | ts | id | value | ts | ++----+-------+---------------------+----+-------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | | | | +| | | | 4 | 400 | 1970-01-01T00:00:06 | ++----+-------+---------------------+----+-------+---------------------+ + +-- FULL OUTER JOIN with complex conditions +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" AND r."value" > 250 ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + ++----+-------+---------------------+----+-------+---------------------+ +| id | name | ts | id | value | ts | ++----+-------+---------------------+----+-------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | | | | +| 2 | Bob | 1970-01-01T00:00:02 | | | | +| 3 | Carol | 1970-01-01T00:00:03 | 3 | 300 | 1970-01-01T00:00:05 | +| | | | 2 | 200 | 1970-01-01T00:00:04 | +| | | | 4 | 400 | 1970-01-01T00:00:06 | ++----+-------+---------------------+----+-------+---------------------+ + +DROP TABLE right_full; + +Affected Rows: 0 + +DROP TABLE left_full; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/full_outer_join.sql b/tests/cases/standalone/common/join/full_outer_join.sql new file mode 100644 index 0000000000..3fe8a6f4f9 --- /dev/null +++ b/tests/cases/standalone/common/join/full_outer_join.sql @@ -0,0 +1,23 @@ +-- Migrated from DuckDB test: test/sql/join/full_outer/test_full_outer_join.test +-- Tests FULL OUTER JOIN scenarios + +CREATE TABLE left_full("id" INTEGER, "name" VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE right_full("id" INTEGER, "value" INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO left_full VALUES (1, 'Alice', 1000), (2, 'Bob', 2000), (3, 'Carol', 3000); + +INSERT INTO right_full VALUES (2, 200, 4000), (3, 300, 5000), (4, 400, 6000); + +-- Basic FULL OUTER JOIN +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + +-- FULL OUTER JOIN with WHERE on result +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" WHERE l."name" IS NULL OR r."value" IS NULL ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + +-- FULL OUTER JOIN with complex conditions +SELECT * FROM left_full l FULL OUTER JOIN right_full r ON l."id" = r."id" AND r."value" > 250 ORDER BY l."id" NULLS LAST, r."id" NULLS LAST; + +DROP TABLE right_full; + +DROP TABLE left_full; diff --git a/tests/cases/standalone/common/join/hash_join_complex.result b/tests/cases/standalone/common/join/hash_join_complex.result new file mode 100644 index 0000000000..62b0e5f9ab --- /dev/null +++ b/tests/cases/standalone/common/join/hash_join_complex.result @@ -0,0 +1,140 @@ +-- Migrated from DuckDB test: test/sql/join/ hash join tests +-- Tests complex hash join scenarios +CREATE TABLE large_table_a("id" INTEGER, value_a VARCHAR, num_a INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE large_table_b("id" INTEGER, value_b VARCHAR, num_b INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO large_table_a VALUES +(1, 'alpha', 100, 1000), (2, 'beta', 200, 2000), (3, 'gamma', 300, 3000), +(4, 'delta', 400, 4000), (5, 'epsilon', 500, 5000), (6, 'zeta', 600, 6000), +(7, 'eta', 700, 7000), (8, 'theta', 800, 8000), (9, 'iota', 900, 9000), +(10, 'kappa', 1000, 10000); + +Affected Rows: 10 + +INSERT INTO large_table_b VALUES +(2, 'second', 20, 1000), (4, 'fourth', 40, 2000), (6, 'sixth', 60, 3000), +(8, 'eighth', 80, 4000), (10, 'tenth', 100, 5000), (12, 'twelfth', 120, 6000), +(14, 'fourteenth', 140, 7000), (16, 'sixteenth', 160, 8000); + +Affected Rows: 8 + +-- Hash join with exact match +SELECT + a."id", a.value_a, a.num_a, b.value_b, b.num_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +ORDER BY a."id"; + ++----+---------+-------+---------+-------+ +| id | value_a | num_a | value_b | num_b | ++----+---------+-------+---------+-------+ +| 2 | beta | 200 | second | 20 | +| 4 | delta | 400 | fourth | 40 | +| 6 | zeta | 600 | sixth | 60 | +| 8 | theta | 800 | eighth | 80 | +| 10 | kappa | 1000 | tenth | 100 | ++----+---------+-------+---------+-------+ + +-- Hash join with multiple key conditions +SELECT + a."id", a.value_a, b.value_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" AND a.num_a > b.num_b * 5 +ORDER BY a."id"; + ++----+---------+---------+ +| id | value_a | value_b | ++----+---------+---------+ +| 2 | beta | second | +| 4 | delta | fourth | +| 6 | zeta | sixth | +| 8 | theta | eighth | +| 10 | kappa | tenth | ++----+---------+---------+ + +-- Hash join with aggregation on both sides +SELECT + joined_data."id", + joined_data.combined_num, + joined_data.value_concat +FROM ( + SELECT + a."id", + a.num_a + b.num_b as combined_num, + a.value_a || '-' || b.value_b as value_concat + FROM large_table_a a + INNER JOIN large_table_b b ON a."id" = b."id" +) joined_data +WHERE joined_data.combined_num > 500 +ORDER BY joined_data.combined_num DESC; + ++----+--------------+--------------+ +| id | combined_num | value_concat | ++----+--------------+--------------+ +| 10 | 1100 | kappa-tenth | +| 8 | 880 | theta-eighth | +| 6 | 660 | zeta-sixth | ++----+--------------+--------------+ + +-- Hash join with filtering on both tables +SELECT + a.value_a, b.value_b, a.num_a, b.num_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +WHERE a.num_a > 500 AND b.num_b < 100 +ORDER BY a.num_a DESC; + ++---------+---------+-------+-------+ +| value_a | value_b | num_a | num_b | ++---------+---------+-------+-------+ +| theta | eighth | 800 | 80 | +| zeta | sixth | 600 | 60 | ++---------+---------+-------+-------+ + +-- Hash join for set operations +SELECT + a."id", + 'Both Tables' as source, + a.value_a as value_from_a, + b.value_b as value_from_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +UNION ALL +SELECT + a."id", + 'Only Table A' as source, + a.value_a, + NULL as value_from_b +FROM large_table_a a +LEFT JOIN large_table_b b ON a."id" = b."id" +WHERE b."id" IS NULL +ORDER BY "id", source; + ++----+--------------+--------------+--------------+ +| id | source | value_from_a | value_from_b | ++----+--------------+--------------+--------------+ +| 1 | Only Table A | alpha | | +| 2 | Both Tables | beta | second | +| 3 | Only Table A | gamma | | +| 4 | Both Tables | delta | fourth | +| 5 | Only Table A | epsilon | | +| 6 | Both Tables | zeta | sixth | +| 7 | Only Table A | eta | | +| 8 | Both Tables | theta | eighth | +| 9 | Only Table A | iota | | +| 10 | Both Tables | kappa | tenth | ++----+--------------+--------------+--------------+ + +DROP TABLE large_table_a; + +Affected Rows: 0 + +DROP TABLE large_table_b; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/hash_join_complex.sql b/tests/cases/standalone/common/join/hash_join_complex.sql new file mode 100644 index 0000000000..ce4a833382 --- /dev/null +++ b/tests/cases/standalone/common/join/hash_join_complex.sql @@ -0,0 +1,78 @@ +-- Migrated from DuckDB test: test/sql/join/ hash join tests +-- Tests complex hash join scenarios + +CREATE TABLE large_table_a("id" INTEGER, value_a VARCHAR, num_a INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE large_table_b("id" INTEGER, value_b VARCHAR, num_b INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO large_table_a VALUES +(1, 'alpha', 100, 1000), (2, 'beta', 200, 2000), (3, 'gamma', 300, 3000), +(4, 'delta', 400, 4000), (5, 'epsilon', 500, 5000), (6, 'zeta', 600, 6000), +(7, 'eta', 700, 7000), (8, 'theta', 800, 8000), (9, 'iota', 900, 9000), +(10, 'kappa', 1000, 10000); + +INSERT INTO large_table_b VALUES +(2, 'second', 20, 1000), (4, 'fourth', 40, 2000), (6, 'sixth', 60, 3000), +(8, 'eighth', 80, 4000), (10, 'tenth', 100, 5000), (12, 'twelfth', 120, 6000), +(14, 'fourteenth', 140, 7000), (16, 'sixteenth', 160, 8000); + +-- Hash join with exact match +SELECT + a."id", a.value_a, a.num_a, b.value_b, b.num_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +ORDER BY a."id"; + +-- Hash join with multiple key conditions +SELECT + a."id", a.value_a, b.value_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" AND a.num_a > b.num_b * 5 +ORDER BY a."id"; + +-- Hash join with aggregation on both sides +SELECT + joined_data."id", + joined_data.combined_num, + joined_data.value_concat +FROM ( + SELECT + a."id", + a.num_a + b.num_b as combined_num, + a.value_a || '-' || b.value_b as value_concat + FROM large_table_a a + INNER JOIN large_table_b b ON a."id" = b."id" +) joined_data +WHERE joined_data.combined_num > 500 +ORDER BY joined_data.combined_num DESC; + +-- Hash join with filtering on both tables +SELECT + a.value_a, b.value_b, a.num_a, b.num_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +WHERE a.num_a > 500 AND b.num_b < 100 +ORDER BY a.num_a DESC; + +-- Hash join for set operations +SELECT + a."id", + 'Both Tables' as source, + a.value_a as value_from_a, + b.value_b as value_from_b +FROM large_table_a a +INNER JOIN large_table_b b ON a."id" = b."id" +UNION ALL +SELECT + a."id", + 'Only Table A' as source, + a.value_a, + NULL as value_from_b +FROM large_table_a a +LEFT JOIN large_table_b b ON a."id" = b."id" +WHERE b."id" IS NULL +ORDER BY "id", source; + +DROP TABLE large_table_a; + +DROP TABLE large_table_b; diff --git a/tests/cases/standalone/common/join/inequality_join.result b/tests/cases/standalone/common/join/inequality_join.result new file mode 100644 index 0000000000..7856cfeb7b --- /dev/null +++ b/tests/cases/standalone/common/join/inequality_join.result @@ -0,0 +1,79 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_range_join.test +-- Tests inequality JOIN conditions +CREATE TABLE events("id" INTEGER, event_time INTEGER, duration INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE time_ranges(start_time INTEGER, end_time INTEGER, range_name VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO events VALUES (1, 10, 5, 1000), (2, 25, 3, 2000), (3, 45, 8, 3000); + +Affected Rows: 3 + +INSERT INTO time_ranges VALUES (0, 20, 'Early', 4000), (20, 40, 'Mid', 5000), (40, 60, 'Late', 6000); + +Affected Rows: 3 + +-- Range join using BETWEEN +SELECT e."id", e.event_time, t.range_name +FROM events e JOIN time_ranges t ON e.event_time BETWEEN t.start_time AND t.end_time +ORDER BY e."id"; + ++----+------------+------------+ +| id | event_time | range_name | ++----+------------+------------+ +| 1 | 10 | Early | +| 2 | 25 | Mid | +| 3 | 45 | Late | ++----+------------+------------+ + +-- Inequality join conditions +SELECT e."id", e.event_time, e.duration, t.range_name +FROM events e JOIN time_ranges t ON e.event_time >= t.start_time AND e.event_time < t.end_time +ORDER BY e."id"; + ++----+------------+----------+------------+ +| id | event_time | duration | range_name | ++----+------------+----------+------------+ +| 1 | 10 | 5 | Early | +| 2 | 25 | 3 | Mid | +| 3 | 45 | 8 | Late | ++----+------------+----------+------------+ + +-- Join with overlap condition +SELECT e."id", t.range_name +FROM events e JOIN time_ranges t ON + e.event_time < t.end_time AND (e.event_time + e.duration) > t.start_time +ORDER BY e."id", t.start_time; + ++----+------------+ +| id | range_name | ++----+------------+ +| 1 | Early | +| 2 | Mid | +| 3 | Late | ++----+------------+ + +-- Self join with inequality +SELECT e1."id" as id1, e2."id" as id2, e1.event_time, e2.event_time +FROM events e1 JOIN events e2 ON e1.event_time < e2.event_time +ORDER BY e1."id", e2."id"; + ++-----+-----+------------+------------+ +| id1 | id2 | event_time | event_time | ++-----+-----+------------+------------+ +| 1 | 2 | 10 | 25 | +| 1 | 3 | 10 | 45 | +| 2 | 3 | 25 | 45 | ++-----+-----+------------+------------+ + +DROP TABLE time_ranges; + +Affected Rows: 0 + +DROP TABLE events; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/inequality_join.sql b/tests/cases/standalone/common/join/inequality_join.sql new file mode 100644 index 0000000000..333b84d8dd --- /dev/null +++ b/tests/cases/standalone/common/join/inequality_join.sql @@ -0,0 +1,35 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_range_join.test +-- Tests inequality JOIN conditions + +CREATE TABLE events("id" INTEGER, event_time INTEGER, duration INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE time_ranges(start_time INTEGER, end_time INTEGER, range_name VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO events VALUES (1, 10, 5, 1000), (2, 25, 3, 2000), (3, 45, 8, 3000); + +INSERT INTO time_ranges VALUES (0, 20, 'Early', 4000), (20, 40, 'Mid', 5000), (40, 60, 'Late', 6000); + +-- Range join using BETWEEN +SELECT e."id", e.event_time, t.range_name +FROM events e JOIN time_ranges t ON e.event_time BETWEEN t.start_time AND t.end_time +ORDER BY e."id"; + +-- Inequality join conditions +SELECT e."id", e.event_time, e.duration, t.range_name +FROM events e JOIN time_ranges t ON e.event_time >= t.start_time AND e.event_time < t.end_time +ORDER BY e."id"; + +-- Join with overlap condition +SELECT e."id", t.range_name +FROM events e JOIN time_ranges t ON + e.event_time < t.end_time AND (e.event_time + e.duration) > t.start_time +ORDER BY e."id", t.start_time; + +-- Self join with inequality +SELECT e1."id" as id1, e2."id" as id2, e1.event_time, e2.event_time +FROM events e1 JOIN events e2 ON e1.event_time < e2.event_time +ORDER BY e1."id", e2."id"; + +DROP TABLE time_ranges; + +DROP TABLE events; diff --git a/tests/cases/standalone/common/join/inequality_joins.result b/tests/cases/standalone/common/join/inequality_joins.result new file mode 100644 index 0000000000..2a8750d9ac --- /dev/null +++ b/tests/cases/standalone/common/join/inequality_joins.result @@ -0,0 +1,167 @@ +-- Migrated from DuckDB test: test/sql/join/iejoin/ inequality join tests +-- Tests inequality join conditions +CREATE TABLE time_events(event_id INTEGER, event_time TIMESTAMP, event_type VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE time_windows(window_id INTEGER, start_time TIMESTAMP, end_time TIMESTAMP, window_name VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO time_events VALUES +(1, '2023-01-01 10:15:00', 'login', 1000), +(2, '2023-01-01 10:30:00', 'purchase', 2000), +(3, '2023-01-01 10:45:00', 'logout', 3000), +(4, '2023-01-01 11:05:00', 'login', 4000), +(5, '2023-01-01 11:20:00', 'view', 5000), +(6, '2023-01-01 11:35:00', 'purchase', 6000); + +Affected Rows: 6 + +INSERT INTO time_windows VALUES +(1, '2023-01-01 10:00:00', '2023-01-01 10:30:00', 'Morning Early', 1000), +(2, '2023-01-01 10:30:00', '2023-01-01 11:00:00', 'Morning Late', 2000), +(3, '2023-01-01 11:00:00', '2023-01-01 11:30:00', 'Noon Early', 3000), +(4, '2023-01-01 11:30:00', '2023-01-01 12:00:00', 'Noon Late', 4000); + +Affected Rows: 4 + +-- Range join: events within time windows +SELECT + e.event_id, e.event_time, e.event_type, w.window_name +FROM time_events e +INNER JOIN time_windows w + ON e.event_time >= w.start_time AND e.event_time < w.end_time +ORDER BY e.event_time; + ++----------+---------------------+------------+---------------+ +| event_id | event_time | event_type | window_name | ++----------+---------------------+------------+---------------+ +| 1 | 2023-01-01T10:15:00 | login | Morning Early | +| 2 | 2023-01-01T10:30:00 | purchase | Morning Late | +| 3 | 2023-01-01T10:45:00 | logout | Morning Late | +| 4 | 2023-01-01T11:05:00 | login | Noon Early | +| 5 | 2023-01-01T11:20:00 | view | Noon Early | +| 6 | 2023-01-01T11:35:00 | purchase | Noon Late | ++----------+---------------------+------------+---------------+ + +-- Inequality join with additional conditions +SELECT + e.event_id, e.event_type, w.window_name +FROM time_events e +INNER JOIN time_windows w + ON e.event_time >= w.start_time + AND e.event_time < w.end_time + AND e.event_type = 'purchase' +ORDER BY e.event_time; + ++----------+------------+--------------+ +| event_id | event_type | window_name | ++----------+------------+--------------+ +| 2 | purchase | Morning Late | +| 6 | purchase | Noon Late | ++----------+------------+--------------+ + +-- Cross-time analysis with inequality joins +SELECT + e1.event_id as first_event, e2.event_id as second_event, + e1.event_type as first_type, e2.event_type as second_type, + e2.event_time - e1.event_time as time_diff +FROM time_events e1 +INNER JOIN time_events e2 + ON e1.event_time < e2.event_time + AND e2.event_time - e1.event_time <= INTERVAL '30 minutes' +ORDER BY e1.event_time, e2.event_time; + ++-------------+--------------+------------+-------------+-----------+ +| first_event | second_event | first_type | second_type | time_diff | ++-------------+--------------+------------+-------------+-----------+ +| 1 | 2 | login | purchase | PT900S | +| 1 | 3 | login | logout | PT1800S | +| 2 | 3 | purchase | logout | PT900S | +| 3 | 4 | logout | login | PT1200S | +| 4 | 5 | login | view | PT900S | +| 4 | 6 | login | purchase | PT1800S | +| 5 | 6 | view | purchase | PT900S | ++-------------+--------------+------------+-------------+-----------+ + +CREATE TABLE price_history(item_id INTEGER, price DOUBLE, effective_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE orders_ineq(order_id INTEGER, item_id INTEGER, order_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO price_history VALUES +(1, 100.00, '2023-01-01', 1000), (1, 110.00, '2023-01-15', 2000), +(2, 50.00, '2023-01-01', 3000), (2, 55.00, '2023-01-20', 4000); + +Affected Rows: 4 + +INSERT INTO orders_ineq VALUES +(1, 1, '2023-01-10', 1000), (2, 1, '2023-01-20', 2000), +(3, 2, '2023-01-05', 3000), (4, 2, '2023-01-25', 4000); + +Affected Rows: 4 + +-- Historical price lookup with inequality join +SELECT + o.order_id, o.order_date, p.price, p.effective_date +FROM orders_ineq o +INNER JOIN price_history p + ON o.item_id = p.item_id + AND o.order_date >= p.effective_date +ORDER BY o.order_id; + ++----------+------------+-------+----------------+ +| order_id | order_date | price | effective_date | ++----------+------------+-------+----------------+ +| 1 | 2023-01-10 | 100.0 | 2023-01-01 | +| 2 | 2023-01-20 | 100.0 | 2023-01-01 | +| 2 | 2023-01-20 | 110.0 | 2023-01-15 | +| 3 | 2023-01-05 | 50.0 | 2023-01-01 | +| 4 | 2023-01-25 | 50.0 | 2023-01-01 | +| 4 | 2023-01-25 | 55.0 | 2023-01-20 | ++----------+------------+-------+----------------+ + +-- Latest price before order date +SELECT + o.order_id, o.order_date, latest_price.price +FROM orders_ineq o +INNER JOIN ( + SELECT + item_id, + price, + effective_date, + ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY effective_date DESC) as rn + FROM price_history +) latest_price + ON o.item_id = latest_price.item_id + AND o.order_date >= latest_price.effective_date + AND latest_price.rn = 1 +ORDER BY o.order_id; + ++----------+------------+-------+ +| order_id | order_date | price | ++----------+------------+-------+ +| 2 | 2023-01-20 | 110.0 | +| 4 | 2023-01-25 | 55.0 | ++----------+------------+-------+ + +DROP TABLE time_events; + +Affected Rows: 0 + +DROP TABLE time_windows; + +Affected Rows: 0 + +DROP TABLE price_history; + +Affected Rows: 0 + +DROP TABLE orders_ineq; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/inequality_joins.sql b/tests/cases/standalone/common/join/inequality_joins.sql new file mode 100644 index 0000000000..f0de1be835 --- /dev/null +++ b/tests/cases/standalone/common/join/inequality_joins.sql @@ -0,0 +1,94 @@ +-- Migrated from DuckDB test: test/sql/join/iejoin/ inequality join tests +-- Tests inequality join conditions + +CREATE TABLE time_events(event_id INTEGER, event_time TIMESTAMP, event_type VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE time_windows(window_id INTEGER, start_time TIMESTAMP, end_time TIMESTAMP, window_name VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO time_events VALUES +(1, '2023-01-01 10:15:00', 'login', 1000), +(2, '2023-01-01 10:30:00', 'purchase', 2000), +(3, '2023-01-01 10:45:00', 'logout', 3000), +(4, '2023-01-01 11:05:00', 'login', 4000), +(5, '2023-01-01 11:20:00', 'view', 5000), +(6, '2023-01-01 11:35:00', 'purchase', 6000); + +INSERT INTO time_windows VALUES +(1, '2023-01-01 10:00:00', '2023-01-01 10:30:00', 'Morning Early', 1000), +(2, '2023-01-01 10:30:00', '2023-01-01 11:00:00', 'Morning Late', 2000), +(3, '2023-01-01 11:00:00', '2023-01-01 11:30:00', 'Noon Early', 3000), +(4, '2023-01-01 11:30:00', '2023-01-01 12:00:00', 'Noon Late', 4000); + +-- Range join: events within time windows +SELECT + e.event_id, e.event_time, e.event_type, w.window_name +FROM time_events e +INNER JOIN time_windows w + ON e.event_time >= w.start_time AND e.event_time < w.end_time +ORDER BY e.event_time; + +-- Inequality join with additional conditions +SELECT + e.event_id, e.event_type, w.window_name +FROM time_events e +INNER JOIN time_windows w + ON e.event_time >= w.start_time + AND e.event_time < w.end_time + AND e.event_type = 'purchase' +ORDER BY e.event_time; + +-- Cross-time analysis with inequality joins +SELECT + e1.event_id as first_event, e2.event_id as second_event, + e1.event_type as first_type, e2.event_type as second_type, + e2.event_time - e1.event_time as time_diff +FROM time_events e1 +INNER JOIN time_events e2 + ON e1.event_time < e2.event_time + AND e2.event_time - e1.event_time <= INTERVAL '30 minutes' +ORDER BY e1.event_time, e2.event_time; + +CREATE TABLE price_history(item_id INTEGER, price DOUBLE, effective_date DATE, ts TIMESTAMP TIME INDEX); +CREATE TABLE orders_ineq(order_id INTEGER, item_id INTEGER, order_date DATE, ts TIMESTAMP TIME INDEX); + +INSERT INTO price_history VALUES +(1, 100.00, '2023-01-01', 1000), (1, 110.00, '2023-01-15', 2000), +(2, 50.00, '2023-01-01', 3000), (2, 55.00, '2023-01-20', 4000); + +INSERT INTO orders_ineq VALUES +(1, 1, '2023-01-10', 1000), (2, 1, '2023-01-20', 2000), +(3, 2, '2023-01-05', 3000), (4, 2, '2023-01-25', 4000); + +-- Historical price lookup with inequality join +SELECT + o.order_id, o.order_date, p.price, p.effective_date +FROM orders_ineq o +INNER JOIN price_history p + ON o.item_id = p.item_id + AND o.order_date >= p.effective_date +ORDER BY o.order_id; + +-- Latest price before order date +SELECT + o.order_id, o.order_date, latest_price.price +FROM orders_ineq o +INNER JOIN ( + SELECT + item_id, + price, + effective_date, + ROW_NUMBER() OVER (PARTITION BY item_id ORDER BY effective_date DESC) as rn + FROM price_history +) latest_price + ON o.item_id = latest_price.item_id + AND o.order_date >= latest_price.effective_date + AND latest_price.rn = 1 +ORDER BY o.order_id; + +DROP TABLE time_events; + +DROP TABLE time_windows; + +DROP TABLE price_history; + +DROP TABLE orders_ineq; diff --git a/tests/cases/standalone/common/join/inner_join_advanced.result b/tests/cases/standalone/common/join/inner_join_advanced.result new file mode 100644 index 0000000000..397055ec21 --- /dev/null +++ b/tests/cases/standalone/common/join/inner_join_advanced.result @@ -0,0 +1,167 @@ +-- Migrated from DuckDB test: test/sql/join/inner/ advanced tests +-- Tests advanced inner join patterns +CREATE TABLE customers(cust_id INTEGER, cust_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE orders(order_id INTEGER, cust_id INTEGER, order_date DATE, amount DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE order_items(item_id INTEGER, order_id INTEGER, product VARCHAR, quantity INTEGER, price DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO customers VALUES +(1, 'John', 'NYC', 1000), (2, 'Jane', 'LA', 2000), (3, 'Bob', 'Chicago', 3000), (4, 'Alice', 'NYC', 4000); + +Affected Rows: 4 + +INSERT INTO orders VALUES +(101, 1, '2023-01-01', 250.00, 1000), (102, 2, '2023-01-02', 180.00, 2000), +(103, 1, '2023-01-03', 420.00, 3000), (104, 3, '2023-01-04', 95.00, 4000), +(105, 4, '2023-01-05', 310.00, 5000); + +Affected Rows: 5 + +INSERT INTO order_items VALUES +(1, 101, 'Widget', 2, 125.00, 1000), (2, 101, 'Gadget', 1, 0.00, 2000), +(3, 102, 'Tool', 3, 60.00, 3000), (4, 103, 'Device', 1, 420.00, 4000), +(5, 104, 'Part', 5, 19.00, 5000), (6, 105, 'Component', 2, 155.00, 6000); + +Affected Rows: 6 + +-- Multi-table inner join +SELECT + c.cust_name, c.city, o.order_id, o.order_date, o.amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +ORDER BY o.order_date, c.cust_name; + ++-----------+---------+----------+------------+--------+ +| cust_name | city | order_id | order_date | amount | ++-----------+---------+----------+------------+--------+ +| John | NYC | 101 | 2023-01-01 | 250.0 | +| Jane | LA | 102 | 2023-01-02 | 180.0 | +| John | NYC | 103 | 2023-01-03 | 420.0 | +| Bob | Chicago | 104 | 2023-01-04 | 95.0 | +| Alice | NYC | 105 | 2023-01-05 | 310.0 | ++-----------+---------+----------+------------+--------+ + +-- Three-way inner join +SELECT + c.cust_name, o.order_id, oi.product, oi.quantity, oi.price +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +INNER JOIN order_items oi ON o.order_id = oi.order_id +ORDER BY c.cust_name, o.order_id, oi.product; + ++-----------+----------+-----------+----------+-------+ +| cust_name | order_id | product | quantity | price | ++-----------+----------+-----------+----------+-------+ +| Alice | 105 | Component | 2 | 155.0 | +| Bob | 104 | Part | 5 | 19.0 | +| Jane | 102 | Tool | 3 | 60.0 | +| John | 101 | Gadget | 1 | 0.0 | +| John | 101 | Widget | 2 | 125.0 | +| John | 103 | Device | 1 | 420.0 | ++-----------+----------+-----------+----------+-------+ + +-- Inner join with complex conditions +SELECT + c.cust_name, o.order_id, o.amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id AND o.amount > 200.00 +ORDER BY o.amount DESC; + ++-----------+----------+--------+ +| cust_name | order_id | amount | ++-----------+----------+--------+ +| John | 103 | 420.0 | +| Alice | 105 | 310.0 | +| John | 101 | 250.0 | ++-----------+----------+--------+ + +-- Inner join with aggregation +SELECT + c.city, + COUNT(o.order_id) as total_orders, + SUM(o.amount) as total_amount, + AVG(o.amount) as avg_order_amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +GROUP BY c.city +ORDER BY total_amount DESC; + ++---------+--------------+--------------+-------------------+ +| city | total_orders | total_amount | avg_order_amount | ++---------+--------------+--------------+-------------------+ +| NYC | 3 | 980.0 | 326.6666666666667 | +| LA | 1 | 180.0 | 180.0 | +| Chicago | 1 | 95.0 | 95.0 | ++---------+--------------+--------------+-------------------+ + +-- Self join +SELECT + o1.order_id as order1, o2.order_id as order2, o1.amount, o2.amount +FROM orders o1 +INNER JOIN orders o2 ON o1.cust_id = o2.cust_id AND o1.order_id < o2.order_id +ORDER BY o1.order_id, o2.order_id; + ++--------+--------+--------+--------+ +| order1 | order2 | amount | amount | ++--------+--------+--------+--------+ +| 101 | 103 | 250.0 | 420.0 | ++--------+--------+--------+--------+ + +-- Join with subquery +SELECT + c.cust_name, high_orders.total_amount +FROM customers c +INNER JOIN ( + SELECT cust_id, SUM(amount) as total_amount + FROM orders + GROUP BY cust_id + HAVING SUM(amount) > 300 +) high_orders ON c.cust_id = high_orders.cust_id +ORDER BY high_orders.total_amount DESC; + ++-----------+--------------+ +| cust_name | total_amount | ++-----------+--------------+ +| John | 670.0 | +| Alice | 310.0 | ++-----------+--------------+ + +-- Join with window functions +SELECT + c.cust_name, + o.order_id, + o.amount, + ROW_NUMBER() OVER (PARTITION BY c.cust_id ORDER BY o.order_date) as order_sequence +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +ORDER BY c.cust_name, order_sequence; + ++-----------+----------+--------+----------------+ +| cust_name | order_id | amount | order_sequence | ++-----------+----------+--------+----------------+ +| Alice | 105 | 310.0 | 1 | +| Bob | 104 | 95.0 | 1 | +| Jane | 102 | 180.0 | 1 | +| John | 101 | 250.0 | 1 | +| John | 103 | 420.0 | 2 | ++-----------+----------+--------+----------------+ + +DROP TABLE customers; + +Affected Rows: 0 + +DROP TABLE orders; + +Affected Rows: 0 + +DROP TABLE order_items; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/inner_join_advanced.sql b/tests/cases/standalone/common/join/inner_join_advanced.sql new file mode 100644 index 0000000000..d6bab7ead3 --- /dev/null +++ b/tests/cases/standalone/common/join/inner_join_advanced.sql @@ -0,0 +1,87 @@ +-- Migrated from DuckDB test: test/sql/join/inner/ advanced tests +-- Tests advanced inner join patterns + +CREATE TABLE customers(cust_id INTEGER, cust_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); +CREATE TABLE orders(order_id INTEGER, cust_id INTEGER, order_date DATE, amount DOUBLE, ts TIMESTAMP TIME INDEX); +CREATE TABLE order_items(item_id INTEGER, order_id INTEGER, product VARCHAR, quantity INTEGER, price DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO customers VALUES +(1, 'John', 'NYC', 1000), (2, 'Jane', 'LA', 2000), (3, 'Bob', 'Chicago', 3000), (4, 'Alice', 'NYC', 4000); + +INSERT INTO orders VALUES +(101, 1, '2023-01-01', 250.00, 1000), (102, 2, '2023-01-02', 180.00, 2000), +(103, 1, '2023-01-03', 420.00, 3000), (104, 3, '2023-01-04', 95.00, 4000), +(105, 4, '2023-01-05', 310.00, 5000); + +INSERT INTO order_items VALUES +(1, 101, 'Widget', 2, 125.00, 1000), (2, 101, 'Gadget', 1, 0.00, 2000), +(3, 102, 'Tool', 3, 60.00, 3000), (4, 103, 'Device', 1, 420.00, 4000), +(5, 104, 'Part', 5, 19.00, 5000), (6, 105, 'Component', 2, 155.00, 6000); + +-- Multi-table inner join +SELECT + c.cust_name, c.city, o.order_id, o.order_date, o.amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +ORDER BY o.order_date, c.cust_name; + +-- Three-way inner join +SELECT + c.cust_name, o.order_id, oi.product, oi.quantity, oi.price +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +INNER JOIN order_items oi ON o.order_id = oi.order_id +ORDER BY c.cust_name, o.order_id, oi.product; + +-- Inner join with complex conditions +SELECT + c.cust_name, o.order_id, o.amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id AND o.amount > 200.00 +ORDER BY o.amount DESC; + +-- Inner join with aggregation +SELECT + c.city, + COUNT(o.order_id) as total_orders, + SUM(o.amount) as total_amount, + AVG(o.amount) as avg_order_amount +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +GROUP BY c.city +ORDER BY total_amount DESC; + +-- Self join +SELECT + o1.order_id as order1, o2.order_id as order2, o1.amount, o2.amount +FROM orders o1 +INNER JOIN orders o2 ON o1.cust_id = o2.cust_id AND o1.order_id < o2.order_id +ORDER BY o1.order_id, o2.order_id; + +-- Join with subquery +SELECT + c.cust_name, high_orders.total_amount +FROM customers c +INNER JOIN ( + SELECT cust_id, SUM(amount) as total_amount + FROM orders + GROUP BY cust_id + HAVING SUM(amount) > 300 +) high_orders ON c.cust_id = high_orders.cust_id +ORDER BY high_orders.total_amount DESC; + +-- Join with window functions +SELECT + c.cust_name, + o.order_id, + o.amount, + ROW_NUMBER() OVER (PARTITION BY c.cust_id ORDER BY o.order_date) as order_sequence +FROM customers c +INNER JOIN orders o ON c.cust_id = o.cust_id +ORDER BY c.cust_name, order_sequence; + +DROP TABLE customers; + +DROP TABLE orders; + +DROP TABLE order_items; diff --git a/tests/cases/standalone/common/join/join_conditions_complex.result b/tests/cases/standalone/common/join/join_conditions_complex.result new file mode 100644 index 0000000000..8c3ac61603 --- /dev/null +++ b/tests/cases/standalone/common/join/join_conditions_complex.result @@ -0,0 +1,162 @@ +-- Migrated from DuckDB test: test/sql/join/ complex condition tests +-- Tests complex join conditions and predicates +CREATE TABLE sales_reps(rep_id INTEGER, "name" VARCHAR, region VARCHAR, quota INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE customer_accounts(account_id INTEGER, account_name VARCHAR, region VARCHAR, rep_id INTEGER, revenue INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO sales_reps VALUES +(1, 'Tom', 'North', 100000, 1000), (2, 'Sarah', 'South', 150000, 2000), +(3, 'Mike', 'East', 120000, 3000), (4, 'Lisa', 'West', 180000, 4000); + +Affected Rows: 4 + +INSERT INTO customer_accounts VALUES +(101, 'TechCorp', 'North', 1, 85000, 1000), (102, 'DataInc', 'South', 2, 195000, 2000), +(103, 'CloudSys', 'North', 1, 110000, 3000), (104, 'NetSoft', 'East', 3, 75000, 4000), +(105, 'WebCo', 'West', 4, 225000, 5000), (106, 'AppDev', 'South', 2, 140000, 6000); + +Affected Rows: 6 + +-- Join with multiple conditions +SELECT + sr."name" as rep_name, ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id AND sr.region = ca.region +ORDER BY sr.rep_id, ca.revenue DESC; + ++----------+--------------+---------+ +| rep_name | account_name | revenue | ++----------+--------------+---------+ +| Tom | CloudSys | 110000 | +| Tom | TechCorp | 85000 | +| Sarah | DataInc | 195000 | +| Sarah | AppDev | 140000 | +| Mike | NetSoft | 75000 | +| Lisa | WebCo | 225000 | ++----------+--------------+---------+ + +-- Join with inequality conditions +SELECT + sr."name", sr.quota, ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id AND ca.revenue < sr.quota +ORDER BY sr.rep_id, ca.revenue; + ++-------+--------+--------------+---------+ +| name | quota | account_name | revenue | ++-------+--------+--------------+---------+ +| Tom | 100000 | TechCorp | 85000 | +| Sarah | 150000 | AppDev | 140000 | +| Mike | 120000 | NetSoft | 75000 | ++-------+--------+--------------+---------+ + +-- Join with range conditions +SELECT + sr."name", ca.account_name, ca.revenue, sr.quota +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND ca.revenue BETWEEN sr.quota * 0.5 AND sr.quota * 1.5 +ORDER BY sr.rep_id, ca.revenue; + ++-------+--------------+---------+--------+ +| name | account_name | revenue | quota | ++-------+--------------+---------+--------+ +| Tom | TechCorp | 85000 | 100000 | +| Tom | CloudSys | 110000 | 100000 | +| Sarah | AppDev | 140000 | 150000 | +| Sarah | DataInc | 195000 | 150000 | +| Mike | NetSoft | 75000 | 120000 | +| Lisa | WebCo | 225000 | 180000 | ++-------+--------------+---------+--------+ + +-- Join with CASE in conditions +SELECT + sr."name", ca.account_name, ca.revenue, + CASE WHEN ca.revenue >= sr.quota THEN 'Met Quota' ELSE 'Below Quota' END as performance +FROM sales_reps sr +INNER JOIN customer_accounts ca ON sr.rep_id = ca.rep_id +ORDER BY sr.rep_id, ca.revenue DESC; + ++-------+--------------+---------+-------------+ +| name | account_name | revenue | performance | ++-------+--------------+---------+-------------+ +| Tom | CloudSys | 110000 | Met Quota | +| Tom | TechCorp | 85000 | Below Quota | +| Sarah | DataInc | 195000 | Met Quota | +| Sarah | AppDev | 140000 | Below Quota | +| Mike | NetSoft | 75000 | Below Quota | +| Lisa | WebCo | 225000 | Met Quota | ++-------+--------------+---------+-------------+ + +-- Join with expression conditions +SELECT + sr."name", ca.account_name, + ca.revenue, sr.quota, + ca.revenue - sr.quota as quota_diff +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND UPPER(sr.region) = UPPER(ca.region) +ORDER BY quota_diff DESC, sr."name" ASC; + ++-------+--------------+---------+--------+------------+ +| name | account_name | revenue | quota | quota_diff | ++-------+--------------+---------+--------+------------+ +| Lisa | WebCo | 225000 | 180000 | 45000 | +| Sarah | DataInc | 195000 | 150000 | 45000 | +| Tom | CloudSys | 110000 | 100000 | 10000 | +| Sarah | AppDev | 140000 | 150000 | -10000 | +| Tom | TechCorp | 85000 | 100000 | -15000 | +| Mike | NetSoft | 75000 | 120000 | -45000 | ++-------+--------------+---------+--------+------------+ + +-- Join with string pattern conditions +SELECT + sr."name", ca.account_name +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND ca.account_name LIKE '%Corp%' +ORDER BY sr."name"; + ++------+--------------+ +| name | account_name | ++------+--------------+ +| Tom | TechCorp | ++------+--------------+ + +-- Complex nested join conditions +SELECT + sr."name", ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca ON ( + sr.rep_id = ca.rep_id + AND (ca.revenue > 100000 OR sr.quota < 130000) + AND sr.region IN ('North', 'South') +) +ORDER BY ca.revenue DESC; + ++-------+--------------+---------+ +| name | account_name | revenue | ++-------+--------------+---------+ +| Sarah | DataInc | 195000 | +| Sarah | AppDev | 140000 | +| Tom | CloudSys | 110000 | +| Tom | TechCorp | 85000 | ++-------+--------------+---------+ + +DROP TABLE sales_reps; + +Affected Rows: 0 + +DROP TABLE customer_accounts; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_conditions_complex.sql b/tests/cases/standalone/common/join/join_conditions_complex.sql new file mode 100644 index 0000000000..c71d58871c --- /dev/null +++ b/tests/cases/standalone/common/join/join_conditions_complex.sql @@ -0,0 +1,83 @@ +-- Migrated from DuckDB test: test/sql/join/ complex condition tests +-- Tests complex join conditions and predicates + +CREATE TABLE sales_reps(rep_id INTEGER, "name" VARCHAR, region VARCHAR, quota INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE customer_accounts(account_id INTEGER, account_name VARCHAR, region VARCHAR, rep_id INTEGER, revenue INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO sales_reps VALUES +(1, 'Tom', 'North', 100000, 1000), (2, 'Sarah', 'South', 150000, 2000), +(3, 'Mike', 'East', 120000, 3000), (4, 'Lisa', 'West', 180000, 4000); + +INSERT INTO customer_accounts VALUES +(101, 'TechCorp', 'North', 1, 85000, 1000), (102, 'DataInc', 'South', 2, 195000, 2000), +(103, 'CloudSys', 'North', 1, 110000, 3000), (104, 'NetSoft', 'East', 3, 75000, 4000), +(105, 'WebCo', 'West', 4, 225000, 5000), (106, 'AppDev', 'South', 2, 140000, 6000); + +-- Join with multiple conditions +SELECT + sr."name" as rep_name, ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id AND sr.region = ca.region +ORDER BY sr.rep_id, ca.revenue DESC; + +-- Join with inequality conditions +SELECT + sr."name", sr.quota, ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id AND ca.revenue < sr.quota +ORDER BY sr.rep_id, ca.revenue; + +-- Join with range conditions +SELECT + sr."name", ca.account_name, ca.revenue, sr.quota +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND ca.revenue BETWEEN sr.quota * 0.5 AND sr.quota * 1.5 +ORDER BY sr.rep_id, ca.revenue; + +-- Join with CASE in conditions +SELECT + sr."name", ca.account_name, ca.revenue, + CASE WHEN ca.revenue >= sr.quota THEN 'Met Quota' ELSE 'Below Quota' END as performance +FROM sales_reps sr +INNER JOIN customer_accounts ca ON sr.rep_id = ca.rep_id +ORDER BY sr.rep_id, ca.revenue DESC; + +-- Join with expression conditions +SELECT + sr."name", ca.account_name, + ca.revenue, sr.quota, + ca.revenue - sr.quota as quota_diff +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND UPPER(sr.region) = UPPER(ca.region) +ORDER BY quota_diff DESC, sr."name" ASC; + +-- Join with string pattern conditions +SELECT + sr."name", ca.account_name +FROM sales_reps sr +INNER JOIN customer_accounts ca + ON sr.rep_id = ca.rep_id + AND ca.account_name LIKE '%Corp%' +ORDER BY sr."name"; + +-- Complex nested join conditions +SELECT + sr."name", ca.account_name, ca.revenue +FROM sales_reps sr +INNER JOIN customer_accounts ca ON ( + sr.rep_id = ca.rep_id + AND (ca.revenue > 100000 OR sr.quota < 130000) + AND sr.region IN ('North', 'South') +) +ORDER BY ca.revenue DESC; + +DROP TABLE sales_reps; + +DROP TABLE customer_accounts; diff --git a/tests/cases/standalone/common/join/join_distinct.result b/tests/cases/standalone/common/join/join_distinct.result new file mode 100644 index 0000000000..10908fc22a --- /dev/null +++ b/tests/cases/standalone/common/join/join_distinct.result @@ -0,0 +1,53 @@ +-- Tests joins with DISTINCT operations +CREATE TABLE products_dist(prod_id INTEGER, prod_name VARCHAR, category VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE sales_dist(sale_id INTEGER, prod_id INTEGER, customer VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO products_dist VALUES (1, 'Widget', 'Tools', 1000), (2, 'Gadget', 'Electronics', 2000); + +Affected Rows: 2 + +INSERT INTO sales_dist VALUES (1, 1, 'Alice', 1000), (2, 1, 'Bob', 2000), (3, 2, 'Alice', 3000), (4, 1, 'Alice', 4000); + +Affected Rows: 4 + +SELECT DISTINCT p.category FROM products_dist p INNER JOIN sales_dist s ON p.prod_id = s.prod_id ORDER BY p.category; + ++-------------+ +| category | ++-------------+ +| Electronics | +| Tools | ++-------------+ + +SELECT DISTINCT s.customer, p.category FROM sales_dist s INNER JOIN products_dist p ON s.prod_id = p.prod_id ORDER BY s.customer, p.category; + ++----------+-------------+ +| customer | category | ++----------+-------------+ +| Alice | Electronics | +| Alice | Tools | +| Bob | Tools | ++----------+-------------+ + +SELECT p.prod_name, COUNT(DISTINCT s.customer) as unique_customers FROM products_dist p LEFT JOIN sales_dist s ON p.prod_id = s.prod_id GROUP BY p.prod_id, p.prod_name ORDER BY unique_customers DESC; + ++-----------+------------------+ +| prod_name | unique_customers | ++-----------+------------------+ +| Widget | 2 | +| Gadget | 1 | ++-----------+------------------+ + +DROP TABLE products_dist; + +Affected Rows: 0 + +DROP TABLE sales_dist; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_distinct.sql b/tests/cases/standalone/common/join/join_distinct.sql new file mode 100644 index 0000000000..b00c065dfb --- /dev/null +++ b/tests/cases/standalone/common/join/join_distinct.sql @@ -0,0 +1,18 @@ +-- Tests joins with DISTINCT operations + +CREATE TABLE products_dist(prod_id INTEGER, prod_name VARCHAR, category VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE sales_dist(sale_id INTEGER, prod_id INTEGER, customer VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO products_dist VALUES (1, 'Widget', 'Tools', 1000), (2, 'Gadget', 'Electronics', 2000); + +INSERT INTO sales_dist VALUES (1, 1, 'Alice', 1000), (2, 1, 'Bob', 2000), (3, 2, 'Alice', 3000), (4, 1, 'Alice', 4000); + +SELECT DISTINCT p.category FROM products_dist p INNER JOIN sales_dist s ON p.prod_id = s.prod_id ORDER BY p.category; + +SELECT DISTINCT s.customer, p.category FROM sales_dist s INNER JOIN products_dist p ON s.prod_id = p.prod_id ORDER BY s.customer, p.category; +SELECT p.prod_name, COUNT(DISTINCT s.customer) as unique_customers FROM products_dist p LEFT JOIN sales_dist s ON p.prod_id = s.prod_id GROUP BY p.prod_id, p.prod_name ORDER BY unique_customers DESC; + +DROP TABLE products_dist; + +DROP TABLE sales_dist; diff --git a/tests/cases/standalone/common/join/join_edge_cases.result b/tests/cases/standalone/common/join/join_edge_cases.result new file mode 100644 index 0000000000..96bb68c1b0 --- /dev/null +++ b/tests/cases/standalone/common/join/join_edge_cases.result @@ -0,0 +1,62 @@ +-- Tests join edge cases and special scenarios +CREATE TABLE empty_table("id" INTEGER, "value" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE single_row("id" INTEGER, "data" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE duplicate_keys("id" INTEGER, "description" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO single_row VALUES (1, 'only_row', 1000); + +Affected Rows: 1 + +INSERT INTO duplicate_keys VALUES (1, 'first', 1000), (1, 'second', 2000), (2, 'third', 3000), (2, 'fourth', 4000); + +Affected Rows: 4 + +-- Join with empty table +SELECT s."id", s."data", e."value" FROM single_row s LEFT JOIN empty_table e ON s."id" = e."id" ORDER BY s."id"; + ++----+----------+-------+ +| id | data | value | ++----+----------+-------+ +| 1 | only_row | | ++----+----------+-------+ + +-- Join with duplicate keys +SELECT s."id", s."data", d."description" FROM single_row s LEFT JOIN duplicate_keys d ON s."id" = d."id" ORDER BY d."description"; + ++----+----------+-------------+ +| id | data | description | ++----+----------+-------------+ +| 1 | only_row | first | +| 1 | only_row | second | ++----+----------+-------------+ + +-- Self-join with duplicates +SELECT d1."description" as desc1, d2."description" as desc2 FROM duplicate_keys d1 INNER JOIN duplicate_keys d2 ON d1."id" = d2."id" AND d1.ts < d2.ts ORDER BY d1.ts, d2.ts; + ++-------+--------+ +| desc1 | desc2 | ++-------+--------+ +| first | second | +| third | fourth | ++-------+--------+ + +DROP TABLE empty_table; + +Affected Rows: 0 + +DROP TABLE single_row; + +Affected Rows: 0 + +DROP TABLE duplicate_keys; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_edge_cases.sql b/tests/cases/standalone/common/join/join_edge_cases.sql new file mode 100644 index 0000000000..54396a6599 --- /dev/null +++ b/tests/cases/standalone/common/join/join_edge_cases.sql @@ -0,0 +1,26 @@ +-- Tests join edge cases and special scenarios + +CREATE TABLE empty_table("id" INTEGER, "value" VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE single_row("id" INTEGER, "data" VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE duplicate_keys("id" INTEGER, "description" VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO single_row VALUES (1, 'only_row', 1000); + +INSERT INTO duplicate_keys VALUES (1, 'first', 1000), (1, 'second', 2000), (2, 'third', 3000), (2, 'fourth', 4000); + +-- Join with empty table +SELECT s."id", s."data", e."value" FROM single_row s LEFT JOIN empty_table e ON s."id" = e."id" ORDER BY s."id"; + +-- Join with duplicate keys +SELECT s."id", s."data", d."description" FROM single_row s LEFT JOIN duplicate_keys d ON s."id" = d."id" ORDER BY d."description"; + +-- Self-join with duplicates +SELECT d1."description" as desc1, d2."description" as desc2 FROM duplicate_keys d1 INNER JOIN duplicate_keys d2 ON d1."id" = d2."id" AND d1.ts < d2.ts ORDER BY d1.ts, d2.ts; + +DROP TABLE empty_table; + +DROP TABLE single_row; + +DROP TABLE duplicate_keys; diff --git a/tests/cases/standalone/common/join/join_large_tables.result b/tests/cases/standalone/common/join/join_large_tables.result new file mode 100644 index 0000000000..50d5a96878 --- /dev/null +++ b/tests/cases/standalone/common/join/join_large_tables.result @@ -0,0 +1,51 @@ +-- Tests joins with larger data sets +CREATE TABLE log_entries(log_id INTEGER, user_id INTEGER, "action" VARCHAR, timestamp_val BIGINT, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE user_profiles(user_id INTEGER, username VARCHAR, signup_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO log_entries VALUES +(1, 1, 'login', 1700000000, 1000), (2, 1, 'view_page', 1700000060, 2000), (3, 2, 'login', 1700000120, 3000), +(4, 2, 'purchase', 1700000180, 4000), (5, 3, 'login', 1700000240, 5000), (6, 1, 'logout', 1700000300, 6000), +(7, 3, 'view_page', 1700000360, 7000), (8, 2, 'logout', 1700000420, 8000), (9, 3, 'purchase', 1700000480, 9000), +(10, 1, 'view_page', 1700000540, 10000), (11, 2, 'view_page', 1700000600, 11000), (12, 3, 'logout', 1700000660, 12000); + +Affected Rows: 12 + +INSERT INTO user_profiles VALUES +(1, 'alice_user', '2022-01-15', 1000), (2, 'bob_user', '2022-03-20', 2000), (3, 'charlie_user', '2022-06-10', 3000); + +Affected Rows: 3 + +SELECT u.username, COUNT(l.log_id) as activity_count, COUNT(DISTINCT l.action) as unique_actions FROM user_profiles u LEFT JOIN log_entries l ON u.user_id = l.user_id GROUP BY u.user_id, u.username ORDER BY activity_count DESC, u.username DESC; + ++--------------+----------------+----------------+ +| username | activity_count | unique_actions | ++--------------+----------------+----------------+ +| charlie_user | 4 | 4 | +| bob_user | 4 | 4 | +| alice_user | 4 | 3 | ++--------------+----------------+----------------+ + +SELECT l."action", COUNT(DISTINCT l.user_id) as unique_users, COUNT(*) as total_actions FROM log_entries l INNER JOIN user_profiles u ON l.user_id = u.user_id GROUP BY l."action" ORDER BY total_actions DESC, l."action" ASC; + ++-----------+--------------+---------------+ +| action | unique_users | total_actions | ++-----------+--------------+---------------+ +| view_page | 3 | 4 | +| login | 3 | 3 | +| logout | 3 | 3 | +| purchase | 2 | 2 | ++-----------+--------------+---------------+ + +DROP TABLE log_entries; + +Affected Rows: 0 + +DROP TABLE user_profiles; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_large_tables.sql b/tests/cases/standalone/common/join/join_large_tables.sql new file mode 100644 index 0000000000..de69c0214e --- /dev/null +++ b/tests/cases/standalone/common/join/join_large_tables.sql @@ -0,0 +1,22 @@ +-- Tests joins with larger data sets + +CREATE TABLE log_entries(log_id INTEGER, user_id INTEGER, "action" VARCHAR, timestamp_val BIGINT, ts TIMESTAMP TIME INDEX); + +CREATE TABLE user_profiles(user_id INTEGER, username VARCHAR, signup_date DATE, ts TIMESTAMP TIME INDEX); + +INSERT INTO log_entries VALUES +(1, 1, 'login', 1700000000, 1000), (2, 1, 'view_page', 1700000060, 2000), (3, 2, 'login', 1700000120, 3000), +(4, 2, 'purchase', 1700000180, 4000), (5, 3, 'login', 1700000240, 5000), (6, 1, 'logout', 1700000300, 6000), +(7, 3, 'view_page', 1700000360, 7000), (8, 2, 'logout', 1700000420, 8000), (9, 3, 'purchase', 1700000480, 9000), +(10, 1, 'view_page', 1700000540, 10000), (11, 2, 'view_page', 1700000600, 11000), (12, 3, 'logout', 1700000660, 12000); + +INSERT INTO user_profiles VALUES +(1, 'alice_user', '2022-01-15', 1000), (2, 'bob_user', '2022-03-20', 2000), (3, 'charlie_user', '2022-06-10', 3000); + +SELECT u.username, COUNT(l.log_id) as activity_count, COUNT(DISTINCT l.action) as unique_actions FROM user_profiles u LEFT JOIN log_entries l ON u.user_id = l.user_id GROUP BY u.user_id, u.username ORDER BY activity_count DESC, u.username DESC; + +SELECT l."action", COUNT(DISTINCT l.user_id) as unique_users, COUNT(*) as total_actions FROM log_entries l INNER JOIN user_profiles u ON l.user_id = u.user_id GROUP BY l."action" ORDER BY total_actions DESC, l."action" ASC; + +DROP TABLE log_entries; + +DROP TABLE user_profiles; diff --git a/tests/cases/standalone/common/join/join_lateral.result b/tests/cases/standalone/common/join/join_lateral.result new file mode 100644 index 0000000000..e5e1685f7b --- /dev/null +++ b/tests/cases/standalone/common/join/join_lateral.result @@ -0,0 +1,44 @@ +-- Tests lateral join patterns and correlated subqueries +CREATE TABLE departments_lat(dept_id INTEGER, dept_name VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE employees_lat(emp_id INTEGER, dept_id INTEGER, salary INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO departments_lat VALUES (1, 'Engineering', 1000), (2, 'Sales', 2000), (3, 'Marketing', 3000); + +Affected Rows: 3 + +INSERT INTO employees_lat VALUES (1, 1, 75000, 1000), (2, 1, 80000, 2000), (3, 2, 65000, 3000), (4, 2, 70000, 4000), (5, 3, 60000, 5000); + +Affected Rows: 5 + +-- Correlated subquery simulating lateral join behavior +SELECT d.dept_name, top_earners.emp_id, top_earners.salary +FROM departments_lat d +INNER JOIN ( + SELECT emp_id, dept_id, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) as rn + FROM employees_lat +) top_earners ON d.dept_id = top_earners.dept_id AND top_earners.rn <= 2 +ORDER BY d.dept_id, top_earners.salary DESC; + ++-------------+--------+--------+ +| dept_name | emp_id | salary | ++-------------+--------+--------+ +| Engineering | 2 | 80000 | +| Engineering | 1 | 75000 | +| Sales | 4 | 70000 | +| Sales | 3 | 65000 | +| Marketing | 5 | 60000 | ++-------------+--------+--------+ + +DROP TABLE departments_lat; + +Affected Rows: 0 + +DROP TABLE employees_lat; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_lateral.sql b/tests/cases/standalone/common/join/join_lateral.sql new file mode 100644 index 0000000000..314eeb0d64 --- /dev/null +++ b/tests/cases/standalone/common/join/join_lateral.sql @@ -0,0 +1,22 @@ +-- Tests lateral join patterns and correlated subqueries + +CREATE TABLE departments_lat(dept_id INTEGER, dept_name VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE employees_lat(emp_id INTEGER, dept_id INTEGER, salary INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO departments_lat VALUES (1, 'Engineering', 1000), (2, 'Sales', 2000), (3, 'Marketing', 3000); + +INSERT INTO employees_lat VALUES (1, 1, 75000, 1000), (2, 1, 80000, 2000), (3, 2, 65000, 3000), (4, 2, 70000, 4000), (5, 3, 60000, 5000); + +-- Correlated subquery simulating lateral join behavior +SELECT d.dept_name, top_earners.emp_id, top_earners.salary +FROM departments_lat d +INNER JOIN ( + SELECT emp_id, dept_id, salary, ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) as rn + FROM employees_lat +) top_earners ON d.dept_id = top_earners.dept_id AND top_earners.rn <= 2 +ORDER BY d.dept_id, top_earners.salary DESC; + +DROP TABLE departments_lat; + +DROP TABLE employees_lat; diff --git a/tests/cases/standalone/common/join/join_mixed_types.result b/tests/cases/standalone/common/join/join_mixed_types.result new file mode 100644 index 0000000000..804e67b82b --- /dev/null +++ b/tests/cases/standalone/common/join/join_mixed_types.result @@ -0,0 +1,44 @@ +-- Tests joins with mixed data types and conversions +CREATE TABLE numeric_keys(int_key INTEGER, float_key DOUBLE, str_val VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE string_keys(str_key VARCHAR, int_val INTEGER, desc_val VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO numeric_keys VALUES (1, 1.0, 'first', 1000), (2, 2.0, 'second', 2000), (3, 3.0, 'third', 3000); + +Affected Rows: 3 + +INSERT INTO string_keys VALUES ('1', 100, 'hundred', 1000), ('2', 200, 'two_hundred', 2000), ('4', 400, 'four_hundred', 3000); + +Affected Rows: 3 + +SELECT n.int_key, n.str_val, s.int_val, s.desc_val FROM numeric_keys n INNER JOIN string_keys s ON CAST(n.int_key AS VARCHAR) = s.str_key ORDER BY n.int_key; + ++---------+---------+---------+-------------+ +| int_key | str_val | int_val | desc_val | ++---------+---------+---------+-------------+ +| 1 | first | 100 | hundred | +| 2 | second | 200 | two_hundred | ++---------+---------+---------+-------------+ + +SELECT n.float_key, s.str_key, s.int_val FROM numeric_keys n LEFT JOIN string_keys s ON n.float_key = CAST(s.str_key AS DOUBLE) ORDER BY n.float_key; + ++-----------+---------+---------+ +| float_key | str_key | int_val | ++-----------+---------+---------+ +| 1.0 | 1 | 100 | +| 2.0 | 2 | 200 | +| 3.0 | | | ++-----------+---------+---------+ + +DROP TABLE numeric_keys; + +Affected Rows: 0 + +DROP TABLE string_keys; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_mixed_types.sql b/tests/cases/standalone/common/join/join_mixed_types.sql new file mode 100644 index 0000000000..755a172b12 --- /dev/null +++ b/tests/cases/standalone/common/join/join_mixed_types.sql @@ -0,0 +1,17 @@ +-- Tests joins with mixed data types and conversions + +CREATE TABLE numeric_keys(int_key INTEGER, float_key DOUBLE, str_val VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE string_keys(str_key VARCHAR, int_val INTEGER, desc_val VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO numeric_keys VALUES (1, 1.0, 'first', 1000), (2, 2.0, 'second', 2000), (3, 3.0, 'third', 3000); + +INSERT INTO string_keys VALUES ('1', 100, 'hundred', 1000), ('2', 200, 'two_hundred', 2000), ('4', 400, 'four_hundred', 3000); + +SELECT n.int_key, n.str_val, s.int_val, s.desc_val FROM numeric_keys n INNER JOIN string_keys s ON CAST(n.int_key AS VARCHAR) = s.str_key ORDER BY n.int_key; + +SELECT n.float_key, s.str_key, s.int_val FROM numeric_keys n LEFT JOIN string_keys s ON n.float_key = CAST(s.str_key AS DOUBLE) ORDER BY n.float_key; + +DROP TABLE numeric_keys; + +DROP TABLE string_keys; diff --git a/tests/cases/standalone/common/join/join_null_handling.result b/tests/cases/standalone/common/join/join_null_handling.result new file mode 100644 index 0000000000..1196217de2 --- /dev/null +++ b/tests/cases/standalone/common/join/join_null_handling.result @@ -0,0 +1,121 @@ +-- Migrated from DuckDB test: test/sql/join/ NULL handling tests +-- Tests join behavior with NULL values +CREATE TABLE table_with_nulls("id" INTEGER, "value" VARCHAR, category INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE lookup_table(category INTEGER, cat_name VARCHAR, priority INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO table_with_nulls VALUES +(1, 'item1', 1, 1000), (2, 'item2', NULL, 2000), (3, 'item3', 2, 3000), +(4, 'item4', NULL, 4000), (5, 'item5', 3, 5000), (6, 'item6', 1, 6000); + +Affected Rows: 6 + +INSERT INTO lookup_table VALUES +(1, 'Category A', 1, 1000), (2, 'Category B', 2, 2000), (3, 'Category C', 3, 3000), (NULL, 'Unknown', 0, 4000); + +Affected Rows: 4 + +-- Inner join with NULLs (NULLs won't match) +SELECT t."id", t."value", l.cat_name +FROM table_with_nulls t +INNER JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + ++----+-------+------------+ +| id | value | cat_name | ++----+-------+------------+ +| 1 | item1 | Category A | +| 3 | item3 | Category B | +| 5 | item5 | Category C | +| 6 | item6 | Category A | ++----+-------+------------+ + +-- Left join with NULLs +SELECT t."id", t."value", t.category, COALESCE(l.cat_name, 'No Category') as category_name +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + ++----+-------+----------+---------------+ +| id | value | category | category_name | ++----+-------+----------+---------------+ +| 1 | item1 | 1 | Category A | +| 2 | item2 | | No Category | +| 3 | item3 | 2 | Category B | +| 4 | item4 | | No Category | +| 5 | item5 | 3 | Category C | +| 6 | item6 | 1 | Category A | ++----+-------+----------+---------------+ + +-- Join with explicit NULL handling +SELECT + t."id", t."value", + CASE + WHEN t.category IS NULL THEN 'NULL Category' + WHEN l.cat_name IS NULL THEN 'Missing Lookup' + ELSE l.cat_name + END as resolved_category +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + ++----+-------+-------------------+ +| id | value | resolved_category | ++----+-------+-------------------+ +| 1 | item1 | Category A | +| 2 | item2 | NULL Category | +| 3 | item3 | Category B | +| 4 | item4 | NULL Category | +| 5 | item5 | Category C | +| 6 | item6 | Category A | ++----+-------+-------------------+ + +-- NULL-safe join using COALESCE +SELECT t."id", t."value", l.cat_name +FROM table_with_nulls t +INNER JOIN lookup_table l ON COALESCE(t.category, -1) = COALESCE(l.category, -1) +ORDER BY t."id"; + ++----+-------+------------+ +| id | value | cat_name | ++----+-------+------------+ +| 1 | item1 | Category A | +| 2 | item2 | Unknown | +| 3 | item3 | Category B | +| 4 | item4 | Unknown | +| 5 | item5 | Category C | +| 6 | item6 | Category A | ++----+-------+------------+ + +-- Aggregation with NULL join results +SELECT + COALESCE(l.cat_name, 'Uncategorized') as category, + COUNT(*) as item_count, + COUNT(l.category) as matched_count, + COUNT(*) - COUNT(l.category) as unmatched_count +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +GROUP BY l.cat_name +ORDER BY item_count DESC, category DESC; + ++---------------+------------+---------------+-----------------+ +| category | item_count | matched_count | unmatched_count | ++---------------+------------+---------------+-----------------+ +| Uncategorized | 2 | 0 | 2 | +| Category A | 2 | 2 | 0 | +| Category C | 1 | 1 | 0 | +| Category B | 1 | 1 | 0 | ++---------------+------------+---------------+-----------------+ + +DROP TABLE table_with_nulls; + +Affected Rows: 0 + +DROP TABLE lookup_table; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_null_handling.sql b/tests/cases/standalone/common/join/join_null_handling.sql new file mode 100644 index 0000000000..3c1f07caa3 --- /dev/null +++ b/tests/cases/standalone/common/join/join_null_handling.sql @@ -0,0 +1,58 @@ +-- Migrated from DuckDB test: test/sql/join/ NULL handling tests +-- Tests join behavior with NULL values + +CREATE TABLE table_with_nulls("id" INTEGER, "value" VARCHAR, category INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE lookup_table(category INTEGER, cat_name VARCHAR, priority INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO table_with_nulls VALUES +(1, 'item1', 1, 1000), (2, 'item2', NULL, 2000), (3, 'item3', 2, 3000), +(4, 'item4', NULL, 4000), (5, 'item5', 3, 5000), (6, 'item6', 1, 6000); + +INSERT INTO lookup_table VALUES +(1, 'Category A', 1, 1000), (2, 'Category B', 2, 2000), (3, 'Category C', 3, 3000), (NULL, 'Unknown', 0, 4000); + +-- Inner join with NULLs (NULLs won't match) +SELECT t."id", t."value", l.cat_name +FROM table_with_nulls t +INNER JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + +-- Left join with NULLs +SELECT t."id", t."value", t.category, COALESCE(l.cat_name, 'No Category') as category_name +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + +-- Join with explicit NULL handling +SELECT + t."id", t."value", + CASE + WHEN t.category IS NULL THEN 'NULL Category' + WHEN l.cat_name IS NULL THEN 'Missing Lookup' + ELSE l.cat_name + END as resolved_category +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +ORDER BY t."id"; + +-- NULL-safe join using COALESCE +SELECT t."id", t."value", l.cat_name +FROM table_with_nulls t +INNER JOIN lookup_table l ON COALESCE(t.category, -1) = COALESCE(l.category, -1) +ORDER BY t."id"; + +-- Aggregation with NULL join results +SELECT + COALESCE(l.cat_name, 'Uncategorized') as category, + COUNT(*) as item_count, + COUNT(l.category) as matched_count, + COUNT(*) - COUNT(l.category) as unmatched_count +FROM table_with_nulls t +LEFT JOIN lookup_table l ON t.category = l.category +GROUP BY l.cat_name +ORDER BY item_count DESC, category DESC; + +DROP TABLE table_with_nulls; + +DROP TABLE lookup_table; diff --git a/tests/cases/standalone/common/join/join_ordering.result b/tests/cases/standalone/common/join/join_ordering.result new file mode 100644 index 0000000000..94e3e55aa8 --- /dev/null +++ b/tests/cases/standalone/common/join/join_ordering.result @@ -0,0 +1,46 @@ +-- Tests join result ordering and deterministic behavior +CREATE TABLE left_data("id" INTEGER, left_val VARCHAR, sort_key INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE right_data("id" INTEGER, right_val VARCHAR, sort_key INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO left_data VALUES (1, 'A1', 10, 1000), (2, 'A2', 5, 2000), (3, 'A3', 15, 3000); + +Affected Rows: 3 + +INSERT INTO right_data VALUES (1, 'B1', 20, 1000), (2, 'B2', 8, 2000), (4, 'B4', 12, 3000); + +Affected Rows: 3 + +SELECT l."id", l.left_val, r.right_val, l.sort_key + r.sort_key as combined_sort +FROM left_data l INNER JOIN right_data r ON l."id" = r."id" ORDER BY l."id"; + ++----+----------+-----------+---------------+ +| id | left_val | right_val | combined_sort | ++----+----------+-----------+---------------+ +| 1 | A1 | B1 | 30 | +| 2 | A2 | B2 | 13 | ++----+----------+-----------+---------------+ + +SELECT l.left_val, r.right_val FROM left_data l FULL OUTER JOIN right_data r ON l."id" = r."id" ORDER BY l."id", r."id"; + ++----------+-----------+ +| left_val | right_val | ++----------+-----------+ +| A1 | B1 | +| A2 | B2 | +| A3 | | +| | B4 | ++----------+-----------+ + +DROP TABLE left_data; + +Affected Rows: 0 + +DROP TABLE right_data; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_ordering.sql b/tests/cases/standalone/common/join/join_ordering.sql new file mode 100644 index 0000000000..9c0d9efb95 --- /dev/null +++ b/tests/cases/standalone/common/join/join_ordering.sql @@ -0,0 +1,18 @@ +-- Tests join result ordering and deterministic behavior + +CREATE TABLE left_data("id" INTEGER, left_val VARCHAR, sort_key INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE right_data("id" INTEGER, right_val VARCHAR, sort_key INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO left_data VALUES (1, 'A1', 10, 1000), (2, 'A2', 5, 2000), (3, 'A3', 15, 3000); + +INSERT INTO right_data VALUES (1, 'B1', 20, 1000), (2, 'B2', 8, 2000), (4, 'B4', 12, 3000); + +SELECT l."id", l.left_val, r.right_val, l.sort_key + r.sort_key as combined_sort +FROM left_data l INNER JOIN right_data r ON l."id" = r."id" ORDER BY l."id"; + +SELECT l.left_val, r.right_val FROM left_data l FULL OUTER JOIN right_data r ON l."id" = r."id" ORDER BY l."id", r."id"; + +DROP TABLE left_data; + +DROP TABLE right_data; diff --git a/tests/cases/standalone/common/join/join_performance_patterns.result b/tests/cases/standalone/common/join/join_performance_patterns.result new file mode 100644 index 0000000000..946edf3ae8 --- /dev/null +++ b/tests/cases/standalone/common/join/join_performance_patterns.result @@ -0,0 +1,121 @@ +-- Migrated from DuckDB test: test/sql/join/ performance pattern tests +-- Tests join patterns common in time-series queries +CREATE TABLE metrics_perf(metric_id INTEGER, metric_name VARCHAR, "value" DOUBLE, timestamp_val BIGINT, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE metadata_perf(metric_id INTEGER, unit VARCHAR, description VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE thresholds_perf(metric_id INTEGER, warning_level DOUBLE, critical_level DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO metrics_perf VALUES +(1, 'cpu_usage', 65.2, 1700000000, 1000), (1, 'cpu_usage', 72.1, 1700000060, 2000), +(2, 'memory_usage', 85.5, 1700000000, 3000), (2, 'memory_usage', 78.3, 1700000060, 4000), +(3, 'disk_io', 120.7, 1700000000, 5000), (3, 'disk_io', 95.2, 1700000060, 6000); + +Affected Rows: 6 + +INSERT INTO metadata_perf VALUES +(1, 'percent', 'CPU utilization percentage', 1000), +(2, 'percent', 'Memory utilization percentage', 2000), +(3, 'MB/s', 'Disk I/O throughput', 3000); + +Affected Rows: 3 + +INSERT INTO thresholds_perf VALUES +(1, 70.0, 90.0, 1000), (2, 80.0, 95.0, 2000), (3, 100.0, 150.0, 3000); + +Affected Rows: 3 + +-- Join for monitoring dashboard +SELECT + m.metric_name, + md.unit, + m."value", + t.warning_level, + t.critical_level, + CASE + WHEN m."value" >= t.critical_level THEN 'CRITICAL' + WHEN m."value" >= t.warning_level THEN 'WARNING' + ELSE 'OK' + END as status +FROM metrics_perf m +INNER JOIN metadata_perf md ON m.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON m.metric_id = t.metric_id +ORDER BY m.timestamp_val, m.metric_id; + ++--------------+---------+-------+---------------+----------------+---------+ +| metric_name | unit | value | warning_level | critical_level | status | ++--------------+---------+-------+---------------+----------------+---------+ +| cpu_usage | percent | 65.2 | 70.0 | 90.0 | OK | +| memory_usage | percent | 85.5 | 80.0 | 95.0 | WARNING | +| disk_io | MB/s | 120.7 | 100.0 | 150.0 | WARNING | +| cpu_usage | percent | 72.1 | 70.0 | 90.0 | WARNING | +| memory_usage | percent | 78.3 | 80.0 | 95.0 | OK | +| disk_io | MB/s | 95.2 | 100.0 | 150.0 | OK | ++--------------+---------+-------+---------------+----------------+---------+ + +-- Time-series join with latest values +SELECT + latest_metrics.metric_name, + latest_metrics.latest_value, + md.unit, + t.warning_level +FROM ( + SELECT + metric_id, + metric_name, + "value" as latest_value, + ROW_NUMBER() OVER (PARTITION BY metric_id ORDER BY timestamp_val DESC) as rn + FROM metrics_perf +) latest_metrics +INNER JOIN metadata_perf md ON latest_metrics.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON latest_metrics.metric_id = t.metric_id +WHERE latest_metrics.rn = 1 +ORDER BY latest_metrics.metric_id; + ++--------------+--------------+---------+---------------+ +| metric_name | latest_value | unit | warning_level | ++--------------+--------------+---------+---------------+ +| cpu_usage | 72.1 | percent | 70.0 | +| memory_usage | 78.3 | percent | 80.0 | +| disk_io | 95.2 | MB/s | 100.0 | ++--------------+--------------+---------+---------------+ + +-- Historical analysis join +SELECT + md.description, + COUNT(*) as total_readings, + AVG(m."value") as avg_value, + COUNT(CASE WHEN m."value" > t.warning_level THEN 1 END) as warning_count, + COUNT(CASE WHEN m."value" > t.critical_level THEN 1 END) as critical_count +FROM metrics_perf m +INNER JOIN metadata_perf md ON m.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON m.metric_id = t.metric_id +GROUP BY md.description, m.metric_id +ORDER BY critical_count DESC, warning_count DESC, avg_value DESC; + ++-------------------------------+----------------+-----------+---------------+----------------+ +| description | total_readings | avg_value | warning_count | critical_count | ++-------------------------------+----------------+-----------+---------------+----------------+ +| Disk I/O throughput | 2 | 107.95 | 1 | 0 | +| Memory utilization percentage | 2 | 81.9 | 1 | 0 | +| CPU utilization percentage | 2 | 68.65 | 1 | 0 | ++-------------------------------+----------------+-----------+---------------+----------------+ + +DROP TABLE metrics_perf; + +Affected Rows: 0 + +DROP TABLE metadata_perf; + +Affected Rows: 0 + +DROP TABLE thresholds_perf; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_performance_patterns.sql b/tests/cases/standalone/common/join/join_performance_patterns.sql new file mode 100644 index 0000000000..11afe36b62 --- /dev/null +++ b/tests/cases/standalone/common/join/join_performance_patterns.sql @@ -0,0 +1,76 @@ +-- Migrated from DuckDB test: test/sql/join/ performance pattern tests +-- Tests join patterns common in time-series queries + +CREATE TABLE metrics_perf(metric_id INTEGER, metric_name VARCHAR, "value" DOUBLE, timestamp_val BIGINT, ts TIMESTAMP TIME INDEX); + +CREATE TABLE metadata_perf(metric_id INTEGER, unit VARCHAR, description VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE thresholds_perf(metric_id INTEGER, warning_level DOUBLE, critical_level DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO metrics_perf VALUES +(1, 'cpu_usage', 65.2, 1700000000, 1000), (1, 'cpu_usage', 72.1, 1700000060, 2000), +(2, 'memory_usage', 85.5, 1700000000, 3000), (2, 'memory_usage', 78.3, 1700000060, 4000), +(3, 'disk_io', 120.7, 1700000000, 5000), (3, 'disk_io', 95.2, 1700000060, 6000); + +INSERT INTO metadata_perf VALUES +(1, 'percent', 'CPU utilization percentage', 1000), +(2, 'percent', 'Memory utilization percentage', 2000), +(3, 'MB/s', 'Disk I/O throughput', 3000); + +INSERT INTO thresholds_perf VALUES +(1, 70.0, 90.0, 1000), (2, 80.0, 95.0, 2000), (3, 100.0, 150.0, 3000); + +-- Join for monitoring dashboard +SELECT + m.metric_name, + md.unit, + m."value", + t.warning_level, + t.critical_level, + CASE + WHEN m."value" >= t.critical_level THEN 'CRITICAL' + WHEN m."value" >= t.warning_level THEN 'WARNING' + ELSE 'OK' + END as status +FROM metrics_perf m +INNER JOIN metadata_perf md ON m.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON m.metric_id = t.metric_id +ORDER BY m.timestamp_val, m.metric_id; + +-- Time-series join with latest values +SELECT + latest_metrics.metric_name, + latest_metrics.latest_value, + md.unit, + t.warning_level +FROM ( + SELECT + metric_id, + metric_name, + "value" as latest_value, + ROW_NUMBER() OVER (PARTITION BY metric_id ORDER BY timestamp_val DESC) as rn + FROM metrics_perf +) latest_metrics +INNER JOIN metadata_perf md ON latest_metrics.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON latest_metrics.metric_id = t.metric_id +WHERE latest_metrics.rn = 1 +ORDER BY latest_metrics.metric_id; + +-- Historical analysis join +SELECT + md.description, + COUNT(*) as total_readings, + AVG(m."value") as avg_value, + COUNT(CASE WHEN m."value" > t.warning_level THEN 1 END) as warning_count, + COUNT(CASE WHEN m."value" > t.critical_level THEN 1 END) as critical_count +FROM metrics_perf m +INNER JOIN metadata_perf md ON m.metric_id = md.metric_id +INNER JOIN thresholds_perf t ON m.metric_id = t.metric_id +GROUP BY md.description, m.metric_id +ORDER BY critical_count DESC, warning_count DESC, avg_value DESC; + +DROP TABLE metrics_perf; + +DROP TABLE metadata_perf; + +DROP TABLE thresholds_perf; diff --git a/tests/cases/standalone/common/join/join_pushdown.result b/tests/cases/standalone/common/join/join_pushdown.result new file mode 100644 index 0000000000..741a909c26 --- /dev/null +++ b/tests/cases/standalone/common/join/join_pushdown.result @@ -0,0 +1,44 @@ +-- Tests join predicate pushdown optimization scenarios +CREATE TABLE events_push(event_id INTEGER, user_id INTEGER, event_type VARCHAR, "value" INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE users_push(user_id INTEGER, user_name VARCHAR, region VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO events_push VALUES (1, 100, 'click', 1, 1000), (2, 100, 'view', 2, 2000), (3, 200, 'click', 1, 3000), (4, 300, 'purchase', 5, 4000); + +Affected Rows: 4 + +INSERT INTO users_push VALUES (100, 'Alice', 'US', 1000), (200, 'Bob', 'EU', 2000), (300, 'Charlie', 'US', 3000); + +Affected Rows: 3 + +SELECT e.event_type, u.region, COUNT(*) as event_count FROM events_push e INNER JOIN users_push u ON e.user_id = u.user_id WHERE u.region = 'US' GROUP BY e.event_type, u.region ORDER BY event_count DESC, e.event_type ASC; + ++------------+--------+-------------+ +| event_type | region | event_count | ++------------+--------+-------------+ +| click | US | 1 | +| purchase | US | 1 | +| view | US | 1 | ++------------+--------+-------------+ + +SELECT u.user_name, SUM(e."value") as total_value FROM users_push u INNER JOIN events_push e ON u.user_id = e.user_id WHERE e.event_type = 'click' GROUP BY u.user_id, u.user_name ORDER BY total_value DESC, u.user_name ASC; + ++-----------+-------------+ +| user_name | total_value | ++-----------+-------------+ +| Alice | 1 | +| Bob | 1 | ++-----------+-------------+ + +DROP TABLE events_push; + +Affected Rows: 0 + +DROP TABLE users_push; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_pushdown.sql b/tests/cases/standalone/common/join/join_pushdown.sql new file mode 100644 index 0000000000..abf2d62439 --- /dev/null +++ b/tests/cases/standalone/common/join/join_pushdown.sql @@ -0,0 +1,17 @@ +-- Tests join predicate pushdown optimization scenarios + +CREATE TABLE events_push(event_id INTEGER, user_id INTEGER, event_type VARCHAR, "value" INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE users_push(user_id INTEGER, user_name VARCHAR, region VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO events_push VALUES (1, 100, 'click', 1, 1000), (2, 100, 'view', 2, 2000), (3, 200, 'click', 1, 3000), (4, 300, 'purchase', 5, 4000); + +INSERT INTO users_push VALUES (100, 'Alice', 'US', 1000), (200, 'Bob', 'EU', 2000), (300, 'Charlie', 'US', 3000); + +SELECT e.event_type, u.region, COUNT(*) as event_count FROM events_push e INNER JOIN users_push u ON e.user_id = u.user_id WHERE u.region = 'US' GROUP BY e.event_type, u.region ORDER BY event_count DESC, e.event_type ASC; + +SELECT u.user_name, SUM(e."value") as total_value FROM users_push u INNER JOIN events_push e ON u.user_id = e.user_id WHERE e.event_type = 'click' GROUP BY u.user_id, u.user_name ORDER BY total_value DESC, u.user_name ASC; + +DROP TABLE events_push; + +DROP TABLE users_push; diff --git a/tests/cases/standalone/common/join/join_self_patterns.result b/tests/cases/standalone/common/join/join_self_patterns.result new file mode 100644 index 0000000000..63ae641750 --- /dev/null +++ b/tests/cases/standalone/common/join/join_self_patterns.result @@ -0,0 +1,38 @@ +-- Tests self-join patterns +CREATE TABLE employee_hierarchy(emp_id INTEGER, emp_name VARCHAR, manager_id INTEGER, level_num INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO employee_hierarchy VALUES +(1, 'CEO', NULL, 1, 1000), (2, 'CTO', 1, 2, 2000), (3, 'Dev1', 2, 3, 3000), (4, 'Dev2', 2, 3, 4000), (5, 'QA1', 2, 3, 5000); + +Affected Rows: 5 + +SELECT e.emp_name as employee, m.emp_name as manager FROM employee_hierarchy e LEFT JOIN employee_hierarchy m ON e.manager_id = m.emp_id ORDER BY e.level_num, e.emp_id; + ++----------+---------+ +| employee | manager | ++----------+---------+ +| CEO | | +| CTO | CEO | +| Dev1 | CTO | +| Dev2 | CTO | +| QA1 | CTO | ++----------+---------+ + +SELECT m.emp_name as manager, COUNT(e.emp_id) as direct_reports FROM employee_hierarchy m LEFT JOIN employee_hierarchy e ON m.emp_id = e.manager_id GROUP BY m.emp_id, m.emp_name ORDER BY direct_reports DESC, manager DESC; + ++---------+----------------+ +| manager | direct_reports | ++---------+----------------+ +| CTO | 3 | +| CEO | 1 | +| QA1 | 0 | +| Dev2 | 0 | +| Dev1 | 0 | ++---------+----------------+ + +DROP TABLE employee_hierarchy; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_self_patterns.sql b/tests/cases/standalone/common/join/join_self_patterns.sql new file mode 100644 index 0000000000..4b5b819b7a --- /dev/null +++ b/tests/cases/standalone/common/join/join_self_patterns.sql @@ -0,0 +1,11 @@ +-- Tests self-join patterns + +CREATE TABLE employee_hierarchy(emp_id INTEGER, emp_name VARCHAR, manager_id INTEGER, level_num INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO employee_hierarchy VALUES +(1, 'CEO', NULL, 1, 1000), (2, 'CTO', 1, 2, 2000), (3, 'Dev1', 2, 3, 3000), (4, 'Dev2', 2, 3, 4000), (5, 'QA1', 2, 3, 5000); + +SELECT e.emp_name as employee, m.emp_name as manager FROM employee_hierarchy e LEFT JOIN employee_hierarchy m ON e.manager_id = m.emp_id ORDER BY e.level_num, e.emp_id; +SELECT m.emp_name as manager, COUNT(e.emp_id) as direct_reports FROM employee_hierarchy m LEFT JOIN employee_hierarchy e ON m.emp_id = e.manager_id GROUP BY m.emp_id, m.emp_name ORDER BY direct_reports DESC, manager DESC; + +DROP TABLE employee_hierarchy; diff --git a/tests/cases/standalone/common/join/join_types.result b/tests/cases/standalone/common/join/join_types.result new file mode 100644 index 0000000000..d6f2a1c707 --- /dev/null +++ b/tests/cases/standalone/common/join/join_types.result @@ -0,0 +1,83 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_join_types.test +-- Tests different join types and conditions +CREATE TABLE customers("id" INTEGER, "name" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE orders(order_id INTEGER, customer_id INTEGER, amount INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO customers VALUES (1, 'Alice', 1000), (2, 'Bob', 2000), (3, 'Carol', 3000); + +Affected Rows: 3 + +INSERT INTO orders VALUES (101, 1, 100, 4000), (102, 1, 200, 5000), (103, 2, 150, 6000), (104, 4, 300, 7000); + +Affected Rows: 4 + +-- INNER JOIN +SELECT c."name", o.amount FROM customers c INNER JOIN orders o ON c."id" = o.customer_id ORDER BY c."name", o.amount; + ++-------+--------+ +| name | amount | ++-------+--------+ +| Alice | 100 | +| Alice | 200 | +| Bob | 150 | ++-------+--------+ + +-- LEFT JOIN showing unmatched customers +SELECT c."name", o.amount FROM customers c LEFT JOIN orders o ON c."id" = o.customer_id ORDER BY c."name", o.amount NULLS LAST; + ++-------+--------+ +| name | amount | ++-------+--------+ +| Alice | 100 | +| Alice | 200 | +| Bob | 150 | +| Carol | | ++-------+--------+ + +-- RIGHT JOIN showing unmatched orders +SELECT c."name", o.amount FROM customers c RIGHT JOIN orders o ON c."id" = o.customer_id ORDER BY c."name" NULLS LAST, o.amount; + ++-------+--------+ +| name | amount | ++-------+--------+ +| Alice | 100 | +| Alice | 200 | +| Bob | 150 | +| | 300 | ++-------+--------+ + +-- FULL OUTER JOIN showing all unmatched +SELECT c."name", o.amount FROM customers c FULL OUTER JOIN orders o ON c."id" = o.customer_id ORDER BY c."name" NULLS LAST, o.amount NULLS LAST; + ++-------+--------+ +| name | amount | ++-------+--------+ +| Alice | 100 | +| Alice | 200 | +| Bob | 150 | +| Carol | | +| | 300 | ++-------+--------+ + +-- JOIN with additional conditions +SELECT c."name", o.amount FROM customers c JOIN orders o ON c."id" = o.customer_id AND o.amount > 150 ORDER BY c."name"; + ++-------+--------+ +| name | amount | ++-------+--------+ +| Alice | 200 | ++-------+--------+ + +DROP TABLE orders; + +Affected Rows: 0 + +DROP TABLE customers; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_types.sql b/tests/cases/standalone/common/join/join_types.sql new file mode 100644 index 0000000000..7e932e0903 --- /dev/null +++ b/tests/cases/standalone/common/join/join_types.sql @@ -0,0 +1,29 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_join_types.test +-- Tests different join types and conditions + +CREATE TABLE customers("id" INTEGER, "name" VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE orders(order_id INTEGER, customer_id INTEGER, amount INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO customers VALUES (1, 'Alice', 1000), (2, 'Bob', 2000), (3, 'Carol', 3000); + +INSERT INTO orders VALUES (101, 1, 100, 4000), (102, 1, 200, 5000), (103, 2, 150, 6000), (104, 4, 300, 7000); + +-- INNER JOIN +SELECT c."name", o.amount FROM customers c INNER JOIN orders o ON c."id" = o.customer_id ORDER BY c."name", o.amount; + +-- LEFT JOIN showing unmatched customers +SELECT c."name", o.amount FROM customers c LEFT JOIN orders o ON c."id" = o.customer_id ORDER BY c."name", o.amount NULLS LAST; + +-- RIGHT JOIN showing unmatched orders +SELECT c."name", o.amount FROM customers c RIGHT JOIN orders o ON c."id" = o.customer_id ORDER BY c."name" NULLS LAST, o.amount; + +-- FULL OUTER JOIN showing all unmatched +SELECT c."name", o.amount FROM customers c FULL OUTER JOIN orders o ON c."id" = o.customer_id ORDER BY c."name" NULLS LAST, o.amount NULLS LAST; + +-- JOIN with additional conditions +SELECT c."name", o.amount FROM customers c JOIN orders o ON c."id" = o.customer_id AND o.amount > 150 ORDER BY c."name"; + +DROP TABLE orders; + +DROP TABLE customers; diff --git a/tests/cases/standalone/common/join/join_window_functions.result b/tests/cases/standalone/common/join/join_window_functions.result new file mode 100644 index 0000000000..658c847c27 --- /dev/null +++ b/tests/cases/standalone/common/join/join_window_functions.result @@ -0,0 +1,37 @@ +-- Tests joins combined with window functions +CREATE TABLE sales_data_win(rep_id INTEGER, sale_amount DOUBLE, sale_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE rep_targets(rep_id INTEGER, quarterly_target DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO sales_data_win VALUES (1, 1000, '2023-01-01', 1000), (1, 1500, '2023-01-15', 2000), (2, 800, '2023-01-02', 3000), (2, 1200, '2023-01-16', 4000), (3, 2000, '2023-01-03', 5000); + +Affected Rows: 5 + +INSERT INTO rep_targets VALUES (1, 5000, 1000), (2, 4000, 2000), (3, 6000, 3000); + +Affected Rows: 3 + +SELECT s.rep_id, s.sale_amount, rt.quarterly_target, SUM(s.sale_amount) OVER (PARTITION BY s.rep_id ORDER BY s.sale_date) as running_total FROM sales_data_win s INNER JOIN rep_targets rt ON s.rep_id = rt.rep_id ORDER BY s.rep_id, s.sale_date; + ++--------+-------------+------------------+---------------+ +| rep_id | sale_amount | quarterly_target | running_total | ++--------+-------------+------------------+---------------+ +| 1 | 1000.0 | 5000.0 | 1000.0 | +| 1 | 1500.0 | 5000.0 | 2500.0 | +| 2 | 800.0 | 4000.0 | 800.0 | +| 2 | 1200.0 | 4000.0 | 2000.0 | +| 3 | 2000.0 | 6000.0 | 2000.0 | ++--------+-------------+------------------+---------------+ + +DROP TABLE sales_data_win; + +Affected Rows: 0 + +DROP TABLE rep_targets; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_window_functions.sql b/tests/cases/standalone/common/join/join_window_functions.sql new file mode 100644 index 0000000000..b316e849e8 --- /dev/null +++ b/tests/cases/standalone/common/join/join_window_functions.sql @@ -0,0 +1,15 @@ +-- Tests joins combined with window functions + +CREATE TABLE sales_data_win(rep_id INTEGER, sale_amount DOUBLE, sale_date DATE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE rep_targets(rep_id INTEGER, quarterly_target DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO sales_data_win VALUES (1, 1000, '2023-01-01', 1000), (1, 1500, '2023-01-15', 2000), (2, 800, '2023-01-02', 3000), (2, 1200, '2023-01-16', 4000), (3, 2000, '2023-01-03', 5000); + +INSERT INTO rep_targets VALUES (1, 5000, 1000), (2, 4000, 2000), (3, 6000, 3000); + +SELECT s.rep_id, s.sale_amount, rt.quarterly_target, SUM(s.sale_amount) OVER (PARTITION BY s.rep_id ORDER BY s.sale_date) as running_total FROM sales_data_win s INNER JOIN rep_targets rt ON s.rep_id = rt.rep_id ORDER BY s.rep_id, s.sale_date; + +DROP TABLE sales_data_win; + +DROP TABLE rep_targets; diff --git a/tests/cases/standalone/common/join/join_with_aggregates.result b/tests/cases/standalone/common/join/join_with_aggregates.result new file mode 100644 index 0000000000..8f5e408038 --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_aggregates.result @@ -0,0 +1,146 @@ +-- Migrated from DuckDB test: test/sql/join/ join with aggregate tests +-- Tests joins combined with aggregate functions +CREATE TABLE sensors(sensor_id INTEGER, sensor_name VARCHAR, "location" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE readings(reading_id INTEGER, sensor_id INTEGER, "value" DOUBLE, reading_time TIMESTAMP, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO sensors VALUES +(1, 'TempSensor1', 'Room A', 1000), (2, 'TempSensor2', 'Room B', 2000), +(3, 'HumiditySensor1', 'Room A', 3000), (4, 'HumiditySensor2', 'Room B', 4000); + +Affected Rows: 4 + +INSERT INTO readings VALUES +(1, 1, 22.5, '2023-01-01 10:00:00', 1000), (2, 1, 23.1, '2023-01-01 11:00:00', 2000), +(3, 1, 21.8, '2023-01-01 12:00:00', 3000), (4, 2, 25.3, '2023-01-01 10:00:00', 4000), +(5, 2, 26.0, '2023-01-01 11:00:00', 5000), (6, 2, 24.7, '2023-01-01 12:00:00', 6000), +(7, 3, 45.2, '2023-01-01 10:00:00', 7000), (8, 3, 46.8, '2023-01-01 11:00:00', 8000), +(9, 4, 52.1, '2023-01-01 10:00:00', 9000), (10, 4, 51.3, '2023-01-01 11:00:00', 10000); + +Affected Rows: 10 + +-- Join with basic aggregation +SELECT + s.sensor_name, s."location", + COUNT(r.reading_id) as reading_count, + AVG(r."value") as avg_value, + MIN(r."value") as min_value, + MAX(r."value") as max_value +FROM sensors s +INNER JOIN readings r ON s.sensor_id = r.sensor_id +GROUP BY s.sensor_id, s.sensor_name, s."location" +ORDER BY s.sensor_name; + ++-----------------+----------+---------------+--------------------+-----------+-----------+ +| sensor_name | location | reading_count | avg_value | min_value | max_value | ++-----------------+----------+---------------+--------------------+-----------+-----------+ +| HumiditySensor1 | Room A | 2 | 46.0 | 45.2 | 46.8 | +| HumiditySensor2 | Room B | 2 | 51.7 | 51.3 | 52.1 | +| TempSensor1 | Room A | 3 | 22.46666666666667 | 21.8 | 23.1 | +| TempSensor2 | Room B | 3 | 25.333333333333332 | 24.7 | 26.0 | ++-----------------+----------+---------------+--------------------+-----------+-----------+ + +-- Join with time-based aggregation +SELECT + s."location", + DATE_TRUNC('hour', r.reading_time) as hour_bucket, + COUNT(*) as readings_per_hour, + AVG(r."value") as avg_hourly_value +FROM sensors s +INNER JOIN readings r ON s.sensor_id = r.sensor_id +GROUP BY s."location", DATE_TRUNC('hour', r.reading_time) +ORDER BY s."location", hour_bucket; + ++----------+---------------------+-------------------+------------------+ +| location | hour_bucket | readings_per_hour | avg_hourly_value | ++----------+---------------------+-------------------+------------------+ +| Room A | 2023-01-01T10:00:00 | 2 | 33.85 | +| Room A | 2023-01-01T11:00:00 | 2 | 34.95 | +| Room A | 2023-01-01T12:00:00 | 1 | 21.8 | +| Room B | 2023-01-01T10:00:00 | 2 | 38.7 | +| Room B | 2023-01-01T11:00:00 | 2 | 38.65 | +| Room B | 2023-01-01T12:00:00 | 1 | 24.7 | ++----------+---------------------+-------------------+------------------+ + +-- Aggregation before join +SELECT + s.sensor_name, s."location", agg_readings.avg_value, agg_readings.reading_count +FROM sensors s +INNER JOIN ( + SELECT sensor_id, AVG("value") as avg_value, COUNT(*) as reading_count + FROM readings + GROUP BY sensor_id +) agg_readings ON s.sensor_id = agg_readings.sensor_id +WHERE agg_readings.avg_value > 30.0 +ORDER BY agg_readings.avg_value DESC; + ++-----------------+----------+-----------+---------------+ +| sensor_name | location | avg_value | reading_count | ++-----------------+----------+-----------+---------------+ +| HumiditySensor2 | Room B | 51.7 | 2 | +| HumiditySensor1 | Room A | 46.0 | 2 | ++-----------------+----------+-----------+---------------+ + +-- Multiple aggregation levels with joins +SELECT + location_summary.location, + location_summary.sensor_count, + location_summary.avg_readings_per_sensor, + location_summary.location_avg_value +FROM ( + SELECT + s."location", + COUNT(DISTINCT s.sensor_id) as sensor_count, + COUNT(r.reading_id) / COUNT(DISTINCT s.sensor_id) as avg_readings_per_sensor, + AVG(r."value") as location_avg_value + FROM sensors s + INNER JOIN readings r ON s.sensor_id = r.sensor_id + GROUP BY s."location" +) location_summary +ORDER BY location_summary.location_avg_value DESC; + ++----------+--------------+-------------------------+--------------------+ +| location | sensor_count | avg_readings_per_sensor | location_avg_value | ++----------+--------------+-------------------------+--------------------+ +| Room B | 2 | 2 | 35.88 | +| Room A | 2 | 2 | 31.880000000000003 | ++----------+--------------+-------------------------+--------------------+ + +-- Join with aggregated conditions +SELECT + s.sensor_name, + high_readings.high_count, + high_readings.avg_high_value +FROM sensors s +INNER JOIN ( + SELECT + sensor_id, + COUNT(*) as high_count, + AVG("value") as avg_high_value + FROM readings + WHERE "value" > 25.0 + GROUP BY sensor_id + HAVING COUNT(*) >= 2 +) high_readings ON s.sensor_id = high_readings.sensor_id +ORDER BY high_readings.avg_high_value DESC; + ++-----------------+------------+----------------+ +| sensor_name | high_count | avg_high_value | ++-----------------+------------+----------------+ +| HumiditySensor2 | 2 | 51.7 | +| HumiditySensor1 | 2 | 46.0 | +| TempSensor2 | 2 | 25.65 | ++-----------------+------------+----------------+ + +DROP TABLE sensors; + +Affected Rows: 0 + +DROP TABLE readings; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_with_aggregates.sql b/tests/cases/standalone/common/join/join_with_aggregates.sql new file mode 100644 index 0000000000..ec9a546f62 --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_aggregates.sql @@ -0,0 +1,92 @@ +-- Migrated from DuckDB test: test/sql/join/ join with aggregate tests +-- Tests joins combined with aggregate functions + +CREATE TABLE sensors(sensor_id INTEGER, sensor_name VARCHAR, "location" VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE readings(reading_id INTEGER, sensor_id INTEGER, "value" DOUBLE, reading_time TIMESTAMP, ts TIMESTAMP TIME INDEX); + +INSERT INTO sensors VALUES +(1, 'TempSensor1', 'Room A', 1000), (2, 'TempSensor2', 'Room B', 2000), +(3, 'HumiditySensor1', 'Room A', 3000), (4, 'HumiditySensor2', 'Room B', 4000); + +INSERT INTO readings VALUES +(1, 1, 22.5, '2023-01-01 10:00:00', 1000), (2, 1, 23.1, '2023-01-01 11:00:00', 2000), +(3, 1, 21.8, '2023-01-01 12:00:00', 3000), (4, 2, 25.3, '2023-01-01 10:00:00', 4000), +(5, 2, 26.0, '2023-01-01 11:00:00', 5000), (6, 2, 24.7, '2023-01-01 12:00:00', 6000), +(7, 3, 45.2, '2023-01-01 10:00:00', 7000), (8, 3, 46.8, '2023-01-01 11:00:00', 8000), +(9, 4, 52.1, '2023-01-01 10:00:00', 9000), (10, 4, 51.3, '2023-01-01 11:00:00', 10000); + +-- Join with basic aggregation +SELECT + s.sensor_name, s."location", + COUNT(r.reading_id) as reading_count, + AVG(r."value") as avg_value, + MIN(r."value") as min_value, + MAX(r."value") as max_value +FROM sensors s +INNER JOIN readings r ON s.sensor_id = r.sensor_id +GROUP BY s.sensor_id, s.sensor_name, s."location" +ORDER BY s.sensor_name; + +-- Join with time-based aggregation +SELECT + s."location", + DATE_TRUNC('hour', r.reading_time) as hour_bucket, + COUNT(*) as readings_per_hour, + AVG(r."value") as avg_hourly_value +FROM sensors s +INNER JOIN readings r ON s.sensor_id = r.sensor_id +GROUP BY s."location", DATE_TRUNC('hour', r.reading_time) +ORDER BY s."location", hour_bucket; + +-- Aggregation before join +SELECT + s.sensor_name, s."location", agg_readings.avg_value, agg_readings.reading_count +FROM sensors s +INNER JOIN ( + SELECT sensor_id, AVG("value") as avg_value, COUNT(*) as reading_count + FROM readings + GROUP BY sensor_id +) agg_readings ON s.sensor_id = agg_readings.sensor_id +WHERE agg_readings.avg_value > 30.0 +ORDER BY agg_readings.avg_value DESC; + +-- Multiple aggregation levels with joins +SELECT + location_summary.location, + location_summary.sensor_count, + location_summary.avg_readings_per_sensor, + location_summary.location_avg_value +FROM ( + SELECT + s."location", + COUNT(DISTINCT s.sensor_id) as sensor_count, + COUNT(r.reading_id) / COUNT(DISTINCT s.sensor_id) as avg_readings_per_sensor, + AVG(r."value") as location_avg_value + FROM sensors s + INNER JOIN readings r ON s.sensor_id = r.sensor_id + GROUP BY s."location" +) location_summary +ORDER BY location_summary.location_avg_value DESC; + +-- Join with aggregated conditions +SELECT + s.sensor_name, + high_readings.high_count, + high_readings.avg_high_value +FROM sensors s +INNER JOIN ( + SELECT + sensor_id, + COUNT(*) as high_count, + AVG("value") as avg_high_value + FROM readings + WHERE "value" > 25.0 + GROUP BY sensor_id + HAVING COUNT(*) >= 2 +) high_readings ON s.sensor_id = high_readings.sensor_id +ORDER BY high_readings.avg_high_value DESC; + +DROP TABLE sensors; + +DROP TABLE readings; diff --git a/tests/cases/standalone/common/join/join_with_expressions.result b/tests/cases/standalone/common/join/join_with_expressions.result new file mode 100644 index 0000000000..ee8dec89ad --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_expressions.result @@ -0,0 +1,147 @@ +-- Migrated from DuckDB test: test/sql/join/ expression join tests +-- Tests joins with complex expressions +CREATE TABLE measurements(measure_id INTEGER, sensor_id INTEGER, reading DOUBLE, measure_time TIMESTAMP, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE sensor_config(sensor_id INTEGER, min_threshold DOUBLE, max_threshold DOUBLE, calibration_factor DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO measurements VALUES +(1, 1, 22.5, '2023-01-01 10:00:00', 1000), (2, 1, 45.8, '2023-01-01 10:05:00', 2000), +(3, 2, 18.2, '2023-01-01 10:00:00', 3000), (4, 2, 89.7, '2023-01-01 10:05:00', 4000), +(5, 3, 35.1, '2023-01-01 10:00:00', 5000), (6, 3, 42.3, '2023-01-01 10:05:00', 6000); + +Affected Rows: 6 + +INSERT INTO sensor_config VALUES +(1, 20.0, 50.0, 1.05, 1000), (2, 15.0, 85.0, 0.98, 2000), (3, 30.0, 70.0, 1.02, 3000); + +Affected Rows: 3 + +-- Join with mathematical expressions +SELECT + m.measure_id, + m.reading, + m.reading * sc.calibration_factor as calibrated_reading, + sc.min_threshold, + sc.max_threshold +FROM measurements m +INNER JOIN sensor_config sc ON m.sensor_id = sc.sensor_id +ORDER BY m.measure_id; + ++------------+---------+--------------------+---------------+---------------+ +| measure_id | reading | calibrated_reading | min_threshold | max_threshold | ++------------+---------+--------------------+---------------+---------------+ +| 1 | 22.5 | 23.625 | 20.0 | 50.0 | +| 2 | 45.8 | 48.089999999999996 | 20.0 | 50.0 | +| 3 | 18.2 | 17.836 | 15.0 | 85.0 | +| 4 | 89.7 | 87.906 | 15.0 | 85.0 | +| 5 | 35.1 | 35.802 | 30.0 | 70.0 | +| 6 | 42.3 | 43.146 | 30.0 | 70.0 | ++------------+---------+--------------------+---------------+---------------+ + +-- Join with conditional expressions +SELECT + m.measure_id, + m.reading, + sc.calibration_factor, + CASE + WHEN m.reading * sc.calibration_factor < sc.min_threshold THEN 'Below Range' + WHEN m.reading * sc.calibration_factor > sc.max_threshold THEN 'Above Range' + ELSE 'In Range' + END as reading_status +FROM measurements m +INNER JOIN sensor_config sc + ON m.sensor_id = sc.sensor_id + AND m.reading BETWEEN sc.min_threshold * 0.5 AND sc.max_threshold * 1.5 +ORDER BY m.measure_id; + ++------------+---------+--------------------+----------------+ +| measure_id | reading | calibration_factor | reading_status | ++------------+---------+--------------------+----------------+ +| 1 | 22.5 | 1.05 | In Range | +| 2 | 45.8 | 1.05 | In Range | +| 3 | 18.2 | 0.98 | In Range | +| 4 | 89.7 | 0.98 | Above Range | +| 5 | 35.1 | 1.02 | In Range | +| 6 | 42.3 | 1.02 | In Range | ++------------+---------+--------------------+----------------+ + +-- Join with aggregated expressions +SELECT + sc.sensor_id, + COUNT(*) as total_readings, + AVG(m.reading * sc.calibration_factor) as avg_calibrated, + COUNT(CASE WHEN m.reading * sc.calibration_factor > sc.max_threshold THEN 1 END) as over_threshold_count +FROM measurements m +INNER JOIN sensor_config sc ON m.sensor_id = sc.sensor_id +GROUP BY sc.sensor_id, sc.calibration_factor, sc.max_threshold +HAVING AVG(m.reading * sc.calibration_factor) > 30.0 +ORDER BY avg_calibrated DESC; + ++-----------+----------------+--------------------+----------------------+ +| sensor_id | total_readings | avg_calibrated | over_threshold_count | ++-----------+----------------+--------------------+----------------------+ +| 2 | 2 | 52.871 | 1 | +| 3 | 2 | 39.474000000000004 | 0 | +| 1 | 2 | 35.8575 | 0 | ++-----------+----------------+--------------------+----------------------+ + +-- Join with string expression conditions +CREATE TABLE devices(device_id INTEGER, device_code VARCHAR, "status" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE device_logs(log_id INTEGER, device_code VARCHAR, log_message VARCHAR, severity INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO devices VALUES +(1, 'DEV001', 'active', 1000), (2, 'DEV002', 'inactive', 2000), (3, 'DEV003', 'active', 3000); + +Affected Rows: 3 + +INSERT INTO device_logs VALUES +(1, 'DEV001', 'System started', 1, 1000), (2, 'DEV001', 'Warning detected', 2, 2000), +(3, 'DEV002', 'Error occurred', 3, 3000), (4, 'DEV003', 'Normal operation', 1, 4000); + +Affected Rows: 4 + +-- Join with string expression matching +SELECT + d.device_id, + d."status", + dl.log_message, + dl.severity +FROM devices d +INNER JOIN device_logs dl + ON UPPER(d.device_code) = UPPER(dl.device_code) + AND d."status" = 'active' +ORDER BY d.device_id, dl.severity DESC; + ++-----------+--------+------------------+----------+ +| device_id | status | log_message | severity | ++-----------+--------+------------------+----------+ +| 1 | active | Warning detected | 2 | +| 1 | active | System started | 1 | +| 3 | active | Normal operation | 1 | ++-----------+--------+------------------+----------+ + +DROP TABLE measurements; + +Affected Rows: 0 + +DROP TABLE sensor_config; + +Affected Rows: 0 + +DROP TABLE devices; + +Affected Rows: 0 + +DROP TABLE device_logs; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_with_expressions.sql b/tests/cases/standalone/common/join/join_with_expressions.sql new file mode 100644 index 0000000000..9778c5b3a2 --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_expressions.sql @@ -0,0 +1,84 @@ +-- Migrated from DuckDB test: test/sql/join/ expression join tests +-- Tests joins with complex expressions + +CREATE TABLE measurements(measure_id INTEGER, sensor_id INTEGER, reading DOUBLE, measure_time TIMESTAMP, ts TIMESTAMP TIME INDEX); + +CREATE TABLE sensor_config(sensor_id INTEGER, min_threshold DOUBLE, max_threshold DOUBLE, calibration_factor DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO measurements VALUES +(1, 1, 22.5, '2023-01-01 10:00:00', 1000), (2, 1, 45.8, '2023-01-01 10:05:00', 2000), +(3, 2, 18.2, '2023-01-01 10:00:00', 3000), (4, 2, 89.7, '2023-01-01 10:05:00', 4000), +(5, 3, 35.1, '2023-01-01 10:00:00', 5000), (6, 3, 42.3, '2023-01-01 10:05:00', 6000); + +INSERT INTO sensor_config VALUES +(1, 20.0, 50.0, 1.05, 1000), (2, 15.0, 85.0, 0.98, 2000), (3, 30.0, 70.0, 1.02, 3000); + +-- Join with mathematical expressions +SELECT + m.measure_id, + m.reading, + m.reading * sc.calibration_factor as calibrated_reading, + sc.min_threshold, + sc.max_threshold +FROM measurements m +INNER JOIN sensor_config sc ON m.sensor_id = sc.sensor_id +ORDER BY m.measure_id; + +-- Join with conditional expressions +SELECT + m.measure_id, + m.reading, + sc.calibration_factor, + CASE + WHEN m.reading * sc.calibration_factor < sc.min_threshold THEN 'Below Range' + WHEN m.reading * sc.calibration_factor > sc.max_threshold THEN 'Above Range' + ELSE 'In Range' + END as reading_status +FROM measurements m +INNER JOIN sensor_config sc + ON m.sensor_id = sc.sensor_id + AND m.reading BETWEEN sc.min_threshold * 0.5 AND sc.max_threshold * 1.5 +ORDER BY m.measure_id; + +-- Join with aggregated expressions +SELECT + sc.sensor_id, + COUNT(*) as total_readings, + AVG(m.reading * sc.calibration_factor) as avg_calibrated, + COUNT(CASE WHEN m.reading * sc.calibration_factor > sc.max_threshold THEN 1 END) as over_threshold_count +FROM measurements m +INNER JOIN sensor_config sc ON m.sensor_id = sc.sensor_id +GROUP BY sc.sensor_id, sc.calibration_factor, sc.max_threshold +HAVING AVG(m.reading * sc.calibration_factor) > 30.0 +ORDER BY avg_calibrated DESC; + +-- Join with string expression conditions +CREATE TABLE devices(device_id INTEGER, device_code VARCHAR, "status" VARCHAR, ts TIMESTAMP TIME INDEX); +CREATE TABLE device_logs(log_id INTEGER, device_code VARCHAR, log_message VARCHAR, severity INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO devices VALUES +(1, 'DEV001', 'active', 1000), (2, 'DEV002', 'inactive', 2000), (3, 'DEV003', 'active', 3000); + +INSERT INTO device_logs VALUES +(1, 'DEV001', 'System started', 1, 1000), (2, 'DEV001', 'Warning detected', 2, 2000), +(3, 'DEV002', 'Error occurred', 3, 3000), (4, 'DEV003', 'Normal operation', 1, 4000); + +-- Join with string expression matching +SELECT + d.device_id, + d."status", + dl.log_message, + dl.severity +FROM devices d +INNER JOIN device_logs dl + ON UPPER(d.device_code) = UPPER(dl.device_code) + AND d."status" = 'active' +ORDER BY d.device_id, dl.severity DESC; + +DROP TABLE measurements; + +DROP TABLE sensor_config; + +DROP TABLE devices; + +DROP TABLE device_logs; diff --git a/tests/cases/standalone/common/join/join_with_nulls.result b/tests/cases/standalone/common/join/join_with_nulls.result new file mode 100644 index 0000000000..81fceba0ff --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_nulls.result @@ -0,0 +1,76 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_join_with_nulls.test +-- Tests JOIN behavior with NULL values +CREATE TABLE null_left("id" INTEGER, val VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE null_right("id" INTEGER, val VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO null_left VALUES (1, 'A', 1000), (2, NULL, 2000), (NULL, 'C', 3000), (4, 'D', 4000); + +Affected Rows: 4 + +INSERT INTO null_right VALUES (1, 'X', 5000), (NULL, 'Y', 6000), (3, 'Z', 7000), (4, NULL, 8000); + +Affected Rows: 4 + +-- INNER JOIN with NULLs (NULLs don't match) +SELECT * FROM null_left l INNER JOIN null_right r ON l."id" = r."id" ORDER BY l.ts; + ++----+-----+---------------------+----+-----+---------------------+ +| id | val | ts | id | val | ts | ++----+-----+---------------------+----+-----+---------------------+ +| 1 | A | 1970-01-01T00:00:01 | 1 | X | 1970-01-01T00:00:05 | +| 4 | D | 1970-01-01T00:00:04 | 4 | | 1970-01-01T00:00:08 | ++----+-----+---------------------+----+-----+---------------------+ + +-- LEFT JOIN with NULLs +SELECT * FROM null_left l LEFT JOIN null_right r ON l."id" = r."id" ORDER BY l.ts; + ++----+-----+---------------------+----+-----+---------------------+ +| id | val | ts | id | val | ts | ++----+-----+---------------------+----+-----+---------------------+ +| 1 | A | 1970-01-01T00:00:01 | 1 | X | 1970-01-01T00:00:05 | +| 2 | | 1970-01-01T00:00:02 | | | | +| | C | 1970-01-01T00:00:03 | | | | +| 4 | D | 1970-01-01T00:00:04 | 4 | | 1970-01-01T00:00:08 | ++----+-----+---------------------+----+-----+---------------------+ + +-- JOIN on string columns with NULLs +SELECT * FROM null_left l INNER JOIN null_right r ON l.val = r.val ORDER BY l.ts; + +++ +++ + +-- JOIN with IS NOT DISTINCT FROM (treats NULL=NULL as true) +SELECT * FROM null_left l INNER JOIN null_right r ON l."id" IS NOT DISTINCT FROM r."id" ORDER BY l.ts; + ++----+-----+---------------------+----+-----+---------------------+ +| id | val | ts | id | val | ts | ++----+-----+---------------------+----+-----+---------------------+ +| 1 | A | 1970-01-01T00:00:01 | 1 | X | 1970-01-01T00:00:05 | +| | C | 1970-01-01T00:00:03 | | Y | 1970-01-01T00:00:06 | +| 4 | D | 1970-01-01T00:00:04 | 4 | | 1970-01-01T00:00:08 | ++----+-----+---------------------+----+-----+---------------------+ + +-- JOIN with NULL-safe conditions +SELECT * FROM null_left l INNER JOIN null_right r ON (l."id" = r."id" OR (l."id" IS NULL AND r."id" IS NULL)) ORDER BY l.ts; + ++----+-----+---------------------+----+-----+---------------------+ +| id | val | ts | id | val | ts | ++----+-----+---------------------+----+-----+---------------------+ +| 1 | A | 1970-01-01T00:00:01 | 1 | X | 1970-01-01T00:00:05 | +| | C | 1970-01-01T00:00:03 | | Y | 1970-01-01T00:00:06 | +| 4 | D | 1970-01-01T00:00:04 | 4 | | 1970-01-01T00:00:08 | ++----+-----+---------------------+----+-----+---------------------+ + +DROP TABLE null_right; + +Affected Rows: 0 + +DROP TABLE null_left; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_with_nulls.sql b/tests/cases/standalone/common/join/join_with_nulls.sql new file mode 100644 index 0000000000..55525f6cb6 --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_nulls.sql @@ -0,0 +1,29 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_join_with_nulls.test +-- Tests JOIN behavior with NULL values + +CREATE TABLE null_left("id" INTEGER, val VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE null_right("id" INTEGER, val VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO null_left VALUES (1, 'A', 1000), (2, NULL, 2000), (NULL, 'C', 3000), (4, 'D', 4000); + +INSERT INTO null_right VALUES (1, 'X', 5000), (NULL, 'Y', 6000), (3, 'Z', 7000), (4, NULL, 8000); + +-- INNER JOIN with NULLs (NULLs don't match) +SELECT * FROM null_left l INNER JOIN null_right r ON l."id" = r."id" ORDER BY l.ts; + +-- LEFT JOIN with NULLs +SELECT * FROM null_left l LEFT JOIN null_right r ON l."id" = r."id" ORDER BY l.ts; + +-- JOIN on string columns with NULLs +SELECT * FROM null_left l INNER JOIN null_right r ON l.val = r.val ORDER BY l.ts; + +-- JOIN with IS NOT DISTINCT FROM (treats NULL=NULL as true) +SELECT * FROM null_left l INNER JOIN null_right r ON l."id" IS NOT DISTINCT FROM r."id" ORDER BY l.ts; + +-- JOIN with NULL-safe conditions +SELECT * FROM null_left l INNER JOIN null_right r ON (l."id" = r."id" OR (l."id" IS NULL AND r."id" IS NULL)) ORDER BY l.ts; + +DROP TABLE null_right; + +DROP TABLE null_left; diff --git a/tests/cases/standalone/common/join/join_with_subqueries.result b/tests/cases/standalone/common/join/join_with_subqueries.result new file mode 100644 index 0000000000..86fb842392 --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_subqueries.result @@ -0,0 +1,155 @@ +-- Migrated from DuckDB test: test/sql/join/ subquery join tests +-- Tests joins involving subqueries +CREATE TABLE products_sub(prod_id INTEGER, prod_name VARCHAR, category_id INTEGER, price DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE categories_sub(cat_id INTEGER, cat_name VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE sales_sub(sale_id INTEGER, prod_id INTEGER, quantity INTEGER, sale_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO products_sub VALUES +(1, 'Laptop', 1, 1200.00, 1000), (2, 'Mouse', 2, 25.00, 2000), +(3, 'Monitor', 1, 400.00, 3000), (4, 'Keyboard', 2, 80.00, 4000), +(5, 'Tablet', 1, 600.00, 5000); + +Affected Rows: 5 + +INSERT INTO categories_sub VALUES +(1, 'Electronics', 1000), (2, 'Accessories', 2000); + +Affected Rows: 2 + +INSERT INTO sales_sub VALUES +(1, 1, 2, '2023-01-01', 1000), (2, 2, 10, '2023-01-02', 2000), +(3, 3, 1, '2023-01-03', 3000), (4, 1, 1, '2023-01-04', 4000), +(5, 4, 5, '2023-01-05', 5000), (6, 5, 2, '2023-01-06', 6000); + +Affected Rows: 6 + +-- Join with aggregated subquery +SELECT + p.prod_name, p.price, sales_summary.total_quantity, sales_summary.total_sales +FROM products_sub p +INNER JOIN ( + SELECT prod_id, SUM(quantity) as total_quantity, COUNT(*) as total_sales + FROM sales_sub + GROUP BY prod_id +) sales_summary ON p.prod_id = sales_summary.prod_id +ORDER BY sales_summary.total_quantity DESC; + ++-----------+--------+----------------+-------------+ +| prod_name | price | total_quantity | total_sales | ++-----------+--------+----------------+-------------+ +| Mouse | 25.0 | 10 | 1 | +| Keyboard | 80.0 | 5 | 1 | +| Laptop | 1200.0 | 3 | 2 | +| Tablet | 600.0 | 2 | 1 | +| Monitor | 400.0 | 1 | 1 | ++-----------+--------+----------------+-------------+ + +-- Join with filtered subquery +SELECT + p.prod_name, c.cat_name, high_sales.quantity +FROM products_sub p +INNER JOIN categories_sub c ON p.category_id = c.cat_id +INNER JOIN ( + SELECT prod_id, quantity + FROM sales_sub + WHERE quantity > 3 +) high_sales ON p.prod_id = high_sales.prod_id +ORDER BY high_sales.quantity DESC; + ++-----------+-------------+----------+ +| prod_name | cat_name | quantity | ++-----------+-------------+----------+ +| Mouse | Accessories | 10 | +| Keyboard | Accessories | 5 | ++-----------+-------------+----------+ + +-- Join with correlated subquery results +SELECT + p.prod_name, p.price, + (SELECT SUM(s.quantity) FROM sales_sub s WHERE s.prod_id = p.prod_id) as total_sold +FROM products_sub p +WHERE EXISTS (SELECT 1 FROM sales_sub s WHERE s.prod_id = p.prod_id) +ORDER BY total_sold DESC; + ++-----------+--------+------------+ +| prod_name | price | total_sold | ++-----------+--------+------------+ +| Mouse | 25.0 | 10 | +| Keyboard | 80.0 | 5 | +| Laptop | 1200.0 | 3 | +| Tablet | 600.0 | 2 | +| Monitor | 400.0 | 1 | ++-----------+--------+------------+ + +-- Join subquery with window functions +SELECT + p.prod_name, ranked_sales.quantity, ranked_sales.sale_rank +FROM products_sub p +INNER JOIN ( + SELECT + prod_id, quantity, + ROW_NUMBER() OVER (PARTITION BY prod_id ORDER BY quantity DESC) as sale_rank + FROM sales_sub +) ranked_sales ON p.prod_id = ranked_sales.prod_id +WHERE ranked_sales.sale_rank = 1 +ORDER BY ranked_sales.quantity DESC, p.prod_name ASC; + ++-----------+----------+-----------+ +| prod_name | quantity | sale_rank | ++-----------+----------+-----------+ +| Mouse | 10 | 1 | +| Keyboard | 5 | 1 | +| Laptop | 2 | 1 | +| Tablet | 2 | 1 | +| Monitor | 1 | 1 | ++-----------+----------+-----------+ + +-- Multiple subquery joins +SELECT + product_stats.prod_name, + product_stats.avg_price, + category_stats.category_sales +FROM ( + SELECT prod_id, prod_name, AVG(price) as avg_price, category_id + FROM products_sub + GROUP BY prod_id, prod_name, category_id +) product_stats +INNER JOIN ( + SELECT c.cat_id, c.cat_name, COUNT(s.sale_id) as category_sales + FROM categories_sub c + INNER JOIN products_sub p ON c.cat_id = p.category_id + INNER JOIN sales_sub s ON p.prod_id = s.prod_id + GROUP BY c.cat_id, c.cat_name +) category_stats ON product_stats.category_id = category_stats.cat_id +ORDER BY category_stats.category_sales DESC, product_stats.prod_name DESC; + ++-----------+-----------+----------------+ +| prod_name | avg_price | category_sales | ++-----------+-----------+----------------+ +| Tablet | 600.0 | 4 | +| Monitor | 400.0 | 4 | +| Laptop | 1200.0 | 4 | +| Mouse | 25.0 | 2 | +| Keyboard | 80.0 | 2 | ++-----------+-----------+----------------+ + +DROP TABLE products_sub; + +Affected Rows: 0 + +DROP TABLE categories_sub; + +Affected Rows: 0 + +DROP TABLE sales_sub; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/join_with_subqueries.sql b/tests/cases/standalone/common/join/join_with_subqueries.sql new file mode 100644 index 0000000000..c005da1f4e --- /dev/null +++ b/tests/cases/standalone/common/join/join_with_subqueries.sql @@ -0,0 +1,90 @@ +-- Migrated from DuckDB test: test/sql/join/ subquery join tests +-- Tests joins involving subqueries + +CREATE TABLE products_sub(prod_id INTEGER, prod_name VARCHAR, category_id INTEGER, price DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE categories_sub(cat_id INTEGER, cat_name VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE sales_sub(sale_id INTEGER, prod_id INTEGER, quantity INTEGER, sale_date DATE, ts TIMESTAMP TIME INDEX); + +INSERT INTO products_sub VALUES +(1, 'Laptop', 1, 1200.00, 1000), (2, 'Mouse', 2, 25.00, 2000), +(3, 'Monitor', 1, 400.00, 3000), (4, 'Keyboard', 2, 80.00, 4000), +(5, 'Tablet', 1, 600.00, 5000); + +INSERT INTO categories_sub VALUES +(1, 'Electronics', 1000), (2, 'Accessories', 2000); + +INSERT INTO sales_sub VALUES +(1, 1, 2, '2023-01-01', 1000), (2, 2, 10, '2023-01-02', 2000), +(3, 3, 1, '2023-01-03', 3000), (4, 1, 1, '2023-01-04', 4000), +(5, 4, 5, '2023-01-05', 5000), (6, 5, 2, '2023-01-06', 6000); + +-- Join with aggregated subquery +SELECT + p.prod_name, p.price, sales_summary.total_quantity, sales_summary.total_sales +FROM products_sub p +INNER JOIN ( + SELECT prod_id, SUM(quantity) as total_quantity, COUNT(*) as total_sales + FROM sales_sub + GROUP BY prod_id +) sales_summary ON p.prod_id = sales_summary.prod_id +ORDER BY sales_summary.total_quantity DESC; + +-- Join with filtered subquery +SELECT + p.prod_name, c.cat_name, high_sales.quantity +FROM products_sub p +INNER JOIN categories_sub c ON p.category_id = c.cat_id +INNER JOIN ( + SELECT prod_id, quantity + FROM sales_sub + WHERE quantity > 3 +) high_sales ON p.prod_id = high_sales.prod_id +ORDER BY high_sales.quantity DESC; + +-- Join with correlated subquery results +SELECT + p.prod_name, p.price, + (SELECT SUM(s.quantity) FROM sales_sub s WHERE s.prod_id = p.prod_id) as total_sold +FROM products_sub p +WHERE EXISTS (SELECT 1 FROM sales_sub s WHERE s.prod_id = p.prod_id) +ORDER BY total_sold DESC; + +-- Join subquery with window functions +SELECT + p.prod_name, ranked_sales.quantity, ranked_sales.sale_rank +FROM products_sub p +INNER JOIN ( + SELECT + prod_id, quantity, + ROW_NUMBER() OVER (PARTITION BY prod_id ORDER BY quantity DESC) as sale_rank + FROM sales_sub +) ranked_sales ON p.prod_id = ranked_sales.prod_id +WHERE ranked_sales.sale_rank = 1 +ORDER BY ranked_sales.quantity DESC, p.prod_name ASC; + +-- Multiple subquery joins +SELECT + product_stats.prod_name, + product_stats.avg_price, + category_stats.category_sales +FROM ( + SELECT prod_id, prod_name, AVG(price) as avg_price, category_id + FROM products_sub + GROUP BY prod_id, prod_name, category_id +) product_stats +INNER JOIN ( + SELECT c.cat_id, c.cat_name, COUNT(s.sale_id) as category_sales + FROM categories_sub c + INNER JOIN products_sub p ON c.cat_id = p.category_id + INNER JOIN sales_sub s ON p.prod_id = s.prod_id + GROUP BY c.cat_id, c.cat_name +) category_stats ON product_stats.category_id = category_stats.cat_id +ORDER BY category_stats.category_sales DESC, product_stats.prod_name DESC; + +DROP TABLE products_sub; + +DROP TABLE categories_sub; + +DROP TABLE sales_sub; diff --git a/tests/cases/standalone/common/join/left_join_patterns.result b/tests/cases/standalone/common/join/left_join_patterns.result new file mode 100644 index 0000000000..26ffe04d38 --- /dev/null +++ b/tests/cases/standalone/common/join/left_join_patterns.result @@ -0,0 +1,115 @@ +-- Migrated from DuckDB test: test/sql/join/left_outer/ pattern tests +-- Tests common left join patterns +CREATE TABLE accounts(acc_id INTEGER, acc_name VARCHAR, balance DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE transactions(txn_id INTEGER, acc_id INTEGER, amount DOUBLE, txn_type VARCHAR, txn_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO accounts VALUES +(1, 'Checking', 1500.00, 1000), (2, 'Savings', 5000.00, 2000), +(3, 'Credit', -250.00, 3000), (4, 'Investment', 10000.00, 4000); + +Affected Rows: 4 + +INSERT INTO transactions VALUES +(1, 1, -100.00, 'withdrawal', '2023-01-01', 1000), +(2, 1, 500.00, 'deposit', '2023-01-02', 2000), +(3, 2, 1000.00, 'deposit', '2023-01-01', 3000), +(4, 3, -50.00, 'purchase', '2023-01-03', 4000), +(5, 1, -25.00, 'fee', '2023-01-04', 5000); + +Affected Rows: 5 + +-- Left join to find accounts with/without transactions +SELECT + a.acc_name, a.balance, + COUNT(t.txn_id) as transaction_count, + COALESCE(SUM(t.amount), 0) as total_activity +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id +GROUP BY a.acc_id, a.acc_name, a.balance +ORDER BY transaction_count DESC; + ++------------+---------+-------------------+----------------+ +| acc_name | balance | transaction_count | total_activity | ++------------+---------+-------------------+----------------+ +| Checking | 1500.0 | 3 | 375.0 | +| Savings | 5000.0 | 1 | 1000.0 | +| Credit | -250.0 | 1 | -50.0 | +| Investment | 10000.0 | 0 | 0.0 | ++------------+---------+-------------------+----------------+ + +-- Left join with date filtering +SELECT + a.acc_name, + COUNT(t.txn_id) as recent_transactions, + SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) as deposits, + SUM(CASE WHEN t.amount < 0 THEN t.amount ELSE 0 END) as withdrawals +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id AND t.txn_date >= '2023-01-02' +GROUP BY a.acc_id, a.acc_name +ORDER BY recent_transactions DESC, a.acc_name ASC; + ++------------+---------------------+----------+-------------+ +| acc_name | recent_transactions | deposits | withdrawals | ++------------+---------------------+----------+-------------+ +| Checking | 2 | 500.0 | -25.0 | +| Credit | 1 | 0.0 | -50.0 | +| Investment | 0 | 0.0 | 0.0 | +| Savings | 0 | 0.0 | 0.0 | ++------------+---------------------+----------+-------------+ + +-- Left join NULL handling +SELECT + a.acc_name, + a.balance, + t.txn_id, + COALESCE(t.amount, 0) as transaction_amount, + CASE WHEN t.txn_id IS NULL THEN 'No Activity' ELSE 'Has Activity' END as status +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id +ORDER BY a.acc_id, t.txn_date; + ++------------+---------+--------+--------------------+--------------+ +| acc_name | balance | txn_id | transaction_amount | status | ++------------+---------+--------+--------------------+--------------+ +| Checking | 1500.0 | 1 | -100.0 | Has Activity | +| Checking | 1500.0 | 2 | 500.0 | Has Activity | +| Checking | 1500.0 | 5 | -25.0 | Has Activity | +| Savings | 5000.0 | 3 | 1000.0 | Has Activity | +| Credit | -250.0 | 4 | -50.0 | Has Activity | +| Investment | 10000.0 | | 0.0 | No Activity | ++------------+---------+--------+--------------------+--------------+ + +-- Left join with complex conditions +SELECT + a.acc_name, + COUNT(large_txn.txn_id) as large_transaction_count, + AVG(large_txn.amount) as avg_large_amount +FROM accounts a +LEFT JOIN ( + SELECT * FROM transactions WHERE ABS(amount) > 100.00 +) large_txn ON a.acc_id = large_txn.acc_id +GROUP BY a.acc_id, a.acc_name +ORDER BY large_transaction_count DESC, a.acc_name ASC; + ++------------+-------------------------+------------------+ +| acc_name | large_transaction_count | avg_large_amount | ++------------+-------------------------+------------------+ +| Checking | 1 | 500.0 | +| Savings | 1 | 1000.0 | +| Credit | 0 | | +| Investment | 0 | | ++------------+-------------------------+------------------+ + +DROP TABLE accounts; + +Affected Rows: 0 + +DROP TABLE transactions; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/left_join_patterns.sql b/tests/cases/standalone/common/join/left_join_patterns.sql new file mode 100644 index 0000000000..731a1acaf0 --- /dev/null +++ b/tests/cases/standalone/common/join/left_join_patterns.sql @@ -0,0 +1,65 @@ +-- Migrated from DuckDB test: test/sql/join/left_outer/ pattern tests +-- Tests common left join patterns + +CREATE TABLE accounts(acc_id INTEGER, acc_name VARCHAR, balance DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE transactions(txn_id INTEGER, acc_id INTEGER, amount DOUBLE, txn_type VARCHAR, txn_date DATE, ts TIMESTAMP TIME INDEX); + +INSERT INTO accounts VALUES +(1, 'Checking', 1500.00, 1000), (2, 'Savings', 5000.00, 2000), +(3, 'Credit', -250.00, 3000), (4, 'Investment', 10000.00, 4000); + +INSERT INTO transactions VALUES +(1, 1, -100.00, 'withdrawal', '2023-01-01', 1000), +(2, 1, 500.00, 'deposit', '2023-01-02', 2000), +(3, 2, 1000.00, 'deposit', '2023-01-01', 3000), +(4, 3, -50.00, 'purchase', '2023-01-03', 4000), +(5, 1, -25.00, 'fee', '2023-01-04', 5000); + +-- Left join to find accounts with/without transactions +SELECT + a.acc_name, a.balance, + COUNT(t.txn_id) as transaction_count, + COALESCE(SUM(t.amount), 0) as total_activity +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id +GROUP BY a.acc_id, a.acc_name, a.balance +ORDER BY transaction_count DESC; + +-- Left join with date filtering +SELECT + a.acc_name, + COUNT(t.txn_id) as recent_transactions, + SUM(CASE WHEN t.amount > 0 THEN t.amount ELSE 0 END) as deposits, + SUM(CASE WHEN t.amount < 0 THEN t.amount ELSE 0 END) as withdrawals +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id AND t.txn_date >= '2023-01-02' +GROUP BY a.acc_id, a.acc_name +ORDER BY recent_transactions DESC, a.acc_name ASC; + +-- Left join NULL handling +SELECT + a.acc_name, + a.balance, + t.txn_id, + COALESCE(t.amount, 0) as transaction_amount, + CASE WHEN t.txn_id IS NULL THEN 'No Activity' ELSE 'Has Activity' END as status +FROM accounts a +LEFT JOIN transactions t ON a.acc_id = t.acc_id +ORDER BY a.acc_id, t.txn_date; + +-- Left join with complex conditions +SELECT + a.acc_name, + COUNT(large_txn.txn_id) as large_transaction_count, + AVG(large_txn.amount) as avg_large_amount +FROM accounts a +LEFT JOIN ( + SELECT * FROM transactions WHERE ABS(amount) > 100.00 +) large_txn ON a.acc_id = large_txn.acc_id +GROUP BY a.acc_id, a.acc_name +ORDER BY large_transaction_count DESC, a.acc_name ASC; + +DROP TABLE accounts; + +DROP TABLE transactions; diff --git a/tests/cases/standalone/common/join/left_outer_join.result b/tests/cases/standalone/common/join/left_outer_join.result new file mode 100644 index 0000000000..0d4b49e1af --- /dev/null +++ b/tests/cases/standalone/common/join/left_outer_join.result @@ -0,0 +1,67 @@ +-- Migrated from DuckDB test: test/sql/join/left_outer/test_left_outer.test +-- Tests LEFT OUTER JOIN functionality +CREATE TABLE left_t (a INTEGER, b INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE right_t (a INTEGER, c INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO left_t VALUES (1, 10, 1000), (2, 20, 2000), (3, 30, 3000); + +Affected Rows: 3 + +INSERT INTO right_t VALUES (1, 100, 4000), (2, 200, 5000), (4, 400, 6000); + +Affected Rows: 3 + +-- Basic LEFT JOIN +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a ORDER BY left_t.a; + ++---+----+---------------------+---+-----+---------------------+ +| a | b | ts | a | c | ts | ++---+----+---------------------+---+-----+---------------------+ +| 1 | 10 | 1970-01-01T00:00:01 | 1 | 100 | 1970-01-01T00:00:04 | +| 2 | 20 | 1970-01-01T00:00:02 | 2 | 200 | 1970-01-01T00:00:05 | +| 3 | 30 | 1970-01-01T00:00:03 | | | | ++---+----+---------------------+---+-----+---------------------+ + +-- LEFT JOIN with WHERE on left table +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a WHERE left_t.b > 15 ORDER BY left_t.a; + ++---+----+---------------------+---+-----+---------------------+ +| a | b | ts | a | c | ts | ++---+----+---------------------+---+-----+---------------------+ +| 2 | 20 | 1970-01-01T00:00:02 | 2 | 200 | 1970-01-01T00:00:05 | +| 3 | 30 | 1970-01-01T00:00:03 | | | | ++---+----+---------------------+---+-----+---------------------+ + +-- LEFT JOIN with WHERE on joined result +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a WHERE right_t.c IS NULL ORDER BY left_t.a; + ++---+----+---------------------+---+---+----+ +| a | b | ts | a | c | ts | ++---+----+---------------------+---+---+----+ +| 3 | 30 | 1970-01-01T00:00:03 | | | | ++---+----+---------------------+---+---+----+ + +-- LEFT JOIN with complex condition +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a AND left_t.b < 25 ORDER BY left_t.a; + ++---+----+---------------------+---+-----+---------------------+ +| a | b | ts | a | c | ts | ++---+----+---------------------+---+-----+---------------------+ +| 1 | 10 | 1970-01-01T00:00:01 | 1 | 100 | 1970-01-01T00:00:04 | +| 2 | 20 | 1970-01-01T00:00:02 | 2 | 200 | 1970-01-01T00:00:05 | +| 3 | 30 | 1970-01-01T00:00:03 | | | | ++---+----+---------------------+---+-----+---------------------+ + +DROP TABLE right_t; + +Affected Rows: 0 + +DROP TABLE left_t; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/left_outer_join.sql b/tests/cases/standalone/common/join/left_outer_join.sql new file mode 100644 index 0000000000..76335e2866 --- /dev/null +++ b/tests/cases/standalone/common/join/left_outer_join.sql @@ -0,0 +1,26 @@ +-- Migrated from DuckDB test: test/sql/join/left_outer/test_left_outer.test +-- Tests LEFT OUTER JOIN functionality + +CREATE TABLE left_t (a INTEGER, b INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE right_t (a INTEGER, c INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO left_t VALUES (1, 10, 1000), (2, 20, 2000), (3, 30, 3000); + +INSERT INTO right_t VALUES (1, 100, 4000), (2, 200, 5000), (4, 400, 6000); + +-- Basic LEFT JOIN +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a ORDER BY left_t.a; + +-- LEFT JOIN with WHERE on left table +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a WHERE left_t.b > 15 ORDER BY left_t.a; + +-- LEFT JOIN with WHERE on joined result +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a WHERE right_t.c IS NULL ORDER BY left_t.a; + +-- LEFT JOIN with complex condition +SELECT * FROM left_t LEFT JOIN right_t ON left_t.a = right_t.a AND left_t.b < 25 ORDER BY left_t.a; + +DROP TABLE right_t; + +DROP TABLE left_t; diff --git a/tests/cases/standalone/common/join/multi_way_joins.result b/tests/cases/standalone/common/join/multi_way_joins.result new file mode 100644 index 0000000000..f0ad34ca7c --- /dev/null +++ b/tests/cases/standalone/common/join/multi_way_joins.result @@ -0,0 +1,183 @@ +-- Migrated from DuckDB test: test/sql/join/ multi-way join tests +-- Tests complex multi-table join scenarios +CREATE TABLE users_multi(user_id INTEGER, username VARCHAR, email VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE posts_multi(post_id INTEGER, user_id INTEGER, title VARCHAR, content TEXT, created_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE comments_multi(comment_id INTEGER, post_id INTEGER, user_id INTEGER, comment_text VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE likes_multi(like_id INTEGER, post_id INTEGER, user_id INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO users_multi VALUES +(1, 'alice', 'alice@example.com', 1000), (2, 'bob', 'bob@example.com', 2000), +(3, 'charlie', 'charlie@example.com', 3000), (4, 'diana', 'diana@example.com', 4000); + +Affected Rows: 4 + +INSERT INTO posts_multi VALUES +(1, 1, 'First Post', 'Hello World', '2023-01-01', 1000), +(2, 2, 'Tech Talk', 'About databases', '2023-01-02', 2000), +(3, 1, 'Update', 'Progress report', '2023-01-03', 3000), +(4, 3, 'Discussion', 'Open topic', '2023-01-04', 4000); + +Affected Rows: 4 + +INSERT INTO comments_multi VALUES +(1, 1, 2, 'Great post!', 1000), (2, 1, 3, 'Thanks for sharing', 2000), +(3, 2, 1, 'Very informative', 3000), (4, 3, 4, 'Keep it up', 4000), +(5, 4, 2, 'Interesting discussion', 5000); + +Affected Rows: 5 + +INSERT INTO likes_multi VALUES +(1, 1, 2, 1000), (2, 1, 3, 2000), (3, 1, 4, 3000), +(4, 2, 1, 4000), (5, 2, 3, 5000), (6, 3, 2, 6000); + +Affected Rows: 6 + +-- Four-way join +SELECT + u.username as author, + p.title, + c.comment_text, + commenter.username as commenter +FROM users_multi u +INNER JOIN posts_multi p ON u.user_id = p.user_id +INNER JOIN comments_multi c ON p.post_id = c.post_id +INNER JOIN users_multi commenter ON c.user_id = commenter.user_id +ORDER BY p.post_id, c.comment_id; + ++---------+------------+------------------------+-----------+ +| author | title | comment_text | commenter | ++---------+------------+------------------------+-----------+ +| alice | First Post | Great post! | bob | +| alice | First Post | Thanks for sharing | charlie | +| bob | Tech Talk | Very informative | alice | +| alice | Update | Keep it up | diana | +| charlie | Discussion | Interesting discussion | bob | ++---------+------------+------------------------+-----------+ + +-- Multi-way join with aggregation +SELECT + u.username, + COUNT(DISTINCT p.post_id) as posts_created, + COUNT(DISTINCT c.comment_id) as comments_made, + COUNT(DISTINCT l.like_id) as likes_given +FROM users_multi u +LEFT JOIN posts_multi p ON u.user_id = p.user_id +LEFT JOIN comments_multi c ON u.user_id = c.user_id +LEFT JOIN likes_multi l ON u.user_id = l.user_id +GROUP BY u.user_id, u.username +ORDER BY posts_created DESC, comments_made DESC; + ++----------+---------------+---------------+-------------+ +| username | posts_created | comments_made | likes_given | ++----------+---------------+---------------+-------------+ +| alice | 2 | 1 | 1 | +| bob | 1 | 2 | 2 | +| charlie | 1 | 1 | 2 | +| diana | 0 | 1 | 1 | ++----------+---------------+---------------+-------------+ + +-- Post engagement analysis +SELECT + p.title, + u.username as author, + COUNT(DISTINCT c.comment_id) as comment_count, + COUNT(DISTINCT l.like_id) as like_count, + COUNT(DISTINCT c.user_id) as unique_commenters +FROM posts_multi p +INNER JOIN users_multi u ON p.user_id = u.user_id +LEFT JOIN comments_multi c ON p.post_id = c.post_id +LEFT JOIN likes_multi l ON p.post_id = l.post_id +GROUP BY p.post_id, p.title, u.username +ORDER BY like_count DESC, comment_count DESC; + ++------------+---------+---------------+------------+-------------------+ +| title | author | comment_count | like_count | unique_commenters | ++------------+---------+---------------+------------+-------------------+ +| First Post | alice | 2 | 3 | 2 | +| Tech Talk | bob | 1 | 2 | 1 | +| Update | alice | 1 | 1 | 1 | +| Discussion | charlie | 1 | 0 | 1 | ++------------+---------+---------------+------------+-------------------+ + +-- Complex multi-join with conditions +SELECT + author.username as post_author, + p.title, + commenter.username as commenter, + liker.username as liker +FROM posts_multi p +INNER JOIN users_multi author ON p.user_id = author.user_id +INNER JOIN comments_multi c ON p.post_id = c.post_id +INNER JOIN users_multi commenter ON c.user_id = commenter.user_id +INNER JOIN likes_multi l ON p.post_id = l.post_id +INNER JOIN users_multi liker ON l.user_id = liker.user_id +WHERE author.user_id != commenter.user_id + AND author.user_id != liker.user_id +ORDER BY p.post_id, commenter.user_id, liker.user_id; + ++-------------+------------+-----------+---------+ +| post_author | title | commenter | liker | ++-------------+------------+-----------+---------+ +| alice | First Post | bob | bob | +| alice | First Post | bob | charlie | +| alice | First Post | bob | diana | +| alice | First Post | charlie | bob | +| alice | First Post | charlie | charlie | +| alice | First Post | charlie | diana | +| bob | Tech Talk | alice | alice | +| bob | Tech Talk | alice | charlie | +| alice | Update | diana | bob | ++-------------+------------+-----------+---------+ + +-- Multi-join with subqueries +SELECT + u.username, + popular_posts.title, + popular_posts.engagement_score +FROM users_multi u +INNER JOIN ( + SELECT + p.post_id, p.user_id, p.title, + COUNT(DISTINCT l.like_id) + COUNT(DISTINCT c.comment_id) as engagement_score + FROM posts_multi p + LEFT JOIN likes_multi l ON p.post_id = l.post_id + LEFT JOIN comments_multi c ON p.post_id = c.post_id + GROUP BY p.post_id, p.user_id, p.title + HAVING COUNT(DISTINCT l.like_id) + COUNT(DISTINCT c.comment_id) > 2 +) popular_posts ON u.user_id = popular_posts.user_id +ORDER BY popular_posts.engagement_score DESC; + ++----------+------------+------------------+ +| username | title | engagement_score | ++----------+------------+------------------+ +| alice | First Post | 5 | +| bob | Tech Talk | 3 | ++----------+------------+------------------+ + +DROP TABLE users_multi; + +Affected Rows: 0 + +DROP TABLE posts_multi; + +Affected Rows: 0 + +DROP TABLE comments_multi; + +Affected Rows: 0 + +DROP TABLE likes_multi; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/multi_way_joins.sql b/tests/cases/standalone/common/join/multi_way_joins.sql new file mode 100644 index 0000000000..c7039e5bed --- /dev/null +++ b/tests/cases/standalone/common/join/multi_way_joins.sql @@ -0,0 +1,110 @@ +-- Migrated from DuckDB test: test/sql/join/ multi-way join tests +-- Tests complex multi-table join scenarios + +CREATE TABLE users_multi(user_id INTEGER, username VARCHAR, email VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE posts_multi(post_id INTEGER, user_id INTEGER, title VARCHAR, content TEXT, created_date DATE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE comments_multi(comment_id INTEGER, post_id INTEGER, user_id INTEGER, comment_text VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE likes_multi(like_id INTEGER, post_id INTEGER, user_id INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO users_multi VALUES +(1, 'alice', 'alice@example.com', 1000), (2, 'bob', 'bob@example.com', 2000), +(3, 'charlie', 'charlie@example.com', 3000), (4, 'diana', 'diana@example.com', 4000); + +INSERT INTO posts_multi VALUES +(1, 1, 'First Post', 'Hello World', '2023-01-01', 1000), +(2, 2, 'Tech Talk', 'About databases', '2023-01-02', 2000), +(3, 1, 'Update', 'Progress report', '2023-01-03', 3000), +(4, 3, 'Discussion', 'Open topic', '2023-01-04', 4000); + +INSERT INTO comments_multi VALUES +(1, 1, 2, 'Great post!', 1000), (2, 1, 3, 'Thanks for sharing', 2000), +(3, 2, 1, 'Very informative', 3000), (4, 3, 4, 'Keep it up', 4000), +(5, 4, 2, 'Interesting discussion', 5000); + +INSERT INTO likes_multi VALUES +(1, 1, 2, 1000), (2, 1, 3, 2000), (3, 1, 4, 3000), +(4, 2, 1, 4000), (5, 2, 3, 5000), (6, 3, 2, 6000); + +-- Four-way join +SELECT + u.username as author, + p.title, + c.comment_text, + commenter.username as commenter +FROM users_multi u +INNER JOIN posts_multi p ON u.user_id = p.user_id +INNER JOIN comments_multi c ON p.post_id = c.post_id +INNER JOIN users_multi commenter ON c.user_id = commenter.user_id +ORDER BY p.post_id, c.comment_id; + +-- Multi-way join with aggregation +SELECT + u.username, + COUNT(DISTINCT p.post_id) as posts_created, + COUNT(DISTINCT c.comment_id) as comments_made, + COUNT(DISTINCT l.like_id) as likes_given +FROM users_multi u +LEFT JOIN posts_multi p ON u.user_id = p.user_id +LEFT JOIN comments_multi c ON u.user_id = c.user_id +LEFT JOIN likes_multi l ON u.user_id = l.user_id +GROUP BY u.user_id, u.username +ORDER BY posts_created DESC, comments_made DESC; + +-- Post engagement analysis +SELECT + p.title, + u.username as author, + COUNT(DISTINCT c.comment_id) as comment_count, + COUNT(DISTINCT l.like_id) as like_count, + COUNT(DISTINCT c.user_id) as unique_commenters +FROM posts_multi p +INNER JOIN users_multi u ON p.user_id = u.user_id +LEFT JOIN comments_multi c ON p.post_id = c.post_id +LEFT JOIN likes_multi l ON p.post_id = l.post_id +GROUP BY p.post_id, p.title, u.username +ORDER BY like_count DESC, comment_count DESC; + +-- Complex multi-join with conditions +SELECT + author.username as post_author, + p.title, + commenter.username as commenter, + liker.username as liker +FROM posts_multi p +INNER JOIN users_multi author ON p.user_id = author.user_id +INNER JOIN comments_multi c ON p.post_id = c.post_id +INNER JOIN users_multi commenter ON c.user_id = commenter.user_id +INNER JOIN likes_multi l ON p.post_id = l.post_id +INNER JOIN users_multi liker ON l.user_id = liker.user_id +WHERE author.user_id != commenter.user_id + AND author.user_id != liker.user_id +ORDER BY p.post_id, commenter.user_id, liker.user_id; + +-- Multi-join with subqueries +SELECT + u.username, + popular_posts.title, + popular_posts.engagement_score +FROM users_multi u +INNER JOIN ( + SELECT + p.post_id, p.user_id, p.title, + COUNT(DISTINCT l.like_id) + COUNT(DISTINCT c.comment_id) as engagement_score + FROM posts_multi p + LEFT JOIN likes_multi l ON p.post_id = l.post_id + LEFT JOIN comments_multi c ON p.post_id = c.post_id + GROUP BY p.post_id, p.user_id, p.title + HAVING COUNT(DISTINCT l.like_id) + COUNT(DISTINCT c.comment_id) > 2 +) popular_posts ON u.user_id = popular_posts.user_id +ORDER BY popular_posts.engagement_score DESC; + +DROP TABLE users_multi; + +DROP TABLE posts_multi; + +DROP TABLE comments_multi; + +DROP TABLE likes_multi; diff --git a/tests/cases/standalone/common/join/multiple_joins.result b/tests/cases/standalone/common/join/multiple_joins.result new file mode 100644 index 0000000000..d2b4d0cf78 --- /dev/null +++ b/tests/cases/standalone/common/join/multiple_joins.result @@ -0,0 +1,87 @@ +-- Migrated from DuckDB test: Multiple join scenarios +-- Tests multiple table joins +CREATE TABLE customers_multi("id" INTEGER, "name" VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE orders_multi("id" INTEGER, customer_id INTEGER, product_id INTEGER, amount DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE products_multi("id" INTEGER, "name" VARCHAR, category VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO customers_multi VALUES (1, 'Alice', 'NYC', 1000), (2, 'Bob', 'LA', 2000), (3, 'Carol', 'Chicago', 3000); + +Affected Rows: 3 + +INSERT INTO orders_multi VALUES (101, 1, 1001, 150.0, 4000), (102, 2, 1002, 200.0, 5000), (103, 1, 1001, 150.0, 6000); + +Affected Rows: 3 + +INSERT INTO products_multi VALUES (1001, 'Laptop', 'Electronics', 1000.0, 7000), (1002, 'Book', 'Education', 25.0, 8000); + +Affected Rows: 2 + +-- Three table join +SELECT c."name", p."name" as product, o.amount +FROM customers_multi c +JOIN orders_multi o ON c."id" = o.customer_id +JOIN products_multi p ON o.product_id = p."id" +ORDER BY c."name", p."name"; + ++-------+---------+--------+ +| name | product | amount | ++-------+---------+--------+ +| Alice | Laptop | 150.0 | +| Alice | Laptop | 150.0 | +| Bob | Book | 200.0 | ++-------+---------+--------+ + +-- Multiple joins with aggregation +SELECT + c.city, + p.category, + COUNT(*) as order_count, + SUM(o.amount) as total_amount +FROM customers_multi c +JOIN orders_multi o ON c."id" = o.customer_id +JOIN products_multi p ON o.product_id = p."id" +GROUP BY c.city, p.category +ORDER BY c.city, p.category; + ++------+-------------+-------------+--------------+ +| city | category | order_count | total_amount | ++------+-------------+-------------+--------------+ +| LA | Education | 1 | 200.0 | +| NYC | Electronics | 2 | 300.0 | ++------+-------------+-------------+--------------+ + +-- Mixed join types +SELECT c."name", p."name" as product, o.amount +FROM customers_multi c +LEFT JOIN orders_multi o ON c."id" = o.customer_id +INNER JOIN products_multi p ON o.product_id = p."id" +ORDER BY c."name", p."name" NULLS LAST; + ++-------+---------+--------+ +| name | product | amount | ++-------+---------+--------+ +| Alice | Laptop | 150.0 | +| Alice | Laptop | 150.0 | +| Bob | Book | 200.0 | ++-------+---------+--------+ + +DROP TABLE products_multi; + +Affected Rows: 0 + +DROP TABLE orders_multi; + +Affected Rows: 0 + +DROP TABLE customers_multi; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/multiple_joins.sql b/tests/cases/standalone/common/join/multiple_joins.sql new file mode 100644 index 0000000000..97f79c92f5 --- /dev/null +++ b/tests/cases/standalone/common/join/multiple_joins.sql @@ -0,0 +1,46 @@ +-- Migrated from DuckDB test: Multiple join scenarios +-- Tests multiple table joins + +CREATE TABLE customers_multi("id" INTEGER, "name" VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE orders_multi("id" INTEGER, customer_id INTEGER, product_id INTEGER, amount DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE products_multi("id" INTEGER, "name" VARCHAR, category VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO customers_multi VALUES (1, 'Alice', 'NYC', 1000), (2, 'Bob', 'LA', 2000), (3, 'Carol', 'Chicago', 3000); + +INSERT INTO orders_multi VALUES (101, 1, 1001, 150.0, 4000), (102, 2, 1002, 200.0, 5000), (103, 1, 1001, 150.0, 6000); + +INSERT INTO products_multi VALUES (1001, 'Laptop', 'Electronics', 1000.0, 7000), (1002, 'Book', 'Education', 25.0, 8000); + +-- Three table join +SELECT c."name", p."name" as product, o.amount +FROM customers_multi c +JOIN orders_multi o ON c."id" = o.customer_id +JOIN products_multi p ON o.product_id = p."id" +ORDER BY c."name", p."name"; + +-- Multiple joins with aggregation +SELECT + c.city, + p.category, + COUNT(*) as order_count, + SUM(o.amount) as total_amount +FROM customers_multi c +JOIN orders_multi o ON c."id" = o.customer_id +JOIN products_multi p ON o.product_id = p."id" +GROUP BY c.city, p.category +ORDER BY c.city, p.category; + +-- Mixed join types +SELECT c."name", p."name" as product, o.amount +FROM customers_multi c +LEFT JOIN orders_multi o ON c."id" = o.customer_id +INNER JOIN products_multi p ON o.product_id = p."id" +ORDER BY c."name", p."name" NULLS LAST; + +DROP TABLE products_multi; + +DROP TABLE orders_multi; + +DROP TABLE customers_multi; diff --git a/tests/cases/standalone/common/join/natural_join.result b/tests/cases/standalone/common/join/natural_join.result new file mode 100644 index 0000000000..6093fb23d3 --- /dev/null +++ b/tests/cases/standalone/common/join/natural_join.result @@ -0,0 +1,60 @@ +-- Migrated from DuckDB test: test/sql/join/natural/natural_join.test +-- Tests NATURAL JOIN functionality +CREATE TABLE emp_natural("id" INTEGER, "name" VARCHAR, dept_id INTEGER, ts1 TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE dept_natural(dept_id INTEGER, dept_name VARCHAR, ts2 TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO emp_natural VALUES (1, 'Alice', 10, 1000), (2, 'Bob', 20, 2000), (3, 'Carol', 10, 3000); + +Affected Rows: 3 + +INSERT INTO dept_natural VALUES (10, 'Engineering', 4000), (20, 'Sales', 5000), (30, 'Marketing', 6000); + +Affected Rows: 3 + +-- NATURAL JOIN (joins on common column names) +SELECT * FROM emp_natural NATURAL JOIN dept_natural ORDER BY "id"; + ++----+-------+---------------------+---------+-------------+---------------------+ +| id | name | ts1 | dept_id | dept_name | ts2 | ++----+-------+---------------------+---------+-------------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | 10 | Engineering | 1970-01-01T00:00:04 | +| 2 | Bob | 1970-01-01T00:00:02 | 20 | Sales | 1970-01-01T00:00:05 | +| 3 | Carol | 1970-01-01T00:00:03 | 10 | Engineering | 1970-01-01T00:00:04 | ++----+-------+---------------------+---------+-------------+---------------------+ + +-- NATURAL LEFT JOIN +SELECT * FROM emp_natural NATURAL LEFT JOIN dept_natural ORDER BY "id"; + ++----+-------+---------------------+---------+-------------+---------------------+ +| id | name | ts1 | dept_id | dept_name | ts2 | ++----+-------+---------------------+---------+-------------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | 10 | Engineering | 1970-01-01T00:00:04 | +| 2 | Bob | 1970-01-01T00:00:02 | 20 | Sales | 1970-01-01T00:00:05 | +| 3 | Carol | 1970-01-01T00:00:03 | 10 | Engineering | 1970-01-01T00:00:04 | ++----+-------+---------------------+---------+-------------+---------------------+ + +-- NATURAL RIGHT JOIN +SELECT * FROM emp_natural NATURAL RIGHT JOIN dept_natural ORDER BY dept_id; + ++----+-------+---------------------+---------+-------------+---------------------+ +| id | name | ts1 | dept_id | dept_name | ts2 | ++----+-------+---------------------+---------+-------------+---------------------+ +| 1 | Alice | 1970-01-01T00:00:01 | 10 | Engineering | 1970-01-01T00:00:04 | +| 3 | Carol | 1970-01-01T00:00:03 | 10 | Engineering | 1970-01-01T00:00:04 | +| 2 | Bob | 1970-01-01T00:00:02 | 20 | Sales | 1970-01-01T00:00:05 | +| | | | 30 | Marketing | 1970-01-01T00:00:06 | ++----+-------+---------------------+---------+-------------+---------------------+ + +DROP TABLE dept_natural; + +Affected Rows: 0 + +DROP TABLE emp_natural; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/natural_join.sql b/tests/cases/standalone/common/join/natural_join.sql new file mode 100644 index 0000000000..20f3a24174 --- /dev/null +++ b/tests/cases/standalone/common/join/natural_join.sql @@ -0,0 +1,23 @@ +-- Migrated from DuckDB test: test/sql/join/natural/natural_join.test +-- Tests NATURAL JOIN functionality + +CREATE TABLE emp_natural("id" INTEGER, "name" VARCHAR, dept_id INTEGER, ts1 TIMESTAMP TIME INDEX); + +CREATE TABLE dept_natural(dept_id INTEGER, dept_name VARCHAR, ts2 TIMESTAMP TIME INDEX); + +INSERT INTO emp_natural VALUES (1, 'Alice', 10, 1000), (2, 'Bob', 20, 2000), (3, 'Carol', 10, 3000); + +INSERT INTO dept_natural VALUES (10, 'Engineering', 4000), (20, 'Sales', 5000), (30, 'Marketing', 6000); + +-- NATURAL JOIN (joins on common column names) +SELECT * FROM emp_natural NATURAL JOIN dept_natural ORDER BY "id"; + +-- NATURAL LEFT JOIN +SELECT * FROM emp_natural NATURAL LEFT JOIN dept_natural ORDER BY "id"; + +-- NATURAL RIGHT JOIN +SELECT * FROM emp_natural NATURAL RIGHT JOIN dept_natural ORDER BY dept_id; + +DROP TABLE dept_natural; + +DROP TABLE emp_natural; diff --git a/tests/cases/standalone/common/join/natural_join_advanced.result b/tests/cases/standalone/common/join/natural_join_advanced.result new file mode 100644 index 0000000000..8c78b30004 --- /dev/null +++ b/tests/cases/standalone/common/join/natural_join_advanced.result @@ -0,0 +1,113 @@ +-- Migrated from DuckDB test: test/sql/join/natural/ advanced tests +-- Tests advanced natural join patterns +CREATE TABLE employees_nat(emp_id INTEGER, "name" VARCHAR, dept_id INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE departments_nat(dept_id INTEGER, dept_name VARCHAR, budget INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE dept_locations(dept_id INTEGER, "location" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO employees_nat VALUES +(1, 'Alice', 10, 1000), (2, 'Bob', 20, 2000), (3, 'Charlie', 10, 3000), +(4, 'Diana', 30, 4000), (5, 'Eve', 20, 5000); + +Affected Rows: 5 + +INSERT INTO departments_nat VALUES +(10, 'Engineering', 500000, 1000), (20, 'Marketing', 300000, 2000), (30, 'Sales', 200000, 3000); + +Affected Rows: 3 + +INSERT INTO dept_locations VALUES +(10, 'Building A', 1000), (20, 'Building B', 2000), (30, 'Building C', 3000); + +Affected Rows: 3 + +-- Basic natural join +SELECT * FROM employees_nat NATURAL JOIN departments_nat ORDER BY emp_id; + ++--------+-------+---------+-------------+--------+---------------------+ +| emp_id | name | dept_id | dept_name | budget | ts | ++--------+-------+---------+-------------+--------+---------------------+ +| 1 | Alice | 10 | Engineering | 500000 | 1970-01-01T00:00:01 | +| 2 | Bob | 20 | Marketing | 300000 | 1970-01-01T00:00:02 | ++--------+-------+---------+-------------+--------+---------------------+ + +-- Natural join with filtering +SELECT * FROM employees_nat NATURAL JOIN departments_nat +WHERE budget > 250000 ORDER BY emp_id; + ++--------+-------+---------+-------------+--------+---------------------+ +| emp_id | name | dept_id | dept_name | budget | ts | ++--------+-------+---------+-------------+--------+---------------------+ +| 1 | Alice | 10 | Engineering | 500000 | 1970-01-01T00:00:01 | +| 2 | Bob | 20 | Marketing | 300000 | 1970-01-01T00:00:02 | ++--------+-------+---------+-------------+--------+---------------------+ + +-- Multi-table natural join +SELECT + emp_id, "name", dept_name, "location", budget +FROM employees_nat +NATURAL JOIN departments_nat +NATURAL JOIN dept_locations +ORDER BY emp_id; + ++--------+-------+-------------+------------+--------+ +| emp_id | name | dept_name | location | budget | ++--------+-------+-------------+------------+--------+ +| 1 | Alice | Engineering | Building A | 500000 | +| 2 | Bob | Marketing | Building B | 300000 | ++--------+-------+-------------+------------+--------+ + +-- Natural join with aggregation +SELECT + dept_name, + COUNT(emp_id) as employee_count, + AVG(budget) as avg_budget, + "location" +FROM employees_nat +NATURAL JOIN departments_nat +NATURAL JOIN dept_locations +GROUP BY dept_name, "location", budget +ORDER BY employee_count DESC, dept_name ASC; + ++-------------+----------------+------------+------------+ +| dept_name | employee_count | avg_budget | location | ++-------------+----------------+------------+------------+ +| Engineering | 1 | 500000.0 | Building A | +| Marketing | 1 | 300000.0 | Building B | ++-------------+----------------+------------+------------+ + +-- Natural join with expressions +SELECT + "name", + dept_name, + budget, + CASE WHEN budget > 400000 THEN 'High Budget' ELSE 'Normal Budget' END as budget_tier +FROM employees_nat NATURAL JOIN departments_nat +ORDER BY budget DESC, "name"; + ++-------+-------------+--------+---------------+ +| name | dept_name | budget | budget_tier | ++-------+-------------+--------+---------------+ +| Alice | Engineering | 500000 | High Budget | +| Bob | Marketing | 300000 | Normal Budget | ++-------+-------------+--------+---------------+ + +DROP TABLE employees_nat; + +Affected Rows: 0 + +DROP TABLE departments_nat; + +Affected Rows: 0 + +DROP TABLE dept_locations; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/natural_join_advanced.sql b/tests/cases/standalone/common/join/natural_join_advanced.sql new file mode 100644 index 0000000000..b999760f2b --- /dev/null +++ b/tests/cases/standalone/common/join/natural_join_advanced.sql @@ -0,0 +1,60 @@ +-- Migrated from DuckDB test: test/sql/join/natural/ advanced tests +-- Tests advanced natural join patterns + +CREATE TABLE employees_nat(emp_id INTEGER, "name" VARCHAR, dept_id INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE departments_nat(dept_id INTEGER, dept_name VARCHAR, budget INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE dept_locations(dept_id INTEGER, "location" VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO employees_nat VALUES +(1, 'Alice', 10, 1000), (2, 'Bob', 20, 2000), (3, 'Charlie', 10, 3000), +(4, 'Diana', 30, 4000), (5, 'Eve', 20, 5000); + +INSERT INTO departments_nat VALUES +(10, 'Engineering', 500000, 1000), (20, 'Marketing', 300000, 2000), (30, 'Sales', 200000, 3000); + +INSERT INTO dept_locations VALUES +(10, 'Building A', 1000), (20, 'Building B', 2000), (30, 'Building C', 3000); + +-- Basic natural join +SELECT * FROM employees_nat NATURAL JOIN departments_nat ORDER BY emp_id; + +-- Natural join with filtering +SELECT * FROM employees_nat NATURAL JOIN departments_nat +WHERE budget > 250000 ORDER BY emp_id; + +-- Multi-table natural join +SELECT + emp_id, "name", dept_name, "location", budget +FROM employees_nat +NATURAL JOIN departments_nat +NATURAL JOIN dept_locations +ORDER BY emp_id; + +-- Natural join with aggregation +SELECT + dept_name, + COUNT(emp_id) as employee_count, + AVG(budget) as avg_budget, + "location" +FROM employees_nat +NATURAL JOIN departments_nat +NATURAL JOIN dept_locations +GROUP BY dept_name, "location", budget +ORDER BY employee_count DESC, dept_name ASC; + +-- Natural join with expressions +SELECT + "name", + dept_name, + budget, + CASE WHEN budget > 400000 THEN 'High Budget' ELSE 'Normal Budget' END as budget_tier +FROM employees_nat NATURAL JOIN departments_nat +ORDER BY budget DESC, "name"; + +DROP TABLE employees_nat; + +DROP TABLE departments_nat; + +DROP TABLE dept_locations; diff --git a/tests/cases/standalone/common/join/outer_join_complex.result b/tests/cases/standalone/common/join/outer_join_complex.result new file mode 100644 index 0000000000..c52df8675a --- /dev/null +++ b/tests/cases/standalone/common/join/outer_join_complex.result @@ -0,0 +1,133 @@ +-- Migrated from DuckDB test: test/sql/join/full_outer/ complex tests +-- Tests complex outer join scenarios +CREATE TABLE employees(emp_id INTEGER, "name" VARCHAR, dept_id INTEGER, salary INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE departments(dept_id INTEGER, dept_name VARCHAR, manager_id INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE projects(proj_id INTEGER, proj_name VARCHAR, dept_id INTEGER, budget INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO employees VALUES +(1, 'Alice', 10, 75000, 1000), (2, 'Bob', 20, 65000, 2000), +(3, 'Charlie', 10, 80000, 3000), (4, 'Diana', 30, 70000, 4000), (5, 'Eve', NULL, 60000, 5000); + +Affected Rows: 5 + +INSERT INTO departments VALUES +(10, 'Engineering', 1, 1000), (20, 'Marketing', 2, 2000), (40, 'HR', NULL, 3000); + +Affected Rows: 3 + +INSERT INTO projects VALUES +(101, 'ProjectA', 10, 100000, 1000), (102, 'ProjectB', 20, 150000, 2000), +(103, 'ProjectC', 30, 75000, 3000), (104, 'ProjectD', 50, 200000, 4000); + +Affected Rows: 4 + +-- Full outer join with multiple conditions +SELECT + e.emp_id, e."name", d.dept_id, d.dept_name +FROM employees e +FULL OUTER JOIN departments d ON e.dept_id = d.dept_id +ORDER BY e.emp_id, d.dept_id; + ++--------+---------+---------+-------------+ +| emp_id | name | dept_id | dept_name | ++--------+---------+---------+-------------+ +| 1 | Alice | 10 | Engineering | +| 2 | Bob | 20 | Marketing | +| 3 | Charlie | 10 | Engineering | +| 4 | Diana | | | +| 5 | Eve | | | +| | | 40 | HR | ++--------+---------+---------+-------------+ + +-- Left outer join with IS NULL filter +SELECT + e.emp_id, e."name", e.dept_id, d.dept_name +FROM employees e +LEFT OUTER JOIN departments d ON e.dept_id = d.dept_id +WHERE d.dept_id IS NULL +ORDER BY e.emp_id; + ++--------+-------+---------+-----------+ +| emp_id | name | dept_id | dept_name | ++--------+-------+---------+-----------+ +| 4 | Diana | 30 | | +| 5 | Eve | | | ++--------+-------+---------+-----------+ + +-- Right outer join +SELECT + e.emp_id, e."name", d.dept_id, d.dept_name +FROM employees e +RIGHT OUTER JOIN departments d ON e.dept_id = d.dept_id +ORDER BY d.dept_id, e.emp_id; + ++--------+---------+---------+-------------+ +| emp_id | name | dept_id | dept_name | ++--------+---------+---------+-------------+ +| 1 | Alice | 10 | Engineering | +| 3 | Charlie | 10 | Engineering | +| 2 | Bob | 20 | Marketing | +| | | 40 | HR | ++--------+---------+---------+-------------+ + +-- Triple outer join +SELECT + e."name", d.dept_name, p.proj_name, p.budget +FROM employees e +FULL OUTER JOIN departments d ON e.dept_id = d.dept_id +FULL OUTER JOIN projects p ON d.dept_id = p.dept_id +ORDER BY e.emp_id, p.proj_id; + ++---------+-------------+-----------+--------+ +| name | dept_name | proj_name | budget | ++---------+-------------+-----------+--------+ +| Alice | Engineering | ProjectA | 100000 | +| Bob | Marketing | ProjectB | 150000 | +| Charlie | Engineering | ProjectA | 100000 | +| Diana | | | | +| Eve | | | | +| | | ProjectC | 75000 | +| | | ProjectD | 200000 | +| | HR | | | ++---------+-------------+-----------+--------+ + +-- Outer join with aggregation +SELECT + d.dept_name, + COUNT(e.emp_id) as employee_count, + AVG(e.salary) as avg_salary, + SUM(p.budget) as total_project_budget +FROM departments d +LEFT OUTER JOIN employees e ON d.dept_id = e.dept_id +LEFT OUTER JOIN projects p ON d.dept_id = p.dept_id +GROUP BY d.dept_name +ORDER BY d.dept_name; + ++-------------+----------------+------------+----------------------+ +| dept_name | employee_count | avg_salary | total_project_budget | ++-------------+----------------+------------+----------------------+ +| Engineering | 2 | 77500.0 | 200000 | +| HR | 0 | | | +| Marketing | 1 | 65000.0 | 150000 | ++-------------+----------------+------------+----------------------+ + +DROP TABLE employees; + +Affected Rows: 0 + +DROP TABLE departments; + +Affected Rows: 0 + +DROP TABLE projects; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/outer_join_complex.sql b/tests/cases/standalone/common/join/outer_join_complex.sql new file mode 100644 index 0000000000..51bc4d9aed --- /dev/null +++ b/tests/cases/standalone/common/join/outer_join_complex.sql @@ -0,0 +1,67 @@ +-- Migrated from DuckDB test: test/sql/join/full_outer/ complex tests +-- Tests complex outer join scenarios + +CREATE TABLE employees(emp_id INTEGER, "name" VARCHAR, dept_id INTEGER, salary INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE departments(dept_id INTEGER, dept_name VARCHAR, manager_id INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE projects(proj_id INTEGER, proj_name VARCHAR, dept_id INTEGER, budget INTEGER, ts TIMESTAMP TIME INDEX); + +INSERT INTO employees VALUES +(1, 'Alice', 10, 75000, 1000), (2, 'Bob', 20, 65000, 2000), +(3, 'Charlie', 10, 80000, 3000), (4, 'Diana', 30, 70000, 4000), (5, 'Eve', NULL, 60000, 5000); + +INSERT INTO departments VALUES +(10, 'Engineering', 1, 1000), (20, 'Marketing', 2, 2000), (40, 'HR', NULL, 3000); + +INSERT INTO projects VALUES +(101, 'ProjectA', 10, 100000, 1000), (102, 'ProjectB', 20, 150000, 2000), +(103, 'ProjectC', 30, 75000, 3000), (104, 'ProjectD', 50, 200000, 4000); + +-- Full outer join with multiple conditions +SELECT + e.emp_id, e."name", d.dept_id, d.dept_name +FROM employees e +FULL OUTER JOIN departments d ON e.dept_id = d.dept_id +ORDER BY e.emp_id, d.dept_id; + +-- Left outer join with IS NULL filter +SELECT + e.emp_id, e."name", e.dept_id, d.dept_name +FROM employees e +LEFT OUTER JOIN departments d ON e.dept_id = d.dept_id +WHERE d.dept_id IS NULL +ORDER BY e.emp_id; + +-- Right outer join +SELECT + e.emp_id, e."name", d.dept_id, d.dept_name +FROM employees e +RIGHT OUTER JOIN departments d ON e.dept_id = d.dept_id +ORDER BY d.dept_id, e.emp_id; + +-- Triple outer join +SELECT + e."name", d.dept_name, p.proj_name, p.budget +FROM employees e +FULL OUTER JOIN departments d ON e.dept_id = d.dept_id +FULL OUTER JOIN projects p ON d.dept_id = p.dept_id +ORDER BY e.emp_id, p.proj_id; + +-- Outer join with aggregation +SELECT + d.dept_name, + COUNT(e.emp_id) as employee_count, + AVG(e.salary) as avg_salary, + SUM(p.budget) as total_project_budget +FROM departments d +LEFT OUTER JOIN employees e ON d.dept_id = e.dept_id +LEFT OUTER JOIN projects p ON d.dept_id = p.dept_id +GROUP BY d.dept_name +ORDER BY d.dept_name; + +DROP TABLE employees; + +DROP TABLE departments; + +DROP TABLE projects; diff --git a/tests/cases/standalone/common/join/right_join_patterns.result b/tests/cases/standalone/common/join/right_join_patterns.result new file mode 100644 index 0000000000..620157b1c3 --- /dev/null +++ b/tests/cases/standalone/common/join/right_join_patterns.result @@ -0,0 +1,113 @@ +-- Migrated from DuckDB test: test/sql/join/right_outer/ pattern tests +-- Tests right join patterns +CREATE TABLE inventory(item_id INTEGER, item_name VARCHAR, stock_quantity INTEGER, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE purchase_orders(po_id INTEGER, item_id INTEGER, ordered_qty INTEGER, order_date DATE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO inventory VALUES +(1, 'Widget A', 100, 1000), (2, 'Widget B', 50, 2000), (3, 'Widget C', 0, 3000); + +Affected Rows: 3 + +INSERT INTO purchase_orders VALUES +(1, 1, 25, '2023-01-01', 1000), (2, 2, 30, '2023-01-02', 2000), +(3, 4, 15, '2023-01-03', 3000), (4, 5, 40, '2023-01-04', 4000), +(5, 1, 10, '2023-01-05', 5000); + +Affected Rows: 5 + +-- Right join to show all orders (including for non-inventory items) +SELECT + po.po_id, po.ordered_qty, po.order_date, + COALESCE(i.item_name, 'Unknown Item') as item_name, + COALESCE(i.stock_quantity, 0) as current_stock +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +ORDER BY po.order_date, po.po_id; + ++-------+-------------+------------+--------------+---------------+ +| po_id | ordered_qty | order_date | item_name | current_stock | ++-------+-------------+------------+--------------+---------------+ +| 1 | 25 | 2023-01-01 | Widget A | 100 | +| 2 | 30 | 2023-01-02 | Widget B | 50 | +| 3 | 15 | 2023-01-03 | Unknown Item | 0 | +| 4 | 40 | 2023-01-04 | Unknown Item | 0 | +| 5 | 10 | 2023-01-05 | Widget A | 100 | ++-------+-------------+------------+--------------+---------------+ + +-- Right join with aggregation +SELECT + po.item_id, + COUNT(po.po_id) as order_count, + SUM(po.ordered_qty) as total_ordered, + COALESCE(MAX(i.stock_quantity), 0) as stock_level, + CASE WHEN i.item_id IS NULL THEN 'Not In Inventory' ELSE 'In Stock' END as inventory_status +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +GROUP BY po.item_id, i.item_id +ORDER BY order_count DESC, po.item_id ASC; + ++---------+-------------+---------------+-------------+------------------+ +| item_id | order_count | total_ordered | stock_level | inventory_status | ++---------+-------------+---------------+-------------+------------------+ +| 1 | 2 | 35 | 100 | In Stock | +| 2 | 1 | 30 | 50 | In Stock | +| 4 | 1 | 15 | 0 | Not In Inventory | +| 5 | 1 | 40 | 0 | Not In Inventory | ++---------+-------------+---------------+-------------+------------------+ + +-- Right join to identify missing inventory records +SELECT + po.item_id as missing_item_id, + COUNT(*) as orders_for_missing_item, + SUM(po.ordered_qty) as total_qty_ordered +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +WHERE i.item_id IS NULL +GROUP BY po.item_id +ORDER BY total_qty_ordered DESC, po.item_id ASC; + ++-----------------+-------------------------+-------------------+ +| missing_item_id | orders_for_missing_item | total_qty_ordered | ++-----------------+-------------------------+-------------------+ +| 5 | 1 | 40 | +| 4 | 1 | 15 | ++-----------------+-------------------------+-------------------+ + +-- Right join with filtering and conditions +SELECT + po.po_id, + po.item_id, + po.ordered_qty, + COALESCE(i.item_name, 'Missing from inventory') as item_description, + CASE + WHEN i.item_id IS NULL THEN 'Order for unknown item' + WHEN i.stock_quantity < po.ordered_qty THEN 'Insufficient stock' + ELSE 'Can fulfill' + END as fulfillment_status +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +ORDER BY po.order_date; + ++-------+---------+-------------+------------------------+------------------------+ +| po_id | item_id | ordered_qty | item_description | fulfillment_status | ++-------+---------+-------------+------------------------+------------------------+ +| 1 | 1 | 25 | Widget A | Can fulfill | +| 2 | 2 | 30 | Widget B | Can fulfill | +| 3 | 4 | 15 | Missing from inventory | Order for unknown item | +| 4 | 5 | 40 | Missing from inventory | Order for unknown item | +| 5 | 1 | 10 | Widget A | Can fulfill | ++-------+---------+-------------+------------------------+------------------------+ + +DROP TABLE inventory; + +Affected Rows: 0 + +DROP TABLE purchase_orders; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/right_join_patterns.sql b/tests/cases/standalone/common/join/right_join_patterns.sql new file mode 100644 index 0000000000..852d93b1b2 --- /dev/null +++ b/tests/cases/standalone/common/join/right_join_patterns.sql @@ -0,0 +1,65 @@ +-- Migrated from DuckDB test: test/sql/join/right_outer/ pattern tests +-- Tests right join patterns + +CREATE TABLE inventory(item_id INTEGER, item_name VARCHAR, stock_quantity INTEGER, ts TIMESTAMP TIME INDEX); + +CREATE TABLE purchase_orders(po_id INTEGER, item_id INTEGER, ordered_qty INTEGER, order_date DATE, ts TIMESTAMP TIME INDEX); + +INSERT INTO inventory VALUES +(1, 'Widget A', 100, 1000), (2, 'Widget B', 50, 2000), (3, 'Widget C', 0, 3000); + +INSERT INTO purchase_orders VALUES +(1, 1, 25, '2023-01-01', 1000), (2, 2, 30, '2023-01-02', 2000), +(3, 4, 15, '2023-01-03', 3000), (4, 5, 40, '2023-01-04', 4000), +(5, 1, 10, '2023-01-05', 5000); + +-- Right join to show all orders (including for non-inventory items) +SELECT + po.po_id, po.ordered_qty, po.order_date, + COALESCE(i.item_name, 'Unknown Item') as item_name, + COALESCE(i.stock_quantity, 0) as current_stock +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +ORDER BY po.order_date, po.po_id; + +-- Right join with aggregation +SELECT + po.item_id, + COUNT(po.po_id) as order_count, + SUM(po.ordered_qty) as total_ordered, + COALESCE(MAX(i.stock_quantity), 0) as stock_level, + CASE WHEN i.item_id IS NULL THEN 'Not In Inventory' ELSE 'In Stock' END as inventory_status +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +GROUP BY po.item_id, i.item_id +ORDER BY order_count DESC, po.item_id ASC; + +-- Right join to identify missing inventory records +SELECT + po.item_id as missing_item_id, + COUNT(*) as orders_for_missing_item, + SUM(po.ordered_qty) as total_qty_ordered +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +WHERE i.item_id IS NULL +GROUP BY po.item_id +ORDER BY total_qty_ordered DESC, po.item_id ASC; + +-- Right join with filtering and conditions +SELECT + po.po_id, + po.item_id, + po.ordered_qty, + COALESCE(i.item_name, 'Missing from inventory') as item_description, + CASE + WHEN i.item_id IS NULL THEN 'Order for unknown item' + WHEN i.stock_quantity < po.ordered_qty THEN 'Insufficient stock' + ELSE 'Can fulfill' + END as fulfillment_status +FROM inventory i +RIGHT JOIN purchase_orders po ON i.item_id = po.item_id +ORDER BY po.order_date; + +DROP TABLE inventory; + +DROP TABLE purchase_orders; diff --git a/tests/cases/standalone/common/join/right_outer_join.result b/tests/cases/standalone/common/join/right_outer_join.result new file mode 100644 index 0000000000..2967ebc1f7 --- /dev/null +++ b/tests/cases/standalone/common/join/right_outer_join.result @@ -0,0 +1,58 @@ +-- Migrated from DuckDB test: test/sql/join/right_outer/test_right_outer.test +-- Tests RIGHT OUTER JOIN scenarios +CREATE TABLE products_right("id" INTEGER, "name" VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE inventory_right(product_id INTEGER, stock INTEGER, "warehouse" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO products_right VALUES (1, 'Laptop', 1000.0, 1000), (2, 'Mouse', 25.0, 2000); + +Affected Rows: 2 + +INSERT INTO inventory_right VALUES (1, 5, 'WH1', 3000), (2, 10, 'WH1', 4000), (3, 15, 'WH2', 5000); + +Affected Rows: 3 + +-- Basic RIGHT JOIN +SELECT * FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id ORDER BY i.product_id; + ++----+--------+--------+---------------------+------------+-------+-----------+---------------------+ +| id | name | price | ts | product_id | stock | warehouse | ts | ++----+--------+--------+---------------------+------------+-------+-----------+---------------------+ +| 1 | Laptop | 1000.0 | 1970-01-01T00:00:01 | 1 | 5 | WH1 | 1970-01-01T00:00:03 | +| 2 | Mouse | 25.0 | 1970-01-01T00:00:02 | 2 | 10 | WH1 | 1970-01-01T00:00:04 | +| | | | | 3 | 15 | WH2 | 1970-01-01T00:00:05 | ++----+--------+--------+---------------------+------------+-------+-----------+---------------------+ + +-- RIGHT JOIN with WHERE on left table +SELECT * FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id WHERE p.price IS NULL ORDER BY i.product_id; + ++----+------+-------+----+------------+-------+-----------+---------------------+ +| id | name | price | ts | product_id | stock | warehouse | ts | ++----+------+-------+----+------------+-------+-----------+---------------------+ +| | | | | 3 | 15 | WH2 | 1970-01-01T00:00:05 | ++----+------+-------+----+------------+-------+-----------+---------------------+ + +-- RIGHT JOIN with aggregation +SELECT i."warehouse", COUNT(*) as items, AVG(p.price) as avg_price +FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id +GROUP BY i."warehouse" ORDER BY i."warehouse"; + ++-----------+-------+-----------+ +| warehouse | items | avg_price | ++-----------+-------+-----------+ +| WH1 | 2 | 512.5 | +| WH2 | 1 | | ++-----------+-------+-----------+ + +DROP TABLE inventory_right; + +Affected Rows: 0 + +DROP TABLE products_right; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/right_outer_join.sql b/tests/cases/standalone/common/join/right_outer_join.sql new file mode 100644 index 0000000000..1f3ad7cf78 --- /dev/null +++ b/tests/cases/standalone/common/join/right_outer_join.sql @@ -0,0 +1,25 @@ +-- Migrated from DuckDB test: test/sql/join/right_outer/test_right_outer.test +-- Tests RIGHT OUTER JOIN scenarios + +CREATE TABLE products_right("id" INTEGER, "name" VARCHAR, price DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE inventory_right(product_id INTEGER, stock INTEGER, "warehouse" VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO products_right VALUES (1, 'Laptop', 1000.0, 1000), (2, 'Mouse', 25.0, 2000); + +INSERT INTO inventory_right VALUES (1, 5, 'WH1', 3000), (2, 10, 'WH1', 4000), (3, 15, 'WH2', 5000); + +-- Basic RIGHT JOIN +SELECT * FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id ORDER BY i.product_id; + +-- RIGHT JOIN with WHERE on left table +SELECT * FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id WHERE p.price IS NULL ORDER BY i.product_id; + +-- RIGHT JOIN with aggregation +SELECT i."warehouse", COUNT(*) as items, AVG(p.price) as avg_price +FROM products_right p RIGHT JOIN inventory_right i ON p."id" = i.product_id +GROUP BY i."warehouse" ORDER BY i."warehouse"; + +DROP TABLE inventory_right; + +DROP TABLE products_right; diff --git a/tests/cases/standalone/common/join/self_join.result b/tests/cases/standalone/common/join/self_join.result new file mode 100644 index 0000000000..4c6825a836 --- /dev/null +++ b/tests/cases/standalone/common/join/self_join.result @@ -0,0 +1,90 @@ +-- Migrated from DuckDB test: Self join scenarios +-- Tests self join operations +CREATE TABLE employees_self( + "id" INTEGER, + "name" VARCHAR, + manager_id INTEGER, + salary INTEGER, + ts TIMESTAMP TIME INDEX +); + +Affected Rows: 0 + +INSERT INTO employees_self VALUES +(1, 'CEO', NULL, 100000, 1000), +(2, 'Manager1', 1, 80000, 2000), +(3, 'Manager2', 1, 75000, 3000), +(4, 'Employee1', 2, 50000, 4000), +(5, 'Employee2', 2, 55000, 5000), +(6, 'Employee3', 3, 48000, 6000); + +Affected Rows: 6 + +-- Basic self join to get employee-manager pairs +SELECT e."name" as employee, m."name" as manager +FROM employees_self e +LEFT JOIN employees_self m ON e.manager_id = m."id" +ORDER BY e."id"; + ++-----------+----------+ +| employee | manager | ++-----------+----------+ +| CEO | | +| Manager1 | CEO | +| Manager2 | CEO | +| Employee1 | Manager1 | +| Employee2 | Manager1 | +| Employee3 | Manager2 | ++-----------+----------+ + +-- Self join to find employees earning more than their manager +SELECT e."name" as employee, e.salary, m."name" as manager, m.salary as manager_salary +FROM employees_self e +JOIN employees_self m ON e.manager_id = m."id" +WHERE e.salary > m.salary +ORDER BY e."name"; + +++ +++ + +-- Self join to find colleagues (same manager) +SELECT e1."name" as employee1, e2."name" as employee2, m."name" as shared_manager +FROM employees_self e1 +JOIN employees_self e2 ON e1.manager_id = e2.manager_id AND e1."id" < e2."id" +JOIN employees_self m ON e1.manager_id = m."id" +ORDER BY shared_manager, employee1; + ++-----------+-----------+----------------+ +| employee1 | employee2 | shared_manager | ++-----------+-----------+----------------+ +| Manager1 | Manager2 | CEO | +| Employee1 | Employee2 | Manager1 | ++-----------+-----------+----------------+ + +-- Hierarchical query using self join +SELECT + e."name" as employee, + e.salary, + m."name" as manager, + COUNT(sub."id") as direct_reports +FROM employees_self e +LEFT JOIN employees_self m ON e.manager_id = m."id" +LEFT JOIN employees_self sub ON e."id" = sub.manager_id +GROUP BY e."id", e."name", e.salary, m."name" +ORDER BY e."id"; + ++-----------+--------+----------+----------------+ +| employee | salary | manager | direct_reports | ++-----------+--------+----------+----------------+ +| CEO | 100000 | | 2 | +| Manager1 | 80000 | CEO | 2 | +| Manager2 | 75000 | CEO | 1 | +| Employee1 | 50000 | Manager1 | 0 | +| Employee2 | 55000 | Manager1 | 0 | +| Employee3 | 48000 | Manager2 | 0 | ++-----------+--------+----------+----------------+ + +DROP TABLE employees_self; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/self_join.sql b/tests/cases/standalone/common/join/self_join.sql new file mode 100644 index 0000000000..1a376354c8 --- /dev/null +++ b/tests/cases/standalone/common/join/self_join.sql @@ -0,0 +1,52 @@ +-- Migrated from DuckDB test: Self join scenarios +-- Tests self join operations + +CREATE TABLE employees_self( + "id" INTEGER, + "name" VARCHAR, + manager_id INTEGER, + salary INTEGER, + ts TIMESTAMP TIME INDEX +); + +INSERT INTO employees_self VALUES +(1, 'CEO', NULL, 100000, 1000), +(2, 'Manager1', 1, 80000, 2000), +(3, 'Manager2', 1, 75000, 3000), +(4, 'Employee1', 2, 50000, 4000), +(5, 'Employee2', 2, 55000, 5000), +(6, 'Employee3', 3, 48000, 6000); + +-- Basic self join to get employee-manager pairs +SELECT e."name" as employee, m."name" as manager +FROM employees_self e +LEFT JOIN employees_self m ON e.manager_id = m."id" +ORDER BY e."id"; + +-- Self join to find employees earning more than their manager +SELECT e."name" as employee, e.salary, m."name" as manager, m.salary as manager_salary +FROM employees_self e +JOIN employees_self m ON e.manager_id = m."id" +WHERE e.salary > m.salary +ORDER BY e."name"; + +-- Self join to find colleagues (same manager) +SELECT e1."name" as employee1, e2."name" as employee2, m."name" as shared_manager +FROM employees_self e1 +JOIN employees_self e2 ON e1.manager_id = e2.manager_id AND e1."id" < e2."id" +JOIN employees_self m ON e1.manager_id = m."id" +ORDER BY shared_manager, employee1; + +-- Hierarchical query using self join +SELECT + e."name" as employee, + e.salary, + m."name" as manager, + COUNT(sub."id") as direct_reports +FROM employees_self e +LEFT JOIN employees_self m ON e.manager_id = m."id" +LEFT JOIN employees_self sub ON e."id" = sub.manager_id +GROUP BY e."id", e."name", e.salary, m."name" +ORDER BY e."id"; + +DROP TABLE employees_self; diff --git a/tests/cases/standalone/common/join/using_clause_joins.result b/tests/cases/standalone/common/join/using_clause_joins.result new file mode 100644 index 0000000000..51f1fb014d --- /dev/null +++ b/tests/cases/standalone/common/join/using_clause_joins.result @@ -0,0 +1,125 @@ +-- Migrated from DuckDB test: test/sql/join/ USING clause tests +-- Tests USING clause join syntax +CREATE TABLE orders_using(order_id INTEGER, customer_id INTEGER, total DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE customers_using(customer_id INTEGER, customer_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE payments_using(payment_id INTEGER, order_id INTEGER, amount DOUBLE, "method" VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO orders_using VALUES +(1, 101, 250.00, 1000), (2, 102, 180.00, 2000), (3, 101, 420.00, 3000), (4, 103, 95.00, 4000); + +Affected Rows: 4 + +INSERT INTO customers_using VALUES +(101, 'John Doe', 'NYC', 1000), (102, 'Jane Smith', 'LA', 2000), (103, 'Bob Wilson', 'Chicago', 3000); + +Affected Rows: 3 + +INSERT INTO payments_using VALUES +(1, 1, 250.00, 'Credit', 1000), (2, 2, 180.00, 'Debit', 2000), +(3, 3, 420.00, 'Credit', 3000), (4, 4, 95.00, 'Cash', 4000); + +Affected Rows: 4 + +-- Basic USING clause join +SELECT order_id, customer_name, total +FROM orders_using +INNER JOIN customers_using USING (customer_id) +ORDER BY order_id; + ++----------+---------------+-------+ +| order_id | customer_name | total | ++----------+---------------+-------+ +| 1 | John Doe | 250.0 | +| 2 | Jane Smith | 180.0 | +| 3 | John Doe | 420.0 | +| 4 | Bob Wilson | 95.0 | ++----------+---------------+-------+ + +-- Multiple USING clause joins +SELECT o.order_id, c.customer_name, p.amount, p."method" +FROM orders_using o +INNER JOIN customers_using c USING (customer_id) +INNER JOIN payments_using p USING (order_id) +ORDER BY o.order_id; + ++----------+---------------+--------+--------+ +| order_id | customer_name | amount | method | ++----------+---------------+--------+--------+ +| 1 | John Doe | 250.0 | Credit | +| 2 | Jane Smith | 180.0 | Debit | +| 3 | John Doe | 420.0 | Credit | +| 4 | Bob Wilson | 95.0 | Cash | ++----------+---------------+--------+--------+ + +-- LEFT JOIN with USING +SELECT c.customer_name, o.order_id, o.total +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +ORDER BY c.customer_id, o.order_id; + ++---------------+----------+-------+ +| customer_name | order_id | total | ++---------------+----------+-------+ +| John Doe | 1 | 250.0 | +| John Doe | 3 | 420.0 | +| Jane Smith | 2 | 180.0 | +| Bob Wilson | 4 | 95.0 | ++---------------+----------+-------+ + +-- USING with aggregation +SELECT + c.city, + COUNT(o.order_id) as order_count, + SUM(o.total) as total_sales +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +GROUP BY c.city +ORDER BY total_sales DESC; + ++---------+-------------+-------------+ +| city | order_count | total_sales | ++---------+-------------+-------------+ +| NYC | 2 | 670.0 | +| LA | 1 | 180.0 | +| Chicago | 1 | 95.0 | ++---------+-------------+-------------+ + +-- USING with complex expressions +SELECT + c.customer_name, + COUNT(o.order_id) as order_count, + COALESCE(SUM(o.total), 0) as total_spent +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +GROUP BY c.customer_id, c.customer_name +HAVING COUNT(o.order_id) != 0 +ORDER BY total_spent DESC; + ++---------------+-------------+-------------+ +| customer_name | order_count | total_spent | ++---------------+-------------+-------------+ +| John Doe | 2 | 670.0 | +| Jane Smith | 1 | 180.0 | +| Bob Wilson | 1 | 95.0 | ++---------------+-------------+-------------+ + +DROP TABLE orders_using; + +Affected Rows: 0 + +DROP TABLE customers_using; + +Affected Rows: 0 + +DROP TABLE payments_using; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/using_clause_joins.sql b/tests/cases/standalone/common/join/using_clause_joins.sql new file mode 100644 index 0000000000..9825f5ae4c --- /dev/null +++ b/tests/cases/standalone/common/join/using_clause_joins.sql @@ -0,0 +1,64 @@ +-- Migrated from DuckDB test: test/sql/join/ USING clause tests +-- Tests USING clause join syntax + +CREATE TABLE orders_using(order_id INTEGER, customer_id INTEGER, total DOUBLE, ts TIMESTAMP TIME INDEX); + +CREATE TABLE customers_using(customer_id INTEGER, customer_name VARCHAR, city VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE payments_using(payment_id INTEGER, order_id INTEGER, amount DOUBLE, "method" VARCHAR, ts TIMESTAMP TIME INDEX); + +INSERT INTO orders_using VALUES +(1, 101, 250.00, 1000), (2, 102, 180.00, 2000), (3, 101, 420.00, 3000), (4, 103, 95.00, 4000); + +INSERT INTO customers_using VALUES +(101, 'John Doe', 'NYC', 1000), (102, 'Jane Smith', 'LA', 2000), (103, 'Bob Wilson', 'Chicago', 3000); + +INSERT INTO payments_using VALUES +(1, 1, 250.00, 'Credit', 1000), (2, 2, 180.00, 'Debit', 2000), +(3, 3, 420.00, 'Credit', 3000), (4, 4, 95.00, 'Cash', 4000); + +-- Basic USING clause join +SELECT order_id, customer_name, total +FROM orders_using +INNER JOIN customers_using USING (customer_id) +ORDER BY order_id; + +-- Multiple USING clause joins +SELECT o.order_id, c.customer_name, p.amount, p."method" +FROM orders_using o +INNER JOIN customers_using c USING (customer_id) +INNER JOIN payments_using p USING (order_id) +ORDER BY o.order_id; + +-- LEFT JOIN with USING +SELECT c.customer_name, o.order_id, o.total +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +ORDER BY c.customer_id, o.order_id; + +-- USING with aggregation +SELECT + c.city, + COUNT(o.order_id) as order_count, + SUM(o.total) as total_sales +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +GROUP BY c.city +ORDER BY total_sales DESC; + +-- USING with complex expressions +SELECT + c.customer_name, + COUNT(o.order_id) as order_count, + COALESCE(SUM(o.total), 0) as total_spent +FROM customers_using c +LEFT JOIN orders_using o USING (customer_id) +GROUP BY c.customer_id, c.customer_name +HAVING COUNT(o.order_id) != 0 +ORDER BY total_spent DESC; + +DROP TABLE orders_using; + +DROP TABLE customers_using; + +DROP TABLE payments_using; diff --git a/tests/cases/standalone/common/join/using_join.result b/tests/cases/standalone/common/join/using_join.result new file mode 100644 index 0000000000..501ab2f40e --- /dev/null +++ b/tests/cases/standalone/common/join/using_join.result @@ -0,0 +1,84 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_using_join.test +-- Tests JOIN USING clause +CREATE TABLE users_join(user_id INTEGER, username VARCHAR, email VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +CREATE TABLE orders_join(order_id INTEGER, user_id INTEGER, amount DOUBLE, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO users_join VALUES (1, 'alice', 'alice@test.com', 1000), (2, 'bob', 'bob@test.com', 2000); + +Affected Rows: 2 + +INSERT INTO orders_join VALUES (101, 1, 150.0, 3000), (102, 1, 200.0, 4000), (103, 2, 75.0, 5000); + +Affected Rows: 3 + +-- JOIN USING (automatically joins on common column) +SELECT * FROM users_join JOIN orders_join USING (user_id) ORDER BY order_id; + ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| username | email | ts | order_id | user_id | amount | ts | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| alice | alice@test.com | 1970-01-01T00:00:01 | 101 | 1 | 150.0 | 1970-01-01T00:00:03 | +| alice | alice@test.com | 1970-01-01T00:00:01 | 102 | 1 | 200.0 | 1970-01-01T00:00:04 | +| bob | bob@test.com | 1970-01-01T00:00:02 | 103 | 2 | 75.0 | 1970-01-01T00:00:05 | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ + +-- LEFT JOIN USING +SELECT * FROM users_join LEFT JOIN orders_join USING (user_id) ORDER BY user_id, order_id NULLS LAST; + ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| username | email | ts | order_id | user_id | amount | ts | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| alice | alice@test.com | 1970-01-01T00:00:01 | 101 | 1 | 150.0 | 1970-01-01T00:00:03 | +| alice | alice@test.com | 1970-01-01T00:00:01 | 102 | 1 | 200.0 | 1970-01-01T00:00:04 | +| bob | bob@test.com | 1970-01-01T00:00:02 | 103 | 2 | 75.0 | 1970-01-01T00:00:05 | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ + +-- JOIN USING with WHERE +SELECT * FROM users_join JOIN orders_join USING (user_id) WHERE amount > 100 ORDER BY amount; + ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| username | email | ts | order_id | user_id | amount | ts | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ +| alice | alice@test.com | 1970-01-01T00:00:01 | 101 | 1 | 150.0 | 1970-01-01T00:00:03 | +| alice | alice@test.com | 1970-01-01T00:00:01 | 102 | 1 | 200.0 | 1970-01-01T00:00:04 | ++----------+----------------+---------------------+----------+---------+--------+---------------------+ + +-- Multiple table JOIN USING +CREATE TABLE user_profiles(user_id INTEGER, age INTEGER, city VARCHAR, ts TIMESTAMP TIME INDEX); + +Affected Rows: 0 + +INSERT INTO user_profiles VALUES (1, 25, 'NYC', 6000), (2, 30, 'LA', 7000); + +Affected Rows: 2 + +SELECT * FROM users_join +JOIN orders_join USING (user_id) +JOIN user_profiles USING (user_id) +ORDER BY order_id; + ++----------+----------------+---------------------+----------+---------+--------+---------------------+---------+-----+------+---------------------+ +| username | email | ts | order_id | user_id | amount | ts | user_id | age | city | ts | ++----------+----------------+---------------------+----------+---------+--------+---------------------+---------+-----+------+---------------------+ +| alice | alice@test.com | 1970-01-01T00:00:01 | 101 | 1 | 150.0 | 1970-01-01T00:00:03 | 1 | 25 | NYC | 1970-01-01T00:00:06 | +| alice | alice@test.com | 1970-01-01T00:00:01 | 102 | 1 | 200.0 | 1970-01-01T00:00:04 | 1 | 25 | NYC | 1970-01-01T00:00:06 | +| bob | bob@test.com | 1970-01-01T00:00:02 | 103 | 2 | 75.0 | 1970-01-01T00:00:05 | 2 | 30 | LA | 1970-01-01T00:00:07 | ++----------+----------------+---------------------+----------+---------+--------+---------------------+---------+-----+------+---------------------+ + +DROP TABLE user_profiles; + +Affected Rows: 0 + +DROP TABLE orders_join; + +Affected Rows: 0 + +DROP TABLE users_join; + +Affected Rows: 0 + diff --git a/tests/cases/standalone/common/join/using_join.sql b/tests/cases/standalone/common/join/using_join.sql new file mode 100644 index 0000000000..7b8f7e8c4d --- /dev/null +++ b/tests/cases/standalone/common/join/using_join.sql @@ -0,0 +1,34 @@ +-- Migrated from DuckDB test: test/sql/join/inner/test_using_join.test +-- Tests JOIN USING clause + +CREATE TABLE users_join(user_id INTEGER, username VARCHAR, email VARCHAR, ts TIMESTAMP TIME INDEX); + +CREATE TABLE orders_join(order_id INTEGER, user_id INTEGER, amount DOUBLE, ts TIMESTAMP TIME INDEX); + +INSERT INTO users_join VALUES (1, 'alice', 'alice@test.com', 1000), (2, 'bob', 'bob@test.com', 2000); + +INSERT INTO orders_join VALUES (101, 1, 150.0, 3000), (102, 1, 200.0, 4000), (103, 2, 75.0, 5000); + +-- JOIN USING (automatically joins on common column) +SELECT * FROM users_join JOIN orders_join USING (user_id) ORDER BY order_id; + +-- LEFT JOIN USING +SELECT * FROM users_join LEFT JOIN orders_join USING (user_id) ORDER BY user_id, order_id NULLS LAST; + +-- JOIN USING with WHERE +SELECT * FROM users_join JOIN orders_join USING (user_id) WHERE amount > 100 ORDER BY amount; + +-- Multiple table JOIN USING +CREATE TABLE user_profiles(user_id INTEGER, age INTEGER, city VARCHAR, ts TIMESTAMP TIME INDEX); +INSERT INTO user_profiles VALUES (1, 25, 'NYC', 6000), (2, 30, 'LA', 7000); + +SELECT * FROM users_join +JOIN orders_join USING (user_id) +JOIN user_profiles USING (user_id) +ORDER BY order_id; + +DROP TABLE user_profiles; + +DROP TABLE orders_join; + +DROP TABLE users_join;