I think the first thing to do is to try to start using our own definitions of the unit relationships (i.e. the objects in daf/butler/core/dataUnits.py) in the preflight code, so you're not relying on the SQLAlchemy foreign key relationships, as we may just not be able to make those as expressive as what we define ourselves.
I'm a bit ambivalent about whether it's a good idea to then use SQLAlchemy to write the query itself - that might make things more portable as long as the Registry is implemented on top of SQLAlchemy, but means we can't use this (shared) component at all if the Registry isn't implemented on top of SQLAlchemy, and while we want to use SQLAlchemy to make it easier to implement Registries, we don't want to make it part of any interface. Of course, using it in the implementation of a shared component isn't quite the same as making it part of an interface - we could always reimplement the shared component when if/when we have a non-SQLAlchemy Registry. In any case, I'm happy to follow your judgement on that question for now, but I wanted to make sure you understood the situation.
Anyhow, I'm hoping we may already have information in our own DataUnit classes that could be used t say that whenever Visit appears, PhysicalFilter should as well. That might make PhysicalFilter appear when it isn't needed, but that's "only" a performance problem, and I hope it may not be a meaningful one. I'm pretty sure our DataUnit classes should report that there is a foreign-key-like relationship between AbstractFilter and PhysicalFilter, even if there isn't one in the actual SQL.