Counting visitors
How attendance is counted, and where the numbers actually come from.
Trade-fair attendance figures are audited. German organisers commonly report them under the FKM scheme — the voluntary audit of trade-fair and exhibition statistics — and audited figures follow rules that do not match naive row counts.
In this data those rules live on the statistics groups, which is why StatisticGroups is a dataset of its own.
Statistics groups are where reporting structure lives
A statistics group is a reporting bucket that articles belong to. The catch: an article is regularly a member of several groups at once, which a single column on the ticket row cannot express. Attendance reporting therefore goes through the article, and from there through its groups.
Join it on MandatorInternalId and ArticleInternalId. Each group row carries two flags:
| Field | Means |
|---|---|
IsFkmTotal | the group counts towards the audited total |
IsFkmVisitor | the group counts as visitors |
These are independent flags, not a partition. A group can carry both, one, or neither:
- A visitor badge group is normally in both.
- A group that was present but does not count as attendees may be in the total only.
- A plain business category — exhibitor, press, other, test — is a row with both false, and that is entirely normal.
Never compute "non-visitors" as total minus visitor. That subtraction has no meaning here, and it produces a number that looks entirely plausible and is wrong. Group by the two flags together and report the combinations.
Two traps when you join
The join multiplies rows. An article in three groups produces three rows per ticket, so a naive COUNT(*) after joining overcounts badly. Count distinct TicketInternalId, and aggregate to the grain you want before or during the join.
An article that belongs to no group has no row at all in StatisticGroups, so an inner join silently drops those tickets. Use a left join and decide deliberately what the unassigned bucket means in your report — often it belongs in an "other" category rather than being dropped.
Sold, admitted, and present
Four different questions, four different answers, none of which need a statistics group at all:
| Question | How |
|---|---|
| How many were sold or issued? | count rows in Tickets |
| How many people came? | count distinct TicketInternalId in TicketUsages |
| How many visits were there? | count rows in TicketUsages (a visitor returning on three days is three) |
| How many were present on a given day? | count distinct tickets with IsFirstOfDay eq true in that day |
The gap between the first and the second is your no-show rate, and it is usually one of the most valuable figures in the whole dataset. Recipes shows the query.
Segmenting attendance
When you need attendance broken down, the dimensions that hold up are:
| Dimension | Where |
|---|---|
| Article | ArticleName, or ArticleType for the coarse kind |
| Statistics group | Name on StatisticGroups, joined via ArticleInternalId |
| Legitimation | Legitimation* on Tickets |
| Issue route | TicketIsIssuedByOrganizer / TicketIsIssuedByExhibitor |
| Sales channel | SaleDistributionChainType |
| Event edition | FaireventName + FaireventYear, or FairBrandName across years |