| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| """ |
| TPC-H Problem Statement Query 2: |
| |
| The Minimum Cost Supplier Query finds, in a given region, for each part of a certain type and size, |
| the supplier who can supply it at minimum cost. If several suppliers in that region offer the |
| desired part type and size at the same (minimum) cost, the query lists the parts from suppliers with |
| the 100 highest account balances. For each supplier, the query lists the supplier's account balance, |
| name and nation; the part's number and manufacturer; the supplier's address, phone number and |
| comment information. |
| |
| The above problem statement text is copyrighted by the Transaction Processing Performance Council |
| as part of their TPC Benchmark H Specification revision 2.18.0. |
| |
| Reference SQL (from TPC-H specification, used by the benchmark suite):: |
| |
| select |
| s_acctbal, |
| s_name, |
| n_name, |
| p_partkey, |
| p_mfgr, |
| s_address, |
| s_phone, |
| s_comment |
| from |
| part, |
| supplier, |
| partsupp, |
| nation, |
| region |
| where |
| p_partkey = ps_partkey |
| and s_suppkey = ps_suppkey |
| and p_size = 15 |
| and p_type like '%BRASS' |
| and s_nationkey = n_nationkey |
| and n_regionkey = r_regionkey |
| and r_name = 'EUROPE' |
| and ps_supplycost = ( |
| select |
| min(ps_supplycost) |
| from |
| partsupp, |
| supplier, |
| nation, |
| region |
| where |
| p_partkey = ps_partkey |
| and s_suppkey = ps_suppkey |
| and s_nationkey = n_nationkey |
| and n_regionkey = r_regionkey |
| and r_name = 'EUROPE' |
| ) |
| order by |
| s_acctbal desc, |
| n_name, |
| s_name, |
| p_partkey limit 100; |
| """ |
| |
| import datafusion |
| from datafusion import SessionContext, col, lit |
| from datafusion import functions as F |
| from datafusion.expr import Window |
| from util import get_data_path |
| |
| # This is the part we're looking for. Values selected here differ from the spec in order to run |
| # unit tests on a small data set. |
| SIZE_OF_INTEREST = 15 |
| TYPE_OF_INTEREST = "BRASS" |
| REGION_OF_INTEREST = "EUROPE" |
| |
| # Load the dataframes we need |
| |
| ctx = SessionContext() |
| |
| df_part = ctx.read_parquet(get_data_path("part.parquet")).select( |
| "p_partkey", "p_mfgr", "p_type", "p_size" |
| ) |
| df_supplier = ctx.read_parquet(get_data_path("supplier.parquet")).select( |
| "s_acctbal", |
| "s_name", |
| "s_address", |
| "s_phone", |
| "s_comment", |
| "s_nationkey", |
| "s_suppkey", |
| ) |
| df_partsupp = ctx.read_parquet(get_data_path("partsupp.parquet")).select( |
| "ps_partkey", "ps_suppkey", "ps_supplycost" |
| ) |
| df_nation = ctx.read_parquet(get_data_path("nation.parquet")).select( |
| "n_nationkey", "n_regionkey", "n_name" |
| ) |
| df_region = ctx.read_parquet(get_data_path("region.parquet")).select( |
| "r_regionkey", "r_name" |
| ) |
| |
| # Filter down parts. The reference SQL uses ``p_type like '%BRASS'`` which |
| # is an ``ends_with`` check; use the dedicated string function rather than |
| # a manual substring match. |
| |
| df_part = df_part.filter( |
| F.ends_with(col("p_type"), lit(TYPE_OF_INTEREST)), |
| col("p_size") == SIZE_OF_INTEREST, |
| ) |
| |
| # Filter regions down to the one of interest |
| |
| df_region = df_region.filter(col("r_name") == REGION_OF_INTEREST) |
| |
| # Now that we have the region, find suppliers in that region. Suppliers are tied to their nation |
| # and nations are tied to the region. |
| |
| df_nation = df_nation.join(df_region, left_on="n_regionkey", right_on="r_regionkey") |
| df_supplier = df_supplier.join(df_nation, left_on="s_nationkey", right_on="n_nationkey") |
| |
| # Now that we know who the potential suppliers are for the part, we can limit out part |
| # supplies table down. We can further join down to the specific parts we've identified |
| # as matching the request |
| |
| df = df_partsupp.join(df_supplier, left_on="ps_suppkey", right_on="s_suppkey") |
| |
| # Locate the minimum cost across all suppliers. There are multiple ways you could do this, |
| # but one way is to create a window function across all suppliers, find the minimum, and |
| # create a column of that value. We can then filter down any rows for which the cost and |
| # minimum do not match. |
| |
| # The default window frame as of 5/6/2024 is from unbounded preceding to the current row. |
| # We want to evaluate the entire data frame, so we specify this. |
| window_frame = datafusion.WindowFrame("rows", None, None) |
| df = df.with_column( |
| "min_cost", |
| F.min(col("ps_supplycost")).over( |
| Window(partition_by=[col("ps_partkey")], window_frame=window_frame) |
| ), |
| ) |
| |
| df = df.filter(col("min_cost") == col("ps_supplycost")).join( |
| df_part, left_on="ps_partkey", right_on="p_partkey" |
| ) |
| |
| # From the problem statement, these are the values we wish to output |
| |
| df = df.select( |
| "s_acctbal", |
| "s_name", |
| "n_name", |
| "p_partkey", |
| "p_mfgr", |
| "s_address", |
| "s_phone", |
| "s_comment", |
| ) |
| |
| # Sort and display 100 entries |
| df = df.sort( |
| col("s_acctbal").sort(ascending=False), |
| "n_name", |
| "s_name", |
| "p_partkey", |
| ).limit(100) |
| |
| # Show results |
| |
| df.show() |