Write SELECT * FROM TENANT WHERE TENANT.HMY = 12345 against a Yardi Voyager database and it fails outright — the column doesn't exist. That's the friendly version of this trap: an error message that tells you immediately something's wrong. The dangerous version is the one that doesn't fail. It just joins to the wrong row.
PERSON is a supertype, not just a table
In Voyager's schema, PERSON isn't only "people" in the everyday sense — it's a supertype that several very different entities extend: TENANT, VENDOR, ROOM, Customer, and OWNER are all subtypes of PERSON. PERSON itself carries the primary key hMy. But its subtypes don't inherit that key name. Their primary key is HMYPERSON — a different column, on a different table, that happens to hold values drawn from the same underlying identity space as PERSON.hMy.
Practically, that means TENANT.HMY isn't a typo you can fix by qualifying the column — it's a column that was never there. The tenant's identity lives in TENANT.HMYPERSON. Same for VENDOR.HMYPERSON, OWNER.HMYPERSON, and the other subtypes. PROSPECT and PERSON itself are the exception — they keep the plain hMy key, which is exactly the kind of inconsistency that makes this trap easy to fall into if you're pattern-matching from one table to the next.
Why the "safe" failure isn't the whole story
A query against TENANT.HMY throws an error and stops you immediately. That's the survivable case. The riskier one shows up in foreign-key columns that reference a subtype table by name: hTenant points at TENANT.HMYPERSON, hVendor points at VENDOR.HMYPERSON. If you write the join correctly — hTenant = TENANT.HMYPERSON — everything works. But because hTenant and hVendor values are drawn from the same shared PERSON identity space, a value that happens to also exist as a row in the wrong subtype table can produce a join that executes cleanly and returns a plausible-looking wrong answer. No error. No warning. Just a row that shouldn't be there, or a row that's missing, sitting quietly in a report nobody double-checks.
That's the real shape of the HMYPERSON trap: it's not primarily about the query that fails. It's about the join that succeeds against the wrong subtype and never tells you.
Where it bites in practice
This trap doesn't stay contained to TENANT and VENDOR. OWNER is a PERSON subtype too — its HMYPERSON primary key doubles as the legal-entity grouping key that properties roll up to for ownership reporting. Get an owner join wrong and a portfolio-level financial statement can misattribute a property to the wrong legal entity.
It also compounds with one of the messiest columns in the whole schema: TRANS.HPERSON. As we covered in decoding TRANS.iType, that single column is polymorphic across the transaction-type discriminator — it points to a tenant on some transaction types, a vendor on others, a bank record on others, and nothing at all on a few. Resolving TRANS.HPERSON correctly requires knowing both which iType you're looking at and which PERSON subtype table that type's HPERSON value actually belongs to. Get either piece wrong and the join still runs.
Why the naming convention doesn't warn you
Voyager's column names follow a Hungarian-style prefix grammar — h for a handle or foreign key, s for string, i for an integer or enum discriminator, and so on. That convention is normally a help: hProp reliably means "foreign key to PROPERTY," and once you know the grammar you can read a lot of the schema without a reference. HMYPERSON breaks the pattern just enough to be dangerous. It reads like a variant spelling of hMy, so it's easy to assume it behaves the same way — a primary key you can join to directly by table name, the way hMy works everywhere else. It doesn't. It's hMy's cousin on the PERSON supertype's subtypes specifically, and the resemblance in spelling is exactly what makes people skip checking which table it actually belongs to before writing the join.
The general pattern behind it
This isn't an isolated oddity — it's one instance of a broader convention in Voyager's schema: foreign-key-shaped columns whose real target depends on something other than the column name alone. The schema knowledge that powers the SQL Query Assistant documents this as a standing category — polymorphic hRecord/hCode foreign keys, where the actual target table is determined by a companion discriminator column in the same row, not by the column name. HMYPERSON subtype resolution is the person-table-specific version of the same underlying discipline: never assume a foreign key points where its name suggests without checking what determines the target.
What a correct join actually looks like
SELECT t.sCode, p.uLastName, t.hProperty
FROM TENANT t
JOIN PERSON p ON p.hMy = t.HMYPERSON
WHERE t.hProperty = {PROPERTY_HMY}
The join key is TENANT.HMYPERSON = PERSON.hMy — not TENANT.hMy, which doesn't exist, and not a bare assumption that any HMYPERSON-shaped value can be joined to any subtype table interchangeably. Every subtype join has to be anchored to the specific table the foreign key is documented to reference.
Don't rebuild this knowledge from scratch
The HMYPERSON subtype trap is exactly the kind of convention that experienced Voyager admins carry in their heads and nobody writes down — until the person who knows it leaves. The SQL Query Assistant carries it as part of its curated naming-grammar knowledge, alongside the H-prefix key conventions, polymorphic hRecord/hCode foreign keys, and the core spine joins between properties, units, tenants, and transactions — so a query drafted inside Claude respects the trap before you run it, not after a report comes back wrong.
It ships in every PropETL tier. Start the free 7-day trial — no credit card — and ask it to walk you through the next join you're not sure about.
