webard/nova-havingable
Adds HAVING clause support to Laravel Nova, letting you filter resources on aggregated values such as counts, sums, and averages. Useful for queries that need conditions on grouped or computed columns, bringing SQL HAVING semantics into Nova's filtering layer.
composer require webard/nova-havingable. This is a Nova add-on, so confirm your laravel/nova major (4.x vs 5.x) and PHP version against the constraints in the package's composer.json before you pull it into an existing app — a v1.0.0 initial release usually targets a narrow range.src/ for the trait or Nova filter/metric class you actually apply, then composer.json for auto-discovery and version pins. There is unlikely to be a publishable config or migration in an initial release; if src/ contains a service provider with publishes(), check whether anything is optional before assuming you need it.SUM(orders.total) is over 10,000" or "products with more than 5 reviews." Nova's stock filters compile to WHERE clauses, so an aggregate predicate has nowhere to go. This package exists to let you express that HAVING condition through the Nova request pipeline instead of abandoning the filter UI for a custom lens with raw SQL.Filter whose apply() sets the aggregate condition and whose options() drives the UI. That way the condition is user-toggleable, shows up in the active-filter pill bar, and survives the request round-trip on sort/pagination — which is exactly where a hand-rolled ->having() in a controller would break.WHERE filters. Combine a normal Nova filter (narrow the row set first) with the having filter (then constrain the grouped set). Doing the WHERE work first keeps the grouped intermediate small and is usually the difference between a fast index and a slow one on large tables.->selectRaw('SUM(...) as total')->groupBy(...), the same having helper lets you filter those groups without re-deriving the query. This is the pattern that pays off most day-to-day: one expression of "groups matching a threshold" shared between the index filter and the trend card.Nova::actingAs(...)->get('/nova-api/...')) confirm the request doesn't blow up, but they won't tell you the SQL is portable. Add a unit test that asserts the generated SQL/ bindings for your filter on the connection you actually deploy against.HAVING without GROUP BY is undefined behaviour. MySQL will happily return a single row; Postgres will error. If the filter is optional and the group-by only applies when it's active, make sure both are toggled together — a half-applied state is the most common failure mode for this kind of package.HAVING total > 100 referencing a SELECT alias; Postgres does not and needs the full expression. If your app targets more than one driver (or you test on SQLite and deploy on MySQL), verify which form the package emits and don't hand-write the other one into your apply() method.ONLY_FULL_GROUP_BY. Every non-aggregated column in your SELECT must appear in GROUP BY, or MySQL rejects the query. Nova's index query adds its own columns and ordering — if the resource has a default sort on a column you didn't group, the query fails only when the feature is switched on. Test with the filter active and a sort applied.HAVING clause you'll get a page count that disagrees with the rows on screen. Sanity-check the last page number on a filtered index before shipping.whereHas-style scope combined with a grouped query can silently multiply rows and skew your aggregate. Check the raw SQL with ->toSql() and ->getBindings() in Tinker, and hook DB::listen() in a local request if the filter isn't producing the rows you expect.composer.json accordingly.How can I help you explore Laravel packages today?