Radial graph of 20 schools that offer an online health professions master's, placed by six-year graduation rate around a central node, with a dashed ring at 60% and the six schools in the answer highlighted.

One question, four query languages: does the language change the answer?

If you pick SPARQL over Cypher, or a graph database over plain SQL, do you get a different answer?

I asked the same question four ways to find out. This is the second experiment in a series that uses one public dataset, NCES IPEDS 2024, to show how languages and models change what a knowledge graph tells you. Experiment 1 compared six ways to write the same facts. This one compares four ways to ask about them. The code, data and results are all on GitHub.

The question

Which schools offer a fully online master’s in the health professions and graduate more than 60% of their students within six years?

It’s the kind of question a prospective student could ask an AI assistant. Answering it means joining four things: institutions, their program offerings, award levels and distance-education flags, and graduation outcomes.

We asked it in four languages and ran each one on a real engine:

LanguageData modelEngine
SQLThree tables, straight from the CSVsDuckDB 2.0
SPARQLRDF: schema.org plus a small kgn: vocabularyrdflib 7.6
CypherLabeled property graphKรนzu 0.11.3
ISO GQLThe same property graphGraphLite 0.0.1

The answer: six schools, and all four languages returned the identical list. University of Southern California (91.8%), New York University (87.6%), University of Washington-Seattle (85.2%), Baylor University (80.0%), University of Central Florida (78.0%) and Keiser University-Ft Lauderdale (61.0%).

See it: four queries, one graph

The visualization below puts the four queries beside a graph of every school that offers an online health master’s. Switch the definition, or try one of the small slips, to see which query lines change and which schools join or leave the answer.

Open the visualization in a new tab

The four queries

Each heading links to the full query file on GitHub.

SQL

SQL states every join as a key comparison. It’s the longest of the four on joins and the most familiar to anyone who has worked with IPEDS files.

SELECT
    i.UNITID                        AS unitid,
    i.name                          AS institution,
    o.gr4_rate_150_all_pct          AS grad_rate_6yr,
    count(*)                        AS online_health_masters
FROM programs      AS p
JOIN institutions  AS i ON i.UNITID = p.UNITID
JOIN outcomes      AS o ON o.UNITID = p.UNITID
WHERE p.cip_2digit = '51'                       -- Health Professions
  AND p.awlevel_code = 7                        -- Master's degree
  AND p.programs_offered_fully_distance > 0     -- offered fully online
  AND o.gr4_rate_150_all_pct > 60               -- 150% of normal time = 6 years
GROUP BY i.UNITID, i.name, o.gr4_rate_150_all_pct
ORDER BY grad_rate_6yr DESC;

SPARQL

SPARQL joins wherever two triple patterns share a variable, like ?school here. It’s shorter on joins and longer on vocabulary.

PREFIX schema: <https://schema.org/>
PREFIX kgn:    <https://knowledgegraphnavigator.com/vocab/ipeds#>

SELECT ?unitid ?institution ?gradRate6yr (COUNT(?program) AS ?onlineHealthMasters)
WHERE {
  ?program a schema:EducationalOccupationalProgram ;
           schema:provider          ?school ;
           kgn:cipFamily            ?family ;
           kgn:awardLevelCode       7 ;          # Master's degree
           kgn:fullyDistancePrograms ?online .
  ?family  kgn:cipCode              "51" .       # Health Professions
  ?school  schema:name              ?institution ;
           kgn:unitid               ?unitid ;
           kgn:gradRate150All       ?gradRate6yr .
  FILTER (?online > 0 && ?gradRate6yr > 60)
}
GROUP BY ?unitid ?institution ?gradRate6yr
ORDER BY DESC(?gradRate6yr)

Cypher

Cypher writes the joins as the shape of the pattern: school, offers, offering, in family. It’s the shortest query of the four.

MATCH (i:Institution)-[:OFFERS]->(o:Offering)-[:IN_FAMILY]->(f:CipFamily {code: '51'})
WHERE o.awardLevelCode = 7
  AND o.fullyDistance > 0
  AND i.gradRate150All > 60
RETURN i.unitid         AS unitid,
       i.name           AS institution,
       i.gradRate150All AS gradRate6yr,
       count(o)         AS onlineHealthMasters
ORDER BY gradRate6yr DESC;

GQL

GQL (ISO/IEC 39075:2024) is the ISO standard for property graph queries. For this question it’s identical to Cypher except for one line: an explicit GROUP BY. Cypher groups implicitly by whatever isn’t aggregated.

MATCH (i:Institution)-[:OFFERS]->(o:Offering)-[:IN_FAMILY]->(f:CipFamily {code: '51'})
WHERE o.awardLevelCode = 7
  AND o.fullyDistance > 0
  AND i.gradRate150All > 60
RETURN i.unitid         AS unitid,
       i.name           AS institution,
       i.gradRate150All AS gradRate6yr,
       count(o)         AS onlineHealthMasters
GROUP BY i.unitid, i.name, i.gradRate150All
ORDER BY gradRate6yr DESC;

If you want the longer comparison of the two graph models behind these queries, see property graph vs RDF.

The definitions moved the answer

The language never changed the answer. The definitions did.

The question sounds precise, but two words in it are choices. When we changed them, all four languages moved together every time:

DefinitionSchools
Strict: fully online programs; all-student six-year rate6
Bachelor’s-seeking students’ six-year rate instead5
Partly online programs also count10
  • Which graduation rate? IPEDS reports a six-year rate for all degree- and certificate-seeking students and a separate one for bachelor’s-seeking students. Keiser’s is 61.0% for all students and 57.7% for bachelor’s-seeking students. Pick the second, and Keiser drops out.
  • What counts as “online”? IPEDS separates programs offered completely through distance education from programs with some distance-education component. Counting both adds Ohio State, San Jose State, DePaul, and St. John’s. None of them offer a fully online health master’s in this data.
  • What about missing data? Touro University California offers online health master’s programs but has no graduation rate, because IPEDS doesn’t collect one for graduate-only institutions. All four languages leave it out without saying so.

None of these is a mistake. Each is a defensible reading of the same sentence, and each gives a different list.

Small slips, big discrepancies

The definitions are deliberate choices. These are accidents: typo-sized changes to the strict query, each one a mistake I could see a careful analyst making. We ran every one in all four languages.

SlipSQLSPARQLCypherGQL
None (the strict question)6666
Write the CIP code as 51 instead of '51'6060
Write the threshold as > 0.6 instead of > 6013131313
Put the rate condition in an outer join or OPTIONAL1414n/an/a
Ask the opposite: which schools do not clear 60%?787error
  • One missing pair of quotes. CIP codes are text. DuckDB and Kรนzu quietly convert the number 51 to text and still return six schools. SPARQL and GraphLite compare types strictly, so they return zero rows and no error.
  • Percent or proportion? IPEDS stores rates as percents from 0 to 100. Writing > 0.6 keeps every school that has a rate: 13 instead of 6. Every language makes this mistake identically, because the query is wrong for the data.
  • A filter in the wrong place. Moving the rate condition into a LEFT JOIN ... ON clause or an OPTIONAL { } block changes what it does. It stops removing schools and only decides whether to attach their rate, so 14 schools come back, eight with a blank rate. The Cypher and GQL versions can’t make this mistake, because the rate lives on the school’s node.
  • Negation meets missing data. SQL and Cypher evaluate NOT (rate > 60) as unknown for Touro, which has no rate, and drop it: 7 schools. SPARQL’s usual idiom, FILTER NOT EXISTS, finds no qualifying rate and keeps Touro: 8 schools. GraphLite stops with an error.

The slip that throws an error is the easy one. The dangerous ones return a plausible answer with no warning: an empty list, or a longer list with blanks in it.

What this means if you work with structured data

In my experience, teams spend a lot of energy choosing a query language and very little writing down what their questions mean. This experiment suggests the effort is backward.

  • Write definitions down before you write queries. “Online,” “graduation rate,” and “missing” each changed the answer more than the language did.
  • Treat an empty result as a question, not an answer. Zero rows from SPARQL looked exactly like “no schools qualify.” It meant “your types don’t match.”
  • Check units at the source. IPEDS uses percents. Plenty of other tools use proportions. The query can be valid and still wrong.

The same holds when an AI assistant answers a question like this one. It still has to pick a definition, and the person asking rarely sees which one it picked.

Simply put, the query language is the interface, not the answer. The model and the definitions underneath it provide the answer.

About the engines

A candid note on tooling. Kรนzu’s developers stopped work on the project in October 2025, and 0.11.3 is its final release. It still installs and runs this experiment, and the Cypher query uses only core syntax, so it should run on Neo4j or another Cypher database with the same schema.

GraphLite is at version 0.0.1, and it shows. Parts of the GQL standard, such as FILTER and LET, don’t parse yet, and loading is slow. By default, the experiment loads only the health-professions slice of the graph into GraphLite, which is everything the question can touch. The full graph (8,117 nodes and 16,068 edges) took about seven minutes to load and returned the same answers. GQL is a published standard, but its tooling is still early.

Run it yourself

Everything behind this post is in the Experiment 2 folder on GitHub: the four query files, the script that builds the SQL tables, RDF graph and property graph from the IPEDS CSVs, the runner that applies every definition and slip, the full results and the visualization. SQL, SPARQL, and Cipher run in about two seconds. GQL needs the GraphLite CLI, and the README covers the setup.

Next in the series: where does the number go? Modeling the same facts in RDF and a property graph.