Survey Data (ESS)
The MCP server hosts the European Social Survey — respondent-level microdata on attitudes and behaviours across European countries — behind three read-only tools: survey__list_datasets, survey__lookup_variable, and survey__query. This page describes what the data contains, how querying works, and walks through worked examples.
What the data is
The ESS is a cross-national, repeated survey run in waves ("rounds") roughly every two years. One observation is one respondent in one round; the survey's own axes are cntry (country, ISO 3166-1 alpha-2), essround (the wave number), and a resolved NUTS region. Questions cover trust, politics, wellbeing, media and internet use, household composition, income, and rotating topical modules.
Two datasets are exposed:
| Dataset | Contents | When to use |
|---|---|---|
ess-integrated |
The pooled cross-round integrated file: rounds 2, 4, 6, 8, 9, 10, and 11 in one table, ~2,800 variables. | The default for almost every question — comparisons across countries, rounds, or respondent groups. |
ess-multilevel |
The round-11 multilevel edition: the same respondent microdata plus a contextual layer of ~3,300 macro indicators (c_*) — GDP, population, area, and other country/region statistics. |
Only when a question mixes individual responses with country- or region-level context. |
Each dataset is versioned by edition — an immutable republished snapshot (e.g. ESS11MD_e01_2). Queries default to the latest edition; the concrete edition used is always echoed back in meta.edition. Rounds are values inside the data (filter on essround), not editions.
To see what's actually in the catalog, use the interactive catalog browser — every dataset, edition, and variable as a searchable tree, grouped by survey family and theme.
survey__list_datasets first in a fresh session — it returns the exact dataset and edition identifiers and which rounds each edition carries.
Variables, value labels, and the codebook
Every column is a variable with a short name (ppltrst, netusoft, agea), a human label ("Most people can be trusted or you can't be too careful"), a set of value labels giving the meaning of each coded answer (e.g. 0 = "You can't be too careful" … 10 = "Most people can be trusted"), and per-variable missing-value codes. This codebook metadata is ingested into a searchable catalog — survey__lookup_variable is the interface to it.
Answers are stored as their raw numeric codes. Always decode a coded value through its value labels before presenting it — a bare netusoft = 5 means nothing to a reader ("every day" does).
The two layers
- Individual layer — one row per respondent, identified by
(idno, cntry, essround). This is what you filter and aggregate. - Contextual layer (
ess-multilevelonly) — macro indicators that attach to a respondent via their resolved region, the finest NUTS level the edition supplies (country | nuts1 | nuts2 | nuts3). Joined on demand withjoinContext; never stored per respondent.
Correctness by construction
The tools do not accept SQL. Queries are expressed in a structured request the survey engine executes server-side, which lets two things be enforced on every query rather than left to the caller:
- Survey weighting. Raw row counts and averages over survey respondents are biased — countries differ in sample size and sampling design. Every aggregate is computed with a design weight (default
anweight) and the response reports which weight was used. - Missing-value exclusion. ESS encodes non-answers as ordinary-looking codes (typically
77= refusal,88= don't know,99= no answer). These are excluded from aggregates automatically, per the codebook's per-variable missing codes. Averaging them in would silently corrupt the estimate.
meta block — dataset, resolved edition, weight used, unweighted respondent count, sum of weights, rows dropped for a missing weight, and a truncation flag. A number without its meta is not a result.
The tools
dataset/edition values.mean / count / share, optionally grouped) is the primary path; rows mode returns bounded raw rows for inspection. Weighting and missing-value exclusion are applied by construction.of, by, select, and every filter.eq / filter.range key take variable names (ppltrst), never labels ("trust in people"). Passing a label or a country name where a variable name belongs is rejected up front with a suggestion.
Query semantics
Two modes
- Aggregate mode — set
aggregate: { op, of, by }.meanis the weighted mean ofof;shareis the weighted share of each value ofof(shares sum to 1 within a group);countis the weighted count — a plain row count whenweight: "none".bygroups the result (e.g.["cntry"], or["cntry", "essround"]for a country-by-round matrix). - Rows mode — omit
aggregateand provide an explicitselect. Returns raw rows, capped at 1,000 by default (rowLimitadjusts;meta.truncatedflags when the cap was hit). A rows query withoutselectis rejected — the tools never dump all columns. Rows mode is for inspecting records, not for computing statistics yourself.
Filters (all conditions AND together)
| Field | Takes | Notes |
|---|---|---|
filter.cntry | ISO 3166-1 alpha-2 codes | ["DK", "SE"] — country names are rejected. |
filter.essround | Integer round numbers | [8, 11] — a round is a value on the survey axis, not an edition. |
filter.region | Resolved NUTS region codes | Matches the finest NUTS level the edition supplies. |
filter.eq | { variable: [values] } | Equality on any variable, e.g. { "gndr": [2] }. |
filter.range | { variable: [min, max] } | Inclusive numeric range, e.g. { "agea": [18, 29] }. |
Weights
| Weight | Use for |
|---|---|
anweight | The default. ESS's own guidance for almost everything; required for pooled cross-round or cross-country estimates. |
pspwght | Post-stratification estimates within a single round and country. |
pweight / dweight | Population-size or design weights — only when specifically required. |
none | Explicitly unweighted. Raw counts for data-quality checks — never a headline figure. |
Rows whose chosen weight is NULL are dropped and reported in meta.droppedForMissingWeight — never silently discarded.
Missing values
All catalog missing codes for the variables being aggregated, grouped, or filtered are excluded by default, and system-missing (NULL) is always excluded with no override. To deliberately treat a missing code as a valid answer — say, to count refusals — pass includeMissing: { "variable": [77] } for that query only.
The meta block
| Field | Meaning |
|---|---|
dataset / edition | What was actually queried. edition is the concrete resolved edition — never "latest". |
weight | The weight applied. State it alongside any figure. |
unweightedN | Distinct respondents behind the estimate. A small n warrants caution. |
sumWeights | Sum of the applied weights (0 when weight: "none"). |
droppedForMissingWeight | Rows excluded because their weight was NULL. |
truncated | true when a rows-mode result hit its cap — the view is partial. |
Worked examples
The standard workflow is: discover the datasets, resolve the human question to a variable name, then aggregate — and read meta before presenting anything.
- 1survey__list_datasets — confirm the dataset, latest edition, and available rounds.
- 2survey__lookup_variable — turn "trust in people" into
ppltrst, and note its value labels and missing codes. - 3survey__query — aggregate mode with
of,by, andfilter; leaveweightat its default. - 4Present with provenance — decode coded values, state the weight and the unweighted n.
1 · Discover what's available
survey__list_datasets()
→ { "datasets": [
{ "dataset": "ess-integrated",
"title": "European Social Survey — respondent files",
"editions": ["Complete_ESS"],
"edition_titles": { "Complete_ESS": "All ESS rounds, pooled" },
"rounds": [2, 4, 6, 8, 9, 10, 11] },
{ "dataset": "ess-multilevel",
"title": "European Social Survey — respondents + context",
"editions": ["ESS11MD_e01_2"],
"edition_titles": { "ESS11MD_e01_2": "ESS round 11 + country/region context (2022)" },
"rounds": [11] }
] }
2 · Find a variable and its value labels
survey__lookup_variable({ "q": "trust in people", "dataset": "ess-integrated" })
→ { "hits": [
{ "variable": "ppltrst",
"label": "Most people can be trusted or you can't be too careful",
"dataset": "ess-integrated", "edition": "Complete_ESS",
"editions": ["Complete_ESS"] }
] }
The full hit also carries the value labels (0 = "You can't be too careful" … 10 = "Most people can be trusted") and the missing codes (77, 88, 99) — keep them for decoding the result.
3 · Weighted mean, grouped by country
"How does trust in people compare across countries in the latest round?"
survey__query({
"dataset": "ess-integrated",
"filter": { "essround": [11] },
"aggregate": { "op": "mean", "of": "ppltrst", "by": ["cntry"] }
})
→ { "rows": [
{ "cntry": "DK", "mean": 6.9 },
{ "cntry": "FI", "mean": 6.8 },
{ "cntry": "HU", "mean": 5.2 },
...
],
"meta": { "dataset": "ess-integrated", "edition": "Complete_ESS",
"weight": "anweight", "unweightedN": 39870,
"sumWeights": 41210.3, "droppedForMissingWeight": 0,
"truncated": false } }
Present it as: "In round 11, mean trust in people ranges from about 6.9 in Denmark to 5.2 in Hungary on the 0–10 scale (anweight, n ≈ 39,900)."
4 · Weighted shares of an answer distribution
"How often did Austrians use the internet in round 8?" — share returns the weighted proportion of each value of of:
The value labels come from survey__lookup_variable — never present the bare codes. To compare distributions across groups, add "by": ["cntry"]; shares then sum to 1 within each group.
5 · Trend over rounds
Group by both country and round to get a time series per country:
survey__query({
"dataset": "ess-integrated",
"filter": { "cntry": ["DK", "SE"] },
"aggregate": { "op": "mean", "of": "ppltrst", "by": ["cntry", "essround"] }
})
→ one row per (country, round) — e.g. { "cntry": "DK", "essround": 8, "mean": 7.0 }
6 · Subgroup filters on any variable
"Mean trust among women aged 18–29" — combine eq and range filters (here gndr = 2 is "Female", per its value labels):
survey__query({
"dataset": "ess-integrated",
"filter": {
"essround": [11],
"eq": { "gndr": [2] },
"range": { "agea": [18, 29] }
},
"aggregate": { "op": "mean", "of": "ppltrst", "by": ["cntry"] }
})
7 · Rows mode: inspect raw records
For eyeballing individual observations — an explicit select is required and results are capped:
survey__query({
"dataset": "ess-integrated",
"filter": { "cntry": ["DK"], "essround": [11] },
"select": ["idno", "agea", "gndr", "ppltrst", "netusoft"],
"rowLimit": 20
})
→ { "rows": [ { "idno": 10012, "agea": 34, "gndr": 2, "ppltrst": 7, "netusoft": 5 }, ... ],
"meta": { ..., "truncated": false } }
If meta.truncated is true the view is partial — narrow the filter or switch to aggregate mode. Do not compute totals or averages from rows-mode output; that bypasses weighting.
8 · Joining the contextual layer (ess-multilevel)
"Does trust track regional GDP?" — request c_* indicators with joinContext; each attaches to a respondent via their resolved region and appears as a column:
survey__query({
"dataset": "ess-multilevel",
"filter": { "cntry": ["DK", "PL"] },
"select": ["idno", "cntry", "ppltrst", "c_gdp_2022"],
"joinContext": ["c_gdp_2022"],
"rowLimit": 100
})
Discover the available indicators with survey__lookup_variable against ess-multilevel. Requesting joinContext on a dataset without a contextual layer is an error.
9 · Counting non-answers deliberately
"How many refused to state their income?" — re-include the refusal code as a valid value, and use an explicit unweighted count for the data-quality view:
survey__query({
"dataset": "ess-integrated",
"filter": { "essround": [11] },
"aggregate": { "op": "count", "by": ["hinctnta"] },
"includeMissing": { "hinctnta": [77] },
"weight": "none"
})
Common errors
| Response | What it means |
|---|---|
unknown variable … did you mean …? | A label or country name was passed where a variable name belongs. Resolve it with survey__lookup_variable and retry. |
rows mode requires an explicit select | Both aggregate and select were omitted. Add an aggregate (preferred) or a select list. |
aggregate op 'mean'/'share' requires of | The measured variable is missing — add it as of. |
no editions found for dataset | The dataset/edition isn't available — use a value returned by survey__list_datasets. |
joinContext requested but … has no contextual layer | Contextual indicators exist only on ess-multilevel. Switch dataset or drop joinContext. |
survey__list_datasets / survey__lookup_variable, it isn't available.
Updated 25 July 2026 at 10:50