Skip to content

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:

FieldMeans
IsFkmTotalthe group counts towards the audited total
IsFkmVisitorthe 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:

QuestionHow
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:

DimensionWhere
ArticleArticleName, or ArticleType for the coarse kind
Statistics groupName on StatisticGroups, joined via ArticleInternalId
LegitimationLegitimation* on Tickets
Issue routeTicketIsIssuedByOrganizer / TicketIsIssuedByExhibitor
Sales channelSaleDistributionChainType
Event editionFaireventName + FaireventYear, or FairBrandName across years