BaCon: Efficient Batch Processing of Counting Queries


Abstract

Counting queries are ubiquitous in database systems, particularly for driving internal system optimization. Learned models for cardinality estimation rely heavily on large-scale training data, yet generating such data by executing massive batches of counting queries is expensive. We propose BaCon, an efficient algorithm for batch evaluation of counting queries on top of a database system, without modifying its internals. BaConintegrates the idea of factorized databases with a workload-aware domain quantization strategy, allowing it to evaluate batches of counting queries using compact data structures rather than materializing massive join results. BaCon’s design is compatible with most database management system, and we have implemented it as a client-side application on PostgreSQL with a lightweight C-language UDF (user-defined function). This implementation delivers speedups between 2\(\times\) and 178\(\times\) over baselines and good performance across various workloads, making training and maintenance of learned cardinality estimation models significantly more practical.

PVLDB Reference Format:
. . PVLDB, 19(9): 2508-2521, 2026.
doi:10.14778/3819518.3819567

1

1 Introduction↩︎

Batches of counting queries are not only useful in their own right for database applications, but also frequently serve to collect basic statistics from data for monitoring and optimization. With the growing popularity of learned query optimization [1][4] in recent years, an interesting workload has emerged: collecting training data for learned cardinality estimation (CE) [5][13]. CE is a critical component of query optimization, as its accuracy directly impacts the quality of query execution plans [14], [15]. In these workloads, queries typically involve joins and selections over base tables but report only the final counts of the result sets. These query-count pairs are subsequently used to train CE models. Such queries may be derived from past workloads or synthesized specifically for training. While the number of distinct join patterns is naturally limited by the database schema, the queries themselves additionally contain a variety of selection conditions, targeting different attributes with varying constants. Beyond simple equality comparisons, many conditions involve inequality or range predicates. 1 summarizes nine public query workloads widely used in CE research for training and evaluation. These workloads span three commonly studied benchmark databases—IMDB [15], STATS [16] and DSB [17]—and capture a broad range of query shapes, join structures, and predicate types.

Executing such workloads is time-consuming. Even when viewed as a one-time training cost, the overhead can be daunting; in our experience, some workloads require hours or even more than a day to execute (5). Furthermore, models are rarely trained once and then forgotten. As the database state changes, they can become outdated [18], [19]: cardinalities from previously executed queries may no longer be accurate, necessitating the re-execution of queries to retrain the models. While monitoring real result counts from query execution feedback can mitigate this issue, simply observing final query result counts is insufficient [9], [13], [16]. Effective training requires (1) the size of intermediate results for subqueries within potentially optimal plans, not just the plan actually executed, and (2) feedback from unseen queries to prepare for potential workload shifts [18], [20], [21]. In a continuously running environment, executing these monitoring or training queries must not disrupt normal workloads. These observations underscore the need for more efficient support for executing batches of counting queries.

Given the extensive literature on query processing, many ideas are applicable to the general problem of batch counting queries—including multi-query optimization, scalable continuous query processing, and factorized databases—see Section 6 for more discussion. A reasonable starting point, leveraging multi-query optimization, is to exploit the fact that many queries share the same join pattern, differing only in their selection conditions. Instead of executing them independently, one could perform the full join first and then apply individual selection conditions to obtain per-query counts. While this baseline enables shared join processing, it leads to materializing the full join result, which can be massive for large databases.

We propose BaCon, a practical, scalable algorithm for efficient Batch processing of Counting queries. A key idea, inspired by work on factorized databases [22][24], is that a counting query over a join can be evaluated without enumerating the join result. While the count operator cannot generally be pushed down through a join, if the join is processed in a “factorized” manner—grouped by joining attribute values—we can simply count the joining “factors” and multiply these counts, avoiding enumeration of the joined result tuples. A second key idea is workload-aware quantization of selection attribute domains. This idea allows the data required for counting queries to be compressed, naturally exploiting the overlap and sharing of selection predicates within the workload. BaConseamlessly combines these ideas, using compact “count maps” rather than bloated join tuples to represent intermediate results; to combine these intermediate results, it uses a pair of operators \(\otimes\) and \(\oplus\), defined with clear semantics and amenable to optimization. Finally, to ensure practicality and ease of adoption, we implement BaCon as a client application on top of a database system (DBMS) without modifying its internals. BaConleverages the DBMS for processing, utilizes user-defined functions (UDFs), and carefully balances database- and client-side execution to minimize interface overhead.

Our experimental results show that BaCon is highly competitive for training workloads for learned CE in 1, achieving \(2\times\) to \(178\times\) speedups over baselines. Moreover, BaConperforms well across diverse workloads, providing significant speedups for expensive join patterns while remaining competitive on those where baseline approaches are already efficient.

Table 1: Summary of nine query workloads across three databases, including the workload name with the link to file, associated database, number of queries (Qs), number of tables per query (Ts), number of join patterns (formally defined in [sec:sec:prelim]), and a short description with source in the second row for each workload.
Query Workload DB # Qs # Ts # join patterns
synthetic [25] IMDB 5,000 1 to 3 16
: generated by the training data generator with a different seed
scale [26] IMDB 500 1 to 5 31
: designed to show how MSCN generalizes to more joins.
job-light [27] IMDB 70 2 to 5 18
: derived from JOB [15], excluding string predicates and disjunctions.
job-light-single [28] IMDB 254 1 1
: single-table sub-plan queries extracted from job-light.
job-light-join [29] IMDB 696 2 to 5 27
: join sub-plan queries extracted from job-light.
stats-ceb [30] STATS 146 2 to 7 58
: designed to be more comprehensive, with more diverse queries and more complex join patterns on STATS, compared to job-light on IMDB.
stats-ceb-single [31] STATS 632 1 1
: single-table sub-plan queries extracted from stats-ceb.
stats-ceb-join [32] STATS 2,603 2 to 7 120
: join sub-plan queries extracted from stats-ceb.
dsb-grasp-20k [33] DSB 20,000 1 to 5 16
A sample of the original workload [34] generated by SeConCDF [20] and GRASP [35] on DSB. From 149,828 original queries, we remove queries not applicable to our methods and randomly sample 20,000 queries (details in [sec:sec:experiment]).

2 Preliminaries↩︎

2.0.0.1 Problem Statement and Notations

Consider a database \(\mathcal{D}\) with \(n\) tables \(R_1, \dots, R_n\), where each table \(R_i\) has a set of attributes denoted \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R_i]}\). We are interested in processing a set of counting queries \(\pmb{\mathcal{Q}}\) over \(\mathcal{D}\).2 Each query counts the number of tuples returned by a single-table selection or a selection-join over a subset of the tables in \(\mathcal{D}\). We assume equality joins with no cycles or self-joins, and that the selection is a conjunction of predicates comparing a single attribute with a literal using \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape=}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape>}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape<=}}}\), etc. We begin by introducing some useful notations (a table of notations is presented in the full version [37]). Given each query \(Q\in \pmb{\mathcal{Q}}\):

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}\) denotes the subset of tables in \(\mathcal{D}\) referenced by \(Q\).

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q}\) denotes the subset of attributes in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}\) referenced by the join predicates of \(Q\).

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{Q}\) denotes \(Q\)’s join predicates, represented as the set of equivalent classes of attributes in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q}\) induced by \(Q\)’s join predicates. Two attributes \(A_1, A_2\) belong to the same equivalent classes iff \(A_1 = A_2\) is logically implied by \(Q\)’s join predicates.

  • Given a pair of disjoint subsets \(\mathcal{R}\) and \(\mathcal{R}'\) of tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q}[\mathcal{R}|\mathcal{R}']\) denotes the subset of attributes of \(\mathcal{R}\) used by \(Q\) to join with \(\mathcal{R}'\); i.e., \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q}[\mathcal{R}|\mathcal{R}'] = \{ A\in \bigcup_{R\in \mathcal{R}} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]} \mid \exists \mathcal{E} \in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{Q}: A\in \mathcal{E} \land (\exists A' \in \mathcal{E}: A' \in \bigcup_{R\in \mathcal{R}'} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}) \}.\)

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}\) denotes the subset of attributes in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}\) referenced by \(Q\)’s selection predicates.

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}\) denotes \(Q\)’s selection predicates, represented as a mapping from each attribute \(A\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}\) to a range \(G\) over the domain of \(A\), such that \(G\) is the widest range such that \(A\in G\) is logically implied by \(Q\)’s selection predicates.

  • Given a subsets \(\mathcal{R}\) of tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}[\mathcal{R}]\) denotes the set of attributes of \(\mathcal{R}\) referenced by \(Q\)’s selection conditions; i.e., \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}[\mathcal{R}] = \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q} \cap \bigcup_{R\in \mathcal{R}} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}\).

For example, [sql:stats_ceb_example] shows the first four queries in the workload stats_ceb [30] over the STATS database [16]. For \(Q_4\):

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q_4} = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape comments}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory}}} \}\);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q_4} = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape comments.UserId}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.UserId}}} \}\);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{Q_4} = \{ \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape comments.UserId}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.UserId}}} \} \}\) (both join attributes are in one equivalence class);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q_4}[\{\text{\scalebox{0.9}[1.0]{\textsf{\upshape comments}}}\}|\{\text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory}}}\}] = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape comments.UserId}}} \}\);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{Q_4}[\{\text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory}}}\}|\{\text{\scalebox{0.9}[1.0]{\textsf{\upshape comments}}}\}] = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.UserId}}} \}\);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q_4} = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.PostHistoryTypeId}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.CreationDate}}} \}\);

  • \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q_4} = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.PostHistoryTypeId}}} \mapsto [1, 1],\\ \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.CreationDate}}} \mapsto [\text{\scalebox{0.9}[1.0]{\textsf{\upshape'2010-09-14 11:59:07'::timestamp}}}, \infty) \}\).

First 4 queries in stats_ceb [30].sql:stats_ceb_example SELECT COUNT(*) FROM badges as b, users as u – \(Q_1\) WHERE b.UserId = u.Id AND u.UpVotes >= 0; SELECT COUNT(*) FROM comments as c, badges as b – \(Q_2\) WHERE c.UserId = b.UserId AND c.Score = 0 AND b.Date <= ‘2014-09-11 14:33:06’::timestamp; SELECT COUNT(*) FROM comments as c, postHistory as ph – \(Q_3\) WHERE c.UserId = ph.UserId AND c.Score = 0 AND ph.PostHistoryTypeId = 1; SELECT COUNT(*) FROM comments as c, postHistory as ph – \(Q_4\) WHERE c.UserId = ph.UserId AND ph.PostHistoryTypeId = 1 AND ph.CreationDate >= ‘2010-09-14 11:59:07’::timestamp;

2.0.0.2 The First Baseline: Independent Processing (IndProc)

A straightforward solution, adopted by most existing work on learned CE, runs the queries in \(\pmb{\mathcal{Q}}\) one by one, independently, using the DBMS that manages \(\mathcal{D}\). We call this approach IndProc for short. The performance of IndProcis heavily dependent on the underlying DBMS. A capable DBMS will optimize each query by pushing down selection conditions, reordering joins, and choosing appropriate join methods, leveraging data statistics and available indices. Caching by the DBMS buffer pool may also improve execution performance across queries. On the other hand, most DBMS lack advanced methods for optimizing multiple queries simultaneously. Moreover, most of them choose to execute a counting query \(Q\) by first joining all tables in \(Q\) before applying the final \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape COUNT}}}\) aggregation, failing to explore opportunities for pushing \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape COUNT}}}\) below joins.

2.0.0.3 Join Patterns

Before presenting a more advanced baseline solution as well as our solution in 3, we introduce a concept used by both. The join pattern of a query \(Q\) is characterized by \(\langle \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{Q}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{Q} \rangle\), i.e., the tables that \(Q\) joins, and \(Q\)’s join predicates. Suppose queries in \(\pmb{\mathcal{Q}}\) have \(q\) distinct join patterns \(\mathfrak{J}_1, \ldots, \mathfrak{J}_q\); these patterns partition \(\pmb{\mathcal{Q}}\) into a disjoint union of \(q\) subsets of queries, denoted \(\pmb{\mathcal{Q}}[\mathfrak{J}_1], \ldots, \pmb{\mathcal{Q}}[\mathfrak{J}_q]\), where each \(\pmb{\mathcal{Q}}[\mathfrak{J}_i]\) is the set of queries with join pattern \(\mathfrak{J}_i\) (but may differ in their selection predicates). For example, \(Q_3\) and \(Q_4\) in [sql:stats_ceb_example] have the same join pattern (with tables \(\{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape comments}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory}}} \}\) and join predicate \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape comments.UserId}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape=}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape postHistory.UserId}}}\), despite having different selection predicates). \(Q_1\) and \(Q_2\) each contribute a distinct join pattern. In practice, because all queries in \(\pmb{\mathcal{Q}}\) come from the same underlying database \(\mathcal{D}\), the number of distinct join patterns tends to be much smaller than the number of queries. For example, JOB’s synthetic [25] contains 5,000 queries but only 16 distinct join patterns.

2.0.0.4 The Second Baseline: Join and Post-Filtering (PostFilt)

This approach seeks to avoid redundant computation across queries with identical join patterns. We pre-process the queries into partitions \(\pmb{\mathcal{Q}}[\mathfrak{J}_1], \ldots, \pmb{\mathcal{Q}}[\mathfrak{J}_q]\) according to their join patterns. This step requires only a scan over \(\pmb{\mathcal{Q}}\) to perform syntactical analysis. For each partition \(\pmb{\mathcal{Q}}[\mathfrak{J}_i]\), we compute the join only once, followed by a post-filtering step, which checks each join result tuple against the selection predicates of queries in \(\pmb{\mathcal{Q}}[\mathfrak{J}_i]\) to determine which queries’ counts to increment. We call this baseline PostFilt.

There are many options for implementing the post-filtering step, as discussed in 6, including methods that first build a data structure for \(\pmb{\mathcal{Q}}[\mathfrak{J}_i]\) for efficient identification and updating of counters in time sublinear to \(|\pmb{\mathcal{Q}}[\mathfrak{J}_i]|\) per join result tuple. While these methods can scale to thousands or millions of queries, the typical workloads we target do not have so many queries per join pattern, as evidenced in 1. Through our experiments, we have found a simple strategy leveraging the underlying DBMS to be the most effective. Given a join pattern \(\mathfrak{J} = \langle \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}} \rangle\) and \(k\) queries \(Q_1, \ldots, Q_k\) sharing this pattern, we issue a single SQL query in [sql:postfilter] to compute all of them. Here, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{}\), and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{}\) are translated into SQL. Each \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape CASE}}}\) expression determines whether a join result tuple contributes to a query \(Q_i\) by checking its selection predicates (a failed check yields \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape NULL}}}\), which is ignored by \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape COUNT}}}\)). In the end, a single \(k\)-component result tuple is computed, with each component holding the result count for one query.

Computing \(k\) queries with join pattern \(\mathfrak{J}\) in PostFilt.sql:postfilter SELECT COUNT(CASE WHEN \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q_1}\) THEN 1 END), ..., COUNT(CASE WHEN \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q_k}\) THEN 1 END) FROM \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) WHERE \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\);

2.0.0.5 Discussion

Compared with IndProc, PostFilteliminates redundant computation of joins among queries sharing the same join pattern. However, IndProccan still outperform PostFiltif the selection predicates of these queries have little overlap, and if database indices enable IndProcto apply selective selection predicates early in query processing. In contrast, PostFilthas no effective means to push selection predicates down because in most workloads, the disjunction of all selection predicates from multiple queries cannot be expressed as a succinct, “sargable” [38] \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape WHERE}}}\) condition without introducing many false positives. Furthermore, most database optimizers do not push aggregation below joins, let alone those with conditional expression inputs as in [sql:postfilter]. Therefore, PostFilteffectively enumerates the full join result before post-filtering and counting—a key limitation that we seek to overcome.

3 Basic BaCon↩︎

This section introduces basic BaCon, focusing on key ideas and the high-level algorithm. Many implementation and optimization details are also crucial to making BaConcompetitive in practice, but to simplify presentation, we defer them to 4. We start with three key ideas, along with some results and definitions based on them; we then describe the algorithm. Like PostFilt, given a set \(\pmb{\mathcal{Q}}\) of queries, BaConpartitions \(\pmb{\mathcal{Q}}\) into subsets according to join patterns, such that queries in each subset \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) share the same join pattern \(\mathfrak{J} = \langle \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}} \rangle\). Hence, most of this section focuses on how to process one such subset of queries given \(\mathfrak{J}\).

Before proceeding, we briefly present a geometric view of the problem for intuition. Each result tuple in the cross product of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) can be seen as a point in a high-dimensional space \(\mathbb{S}\), where each dimension corresponds to an attribute in \(\bigcup_{R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}\), ignoring those not referenced by any predicate in \(\pmb{\mathcal{Q}}[\mathfrak{J}]\). Let \(J\) denote the result of the full join of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) using \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\). Points in \(J\) are those cross-product points that lie on a hyperplane \(\mathbb{J}\) in \(\mathbb{S}\) defined by \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\). Each query \(Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]\) corresponds to an orthogonal hyperrectangle in \(\mathbb{S}\) with range predicates restricted to dimensions in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}\). Our problem boils down to counting, for each \(Q\), how many points in \(J\) fall into \(Q\)’s hyperrectangle.

Intuitively, these points are not positioned arbitrarily: not only do they lie on \(\mathbb{J}\) because of the join predicates, but they also come from the cross product of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\), meaning that their projections onto the subspace \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}]\) for each \(R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) cannot exceed \(|R|\) distinct points, which is often much smaller than \(|J|\). In fact, if we consider the subset of \(J\) restricted to any particular combination of values for all join attributes, their projections onto \(\mathbb{S}[\bigcup_{R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}]\) will form a perfect Cartesian product of their projections onto each of \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}]\) for \(R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) (which we shall substantiate in 3.1). This property, along with the fact the query hyperrectangles may overlap significantly, makes it possible to perform counting tasks more efficiently than simply enumerating \(J\) upfront (which \(\mathrm{\small PostFilt}\) does).

To further exploit the overlaps among the query hyperrectangles, consider a subset of \(J\) that forms a Cartesian product described above. We can partition its projection onto to each subspace \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}]\) into a coarse grid, using the endpoints of the query range predicates (a process we call “quantization” later in 3.2). For example, in 2, this projection consists of a collection of red points, partitioned by a \(4 \times 4\) grid. A key property of this grid is that all points within a cell lie in the same subset of query hyperrectangles. Therefore, we can compress the red points in each cell to a single “weighted” point, yielding a compressed representation of \(J\) (which we refer to as a “count map” later in 3.3). This compressed representation is constructed recursively for each \(\mathfrak{J}\), without having to enumerate \(J\) first (3.4).

3.1 Conditional Orthogonality of Joins↩︎

The first idea has its roots in the well-studied problem of factorized databases and related worst-case optimal join algorithms (further discussed in 6). A simple observation is that, in order to count the number of tuples in a cross product between two tables \(R_1\) and \(R_2\), we just need to calculate \(|R_1| \times |R_2|\), without enumerating \(R_1 \times R_2\). While the same observation no longer holds for \(|R_1 \Join_{R_1.A=R_2.A} R_2|\) when join predicate exists, if we additionally set the join attribute value \(A=x\), then the number of result tuples conditioned on this specific setting can still be computed directly: i.e., \(|\sigma_{A=x} (R_1 \Join_{R_1.A=R_2.A} R_2)| = |\sigma_{A=x} R_1| \times |\sigma_{A=x} R_2|\). We can generalize this idea further to a star-shaped join as follows.

Lemma 1 (Conditional Orthogonality of Joins). Consider a star-shaped join query centered at \(E_0\): \[E_0 \Join_{\theta_1} E_1 \Join_{\theta_2} \cdots \Join_{\theta_n} E_n.\] Here, the \(E_i\)’s are subqueries, and for each \(i = 1, \ldots, n\), \(\theta_i\) is a conjunctive predicate equating pairs of attributes from \(E_0\) and \(E_i\). Note that there are no join predicates across \(E_1, \ldots, E_n\). Denote by \(\mathcal{A}_0\) the set of join attributes from \(E_0\) referenced by \(\theta_1, \ldots, \theta_n\). Let \(v\) be any mapping of every attribute \(A\in \mathcal{A}_0\) to a value \(v(A)\) in \(A\)’s domain, and let \(v\llbracket\theta_i\rrbracket\) denote the condition obtained by applying \(v\) to \(\theta_i\) (i.e., replacing each \(A\in \mathcal{A}_0\) by \(v(A)\)—note that the resulting condition becomes a selection over \(E_i\)). The following equivalence holds: \[\begin{align} &\phantom{{}={}} \sigma_{\land_{A\in \mathcal{A}_0} A= v(A)} \left( E_0 \Join_{\theta_1} E_1 \Join_{\theta_2} \cdots \Join_{\theta_n} E_n \right) \\ &= \left(\sigma_{\land_{A\in \mathcal{A}_0} A= v(A)} E_0\right) \times \left(\sigma_{v\llbracket\theta_1\rrbracket} E_1\right) \times \cdots \times \left(\sigma_{v\llbracket\theta_n\rrbracket} E_n\right). \end{align}\]

Figure 1: A join pattern in the IMDB schema [39].

Example 1. Consider the join pattern in 1. Tables \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape title AS t}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape kind\_type AS kt}}}\), and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape movie\_companies AS mc}}}\) correspond to \(E_0, E_1, E_2\), while the join between \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape cast\_info AS ci}}}\) and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape role\_type AS rt}}}\) corresponds to \(E_3\). In \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape title}}}\), suppose we fix \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id=}}}\) \(x\) (which joins \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape kt.id}}}\)) and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id=}}}\) \(y\) (which joins \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc.movie\_id}}}\) and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}}\Join\text{\scalebox{0.9}[1.0]{\textsf{\upshape rt}}}\)). By 1, the number of full join result tuples with \((\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}}) = (x, y)\) can be computed directly as: \(|\sigma_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}=x \land \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}}=y} \text{\scalebox{0.9}[1.0]{\textsf{\upshape t}}}| \times |\sigma_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape kt.id}}}=x} \text{\scalebox{0.9}[1.0]{\textsf{\upshape kt}}}| \times |\sigma_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc.movie\_id}}}=y} \text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}| \times |\sigma_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci.movie\_id}}}=y} (\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}} \Join\text{\scalebox{0.9}[1.0]{\textsf{\upshape rt}}})|\), without enumerating the full join result tuples. To compute the full join result size overall, we iterate over all possible combinations of \((\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}})\) values in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t}}}\), apply the above procedure to each combination, and tally the total.

The style of processing illustrated by the above example has been used recently for efficient computation of aggregate queries [40]. The subexpression \(|\sigma_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci.movie\_id}}}=y} (\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}} \Join\text{\scalebox{0.9}[1.0]{\textsf{\upshape rt}}})|\) can be processed by the same procedure. Later in this section, we will see how to extend the idea to computing result counts of multiple queries with different selection predicates beyond simply counting the full join result.

3.2 Quantization of Selection Attributes↩︎

While tables and attribute domains can be large, the number of queries in \(\pmb{\mathcal{Q}}\) places a natural constraint on the number of constants from each domain appearing in selection predicates. In other words, from the perspective of \(\pmb{\mathcal{Q}}\), fine-grained differences among attribute values may not affect result counts. Our key idea is to compress the attribute domains using workload-aware quantiziation, turning large, complex domains into a small range of integers that are efficient to work with.

Given a join pattern \(\mathfrak{J}\), let \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}} = \bigcup_{Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{Q}\) denote the set of selection attributes in all queries of pattern \(\mathfrak{J}\). We construct a quantization scale \(\mathfrak{b}_A\) for each selection attribute \(A\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}}\) as follows. First, we extract from \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) the set \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{\mathfrak{J}}(A) = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}(A) \mid \exists Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]: A\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q} \}\) of predicate ranges associated with \(A\). We sort all range endpoints, which partition the domain of \(A\) into an ordered list of atomic ranges. For each atomic range contained in at least one query range in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{\mathfrak{J}}(A)\), we create a new bucket in \(\mathfrak{b}_A\) and assign it a serial integer id (starting with \(1\)). Hence, \(\mathfrak{b}_A\) maps each relevant atomic range (bucket) to an integer, preserving order. A value \(x\) from \(A\)’s domain is quantized into an integer \(\mathfrak{b}_A(x)\), the id of the bucket containing \(x\), or \(0\) if \(x\) lies outside all of buckets in \(\mathfrak{b}_A\). We present the detailed construction algorithm in the full version [37], which handles additional intricacies with open or close intervals.

Let \(\mathfrak{B}_\mathfrak{J} = \{ \mathfrak{b}_A \mid A \in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}} \}\) denote the collection of all attribute quantization scales for queries in \(\pmb{\mathcal{Q}}[\mathfrak{J}]\), and \(\mathfrak{B}_\mathfrak{J}[R] = \{ \mathfrak{b}_A \mid A \in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}} \cap \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]} \}\) denote those for attributes in table \(R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\). Returning to the geometric view introduced at the beginning of the section, \(\mathfrak{B}_\mathfrak{J}[R]\) induces a grid over the subspace of \(\mathbb{S}\) spanning the selection attribute dimensions of \(R\). This grid allows us to map each tuple in \(R\), by its selection attribute values \(\langle x_1, \ldots, x_k \rangle\), to a grid coordinate \(\smash{\vec{b}} = (b_1, \ldots, b_k)\), where each \(b_i = \mathfrak{b}_{A_i}(x_i)\) is the bucket id for value \(x_i\) in the quantization scale for attribute \(A_i\).

Continuing with the geometric intuition, collectively, quantization scales \(\mathfrak{B}_\mathfrak{J}\) induces a grid over the subspace \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}}]\) consisting of the selection attribute dimensions across all tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\). By construction of \(\mathfrak{B}_\mathfrak{J}\), all query hyperrectangles in \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) perfectly align with grid boundaries. Hence, all selection predicates can be quantized using the same scales. Lemma 2 below formalizes the guarantee that precise evaluation of selection predicates is possible in the quantized space.

Lemma 2 (Quantization Preserves Selections). Given a set of selection-join queries \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) and quantization scales \(\mathfrak{B}_\mathfrak{J}\) constructed from \(\pmb{\mathcal{Q}}[\mathfrak{J}]\), there exists a function \(f(\mathfrak{b}_A, \delta)\) returning an integer range \([i_1, i_2]\) for a range \(\delta\) over an attribute \(A\) with quantization scale \(\mathfrak{b}_A\in \mathfrak{B}_\mathfrak{J}\), such that for any \(Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]\) and every selection predicate \(A\mapsto \delta\) in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}\): \(x \in \delta \Leftrightarrow \mathfrak{b}_A(x) \in f(\mathfrak{b}_A, \delta)\).

As an example, 2 shows the grid over table \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}\) induced by two queries. The quantization scales \(\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape company\_type\_id}}}}\) and \(\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape company\_id}}}}\) respectively correspond to the two \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}\) attributes referenced by the queries’ selection predicates. (For now, ignore the mention of “projected subslice,” which will be formally introduced in 3.4.) The buckets for \(\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape company\_type\_id}}}}\), numbered \(1\) through \(3\), are \([a_1,a_2)\), \([a_2,\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(a_2))\), and \([\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(a_2), \infty)\), where \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(a_2)\) denotes the successor value of \(a_2\) in the domain; any value in \((-\infty, a_1)\) will be quantized to special bucket id \(0\) because this range is not contained in any selection predicate. The buckets for \(\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape company\_id}}}}\) are similarly induced by selection predicates involving \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape company\_id}}}\).

Figure 2: Quantized selection count map for a projected subslice of table \text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}. The quantization scales for \text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}} are induced by queries Q_{1} and Q_{2} (only selection predicates on \text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}} are shown).

3.3 Quantized Selection Count Maps↩︎

Since queries in \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) ultimately only care about counts, a natural idea following selection attribute quantization is to further aggregate the points that fall into each grid cell induced by \(\mathfrak{B}_\mathfrak{J}\) into a single count, instead of enumerating them.

Given a table \(R\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) with selection attributes \(\{ A_1, \ldots, A_k \}\) and quantization scales \(\mathfrak{B}_\mathfrak{J}[R] = \{ \mathfrak{b}_{A_1}, \ldots, \mathfrak{b}_{A_k} \}\), we compress a subset of tuples in \(R\) into a (quantized selection) count map \(\mathfrak{M}\): each entry of \(\mathfrak{M}\) maps a grid coordinate \(\smash{\vec{b}} = (b_1, \ldots, b_k)\) to \(\mathfrak{M}[\smash{\vec{b}}]\), the count of tuples within the grid cell—i.e., any tuple \(t\) satisfying \(\mathfrak{b}_{A_i}(t.A_i) = b_i\) for \(i = 1, \ldots, k\). For example, 2 shows the count map for the set of tuples (2D points in the geometric view).

We generalize the concept of count map \(\mathfrak{M}\) over any subset of tables \(\mathcal{R} \subseteq \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\). Let \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}}[\mathcal{R}] = \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathfrak{J}} \cap \bigcup_{R\in \mathcal{R}} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}\) denote the set of selection attributes in \(\mathcal{R}\) from all queries of pattern \(\mathfrak{J}\). A grid coordinate for \(\mathfrak{M}\), with one component for each attribute in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathcal{R}}\), identifies a grid cell induced by quantization scales \(\{ \mathfrak{b}_A \mid A \in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathcal{R}} \}\) in subspace \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathcal{R}}]\). Given a subset \(T\) of tuples in the cross product of \(\mathcal{R}\) corresponding to points in \(\mathbb{S}[\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\sigma}_{\mathcal{R}}]\), \(\mathfrak{M}\) counts the corresponding points of \(T\) in each grid cell.

Our goal is to construct a count map over the entire \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) for the full join result set \(J\), but importantly, without enumerating \(J\). Intuitively, count maps represent intermediate results compactly. Obtaining a count map for a single table is easy, but combining count maps for different subsets of tables into a big one is complicated by joins, because not all points in two grid cells in orthogonal subspaces join with each other. However, leveraging 1, we can process points in \(J\) in groups: points in each group all share appropriate join attribute values, thereby allowing count maps to be computed for different subsets of tables and then conveniently “multiplied.” Then, the product count maps across groups can be “added” to obtain the final result count map. An example will be provided in 3.4.

We formally define “multiply” and “add” as follows:

\(\mathfrak{M}_1\) and \(\mathfrak{M}_2\) are count maps over disjoint subsets \(\mathcal{R}_1\) and \(\mathcal{R}_2\) of tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\). We define \(\mathfrak{M}_1 \otimes \mathfrak{M}_2\), a count map over \(\mathcal{R}_1 \cup \mathcal{R}_2\), as \(\smash{\big\{ \smash{\vec{b}}_1\mathbin{\smallsmile}\smash{\vec{b}}_2 \mapsto (\mathfrak{M}_1[\smash{\vec{b}}_1]\cdot\mathfrak{M}_2[\smash{\vec{b}}_2]) \mid \smash{\vec{b}}_1 \in \mathfrak{M}_1, \smash{\vec{b}}_2 \in \mathfrak{M}\big\}}\), where \(\mathbin{\smallsmile}\) concatenates grid coordinate vectors.

\(\mathfrak{M}_1\) and \(\mathfrak{M}_2\) are count maps over the same subset of tables \(\mathcal{R} \subseteq \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) (and thus same quantization scales). We define \(\mathfrak{M}_1 \oplus \mathfrak{M}_2\), a count map over \(\mathcal{R}\), as \(\smash{\big\{ \smash{\vec{b}} \mapsto (\mathfrak{M}_1[\smash{\vec{b}}]+\mathfrak{M}_2[\smash{\vec{b}}]) \mid \smash{\vec{b}} \in }\) \(\smash{\mathfrak{M}_1 \big\}}\).

The following lemmas establish the correctness of using \(\otimes\) and \(\oplus\) for computing count maps. Formally, given \(\pmb{\mathcal{Q}}[\mathfrak{J}]\), quantization scales \(\mathfrak{B}_\mathfrak{J}\), a subset of tables \(\mathcal{R} \subseteq \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\), and a subset \(T\) of tuples in the cross product of \(\mathcal{R}\), we say that a count map \(\mathfrak{M}\) over \(\mathcal{R}\) is complete with respect to \(T\) if for any query \(Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]\), the size of the intersection between \(T\) and the selection-join subquery of \(Q\) restricted3 to \(\mathcal{R}\) can be computed from \(\mathfrak{M}\) and \(\mathfrak{B}_\mathfrak{J}\).

Lemma 3 (Multiplying Count Maps). Given \(\pmb{\mathcal{Q}}[\mathfrak{J}]\), consider disjoint subsets \(\mathcal{R}_0, \ldots, \mathcal{R}_n\) of tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\), where \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\) implies a join condition \(\theta_i\) relating \(\mathcal{R}_0\) to \(\mathcal{R}_i\) for each \(i = 1,\ldots,n\), but there is no join condition across \(\mathcal{R}_1, \ldots, \mathcal{R}_n\) that is not already implied by \(\bigwedge_i \theta_i\). For each \(i = 1, \ldots, n\), let \(E_i\) denote the join subquery of tables in \(\mathcal{R}_i\), with condition implied by \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\). Consider the star-shaped join query centered at \(E_0\): \[E_0 \Join_{\theta_1} E_1 \Join_{\theta_2} \cdots \Join_{\theta_n} E_n,\] which conforms to the query structure in 1. Denote by \(\mathcal{A}_0 = \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{\mathfrak{J}}[\mathcal{R}_0\,|\,\cup_{i\in[1,n]} \mathcal{R}_i]\) the set of join attributes from \(E_0\) referenced by \(\theta_1, \ldots, \theta_n\). Let \(v\) denote any mapping of every attribute \(A\in \mathcal{A}\) to a value \(v(A)\) in \(A\)’s domain, for some attribute set \(\mathcal{A}\) where \(\mathcal{A}_0 \subseteq \mathcal{A} \subseteq \bigcup_{R\in \mathcal{R}_0} \text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}{[R]}\). Suppose:

  • \(\mathfrak{M}_0\) is a count map over \(E_0\) complete w.r.t.\(\sigma_{\land_{A\in \mathcal{A}} A= v(A)} E_0\); and

  • \(\forall i = 1,\ldots,n\): \(\mathfrak{M}_i\) is a count map over \(E_i\) complete w.r.t.\(\sigma_{v\llbracket\theta_1\rrbracket} E_i\).

Then, \(\mathfrak{M}_0 \otimes \mathfrak{M}_1 \otimes \cdots \otimes \mathfrak{M}_n\) is complete with respect to \[\sigma_{\land_{A\in \mathcal{A}} A= v(A)} \left( E_0 \Join_{\theta_1} E_1 \Join_{\theta_2} \cdots \Join_{\theta_n} E_n \right).\]

Lemma 4 (Adding Count Maps). Given \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) and a subset of tables \(\mathcal{R} \subseteq \text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\), suppose \(\mathfrak{M}_1, \ldots, \mathfrak{M}_n\) are count maps over \(\mathcal{R}\), where each \(\mathfrak{M}_i\) is complete with respect to a subset \(T_i\) of tuples in the cross product of \(\mathcal{R}\). If \(T_1, \ldots, T_n\) are disjoint, then \(\mathfrak{M}_1 \oplus \cdots \oplus \mathfrak{M}_n\) is complete with respect to \(\bigcup_{i \in [1,n]} T_i\).

3.4 Basic BaConAlgorithm↩︎

Figure 3: \mathrm{\small BaCon}(\pmb{\mathcal{Q}}, \mathcal{D})

We are now ready to describe the overall BaConalgorithm (3). First, we make a pass over all queries and partition them into subset where each contains queries with the same join pattern. For each subset \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) with join pattern \(\mathfrak{J}\), we construct the quantization scales for them, as discussed in 3.2. We then determine an “execution plan” for \(\mathfrak{J}\) as an ordered tree, akin to the notion of “(paths in) f-tree” in factorized database literature [22], [23]. Nodes in this tree correspond to the tables of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\), and edges connecting the nodes represent equijoin conditions among them. Assuming that queries are acyclic and contain no cross products, one such tree always exists to capture \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\) precisely. Indeed, there can be multiple alternative plan trees; we defer the discussion of how to choose one to 4. At a high level, we process \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) using an in-order traversal of the plan tree starting from its root, by calling its main workhorse, the recursive procedure \(\mathrm{\small BaCon}\mathrm{\small Recurse}\) (Line [alg:our95main:call95recurisve]), to compute a count map for \(\pmb{\mathcal{Q}}[\mathfrak{J}]\). This count map is then used to calculate the result counts for queries, which can be done naively by summing up the counts at grid coordinates in each query hyperrectangle; we describe an optimized implementation in 4.

Figure 4: \mathrm{\small BaCon}\mathrm{\small Recurse}(R, u)

BaConRecurse(4, illustrated in 5) is called on a plan tree node (table) \(R\) and a filter specified by a mapping \(u\) that binds some attributes of \(R\) to specific values, which defines a “slice,” or the subset \(T\) of result tuples, of the join of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape subtree}}}(R)\) (set of tables in the subtree rooted at \(R\)) satisfying \(u\). The goal of BaConRecurseis to compute a count map for this slice \(T\) (the call to \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape root}}}(\mathfrak{J})\) has \(u = \emptyset\), so its slice in fact contains all result tuples in the full join of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\)). As discussed in 3.3, processing the entirety of \(T\) efficiently is hard; instead, BaConRecursepartitions the slice into “subslices,” where each subslice \(T_v\) binds a particular combination \(v\) of values for \(R\)’s attributes that join with its children, i.e., \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Attrs}}}^{\Join}_{\mathfrak{J}}[R\,|\,\text{\scalebox{0.9}[1.0]{\textsf{\upshape children}}}(R)]\). To this end, BaConRecursecalls ProcessTable([sql:count-table]) to enumerate all possible such bindings (Line [alg:our95recursive:loop]). Each iteration of the loop (Lines [alg:our95recursive:loop][alg:our95recursive:loop-end]) computes a count map for the subslice \(T_v\) defined by a particular \(v\). By 3, this count map can be computed by projecting \(T_v\) into “projected sublices,” one for \(R\) and each of \(R\)’s child subqueries, and multiplying the count maps of these projected subslices. Conveniently, the call to ProcessTablecomputes, for each projected subslice of \(R\), the coordinate-count pairs to populate the count map. We recursively call BaConRecurseon each child of \(R\) to compute the count map for its projected subslice. Line [alg:our95recursive:multiply], using \(\otimes\), combines these count maps into a single count map for \(T_v\). Finally, applying 4, Line [alg:our95recursive:add] uses \(\oplus\) to accumulate the count maps for all \(T_v\)’s into the final count map for \(T\) to be returned.

Figure 5: Illustration of 4, continuing 1.

Example 2. Following 5, at the root \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape title}}}\), BaCon iterates over all possible bindings for \((\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}})\) in turn. Suppose the blue subslice fixes \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}=1, \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}}=1\). For the subslice projected to \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t}}}\), suppose \(\mathfrak{M}_{0} = \mathfrak{M}{\text{\scalebox{0.9}[1.0]{\textsf{\upshape t}}}} = \{(9) \mapsto 2, \dots\}\). We recursively call each child with the corresponding binding, as indicated by the arrows pointing to each child. For the “projected subslices” on children, we further assume that \(\mathfrak{M}_{2} = \mathfrak{M}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}}\) is the one shown in 2. From the geometric view, \(\mathfrak{M}_{2}\) is computed by the flattened hyperrectangle (in 2) with \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc.movie\_id}}} = \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}} = 1\). For illustration, let us say that \(\mathfrak{M}_{1}\) (\(\mathfrak{M}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape kt}}}}\)) \(=\{\dots, (1) \mapsto 1, \dots\}\) and \(\mathfrak{M}_{3}\) (\(\mathfrak{M}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}} \Join\text{\scalebox{0.9}[1.0]{\textsf{\upshape rt}}}}\)) \(=\{\dots, (3, 3, 3) \mapsto 3, \dots\}\).

This subslice contributes to the global count map \(\mathfrak{M}\) as follows: \(\mathfrak{M}\gets \mathfrak{M}\oplus (\mathfrak{M}_0 \otimes \dots \otimes \mathfrak{M}_3)\). We zoom in on one example—how the global count map entry at grid coordinate \((9, 1, 2, 2, 3, 3, 3)\) is updated. Concretely, this global grid coordinate is the concatenation of \((9)\) from \(\mathfrak{M}_{0}\), \((1)\) from \(\mathfrak{M}_{1}\), \((2, 2)\) from \(\mathfrak{M}_{2}\), and \((3, 3, 3)\) from \(\mathfrak{M}_{3}\). Recall that the counts in \(\mathfrak{M}_{0}, \ldots, \mathfrak{M}_{3}\) at these coordinates are \(2, 1, 2, 3\), respectively; therefore, the contribution to the global count map entry is \(2 \times 1 \times 2 \times 3 = 12\). After processing this subslice, BaConRecurse proceeds to the next \((\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.kind\_id}}}, \text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}})\) combination in order.

SQL code for \(\mathrm{\small ProcessTable}(R, u, \mathcal{A}^{\texttt{out}})\).sql:count-table’ ‘Input: Mapping \(u\) binds attributes \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \ldots\) to specific values, and \(\mathcal{A}^{\texttt{out}} = \{J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2, \ldots\}\) specifies the set of attributes for partitioning result entries. The quantization scales are \(\mathfrak{B}_\mathfrak{J}[R] = \{ \mathfrak{b}_{S_1}, \mathfrak{b}_{S_2}, \ldots \}\).’ ‘Output: Result entries have the form \(\langle v, \smash{\vec{b}}, c\rangle\), sorted by \(v\), where \(v\) is a mapping from \(\mathcal{A}^{\texttt{out}}\) to values, \(\smash{\vec{b}}\) is a grid coordinate for \(\mathfrak{B}_\mathfrak{J}[R]\), and \(c\) is the associated count.’ ’


’ SELECT \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., B1, B2, ..., COUNT(*) FROM ( SELECT \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., quantize(\(S_1, \mathfrak{b}_{S_1}\)) AS B1, quantize(\(S_2, \mathfrak{b}_{S_2}\)) AS B2, ... FROM \(R\) WHERE \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\) = \(u(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1)\) AND \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\) = \(u(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2)\) AND ... ) AS TMP GROUP BY \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., B1, B2, ... ORDER BY \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ...;

Delving into ProcessTable([sql:count-table]), we see how a single SQL query over a table \(R\) performs “subslicing” (by \(\mathcal{A}^\texttt{out}\)) and computes the count maps of all projected subslices. In the \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape FROM}}}\) subquery that defines \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape TMP}}}\), the \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape WHERE}}}\) condition applies the attribute binding \(u\) that defines \(T\), the slice of interest; the \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape SELECT}}}\) clause uses a user-defined function \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape quantize}}}\) to check each selection attribute value against a quantization scale and returns its grid coordinate. The outer query groups the \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape TMP}}}\) tuples by \(\mathcal{A}^\texttt{out}\) to perform subslicing, and then by their grid coordinates to compute tuple count per grid cell for each projected subslice. Finally, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ORDER}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape BY}}}\) ensures that entries for the same projected subslice, i.e., those with the same binding \(v\) for \(\mathcal{A}^\texttt{out}\), are consecutive in the output. This ordering allows the caller BaConRecurseto detect where a sequence of entries with a new \(v\) starts, so it can start processing a new subslice.

4 Additional Optimization and Implementation Details↩︎

This section fills in some of the details about BaConnot covered by 3 and describes additional optimizations needed to make BaConpractical for query workloads for training learned CE models. Before delving into details, we note that BaContakes the high-level approach of leveraging DBMS for processing, not only because it houses the data, but also because it already provides versatile support for indexing and querying. To make BaConeasy to adopt, we do not modify any DBMS internals but instead implement, in a client application, parts of the algorithm that are inefficient for the DBMS. Many optimizations thus involve balancing database- and client-side processing options and mitigating the overhead of interfacing them. Overall, we perform ProcessTable([sql:count-table]) inside the DBMS, using cursors to fetched join values, quantization grid coordinates, and associated counts. The remainder of BaConRecurse(i.e., merging of count maps along the tree structure) and the remainder of BaCon(i.e., ComputeCounts) are performed on the client side. The associated optimizations are presented below. While BaConis currently implemented on top of PostgreSQL, the ideas in this section generalize to other DBMSs, although details may differ.

Note that BaConis not optimized for join patterns characterized by queries exhibiting little opportunity for shared processing (e.g., when there are too few of them, or their predicates overlap very little), or an underlying full join that is small and cheap to compute to begin with. In such cases, BaConmay not outperform baselines IndProcor PostFilt. Therefore, we also have developed a method called Hybrid, which acts as an optimizer to choose the appropriate method to use among IndProc, PostFilt, and BaConfor a given join pattern. In our experimental comparison of these methods in 5, we empirically analyze the cases where BaConis not optimal (5.2): they follow our characterizations above and play only a small role in the overall performance of query workloads found in the learned CE literature. Furthermore, despite Hybrid’s ability to make on per-pattern decisions, it offers no consistent improvement over BaConoverall. This observation speaks to the effectiveness of BaCon, even without Hybrid, for handling practical CE training workloads. More details on Hybridcan be found in the full version [37].

4.1 Choosing Execution Plan for a Join Pattern↩︎

Give a join pattern \(\mathfrak{J}\), the cost of BaConRecurseto process \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) depends on the choice of a tree-based execution plan, introduced in 3.4. A cost-based decision requires fine-grained knowledge of the data distribution, which itself is expensive to acquire. Instead, we follow some heuristics below to construct a tree-based plan.

The first heuristic is to pick tree root to be the table \(R\) with the highest degree in \(\mathfrak{J}\)’s join graph: i.e., the root joins with the largest number of other tables in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Tables}}}_{\mathfrak{J}}\) according to \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\). The rationale is to maximize the saving opportunities identified by 3: a root with a large in-degree can potentially avoid enumerating more cross products.

Geometrically, this heuristic ensures that, during recursion, the grid coordinates in each descendant lie in low-dimensional subspaces.

Once we pick the root, the rest of the tree structure follows naturally from \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{\mathfrak{J}}\). Since we assume queries to be acyclic, no additional join conditions are needed beyond those corresponding to the tree edges. For example, in 1, even though \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}\) and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}}\) can join on \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc.movie\_id = ci.movie\_id}}}\), there is no edge between them in the tree, because the two join conditions from root \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t}}}\) to \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc}}}\) and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci}}}\), involving \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape t.id}}}\), together imply \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mc.movie\_id = ci.movie\_id}}}\).

The next optimization then marks certain tree edges that correspond to joining a foreign key (FK) of the parent table \(R\) to a primary key (PK) of the child table \(R'\). For such a marked FK-PK edge, when BaConRecurseprocesses \(R\), it will simultaneously process \(R'\): on Line [alg:our95recursive:loop], ProcessTablewill additionally query \(R'\) and compute its count map along with that for \(R\) (with appropriate changes to [sql:count-table]); and on Line [alg:our95recursive:recursive95loop], children of \(R'\) will be directly included, instead of \(R'\) itself. The justification behind this optimization is that, because of the FK-PK join, each \(v\) would yield only one joining tuple from \(R'\), meaning there is no cross product to avoid in the first place. Therefore, we should “short-cut” this edge to eliminate the associated overhead.4 For simplicity, our implementation currently restricts this optimization to edges incident to the root, but applying it to the entire tree is possible as future work.

The last heuristic dynamically reorders subtrees under each node. We start with an arbitrary ordering. For a node \(R\), after some iterations of the loop on Lines [alg:our95recursive:loop][alg:our95recursive:loop-end] of 4, we count, for each child \(R_i\), how many settings of \(v\) yield no tuples from \(R_i\). Then, we reorder the children so that those with more such cases come first. Intuitively, they are more likely to produce an empty count map, with which we can “short-circuit” the sequence of multiplies.

4.2 Mitigating SQL Querying Overhead↩︎

4.2.0.1 Server- vs.Client-Side Cursors

When BaConruns a SQL query on the underlying database, we can use either a server-side cursor [41] or a client-side one [42]. A client-side cursor fetches the entire query result set into client memory. A server-side cursor, in contrast, allows the client to iterate row-by-row without materializing a potentially massive result set. While server-side cursors offer more flexibility and improve scalability, they are considerably slower and require state to be maintained on the database server; a large number of concurrent server-side cursors can cause performance issues. Therefore, BaConuses a server-side cursor for ProcessTableon the root, but switches to client-side cursors for all other calls. The rationale is that the call on the root table (from Line [alg:our95main:call95recurisve] of 3) has no bound attributes (\(u = \emptyset\)) and may generate a large result set. In contrast, other calls access much fewer tuples per subslice, and even with the batching optimization below, their result sizes remain manageable for client-side cursors.

4.2.0.2 Batching of ProcessTable

During recursive execution of BaConRecurseover the plan tree, the overhead of issuing many SQL queries via ProcessTablecan accumulate, especially when many of these queries, with specific attribute bindings, return small result sets. To mitigate this overhead, we batch multiple invocations of ProcessTablefor the same table \(R\), using a set of bindings \(U\) rather than a single binding \(u\), into one SQL query. Specifying the combined condition precisely in the \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape WHERE}}}\) clause of [sql:count-table] is generally infeasible, as it would have to enumerate all bindings in \(U\). Instead, we compute the minimum and maximum elements5 of \(U\) and use \([\min(U), \max(U)]\) as a safe but possibly imprecise range bound in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape WHERE}}}\), i.e., \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape WHERE}}}\) \((J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \ldots)\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape BETWEEN}}}\) \(\min(U)\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape AND}}}\) \(\max(U)\). We extend the query to additionally group by \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \ldots\) and return them, such that ProcessTablecan, using these attribute values, filter out result tuples that do not belong to \(U\).

In BaConRecurse, we implement this optimization by collecting \(\beta\) consecutive settings of \(v\) into a set \(V\) for processing as a batch, analogous to a \(\beta\)-fold loop unrolling on Lines [alg:our95recursive:loop][alg:our95recursive:loop-end] of 4. For each child \(R_i\), we project \(V\) to obtain the set \(U_i\) of unique bindings on its join attributes with \(R\) (Line [alg:our95recursive:project95binding]), and invoke ProcessTableon \(R_i\) with \(U_i\). The detailed algorithm is presented in the full version [37].

We now provide further analysis of this batching optimization. First, beyond reducing the overhead of issuing many queries, batching promotes reuse of ProcessTablecalls on the same table and attribute binding. Without batching, consecutive settings of \(v\) in the loop on Lines [alg:our95recursive:loop][alg:our95recursive:loop-end] of 4: it is likely that they agree on most components since they are sorted. Therefore, when \(v\) is projected to obtain some \(u_i\), the same \(u_i\) setting in the previous iteration may be used again to call ProcessTableon the same table \(R_i\), leading to waste unless we cache the results from previous calls. With batching, however, such reuse occurs naturally. So we have not implemented additional caching mechanisms for BaCon, although further exploration will be a good future work.

Second, compressing a set of bindings into a single range may introduce false positives. Indeed, \(R_i\) may always contain some tuples whose join attribute values lie in \([\min(U_i), \max(U_i)]\) but are not contained in \(U_i\), because \(U_i\) is generated from \(V\) and subject to all other constraints on the slice. Nonetheless, the concern with false positives is at least partly alleviated by the fact that BaConRecurseprocesses settings of \(v\) in order, meaning that it will call ProcessTableon \(R_i\) with “clustered” settings of \(u\) in \(U\).

4.2.0.3 Implementation of \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape quantize}}}\)

Since the UDF \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape quantize}}}\) in [sql:count-table] is invoked many times, efficient implementation is crucial for performance. We implement \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape quantize}}}\) as C-language UDFs in PostgreSQL [43], with one variant for each SQL data type. The quantization scale is implemented as an array of sorted boundary points, and a binary search is used to determine the correct bucket id. These C-language UDFs run as compiled code natively inside the server process, greatly outperforming SQL-based UDFs. BaConcreates these extensions at the beginning of its execution and drops them at the end. More generally, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape quantize}}}\) can be implemented in other DBMSs using available extensions, including CLR (common language runtime) integration for Microsoft SQL Server [44] or external procedures for Oracle Database [45].

4.3 Implementation of ComputeCounts↩︎

ComputeCountsis invoked by BaConto compute the result counts of queries in \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) given the final count map \(\mathfrak{M}\) computed by BaConRecurse. Conceptually, it performs a “join” between a set of query hyperrectangles (\(\pmb{\mathcal{Q}}[\mathfrak{J}]\)) and a set of weighted points (\(\mathfrak{M}\)), and reports the total weights within each hyperrectangle. Many processing strategies are possible, and the best depends on query and data distributions. For our target of query workloads for training learned CE models, we observe that \(|\pmb{\mathcal{Q}}[\mathfrak{J}]|\) is moderate (in hundreds or thousands) and \(\mathfrak{M}\) tends to be sparse (32% on average across join patterns in JOB’s synthetic). After experimenting with several methods, we have found a simple method based on nested loops to be the most effective for this setting. Basically, for each \(Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]\) and for each non-zero entry \(\smash{\vec{b}} \mapsto c\) of \(\mathfrak{M}\), we check whether, for every dimension of \(Q\)’s hyperrectangle with a range bound, the corresponding coordinate in \(\smash{\vec{b}}\) falls within this bound. If yes, we add the entry’s count \(c\) to \(Q\)’s running total. While BaConis implemented in Python, we specifically use Numba’s just-in-time compilation [46] for ComputeCounts. With the help from Numba’s compiler optimizations, this simple implementation was able to beat more sophisticated methods.

5 Experiments↩︎

We compare IndProc, PostFilt,6 and BaCon. All algorithms are implemented in Python3, using psycopg2 [47] to connect to PostgreSQL V16.8 with default configuration. All experiments are conducted on a Linux server with an Intel(R) Xeon(R) Gold 5215 CPU (40 logical cores, 2.50GHz), 256GB of main memory, and 1TB of disk storage. We set the batching parameter \(\beta{}\) (4.2) to 50,000 for BaCon for all experiments and present results for different \(\beta\)’s in the full version [37]. Code is available in the GitHub repository [48].

We test three popular datasets: IMDB [15], STATS [16], and DSB [17] (of scaling factor 2). They are loaded and initialized using the provided scripts [49][51], and the algorithms use exactly the indices provided therein without creating any additional ones.

We evaluate the algorithms on nine publicly available workloads in 1. These workloads span a wide range of sizes, query templates, join structures, predicate types, and result sizes, and are widely used for training and evaluating cardinality estimation techniques [16]. Among the workloads, only dsb-grasp-20k is post-processed [52] from the source file [34]. Specifically, CE work, SeConCDF [20] and GRASP [35], have numerical/categorical definitions for several attributes that are originally defined as \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape char(...)}}}\) in DSB (e.g., \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape cd.cd\_education\_status}}}\)). As these transformations are not clearly specified, we remove the queries involving such attributes, and then randomly sample 20k queries to ensure that baselines complete within a reasonable time. We additionally construct three new workloads based on job-light, used exclusively in 5.3.

Running time is our main performance metric. For IndProc, end-to-end running time is the sum over all queries. For PostFiltand BaCon, it includes pre-processing and time for each join pattern. To prevent baselines from running for too long, we cap IndProcat 900s per query, and PostFiltat 3,600s per join pattern; therefore, some baseline numbers are only lower bounds.

In terms of client-side memory usage, because BaCon processes each join pattern separately, memory peaks at the single most complex join pattern in the workload. Across all nine workloads, the maximum memory footprint of BaCon occurs for a join pattern in synthetic, at 1.7GB. We do not report memory usage for PostFiltand IndProc, as they executed entirely inside the database server.

5.0.0.1 Single-Threaded vs.Parallel Execution

Results below focus on single-threaded execution to isolate the algorithmic differences of BaCon compared with the baselines, without the potentially confounding factor of parallelization. We disable parallelism in PostgreSQL by setting \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape max\_parallel\_workers\_per\_gather}}}\) to 0, and restrict all client-side code to a single thread. Besides this single-threaded setting, we have also evaluated all methods in a parallel setting. Under the parallel setting, IndProc and PostFilt fully leverage PostgreSQL’s built-in parallelism; for BaCon, we use a simple multi-process design in which each process handles a subset of join patterns (more sophisticated parallelization strategies for BaCon are possible as future work). Briefly, the results show that, even with this simple strategy, BaConachieves end-to-end speedups of 2\(\times\) to 598\(\times\) compared with IndProc, consistent with the single-threaded results shown below in 5.1. Additional details on the parallel setting can be found in the full version [37].

5.1 End-to-End Running Time↩︎

2 reports the end-to-end running times of all three approaches across all nine real workloads. Lower-bound values (followed by “+”) are reported if a baseline runs out of time. We additionally show the relative speedups of BaCon over IndProc, which range from approximately 2\(\times\) to 178\(\times\). Speedups over PostFilt are omitted, as IndProc outperforms PostFilt on most long-running workloads. Overall, BaCon consistently outperforms both baselines and never times out: the worst per-join-pattern overhead of BaCon across all workloads is below 380s, far below the thresholds for baselines. The observed performance trends between IndProc and PostFilt align with our analysis in 2. In particular, PostFilt is advantageous for join patterns with many queries—where predicates tend to cover larger regions—and without intermediate join blow-ups (e.g., synthetic and dsb-grasp-20k). In the remaining cases, IndProc performs substantially better due to selection push-down in each query. BaCon is able to effectively combine the strengths of both IndProc and PostFilt.

Table 2: End-to-end running times (seconds, rounded) and relative speedups. +: lower-bound values, as is capped at 900s per query and at 3,600s per join pattern.
Query Workload Speedup vs.
synthetic 19,663 11,329+ 1,574 \(\uparrow\) 12.49 \(\times\)
scale 21,100+ 26,342+ 3,475 \(\uparrow\) 6.07+ \(\times\)
job-light 1,620+ 13,662+ 728 \(\uparrow\) 2.22+ \(\times\)
job-light-single 249 13 11 \(\uparrow\) 22.50 \(\times\)
job-light-join 8,601+ 18,353+ 1,541 \(\uparrow\) 5.58+ \(\times\)
stats-ceb 4,274+ 47,353+ 58 \(\uparrow\) 73.62+ \(\times\)
stats-ceb-single 18 2 2 \(\uparrow\) 8.70 \(\times\)
stats-ceb-join 35,292+ 53,363+ 198 \(\uparrow\) 178.14+ \(\times\)
dsb-grasp-20k 19,746 4,211 928 \(\uparrow\) 21.28 \(\times\)

Since both PostFilt and BaCon are designed around the notion of join patterns, we further analyze the results on a per-join-pattern basis in 5.2. Before doing so, we briefly summarize the other overheads—which are negligible compared with per-join-pattern running time—and omit them from ensuing discussions. IndProc incurs no such overheads. Both PostFilt and BaCon require loading and parsing SQL queries, extracting join patterns, and grouping queries accordingly, with complexity linear in the number of queries, tables, and selection attributes. This pre-processing step happens at the beginning when processing each join pattern, and typically completes within a few seconds; the only exception is dsb-grasp-20k, where it reaches \(\approx\)​20s due to the large number of queries (but is still small relative to the end-to-end running time).

5.2 Per-Join-Pattern Result↩︎

In this section, we analyze the results on a per-join-pattern basis. Although IndProc does not operate on join patterns, we group its results accordingly to facilitate direct comparison with the others.

Table 3: Number of timed-out cases. In (\(a\), \(b\)), \(a\) is the number of timed-out queries, and \(b\) is the number of join patterns that contain any timed-out queries.
Query Workload # queries # join patterns
synthetic 5,000 16 (0, 0) (251, 1)
scale 500 31 (20, 7) (84, 6)
job-light 70 18 (1, 1) (8, 2)
job-light-single 254 1 (0, 0) (0, 0)
job-light-join 696 27 (3, 1) (32, 3)
stats-ceb 146 58 (3, 2) (30, 11)
stats-ceb-single 632 1 (0, 0) (0, 0)
stats-ceb-join 2,603 120 (22, 7) (166, 13)
dsb-grasp-20k 20,000 16 (0, 0) (0, 0)

We first report the number of timed-out cases in 3. BaCon never times out. Overall, PostFilt times out more frequently: since it always materializes the unfiltered join, if that times out, all queries associated with the pattern time out. Additionally, workloads on STATS exhibit more time-outs, primarily due to larger intermediate joins than those on IMDB, as noted by [16].

Figure 6: Running time (seconds) per join pattern across 283 join patterns from 9 workloads. Join patterns are ordered by IndProc’s times. The inset is the zoom-in of the first 200 indices.

Next, we analyze the per-join-pattern overhead across all workloads. For clarity, consistent with the pre-defined time-out thresholds, we assign 3,600s to timed-out join patterns in PostFilt and 900s to timed-out queries in IndProc; actual running times will be higher. 6 reports the running times of all join patterns, ordered by IndProc’s times. Before index 120, all join patterns under IndProc complete within 10s; beyond this point, running times increase markedly. The inset zooms in the region before index 200, where BaCon closely tracks IndProc, sometimes faster and sometimes slower within a bounded margin, while PostFilt already times out on several patterns. Between indices 160 and 200, BaCon exhibits a small number of clear regressions relative to IndProc, with the largest gap (98s) at index 165; we analyze these cases in detail later. Outside the zoomed region, where join patterns run substantially longer, BaCon consistently and significantly outperforms IndProc and PostFilt.

Figure 7: Log-scale running time per join pattern in scale. Join patterns are ordered by the number of tables involved, and the figure is partitioned accordingly (labels shown above). Hatched bars (baselines only) indicate the lower bounds for timed-out cases.

Additionally, we present a detailed per-join-pattern analysis for scale and job-light here; results for the remaining workloads are in the full version [37]. As shown in 7, scale contains join patterns involving 1 to 5 tables, with more tables generally run longer. Timed-out queries arise mainly from patterns with many tables. For most long-running join patterns, BaCon consistently outperforms the baselines. For fast patterns, BaCon remains competitive, as the time gaps are small. However, there are several patterns where BaCon is noticeably slower than either baseline. Taking join pattern 6 as an example, IndProc, PostFilt, and BaCon take 61s, 35s, and 153s, respectively. This pattern has conditions \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ci.movie\_id = t.id AND mc.movie\_id = t.id}}}\) and only five queries. A breakdown of BaCon’s running time shows 118s (76%) for executing SQLs, 5s (3%) for aggregating rows by join attribute bindings and coordinates, and 18s (15%) for merging count maps; thus, SQL execution dominates and already exceeds the running times of the baselines. This regression stems from two factors. First, algorithmically, enumerating tuples from each table and quantizing them can be less efficient than IndProc when predicates are selective and overlaps are limited. Second, although BaConperforms less work than PostFilt, BaCon’s cursor invocations and row fetching/parsing incur more overhead than PostFilt’s server-side processing.

Figure 8: Log-scale running time per join pattern in job-light, with same format as 7.

We have similar observations from 8. As discussed by [16], job-light is a classical but relatively simple workload with 1 to 8 queries for each join pattern. The true cardinality range is also a magnitude smaller than that of stats-ceb. Therefore, IndProc consistently performs well on all join patterns, except one severe time-out for join pattern 17. However, BaCon remains competitive across join patterns and never times out. Regression compared with baselines is limited: in the worst case, BaCon is 55s slower compared with IndProc on join pattern 8, and 100s compared with PostFilt on join pattern 7.

Overall, consistent with the results in 5.1, BaCon does well across join patterns: it significantly outperforms the baselines on expensive patterns and remains acceptable on cheap ones.

5.3 Scalability↩︎

In this section, we evaluate the scalability of the three approaches using three synthetic workloads, denoted job-light-*. We choose job-light as the reference because, as shown in 8, IndProc performs consistently well, except for a single join pattern that times out. Specifically, on jog-light, BaCon outperforms IndProc in only 7 out of the 18 (39%) join patterns. Earlier, we have made the observation that the baselines—especially IndProc—can outperform BaCon on simple join patterns, e.g., where the number of queries is small and intermediate join sizes remain moderate. In particular, if the number of queries is small, there is less opportunity for sharing, and IndProc’s simple approach of optimizing and evaluating queries one by one works fine. Here, we further investigate how each approach scales as the workload size increases while preserving the original distribution.

We construct workloads with 1,000, 2,000, and 4,000 queries each. Queries are generated randomly and independently. We randomly select a query from job-light, reuse its template (and thus its join pattern), but modify its predicate constants: 1) for a constant in equality and inequality predicates, we replace it with a random value from the active domain of the corresponding attribute; 2) for range predicates, we randomize the left endpoint and adjust the right accordingly to preserve the range length.

Table 4: End-to-end running times (seconds, rounded) and relative speedups. +: lower-bound values, as is capped at 900s per query and at 3,600s per join pattern.
Query Workload Speedup vs.
job-light-1k 6,208 22,490+ 1,020 \(\uparrow\) 6.09 \(\times\)
job-light-2k 13,818 26,923+ 1,051 \(\uparrow\) 13.15 \(\times\)
job-light-4k 23,617+ 33,981+ 1,109 \(\uparrow\) 21.29+ \(\times\)
Table 5: End-to-end (E2E) running times for and , with the number of join patterns handled by each algorithm. I/P/B denotes //.
Query Workload E2E running time E2E running time # join patterns using I/P/B
scale 3,475 3,319 0/9/22
stats-ceb 58 195 3/1/54
other 5 - - 0/0/all

4 shows end-to-end running times and relative speedups. As expected, IndProc’s running time grows roughly linearly with workload size, since queries are processed independently. PostFilt grows more slowly because its dominant cost—computing the full join—is amortized across queries with the same join pattern; nevertheless, it still times out frequently. BaCon scales better for two reasons. First, quantization generally benefits larger workloads more. Second, the costs of enumerating join attribute bindings and quantization remain mostly stable: as the size of each quantization scale is bounded by the workload size \(|\pmb{\mathcal{Q}}[\mathfrak{J}]|\), the cost of quantizing an attribute is only \(O(\log(|\pmb{\mathcal{Q}}[\mathfrak{J}]|))\) thanks to binary search. Per-join-pattern results are provided in the full version [37]. While the number of join patterns remains 18, the number of them where BaCon outperforms IndProc increases to 14 (78%) in job-light-1k, 17 (94%) in job-light-2k, and finally 18 (100%) in job-light-4k. Overall, BaCon demonstrates superior scalability compared with both IndProc and PostFilt.

5.4 Validation against Hybrid↩︎

Figure 9: Log-scale running time per join pattern (where a baseline is chosen) in scale and stats_ceb, including Hybrid’s.

As shown in 5.2, there exist join patterns for which a baseline, IndProc or PostFilt, outperforms BaCon. These cases typically involve few queries and small intermediate join sizes. As discussed in the full version [37], we can adopt a hybrid approach that uses a classifier to pick which method to use given a join pattern. Here, we train Hybridusing observations from job_light_join and stats_ceb_join, and evaluate it on the remaining seven workloads. 5 reports the E2E running times of Hybrid (using BaCon as the reference) on the seven test workloads. Overall, Hybrid selects a baseline for 9 join patterns in scale and 4 join patterns in stats_ceb, while selecting BaConfor all other join patterns and workloads. Even with Hybrid’s conservative design, end-to-end running times show that mispredictions can occur, sometimes catastrophically. To take a closer look, 9 presents a per-join-pattern analysis, focusing on patterns where Hybridchooses a baseline. As shown in 9, for scale, all 9 switches to PostFilt are beneficial, yielding a total saving of 156s. In stats_ceb, Hybrid gains minor savings by choosing PostFilt on pattern 27 and IndProc on pattern 57, but incorrectly selecting IndProc on patterns 40 and 56 causes slowdowns of 1s and 129s, respectively. Overall, these results confirm that BaCon remains a safe bet for practical workloads. With additional data statistics or training, a better Hybridmight be possible as future work, but the overhead for statistics collection and training may outweigh its benefit.

6 Related Work↩︎

6.0.0.1 Select-Join-Aggregate Query Processing.

Counting join results (with selection predicates on base tables) is a special form of select-join-aggregate queries. Standard processing typically involves pushing down selection predicates before evaluating the join and then aggregate. The classic Yannakakis algorithm [53] computes free-connex join-count queries in \(O(N)\) time, where \(N\) is the number of tuples. Recent advancements [54] exploit a hybrid strategy of Yannakakis algorithm that handles acyclic join-count queries in \(\smash{O(N+N \cdot \mathsf{OUT}^{1-\frac{1}{w}})}\) time. BaCon draws inspiration from these techniques, specifically the recursive computation and merging of coordinate-to-count maps during backtracking from  [55][58]. Cost-based optimizers in these systems, such as Free Join’s [58], together with the AGM bound [59], [60], further inform our initial exploration of cost-based hybrid mechanism detailed in the full version [37]. However, more accurate cost estimates may conversely require more expensive estimation methods, such as LpBound [61], and/or specialized data storages and execution engines, such as Free Join’s COLT [58] and its integration to DuckDB [62].

6.0.0.2 Aggregation Push-down and Factorizations

Aggregation push-down reduces intermediate results via early or deferred aggregation. Classical approaches include eager and lazy aggregation [63] and integrating \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape GROUP BY}}}\) into cost-based optimization [64]. GuAo [40] identifies cases where join materialization can be avoided entirely and introduces a corresponding physical operator for SparkSQL [65]. Its propagation of frequencies grouped by join attributes resembles BaCon’s bucketization in single-query settings. However, it relies on binary joins, whereas our approach support multi-way merging of count maps. Factorized databases [22][24] eliminate tuple-level redundancy in query results through compact representations that can support efficient aggregation beyond \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape COUNT}}}\). These works, focusing on eliminating intra-query redundancy, are complementary to our approach, which also exploits inter-query sharing. Furthermore, these works typically employ non-traditional storage layout different from traditional relational databases.

6.0.0.3 Optimized Batch Processing of Aggregate Queries

LMFAO [66] evaluates batches of ground-by aggregates over shared joins without materializing intermediates by decomposing queries into views, organizing them via a join tree, and executing a multi-output plan. Like PostFilt, it encodes selection predicates as conditional expressions within aggregates. Unlike BaCon, LMFAO does not pre-process overlaps among cross-query predicates, but instead focuses on sharing computation of join tree traversal and attribute-ordered evaluation, and is implemented as a standalone execution engine.

6.0.0.4 Multi-Query & Shared Workload Optimization

Multi-query optimization (MQO) [67] identifies shared/similar subexpressions across queries and evaluate them jointly or via plan rewriting. Zhou et al. [68] propose a common subexpression manager integrated into Microsoft SQL Server, demonstrating improvements on workloads of tens of queries. Shared Workload Optimization (SWO) [69] extends this idea to larger workloads by sharing operators, often requiring specialized execution engines. These approaches target general workloads and require accurate cost estimates.

6.0.0.5 Learned Cardinality Estimation

Query-aware cardinality estimation (CE) models [5][12], [18], [70] learn from query-count pairs. Training is typically performed either in a single-shot manner, where a model is (re-)trained from scratch on a batch of queries [6], [7], [12], [18], [71], or in multiple rounds, where the model is incrementally updated as new query-count pairs become available [5], [70], [72]. Some hybrid CE models [9], [70] additionally require data statistics, e.g., samples/histograms. Recent work studies robustness, training efficiency, and adaptivity to data updates and workload shifts [13], [18][21], [70], [72], [73], increasing the demand for efficiently supporting counting queries.

Our work is also related to continuous query processing. More details are provided in the full version [37].

7 Conclusion↩︎

In this paper, we presented BaCon, a method for efficient batch processing of counting queries. BaConbrings together multiple optimization ideas, focusing particularly on developing compact, alternative representations of intermediate results that enable fine-grained sharing of computation. BaConcombines lightweight SQL execution with client-side processing, incorporating a suite of optimizations to ensure practical performance on our target workloads. A strength of BaConis its practicality: it can be deployed without modifying DBMS internals or physical designs. Components of BaConcan be accelerated using efficient UDF implementations (such as C-extensions in PostgreSQL), if they are supported by the DBMS. Our results demonstrate significant performance gains across real workloads with diverse characteristics. This level of improvement is empowering: shorter (re)training times make learned CE more practical and allow larger, more comprehensive training workloads.

BaConopens up several promising directions for future work. Extending it to support richer join structures, more expressive selection predicates, and more complex aggregates (complex expression inputs or non-algebraic aggregate functions) would further broaden its applicability. Beyond functional extensions, there are opportunities for more optimization, such as sharing computation across join patterns; delayed evaluation of \(\oplus\) and \(\otimes\) in count map expressions (and adapting ComputeCountsto take advantage); more intelligent selection of tree-based execution plans; aggressive “short-cutting” of all FK-PK edges; caching and more sophisticated parallelization. Finally, new applications and additional optimizations will likely require models beyond the current Hybridto enable cost-based selection of processing methods.

This work ofJ.Y., P.A., and Y.L. was partially supported by NSF Grant IIS-2402823. P.A. was also supported by NSF Grant CCF-2223870 and a US-Israel Binational Science Foundation Grant 2022131. This work of X.H. was supported by the Natural Sciences and Engineering Research Council of Canada Discovery Grant.

8 Notation Table↩︎

Notation Description
\(\database\) Database
\(\tabs\) A (sub)set of tables in \(\database\)
\(\tab\) A table in \(\database\)
\(\Attrs{\tab}\) Set of attributes of table \(\tab\)
\(\queryset\) Set of all counting queries to evaluate over \(\database\)
\(\query\) A counting query
\(\Tables{\query}\) Set of tables referenced by \(\query\)
\(\JPreds{\query}\) \(\query\)’s join predicates
\(\SPreds{\query}\) \(\query\)’s selection predicates
\(\SPreds{\query}(A)\) query range for selection attribute \(A\)
\(\JAttrs{\query}\) Set of attributes referenced by \(\query\)’s join predicates
\(\JAttrs{\query}[\tabs|\tabs']\) Subset of attributes of \(\tabs\) used by \(\query\) to join with \(\tabs'\), where \(\tabs\) and \(\tabs'\) are a pair of disjoint subsets of tables in \(\Tables{\query}\)
\(\SAttrs{\query}\) Set of attributes referenced by \(\query\)’s selection predicates
\(\SAttrs{\query}[\tabs]\) Subset of attributes of \(\tabs\) referenced by \(\query\)’s selection predicates, where \(\tabs\) is a subset of tables in \(\Tables{\query}\)
\(\jp = \langle \Tables{\jp}, \JPreds{\jp} \rangle\) A join pattern characterized by a set tables \(\Tables{\jp}\) joined by predicates \(\JPreds{\jp}\) (with no selection predicates)
\(\queryset[\jp]\) Subset of queries in \(\queryset\) with join pattern \(\jp\)
\(\SAttrs{\jp}\) Set of attributes involved in selection predicates in all queries of \(\queryset[\jp]\)
\(\SPreds{\jp}(A)\) Set of query ranges associated with selection attribute \(A\) in all queries of \(\queryset[\jp]\)
\(\QuantScales_\jp\) The collection of all quantization scales for queries in \(\queryset_\jp\), one for each selection attribute in \(\SAttrs{\jp}\), inducing a grid over a ...
\(\QuantScales_\jp[\tab]\) Subset of quantization scales for selection attributes in \(\SAttrs{\jp}\) that belong to table \(R\)
\(\QuantScale_\attr \in \QuantScales_\jp\) Quantization scale for attribute \(\attr \in \SAttrs{\jp}\)
\(\QuantScale_\attr(x)\) Serial integer id of the bucket containing \(x\) (or \(0\) if \(x\) lies outside all buckets), where \(x\) is a value from \(\attr\)’s domain
\(\myvec{b}\) A grid coordinate, with one integer component per selection attribute
\(\CountMap\) A count map
\(\CountMap[\myvec{b}]\) The count of tuples falling within the grid cell with coordinate \(\myvec{b}\)
\(\beta\) Parameter used by optimized to batch calls ([sec:sec:implementation:sql])

9 More Details of Basic BaCon↩︎

We present the detailed construction algorithm of \(\mathfrak{b}_{A}\) for a selection attribute \(A{}\) in 9.1.

9.1 Construction of Buckets↩︎

Continuing from 3.2, we extract from \(\pmb{\mathcal{Q}}[\mathfrak{J}]\) the set \(\Delta = \{ \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}(A) \mid \exists Q\in \pmb{\mathcal{Q}}[\mathfrak{J}]: A\in \text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q} \}\) of predicate ranges associated with \(A\). We transform each range \(G\) specified by \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}(A)\) to a left-closed, right-open interval. Concretely, a left-open boundary (\(l\)) is converted to a left-closed boundary \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(l)\), and a right-closed boundary (\(r\)) is converted to a right-open boundary \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(r)\), where \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape succ}}}(\cdot)\) advances the value by one unit at the attribute’s resolution 7 . Next, we sort the boundaries in ascending order and remove duplicates. The \(i\)-th boundary and the \((i + 1)\)-th one (if exists), where there are more left boundaries than right ones among the first \(i\) boundaries, form a left-closed-right-open interval in the domain of \(A\) that is of interest to at least one query. We refer to each of these intervals as a bucket, and all these buckets form \(\mathfrak{b}_{A}\). As detailed in 3.2, a value \(v\) is quantized into an integer, i.e., the id of the bucket containing \(v\), or 0 if \(v\) lies outside all of the buckets in \(\mathfrak{b}_{A}\). Note that if a query in \(\mathfrak{J}\) has no selection predicates on \(A{}\), it still satisfies 2 by setting \(i_1 = 0\) and \(i_2 = |\mathfrak{b}_{A}|\).

We present an example of quantization in 3.

Example 3. Continuing from [sql:stats_ceb_example], we have

\[\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape c.Score}}}} = \{[0, 1)\}\],

\[\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.PostHistoryTypeId}}}} = \{[1, 2)\}\], and

\[\mathfrak{b}_{\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.CreationDate}}}} = \{[\text{\scalebox{0.9}[1.0]{\textsf{\upshape 2010-09-14 11:59:07}}}, \infty)\}\].

By 2, the integer range associated with \(Q_3\) is \([1, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape c.Score}}}\), \([1, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.PostHistoryTypeId}}}\), and \([0, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.Creat}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape-ionDate}}}\). Similarly, the integer range associated with \(Q_4\) is \([0, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape c.Score}}}\), \([1, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.PostHistoryTypeId}}}\), and \([1, 1]\) for \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape ph.Creat}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape-ionDate}}}\).

10 More Details of BaCon Implementation↩︎

We present the details of \(\beta\)-ProcessTable and \(\beta\)-BaConRecurse in 10.1.

10.1 Details of \(\beta\)-ProcessTable and \(\beta\)-BaConRecurse↩︎

SQL code for \(\beta\mathrm{\small -}\mathrm{\small ProcessTable}(R, U, \mathcal{A}^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in \& out}}})\).sql:opt-count-table ‘Input: Set \(U\) of mappings, with each binds attributes \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \ldots\) to specific values, and \(\mathcal{A}^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in \& out}}} = \{J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, ..., J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2, \ldots\}\) specifies the set of attributes for partitioning result entries. The quantization scales are \(\mathfrak{B}_\mathfrak{J}[R] = \{ \mathfrak{b}_{S_1}, \mathfrak{b}_{S_2}, \ldots \}\).’ ‘Output: Result entries have the form \(\langle v, \smash{\vec{b}}, c\rangle\), sorted by \(v\), where \(v\) is a mapping from \(\mathcal{A}^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in \& out}}}\) to values, \(\smash{\vec{b}}\) is a grid coordinate for \(\mathfrak{B}_\mathfrak{J}[R]\), and \(c\) is the associated count.’ ’


’ SELECT \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\), ..., \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., B1, B2, ..., COUNT(*) FROM ( SELECT \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\), ..., \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., quantize(\(S_1, \mathfrak{b}_{S_1}\)) AS B1, quantize(\(S_2, \mathfrak{b}_{S_2}\)) AS B2, ... FROM \(R\) WHERE \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\) BETWEEN \(\min(U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1))\) AND \(\max(U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1))\) \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\) BETWEEN \(\min(U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2))\) AND \(\max(U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2))\) AND ... ) AS TMP GROUP BY \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\), ..., \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ..., B1, B2, ... ORDER BY \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2\), ..., \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1\), \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2\), ...;

Figure 10: \beta\mathrm{\small -}\mathrm{\small BaCon}\mathrm{\small Recurse}(R, U)

In \(\beta\)-ProcessTable ([sql:opt-count-table]), the second and third arguments differ from those of ProcessTable. Specifically, \(\beta\)-ProcessTable takes a set \(U\) of bindings rather than a single binding \(u\), and it is passed both \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \dots\) and \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape out}}}_2, \dots\) to enable partition of result entries. Moreover, in the inner block, \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \dots\) are filtered using safe but potentially imprecise range bounds. Concretely, \(\min{U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_i)}\) (resp. \(\max{U(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_i)}\)) denotes the minimum (resp. maximum) value in the set \(\{u(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_i)~\mid~\forall u \in U\}\). Finally, \(\beta\)-ProcessTable also \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape SELECT}}}\)s \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \dots\) and partitions (and orders) the result entries by these attributes, allowing the receiver, \(\beta\)-BaConRecurse, to discard tuples that do not correspond to any binding in \(U\).

We propose \(\beta\)-BaConRecurse (10) by incorporating batching into BaConRecurse. Instead of producing a single count map, \(\beta\)-BaConRecurse outputs a dictionary that maps each binding \(u \in U\) to a corresponding count map, enabling batch processing of \(\beta\) consecutive bindings. Lines [alg:our95opt95recursive:loop]-[alg:our95opt95recursive:loop-end] enumerate subsequences of result entries. Unlike BaConRecurse, these subsequences are not processed immediately; computation is deferred until either \(\beta{}\) subsequences have been accumulated or the end of the returned entries is reached (Line [alg:our95opt95recursive:batch]). As discussed earlier, returned tuples may not belong to \(U\). Therefore, a post-check filters tuples by testing whether \(v\)’s projection onto \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_1, J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}_2, \dots\) is contained in \(U\) (Lines [alg:our95opt95recursive:postcheck]-[alg:our95opt95recursive:postcheck-end]). During batch processing (Lines [alg:our95opt95recursive:batch]-[alg:our95opt95recursive:batch-end]), we first collect the set \(U_i\) of distinct bindings on the join attributes between the child table \(R_i\) and \(R{}\) (Lines [alg:our95opt95recursive:binding-collection]-[alg:our95opt95recursive:binding-collection-end]). We then recursively invoke \(\beta\)-BaConRecurse for each pair (\(R_i, U_i\)). Finally, Lines [alg:our95opt95recursive:contribute-to-dict]-[alg:our95opt95recursive:contribute-to-dict-end] update the dictionary \(\mathbf{M}{}\) by aggregating the contribution of each subsequence. The procedure for merging count maps largely follows BaConRecurse, with two key differences: \(R_i\)’s count map is indexed by \(u_i\) and obtained from the returned dictionary; and the merged count maps are accumulated into the dictionary entry indexed by \(\omega^{\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}}{}\). Note that \(\omega^{\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}}{}\) (and similarly \(v^{\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}}{}\) in Line [alg:our95opt95recursive:postcheck]) is empty for the root table, which has no \(J^\text{\scalebox{0.9}[1.0]{\textsf{\upshape in}}}\) attributes.

10.2 Choosing among BaCon/IndProc/PostFilt↩︎

We introduce an approach called Hybrid for picking the appropriate method to use among the three for a given query pattern. As we will see in 5.4, BaConperforms well across our target workloads, so Hybridonly serves to validate the robustness of BaCon. Nonetheless, we briefly describe Hybridhere for completeness.

We train Hybridfrom sample workloads as a classifier that predicts, given a join pattern, one of three classes \(\{0, 1, 2\}\) representing BaCon, IndProc, and PostFiltrespectively. We use a lightweight model based on RandomForestClassifier [74] over a suite of features — the most important of which are illustrated in 11. Features with prefixes \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape sum\_}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape max\_}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mean\_}}}\), and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape prod\_}}}\) aggregate a collection of values: for example, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape prod\_number\_of\_bucket}}}\) is the product of the number of buckets across all quantization scales for a join pattern; \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape mean\_query\_coverage\_ratio}}}\) is the average, taken per query, of the ratio between the number of grid cells queried and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape prod\_number\_of\_bucket}}}\). Features containing \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape intermediate\_join\_size}}}\) are AGM bounds [59], [60] (i.e., overestimation) of intermediate result sizes of join subplans chosen by PostgreSQL for joining all tables in the join pattern. Features with \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape cost\_of\_scanning}}}\) denote PostgresSQL’s estimated cost for enumerating a table ordered by join attributes that connects it to its descendant tables (if any), reflecting a key component of BaCon. In general, we include as features per-join-pattern statistics that are inexpensive to collect at runtime. However, we exclude more advanced selectivity estimates provided by PostgreSQL, because we have found them to be unreliable and systematically underestimating the costs of baselines.

Figure 11: Features used by Hybrid, and their importance in a model trained as described in 5.4.

To ensure a lightweight model and fast inference, we train our model with \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape train\_val\_split}}}\)=0.25, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape n\_estimators}}}\)=100, \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape min\_samples\_le}}}\) \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape-af}}}\)=2, and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape class\_weight}}}\)={“0”: 1, “1”: 2, “2”: 2}. The higher weights for classes 1 and 2 penalize mistakenly picking baseline approaches, because such mistakes cost much higher. To obtain class labels for training data, we measure and compare the running time for all three methods. We deem a baseline method safe if its running time is either (i) at least \(1.5\times\) faster than BaCon, or (ii) no less than \(10\) seconds faster than BaCon. We assign class \(1\) only when IndProcis safe and PostFiltis either unsafe or slower; class \(2\) is assigned analogously; all remaining cases are labeled as class \(0\). For inference, rather than selecting the class with the highest posterior probability, we require the predicted probability of choosing IndProcor PostFiltto exceed a precision threshold (\(0.9\) by default), calibrated to guarantee minimum precision on held-out validation data. If no probability threshold can guarantee the required precision, the one with highest precision is selected. This robust thresholding strategy deliberately trades recall for safety, selecting baselines only under high confidence.

11 More Details of Experiments↩︎

We present the results of materialized-view-based postfiltering in 11.1, the results of multi-threaded execution in 11.2, and the results of varying \(\beta\) in 11.3. We present the per-join-pattern results of both the remaining real workloads and scalability workloads in 11.4. Additionally, the most detailed result of BaCon and PostFilt can be found in the log files of the form results/*.print in our repository [48].

11.1 Experiments of Materialized-View-Based Postfiltering↩︎

An alternative implementation of PostFilt materializes the unfiltered join for each join pattern and performs aggregations over it. However, this approach is impractical for join patterns with very large unfiltered join sizes, plus this information is not free, due to prohibitive storage overhead. Therefore, we evaluate the materialized-view-based variant only on DSB’s dsb-grasp-20k and JOB’s synthetic, where the materialized view cardinalities remain manageable and PostFilt outperforms IndProc (as shown in 2). The result is presented in ¿tbl:tab:end95to95end95materialized?.

Query Workload View Cardinalities Materialized-view -based Postfiltering
dsb-grasp-20k \(< 6 \cdot 10^6\) 4,211 4,356
synthetic \(< 5 \cdot 10^8\) 11,329+ 12,264+

11.2 Experiments of Multi-threaded Execution↩︎

We reports results for IndProc, PostFilt, and BaCon under multi-threaded execution. For the baselines, IndProc and PostFilt, we enable PostgreSQL’s parallel execution by setting \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape max\_parallel\_workers}}}\) to 8 and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape max\_parallel\_workers\_per\_gather}}}\) to 4. Since all their computation is performed within the database server, they can fully exploit this parallelism. In contrast, without an elaborate parallelization design, BaCon uses 4 processes, each handling a subset of join patterns assigned in a round-robin manner, while keeping the PostgreSQL server in single-threaded mode. The results are presented in ¿tbl:tab:end95to95end95multi95threaded?. While we adopt this simple, join-pattern-level parallelization, more sophisticated strategies for BaCon are possible (e.g., parallel merging of count maps from descendants or cost-based partitioning of join patterns), while we leave as future work.

Query Workload (parallel) (parallel) (parallel) Speedup vs.
synthetic 5,271 2,565 531 \(\uparrow\) 9.92 \(\times\)
scale 11,003+ 14,715+ 1,097 \(\uparrow\) 10.03+ \(\times\)
job-light 540 8,046 273 \(\uparrow\) 1.98+ \(\times\)
job-light-single 66 3 11 \(\uparrow\) 5.83 \(\times\)
job-light-join 2,595 11,198+ 510 \(\uparrow\) 5.09 \(\times\)
stats-ceb 3,390+ 43,661+ 19 \(\uparrow\) 180.79+ \(\times\)
stats-ceb-single 17 2 2 \(\uparrow\) 8.48 \(\times\)
stats-ceb-join 32,384+ 51,120+ 54 \(\uparrow\) 598.48+ \(\times\)
dsb-grasp-20k 6,241 913 359 \(\uparrow\) 17.37 \(\times\)

11.3 Experiments of Various \(\beta\)↩︎

We report results for \(\beta\) \(=\) 5,000, 50,000 and 500,000 respectively, with \(\beta\) \(=\) 50,000 as the default setting used throughout 5. The corresponding end-to-end running times are shown in 6. Overall, increasing \(\beta\) reduces the end-to-end running time, at the cost of higher space overhead (e.g., larger \(\mathbf{M}_i\) in Line [alg:our95opt95recursive:child-dict] of 10). As indicated by the last two columns of 6, the primary source of performance differences lie in the number of SQLs issued to the underlying database and the time spent fetching their results. With larger \(\beta\)s, BaCon issues fewer SQLs and incurs lower result-fetching overhead, resulting in more savings on the end-to-end running times.

Table 6: job-light: end-to-end running times (in seconds, rounded), together with the total number of SQLs executed on the underlying database and the total time spent fetching their results, for different values of . The default configuration in [sec:sec:experiment] ( = 50,000) is shown in bold.
E2E running time Total # of SQLs Total result-fetching overhead
5,000 922 19,427 639
50,000 728 1,974 497
500,000 584 243 288

11.4 More Per-Join-Pattern Results↩︎

Figure 12: Log-scale running time per join pattern in synthetic.
Figure 13: Log-scale running time per join pattern in job_light_single.
Figure 14: Log-scale running time per join pattern in job_light_join.
Figure 15: Log-scale running time per join pattern in stats_ceb.
Figure 16: Log-scale running time per join pattern in stats_ceb_single.
Figure 17: Log-scale running time per join pattern in stats_ceb_join.
Figure 18: Log-scale running time per join pattern in dsb_grasp_20k.
Figure 19: Log-scale running time per join pattern in job_light_1k.
Figure 20: Log-scale running time per join pattern in job_light_2k.
Figure 21: Log-scale running time per join pattern in job_light_4k.

12 More Details of Related Work↩︎

12.0.0.1 Select-Join-Aggregate Query Processing.

Counting the join results (with selection predicate on the base tables) is a special class of select-join-aggregate queries (intuitively, applying an aggregate function on top of the select-join queries) with the output size as \(1\). Processing such queries usually first push down the selection predicate to the base table, and ends up with processing join-count queries, which is the focus below.

The previous works have achieved two flavors of results: worst-case optimal and output-sensitive. Namely, worst-case optimal algorithms work only on pathological instances with huge outputs, which are rare in practice. In contrast, output-sensitive algorithms express the runtime as a function of the input size and output size, which are more practically meaningful, especially for queries where the aggregation (such as count) may significantly reduce the output size. The classical Yannakakis algorithm [53] can compute free-connex join-count queries in \(O(N)\) time, where \(N\) is the total number of tuples in the database. Very recently, [54] exploits the hybrid strategy of Yannakakis algorithms, which can compute acyclic join-count queries in \(O(N \cdot \mathsf{OUT}^{1-\frac{1}{\textsf{fnfhtw}}} + \textsf{OUT})\) time, where \(\mathsf{OUT}\) is the size of the projection of the join results onto the counting columns, and \(\textsf{fnfhtw}\) is the free-connex fractional hypertree width of the query. For general join-count queries, the state-of-the-art approach is to convert the query into a free-connex one using the tree decomposition technique and the worst-case optimal join algorithm [75][78], and then run the hybrid Yannakakis algorithm on the tree decomposition.

Our work is also inspired by some ideas in these works. The instantiations of worst-case optimal join algorithms [55][58] motivate BaCon to decompose computation via child recursions and combine multiple coordinate-to-count maps during backtracking. Cost-based optimizers in these systems, such as Free Join’s [58], together with the AGM bound [59], [60], further inform our initial exploration of cost-based hybrid mechanism. However, achieving more accurate cost estimates for BaCon may conversely require more accurate (pessimistic) cardinality estimation at a higher cost, such as LpBound [61], and/or specialized data storages and execution engines, such as Free Join’s COLT [58] and its integration to DuckDB [62].

12.0.0.2 Aggregation Push-down

Early works explore aggregation push-down in query plans to reduce intermediate result sizes. Eager aggregation and lazy aggregation [63] introduce rules for early or deferred partial aggregation based on algebraic properties, while [64] integrate \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape GROUP BY}}}\) into cost-based optimization; both focus on intra-query optimization via query rewriting. More recently, GuAo [40] identifies aggregate queries that can be evaluated without materializing any joins and introduces a corresponding physical operator for SparkSQL [65]. GuAo targets single-query execution and assumes selection push-down at table scans. For the case without \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape GROUP BY}}}\), its propagation of frequencies grouped by join attributes resembles BaCon’s bucketization when reduced to a single-query join pattern. Unlike BaCon, however, GuAo merges frequencies via standard binary joins in SparkSQL rather than supporting multi-way combination during count-map merging.

12.0.0.3 Factorizations

Factorized databases (FDBs) propose the factorized representations of query results [22] to eliminate tuple-level redundancy and boost the relational processing performance [23], supporting aggregations beyond \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape COUNT}}}\) [24]. These works focus on intra-query redundancy in query output and are different but complementary to our approach. Moreover, they assume storage layout different from traditional relational databases, like PostgreSQL.

12.0.0.4 Optimized Batch Processing of Aggregate Queries

LMFAO [66] is a framework proposed to efficiently compute a batch of group-by aggregates over shared joins without fully materializing intermediate results. It decomposes queries into views, groups them according to a join tree, and evaluates each view group using a multi-output plan. Similar to PostFilt, LMFAO supports selection predicates by encoding them as conditional expressions inside aggregate functions. In contrast to BaCon, LMFAO does not analyze or pre-process overlaps among cross-query predicates (e.g., via bucket construction), but instead focuses on sharing computation at the level of join tree traversal and attribute-ordered evaluation, reusing identical conditional expressions and partial products within a view group. Moreover, LMFAO is implemented as a standalone engine, rather than being integrated into a traditional DBMS.

12.0.0.5 Multi-Query Optimization & Shared Workload Optimization

Multi-query optimization (MQO) [67] identifies shared or similar subexpressions across queries and performs inter-query optimization by executing the common subexpressions jointly or by rewriting query plans with a boarder subquery. Along this line, Zhou et al. [68] propose a practical common subexpression manager integrated into Microsoft SQL Server and its cost-based query optimizer, demonstrating improvements on workloads of a few tens of queries. As the search space for identifying common subplans grow rapidly with the number of queries, Shared Workload Optimization (SWO) [69] optimizes an entire workload – often comprising hundreds or thousands of concurrent queries – by identifying shared operators instead. This typically requires specialized or substantially modified execution engines (e.g., shared work systems) to support shared physical operators and coordinated execution. Both lines of work target more general queries and require accurate cost estimates, which motivates future extensions of BaCon toward tighter, cost-based integration with DBMSs.

12.0.0.6 Continuous Query Processing

Related works on continuous query processing [79], [80] in stream systems explore shared computations by maintaining data summaries or predicate indices over evolving streams. These approaches are designed for workloads with large numbers of continuous queries or filters (e.g., on the order of 100k), usually exceeding the workload sizes considered in our setting, and rely on specialized structures to efficiently support updates. Nevertheless, they offer complementary insights that could inform strategies for handling workloads at different scales as future work.

12.0.0.7 Learned Cardinality Estimation

As the main motivation of BaCon, query-aware CE models [5][12], [18], [70] learns to predict cardinalities from a training set of query-count pairs. Training is typically performed either in a single-shot manner, where a model is (re-)trained from scratch on a collected batch of queries [6], [7], [12], [18], [71], or in multiple rounds, where the model is incrementally updated as new query-count pairs occur (e.g., via fine-tuning) [5], [70], [72]. The required input information varies across approaches. Pure data-driven CE models [81], [82] learn data distributions directly from the tables and do not require query-count pairs for training, although query workloads are still used for evaluation. Some hybrid models [9], [70] additionally require data statistics, such as samples or histograms. Beyond model design, recent work has focused on model robustness [19], [20], training efficiency [13], and adaptivity to updates and drifts [18], [21], [70], [72], [73], which further increases the demand for diverse query workloads.

References↩︎

[1]
R. Zhu, L. Weng, B. Ding, and J. Zhou, “Learned query optimizer: What is new and what is next,” in Companion of the 2024 international conference on management of data, 2024, pp. 561–569, doi: 10.1145/3626246.3654692.
[2]
R. Marcus, P. Negi, H. Mao, N. Tatbul, M. Alizadeh, and T. Kraska, “Bao: Making learned query optimization practical,” in Proceedings of the 2021 international conference on management of data, 2021, pp. 1275–1288, doi: 10.1145/3448016.3452838.
[3]
R. Marcus et al., “Neo: A learned query optimizer,” Proc. VLDB Endow., vol. 12, no. 11, pp. 1705–1718, Jul. 2019, doi: 10.14778/3342263.3342644.
[4]
L. Doshi et al., “Kepler: Robust learning for parametric query optimization,” Proc. ACM Manag. Data, vol. 1, no. 1, May 2023, doi: 10.1145/3588963.
[5]
Y. Park, S. Zhong, and B. Mozafari, “Quicksel: Quick selectivity learning with mixture models,” in Proc. 39th ACM SIGMOD int. Conf. Management data, 2020, pp. 1017–1033.
[6]
A. Dutt, C. Wang, A. Nazi, S. Kandula, V. Narasayya, and S. Chaudhuri, “Selectivity estimation for range predicates using lightweight models,” Proc. VLDB Endow., vol. 12, no. 9, pp. 1044–1057, May 2019, doi: 10.14778/3329772.3329780.
[7]
J. Liu, W. Dong, Q. Zhou, and D. Li, “Fauce: Fast and accurate deep ensembles with uncertainty for cardinality estimation,” Proc. VLDB Endow., vol. 14, no. 11, pp. 1950–1963, Jul. 2021, doi: 10.14778/3476249.3476254.
[8]
P. Wu and G. Cong, “A unified deep model of learning from both data and queries for cardinality estimation,” in Proceedings of the 2021 international conference on management of data, 2021, pp. 2009–2022.
[9]
A. Kipf, T. Kipf, B. Radke, V. Leis, P. Boncz, and A. Kemper, “Learned cardinalities: Estimating correlated joins with deep learning,” arXiv preprint arXiv:1809.00677, 2018.
[10]
P. Negi et al., “Flow-loss: Learning cardinality estimates that matter,” Proc. VLDB Endow., vol. 14, no. 11, pp. 2019–2032, Jul. 2021, doi: 10.14778/3476249.3476259.
[11]
M. Müller, L. Woltmann, and W. Lehner, “Enhanced featurization of queries with mixed combinations of predicates for ML-based cardinality estimation,” in Proceedings 26th international conference on extending database technology, EDBT 2023, ioannina, greece, march 28-31, 2023, 2023, pp. 273–284, doi: 10.48786/EDBT.2023.22.
[12]
X. Hu et al., “Selectivity functions of range queries are learnable,” in SIGMOD, 2022, pp. 959–972.
[13]
S. Reiner and M. Grossniklaus, “Sample-efficient cardinality estimation using geometric deep learning,” Proc. VLDB Endow., vol. 17, no. 4, pp. 740–752, Dec. 2023, doi: 10.14778/3636218.3636229.
[14]
W. Scheufele and G. Moerkotte, “On the complexity of generating optimal plans with cross products,” in Proceedings of the sixteenth ACM SIGACT-SIGMOD-SIGART symposium on principles of database systems, 1997, pp. 238–248.
[15]
V. Leis, A. Gubichev, A. Mirchev, P. Boncz, A. Kemper, and T. Neumann, “How good are query optimizers, really?” Proc. VLDB Endow., vol. 9, no. 3, pp. 204–215, Nov. 2015, doi: 10.14778/2850583.2850594.
[16]
Y. Han et al., “Cardinality estimation in DBMS: A comprehensive benchmark evaluation,” Proc. VLDB Endow., vol. 15, no. 4, pp. 752–765, Dec. 2021, doi: 10.14778/3503585.3503586.
[17]
B. Ding, S. Chaudhuri, J. Gehrke, and V. Narasayya, “DSB: A decision support benchmark for workload-driven and traditional database systems,” Proc. VLDB Endow., vol. 14, no. 13, pp. 3376–3388, Sep. 2021, doi: 10.14778/3484224.3484234.
[18]
B. Li, Y. Lu, and S. Kandula, “Warper: Efficiently adapting learned cardinality estimators to data and workload drifts,” in Proceedings of the 2022 international conference on management of data, 2022, pp. 1920–1933, doi: 10.1145/3514221.3526179.
[19]
M. Kurmanji and P. Triantafillou, “Detect, distill and update: Learned DB systems facing out of distribution data,” Proc. ACM Manag. Data, vol. 1, no. 1, May 2023, doi: 10.1145/3588713.
[20]
P. Wu, H. Xu, R. Marcus, and Z. G. Ives, “A practical theory of generalization in selectivity learning,” Proc. VLDB Endow., vol. 18, no. 6, pp. 1811–1824, Feb. 2025, doi: 10.14778/3725688.3725708.
[21]
P. Wu and Z. G. Ives, “Modeling shifting workloads for learned database systems,” Proc. ACM Manag. Data, vol. 2, no. 1, Mar. 2024, doi: 10.1145/3639293.
[22]
N. Bakibayev, D. Olteanu, and J. Závodný, “FDB: A query engine for factorised relational databases,” Proc. VLDB Endow., vol. 5, no. 11, pp. 1232–1243, Jul. 2012, doi: 10.14778/2350229.2350242.
[23]
D. Olteanu and J. Zavodny, “Factorised representations of query results: Size bounds and readability,” in 15th international conference on database theory, ICDT ’12, berlin, germany, march 26-29, 2012, 2012, pp. 285–298, doi: 10.1145/2274576.2274607.
[24]
N. Bakibayev, T. Kočiský, D. Olteanu, and J. Závodný, “Aggregation and ordering in factorised databases,” Proc. VLDB Endow., vol. 6, no. 14, pp. 1990–2001, Sep. 2013, doi: 10.14778/2556549.2556579.
[25]
A. Kipf, T. Kipf, B. Radke, V. Leis, P. Boncz, and A. Kemper, “Synthetic.sql.” 2018, [Online]. Available: https://github.com/andreaskipf/learnedcardinalities/blob/master/workloads/synthetic.sql.
[26]
A. Kipf, T. Kipf, B. Radke, V. Leis, P. Boncz, and A. Kemper, “Scale.sql.” 2018, [Online]. Available: https://github.com/andreaskipf/learnedcardinalities/blob/master/workloads/scale.sql.
[27]
A. Kipf, T. Kipf, B. Radke, V. Leis, P. Boncz, and A. Kemper, “Job-light.sql.” 2018, [Online]. Available: https://github.com/andreaskipf/learnedcardinalities/blob/master/workloads/job-light.sql.
[28]
[29]
[30]
[31]
[32]
[33]
Y. Liu, X. Hu, P. Agarwal, and J. Yang, “Dsb_grasp_20k.sql.” 2026, [Online]. Available: https://github.com/louisja1/bacon/blob/main/workload/dsb_grasp_20k.sql.
[34]
P. Wu, R. Kang, T. Zhang, J. Chen, R. Marcus, and Z. G. Ives, “Original query workload of GRASP.” 2025, [Online]. Available: https://github.com/shoupzwu/GRASP/blob/master/queries/dsb.csv.
[35]
P. Wu, R. Kang, T. Zhang, J. Chen, R. Marcus, and Z. G. Ives, “Data-agnostic cardinality learning from imperfect workloads,” Proc. VLDB Endow., vol. 18, no. 8, pp. 2519–2532, Apr. 2025, doi: 10.14778/3742728.3742745.
[36]
J. Gray et al., “Data cube: A relational aggregation operator generalizing group-by, cross-tab, and sub-totals,” Data Min. Knowl. Discov., vol. 1, no. 1, pp. 29–53, Jan. 1997, doi: 10.1023/A:1009726021843.
[37]
Y. Liu, X. Hu, P. Agarwal, and J. Yang, “[Full version] BaCon: Efficient batch processing of counting queries.” 2026, [Online]. Available: https://github.com/louisja1/bacon/blob/main/fullversion.pdf.
[38]
P. G. Selinger, M. M. Astrahan, D. D. Chamberlin, R. A. Lorie, and T. G. Price, “Access path selection in a relational database management system,” in Proceedings of the 1979 ACM SIGMOD international conference on management of data, boston, massachusetts, USA, may 30 - june 1, 1979, pp. 23–34, doi: 10.1145/582095.582099.
[39]
V. Leis, A. Gubichev, A. Mirchev, P. Boncz, A. Kemper, and T. Neumann, “IMDB relational schema by JOB.” 2015, [Online]. Available: https://event.cwi.nl/da/job/.
[40]
M. Lanzinger, R. Pichler, and A. Selzer, “Avoiding materialisation for guarded aggregate queries,” Proc. VLDB Endow., vol. 18, no. 5, pp. 1398–1411, Jan. 2025, doi: 10.14778/3718057.3718068.
[41]
T. P. Team, “Psycopg – server-side cursors.” 2001-2021, [Online]. Available: https://www.psycopg.org/docs/usage.html#server-side-cursors.
[42]
T. P. Team, “Psycopg – client-side cursors.” 2001-2021, [Online]. Available: https://www.psycopg.org/docs/cursor.html.
[43]
P. 17, “Extending SQL: C-language functions.” 2025, [Online]. Available: https://www.postgresql.org/docs/current/xfunc-c.html.
[44]
Microsoft, “Microsoft SQL server: Common language runtime (CLR) integration.” [Online]. Available: https://learn.microsoft.com/en-us/sql/relational-databases/clr-integration/common-language-runtime-integration-overview?view=sql-server-ver17.
[45]
Oracle, “Oracle database: External procedures.” [Online]. Available: https://docs.oracle.com/en/database/oracle/oracle-database/19/ntqrf/external-procedures-overview.html.
[46]
Inc. Anaconda et al., “Numba - a just-in-time compiler for python that works best on code that uses NumPy arrays and functions, and loops.” 2025, [Online]. Available: https://numba.pydata.org/numba-doc/dev/index.html#.
[47]
T. P. Team, “Psycopg – PostgreSQL database adapter for python.” 2001-2021, [Online]. Available: https://www.psycopg.org/docs/#.
[48]
Y. Liu, X. Hu, P. Agarwal, and J. Yang, “Github repository of BaCon.” 2026, [Online]. Available: https://github.com/louisja1/bacon.
[49]
V. Leis, A. Gubichev, A. Mirchev, P. Boncz, A. Kemper, and T. Neumann, “IMDB initialization files and scripts.” 2013-2019, [Online]. Available: https://event.cwi.nl/da/job/.
[50]
Y. Han et al., “STATS initialization files and scripts.” 2023, [Online]. Available: https://github.com/Nathaniel-Han/End-to-End-CardEst-Benchmark/tree/master/scripts/sql.
[51]
B. Ding, S. Chaudhuri, J. Gehrke, and V. Narasayya, “DSB initialization files and scripts.” 2021, [Online]. Available: https://github.com/microsoft/dsb/tree/main/scripts.
[52]
Y. Liu, X. Hu, P. Agarwal, and J. Yang, “Script for generating dsb_grasp_20k.sql.” 2026, [Online]. Available: https://github.com/louisja1/bacon/blob/main/workload/raw/dsb_grasp_csv_to_sql.py.
[53]
M. Yannakakis, “Algorithms for acyclic database schemes,” in Proceedings of the seventh international conference on very large data bases - volume 7, 1981, pp. 82–94.
[54]
X. Hu, “Output-optimal algorithms for join-aggregate queries,” Proc. ACM Manag. Data, vol. 3, no. 2, Jun. 2025, doi: 10.1145/3725241.
[55]
C. R. Aberger, A. Lamb, S. Tu, A. Nötzli, K. Olukotun, and C. Ré, “EmptyHeaded: A relational engine for graph processing,” ACM Trans. Database Syst., vol. 42, no. 4, Oct. 2017, doi: 10.1145/3129246.
[56]
M. Freitag, M. Bandle, T. Schmidt, A. Kemper, and T. Neumann, “Adopting worst-case optimal joins in relational database systems,” Proc. VLDB Endow., vol. 13, no. 12, pp. 1891–1904, Jul. 2020, doi: 10.14778/3407790.3407797.
[57]
A. Mhedhbi and S. Salihoglu, “Optimizing subgraph queries by combining binary and worst-case optimal joins,” Proc. VLDB Endow., vol. 12, no. 11, pp. 1692–1704, Jul. 2019, doi: 10.14778/3342263.3342643.
[58]
Y. R. Wang, M. Willsey, and D. Suciu, “Free join: Unifying worst-case optimal and traditional joins,” Proc. ACM Manag. Data, vol. 1, no. 2, Jun. 2023, doi: 10.1145/3589295.
[59]
A. Atserias, M. Grohe, and D. Marx, “Size bounds and query plans for relational joins.” 2017, [Online]. Available: https://arxiv.org/abs/1711.03860.
[60]
G. Gottlob, S. T. Lee, G. Valiant, and P. Valiant, “Size and treewidth bounds for conjunctive queries,” J. ACM, vol. 59, no. 3, Jun. 2012, doi: 10.1145/2220357.2220363.
[61]
H. Zhang, C. Mayer, M. A. Khamis, D. Olteanu, and D. Suciu, “LpBound: Pessimistic cardinality estimation using \(\mathscr{l}\)\({}_{\mbox{p}}\)-norms of degree sequences,” Proc. ACM Manag. Data, vol. 3, no. 3, pp. 184:1–184:27, 2025, doi: 10.1145/3725321.
[62]
M. Raasveldt, “DuckDB - a modern modular and extensible database system,” in CDMS@VLDB, 2022, [Online]. Available: https://api.semanticscholar.org/CorpusID:252384081.
[63]
W. P. Yan and P.-Å. Larson, “Eager aggregation and lazy aggregation,” in Proceedings of the 21th international conference on very large data bases, 1995, pp. 345–357.
[64]
S. Chaudhuri and K. Shim, “Including group-by in query optimization,” in Proceedings of the 20th international conference on very large data bases, 1994, pp. 354–366.
[65]
M. Armbrust et al., “Spark SQL: Relational data processing in spark,” in Proceedings of the 2015 ACM SIGMOD international conference on management of data, 2015, pp. 1383–1394, doi: 10.1145/2723372.2742797.
[66]
M. Schleich, D. Olteanu, M. Abo Khamis, H. Q. Ngo, and X. Nguyen, “A layered aggregate engine for analytics workloads,” in Proceedings of the 2019 international conference on management of data, 2019, pp. 1642–1659, doi: 10.1145/3299869.3324961.
[67]
T. K. Sellis, “Multiple-query optimization,” ACM Trans. Database Syst., vol. 13, no. 1, pp. 23–52, Mar. 1988, doi: 10.1145/42201.42203.
[68]
J. Zhou, P.-A. Larson, J.-C. Freytag, and W. Lehner, “Efficient exploitation of similar subexpressions for query processing,” in Proceedings of the 2007 ACM SIGMOD international conference on management of data, 2007, pp. 533–544, doi: 10.1145/1247480.1247540.
[69]
G. Giannikis, D. Makreshanski, G. Alonso, and D. Kossmann, “Shared workload optimization,” Proc. VLDB Endow., vol. 7, no. 6, pp. 429–440, Feb. 2014, doi: 10.14778/2732279.2732280.
[70]
P. Li, W. Wei, R. Zhu, B. Ding, J. Zhou, and H. Lu, “ALECE: An attention-based learned cardinality estimator for SPJ queries on dynamic workloads,” Proc. VLDB Endow., vol. 17, no. 2, pp. 197–210, Oct. 2023, doi: 10.14778/3626292.3626302.
[71]
B. Li, Y. Lu, C. Wang, and S. Kandula, “Cardinality estimation: Is machine learning a silver bullet?” in AIDB, 2021, [Online]. Available: https://www.microsoft.com/en-us/research/publication/cardinality-estimation-is-machine-learning-a-silver-bullet/.
[72]
M. Heddes, I. Nunes, T. Givargis, and A. Nicolau, “Convolution and cross-correlation of count sketches enables fast cardinality estimation of multi-join queries,” Proc. ACM Manag. Data, vol. 2, no. 3, May 2024, doi: 10.1145/3654932.
[73]
P. Negi et al., “Robust query driven cardinality estimation under changing workloads,” Proc. VLDB Endow., vol. 16, no. 6, pp. 1520–1533, Feb. 2023, doi: 10.14778/3583140.3583164.
[74]
scikit-learn developers, “Scikit-learn – RandomForestClassifier.” 2007-2025, [Online]. Available: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#randomforestclassifier.
[75]
H. Q. Ngo, “Worst-case optimal join algorithms: Techniques, results, and open problems,” in Proceedings of the 37th ACM SIGMOD-SIGACT-SIGAI symposium on principles of database systems, 2018, pp. 111–124, doi: 10.1145/3196959.3196990.
[76]
H. Q. Ngo, E. Porat, C. Ré, and A. Rudra, “Worst-case optimal join algorithms.” 2012, [Online]. Available: https://arxiv.org/abs/1203.1952.
[77]
H. Q. Ngo, C. Ré, and A. Rudra, “Skew strikes back: New developments in the theory of join algorithms,” SIGMOD Rec., vol. 42, no. 4, pp. 5–16, Feb. 2014, doi: 10.1145/2590989.2590991.
[78]
T. L. Veldhuizen, “Leapfrog triejoin: A worst-case optimal join algorithm.” 2013, [Online]. Available: https://arxiv.org/abs/1210.0481.
[79]
S. Madden, M. Shah, J. M. Hellerstein, and V. Raman, “Continuously adaptive continuous queries over streams,” in Proceedings of the 2002 ACM SIGMOD international conference on management of data, 2002, pp. 49–60, doi: 10.1145/564691.564698.
[80]
P. K. Agarwal, J. Xie, J. Yang, and H. Yu, “Scalable continuous query processing by tracking hotspots,” in Proceedings of the 32nd international conference on very large data bases, 2006, pp. 31–42.
[81]
Z. Yang et al., “Deep unsupervised cardinality estimation,” Proc. VLDB Endow., vol. 13, no. 3, pp. 279–292, Nov. 2019, doi: 10.14778/3368289.3368294.
[82]
Z. Yang et al., “NeuroCard: One cardinality estimator for all tables,” Proc. VLDB Endow., vol. 14, no. 1, pp. 61–73, Sep. 2020, doi: 10.14778/3421424.3421432.

  1. This work is licensed under the Creative Commons BY-NC-ND 4.0 International License. Visit https://creativecommons.org/licenses/by-nc-nd/4.0/ to view a copy of this license. For any use beyond those covered by this license, obtain permission by emailing . Copyright is held by the owner/author(s). Publication rights licensed to the VLDB Endowment.

    Proceedings of the VLDB Endowment, Vol. 19, No. 9ISSN 2150-8097.
    doi:10.14778/3819518.3819567
    ↩︎

  2. For simplicity, we discuss only counting queries here, but our methods easily generalize to all distributive and algebraic SQL aggregates [36] including also \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape SUM}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape MIN}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape MAX}}}\), \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape AVG}}}\), and \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape STDDEV}}}\), where each input expression is a single attribute or computable over attributes from the same table.↩︎

  3. More precisely, we remove from \(Q\) any table outside \(\mathcal{R}\); remove from any equivalent class in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\Join}_{Q}\) any attribute outside \(\mathcal{R}\) and then remove any empty or singleton equivalent class; and remove any entry in \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape Preds}}}^{\sigma}_{Q}\) for an attribute outside \(\mathcal{R}\).↩︎

  4. Note that the direction is important: a PK-FK edge, where the parent table has the primary key, would be a bad candidate to “short-cut,” with an opposite argument.↩︎

  5. If each binding involves multiple attributes, we order the bindings lexicographically.↩︎

  6. We have also evaluated an alternative implementation of PostFilt based on materialized views. The details and results, reported in the full version [37], show that materialization introduces additional storage costs and preparation-time overhead, without providing overall performance benefits. Hence, 5 focuses on the current implementation of PostFilt.↩︎

  7. For example, if \(\alpha\) is an integer, \(\text{next}{(\alpha)} = \alpha + 1\); if \(\alpha\) is a \(\text{\scalebox{0.9}[1.0]{\textsf{\upshape timestamp without time zone}}}\), \(\text{next}{(\alpha)} = \alpha + 1\) microsecond; and so on.↩︎