Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jbarrasa/goingmeta/llms.txt

Use this file to discover all available pages before exploring further.

Session 10 of Going Meta, broadcast on November 1, 2022, demonstrates how to treat SPARQL endpoints as live data sources that Neo4j can query directly — no pre-downloaded dumps, no ETL pipelines. Jesus shows how a handful of company ticker symbols stored in Neo4j can be enriched on demand with rich structured data from DBpedia, using both lightweight CSV fetches for simple lookups and full SPARQL CONSTRUCT queries that import subgraphs of related people and places.

What You Will Learn

  • How to seed a Neo4j graph with minimal Organization nodes and enrich them from a SPARQL endpoint
  • How to use LOAD CSV as an HTTP client for simple SELECT queries that return CSV
  • How to use apoc.text.urlencode to safely inject graph property values into SPARQL query strings
  • How to issue a SPARQL CONSTRUCT query and import the resulting RDF subgraph directly via n10s.rdf.import.fetch
  • How to create “RDF twins” — Resource nodes that bridge Neo4j entities to their linked-data counterparts
  • How to federate across DBpedia ontology classes for companies, people, and places in a single query
Tags: DBPedia · Cypher · SPARQL — Broadcast November 1 2022

Setup — Seed the Graph

Start with a minimal set of company nodes identified only by their stock ticker symbols:
MERGE (n:Organization { ticker:"CAT" });
MERGE (n:Organization { ticker:"ACN" });
MERGE (n:Organization { ticker:"FTNT" });
MERGE (n:Organization { ticker:"WDAY" });
MERGE (n:Organization { ticker:"SNOW" });
MERGE (n:Organization { ticker:"AVGO" });
MERGE (n:Organization { ticker:"FB" });
MERGE (n:Organization { ticker:"DUOL" });

Pattern 1 — Simple Lookup via SELECT + LOAD CSV

The basic SPARQL query that resolves a ticker symbol to a DBpedia URI and English company name:
SELECT (?company AS ?uri) (?name AS ?companyname)
WHERE {
  # match company by ticker
  ?company a dbo:Company ;
        dbp:symbol "FB"@en ;
        rdfs:label ?name .

  FILTER(lang(?name) = "en")
}
To run this for every ticker in the graph, parameterize the query and consume the SPARQL endpoint like any other CSV URL:
WITH 'https://dbpedia.org/sparql/?format=text%2Fcsv&query=' AS endpoint,
'SELECT (?company AS ?uri) (?name AS ?companyname)
WHERE {
  # match company by ticker
  ?company a dbo:Company ;
        dbp:symbol "<<ticker>>"@en ;
        rdfs:label ?name .

  FILTER(lang(?name) = "en")
}
' AS query

MATCH (o:Organization) WHERE o.ticker IS NOT NULL

LOAD CSV WITH HEADERS FROM endpoint + apoc.text.urlencode(replace(query,"<<ticker>>", o.ticker)) AS row

RETURN o.ticker, row.companyname, row.uri
LOAD CSV treats any HTTP URL that returns comma-separated data as a valid source. Combined with apoc.text.urlencode, it becomes a universal REST/SPARQL client within plain Cypher.

Pattern 2 — Create RDF Twin Nodes

To enable full RDF import later, create a Resource node whose URI matches the DBpedia entity, and link it to your domain node:
-- Option A: create a separate twin node
MERGE (r:Resource { uri: row.uri })
MERGE (o)-[:wikidata_twin]->(r)

-- Option B: promote the Organization node itself to a Resource
SET o:Resource, o.uri = row.uri
Making an existing node a Resource (Option B) allows n10s.rdf.import.fetch to merge incoming RDF triples directly onto your domain nodes rather than creating duplicates.

Pattern 3 — Full Subgraph Import via CONSTRUCT

For deep enrichment, use a SPARQL CONSTRUCT query to fetch a company along with all related people and places from DBpedia, then import the result as RDF:
WITH '
CONSTRUCT {
    ?company a dbo:Company ;  ?cpred ?cobj ; ?relToPerson ?person ; ?relToPlace ?place .
    ?person a dbo:Person ; rdfs:label ?pobj ; ?relToPersonInv ?company .
    ?place a dbo:Place ;  rdfs:label ?plobj ; ?relToPlaceInv ?place .
}
WHERE {
  ?company a dbo:Company ; dbp:symbol "<<ticker>>"@en .
  ?company ?cpred ?cobj .
      FILTER(isLiteral(?cobj) && (lang(?cobj)="en" || lang(?cobj)="")
             && (strStarts(str(?cpred), "http://dbpedia.org/ontology/")
             || strStarts(str(?cpred), "http://www.w3.org/2000/01/rdf-schema#label")))
  OPTIONAL {
         { ?company ?relToPerson ?person . FILTER(strStarts(str(?relToPerson), "http://dbpedia.org/ontology/") && ?relToPerson != dbo:wikiPageWikiLink) }
          UNION
         { ?person ?relToPersonInv ?company . FILTER(strStarts(str(?relToPersonInv), "http://dbpedia.org/ontology/") && ?relToPersonInv != dbo:wikiPageWikiLink) }
         ?person a dbo:Person ; rdfs:label ?pobj .
         FILTER(lang(?pobj)="en" || lang(?pobj)="")
  }
  OPTIONAL {
        { ?company ?relToPlace ?place . FILTER(strStarts(str(?relToPlace), "http://dbpedia.org/ontology/") && ?relToPlace != dbo:wikiPageWikiLink) }
         UNION
        { ?place ?relToPlaceInv ?company . FILTER(strStarts(str(?relToPlaceInv), "http://dbpedia.org/ontology/") && ?relToPlaceInv != dbo:wikiPageWikiLink) }
         ?place a dbo:Place ; rdfs:label ?plobj .
         FILTER(lang(?plobj)="en" || lang(?plobj)="")
  }
}
' AS sparql,
'http://dbpedia.org/sparql?format=text/plain&query=' AS endpoint

MATCH (o:Organization) WHERE o.ticker IS NOT NULL

CALL n10s.rdf.import.fetch(
  endpoint + apoc.text.urlencode(replace(sparql,"<<ticker>>", o.ticker)),
  'Turtle'
) YIELD terminationStatus, triplesLoaded, triplesParsed, namespaces, extraInfo
RETURN o.ticker, terminationStatus, triplesLoaded, triplesParsed, namespaces, extraInfo
Before running the CONSTRUCT import, ensure n10s is configured:
CREATE CONSTRAINT n10s_unique_uri FOR (r:Resource) REQUIRE r.uri IS UNIQUE;
CALL n10s.graphconfig.init({handleVocabUris:"IGNORE"});

Resources

Watch the Recording

Full live-stream on YouTube — Session 10, November 1 2022

Source Code on GitHub

Cypher scripts, SPARQL queries, and session slides

Build docs developers (and LLMs) love