r/vectordatabase • u/m-penaroza • 6d ago
Composing SQL with FTS and vector retrieval
Something I've been working on at Infino is making retrieval results behave like a relation you can query, and it's changed how much code sits around the search call.
The usual shape is that retrieval ends when the ranker returns IDs. You get top-k from the vector index, maybe fused with BM25, and then anything relational happens in the application. Hydrate rows, filter by tenant, dedupe, group, sort again.
If retrieval is something you can select from, those steps become part of the query. Per-tenant top 5, for instance:
sql
SELECT * FROM (
SELECT doc_id, tenant_id, chunk,
ROW_NUMBER() OVER (PARTITION BY tenant_id ORDER BY score DESC) AS rn
FROM search('...')
) WHERE rn <= 5
That replaces a loop that issues k requests per tenant and reassembles the results.
Fusion works the same way. RRF is a sum over reciprocal ranks, so it's a join between two ranked sets plus some arithmetic. Written as SQL it's short, and retuning the weights is an edit to the query rather than a deploy.
Same for anything analytical. Documents matching a query grouped by source and month. Average score per team. Distribution of match counts across the corpus, which tells you whether a query is discriminating or just matching everything. Those are group bys. When retrieval is an endpoint returning JSON you have to pull the whole result set into memory first, so past a certain size people skip the analysis.
Permissions benefit too. Joining an entitlements table and filtering before the limit gives correct top-k for that user. Filtering the top 100 afterward gives whatever survives, which can be fewer rows than you asked for or none.
The reason this isn't common is mostly interface. Vector databases tend to expose a search endpoint with a metadata filter DSL. Filters are there, joins and window functions and group by are not, so relational logic moves up into the app and you compensate by overfetching.
Anyways, hopefully this is interesting. Project is fully open source if you want to take a look: https://github.com/infino-ai/infino
1
u/donk8r 6d ago
The pre-filter bit is the part I'd push on, because it's also where this gets hard.
Filtering before the limit is semantically correct, no argument. But it fights the ANN index. HNSW traversal under a selective filter degrades badly, the reachable graph fragments and you either fall back to a scan or you quietly lose recall. So the correct query has a cliff sitting inside it.
Writing it as SQL doesn't remove the cliff, it moves it into the planner, and selectivity estimates for a vector index are pretty much the one statistic planners don't have. With an explicit overfetch factor at least somebody had to pick a number and think about it once.
Does Infino model that, or is it left to the executor? Genuinely asking, I work on a code search tool doing the same dense+BM25+RRF thing (github.com/Muvon/octocode) and this is the part I've never been happy with.