Si trabajas con grafos de conocimiento, sabes que SPARQL no se aprende leyendo teoría, sino viendo cómo se resuelven problemas reales.
En este artículo voy a explicarlo con ejemplos que uso en producción sobre:
- Wikidata.
- Grafos propios (patrimonio, lugares, genealogías)
- Consultas federadas reales.
- Casos complejos (inferencias, agregaciones, grafos distribuidos)
Además, podrás ejecutar las consultas en vivo gracias a endpoints propios basados en Apache Fuseki + YASGUI.
¿Qué es SPARQL (en 30 segundos)?
SPARQL es el lenguaje de consulta para RDF, equivalente a SQL en bases de datos relacionales, pero pensado para grafos.
La idea clave:
No consultas tablas → consultas relaciones (tripletas)
1. Primer ejemplo real: consultar un grafo propio
Vamos a empezar con algo sencillo: obtener monumentos y su municipio desde un grafo de patrimonio.
PREFIX monu: <http://www.monumentos.es/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT *
WHERE {
?monumento a monu:Sitio_de_interés.
?monumento rdfs:label ?label.
?monumento monu:Está_en_Municipio ?municipio.
?municipio rdfs:label ?municipiolabel.
}
LIMIT 25
Lenguaje del código: SQL (Structured Query Language) (sql)
Qué está pasando aquí
- Filtramos por tipo
- Navegamos relaciones
- Recuperamos etiquetas
Ejecutar consulta en vivo: Abrir en YASGUI
2. Consultar Wikidata con grafos: descendientes de Alfonso X
PREFIX bd: <http://www.bigdata.com/rdf#>
PREFIX wikibase: <http://wikiba.se/ontology#>
PREFIX schema: <http://schema.org/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX gas: <http://www.bigdata.com/rdf/gas#>
SELECT ?item ?itemLabel ?linkTo ?depth ?comment (SAMPLE (?EjemfechaNaci) AS ?fechaNaci) (SAMPLE (?pic) AS ?unica_pic)
WHERE
{
SERVICE gas:service {
gas:program gas:gasClass "com.bigdata.rdf.graph.analytics.SSSP" ;
gas:in wd:Q47595; #Alfonso X
gas:traversalDirection "Forward";
gas:out ?item;
gas:out1 ?depth;
gas:maxIterations 3;
gas:linkType wdt:P40; #descendiente
}
OPTIONAL { ?item wdt:P40 ?linkTo.}
OPTIONAL { ?item wdt:P18 ?pic.}
OPTIONAL { ?item wdt:P569 ?EjemfechaNaci}
OPTIONAL{?item schema:description ?comment.
FILTER (lang(?comment) = 'es').}
SERVICE wikibase:label { bd:serviceParam wikibase:language"[AUTO_LANGUAGE],es,en,ar,be,bg,bn,ca,cs,da,de,el,fr,et,fa,fi,he,hi,hu,hy,id,it,ja,jv,ko,nb,nl,eo,pa,pl,pt,ro,ru,sh,sk,sr,sv,sw,te,th,tr,uk,yue,vec,vi,zh".}
}
GROUP BY ?item ?itemLabel ?linkTo ?unica_pic ?depth ?comment ?fechaNaci
ORDER BY ?depth
LIMIT 200Lenguaje del código: PHP (php)
Qué hace esto
- Aplica Single Source Shortest Path (SSSP)
- Parte de Alfonso X.
- Recorre descendientes.
Por qué es potente: estás haciendo análisis de grafos dentro de SPARQL.
Ejecutar consulta en vivo: Abrir demo
Consultas federadas: unir múltiples grafos
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix monu: <http://www.monumentos.es/>
prefix lugares: <http://www.monumentos.es/lugares/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
select (sample (?EjemplolugarActual) as ?lugarActual) ?monu ?labelMonu ?lugarHist ?labelHis ?labelActual ?point ?pointMonu ?image ?definition
WHERE {
?monu monu:Estuvo_situado_en ?lugarHist.
?lugarHist lugares:LugHistóricoÉpoca monu:Edad_Antigua. # monu:Edad_del_Hierro
?lugarHist lugares:Se_refiere_a_lugar_actual ?EjemplolugarActual
service <https://javiermurcia.tech/fuseki/tesauros-mec/> {
?lugarHist skos:prefLabel ?labelHis.
?lugarHist skos:definition ?definition.
FILTER (LANG (?labelHis) = 'es')
}
service <https://javiermurcia.tech/fuseki/lugares-espana/> {
?EjemplolugarActual rdfs:label ?labelActual.
FILTER (LANG (?labelActual) = 'es')
?EjemplolugarActual <http://www.wikidata.org/prop/direct/P625> ?point.
}
service <https://javiermurcia.tech/fuseki/monumentos-espana/> {
?monu rdfs:label ?labelMonu.
FILTER (LANG (?labelMonu) = 'es')
?monu <http://www.wikidata.org/prop/direct/P625> ?pointMonu.
?monu monu:Imagen ?image.
}
}group by ?lugarActual ?monu ?labelMonu ?lugarHist ?labelHis ?labelActual ?point ?pointMonu ?image ?definition
limit 35Lenguaje del código: PHP (php)
Qué está pasando
Estás consultando varios grafos a la vez y unificando resultados en una sola query. La consulta va saltando por varios grafos hasta unificar lugares, monumentos y toponimia histórica
Ejecutar consulta en vivo: Abrir demo
Agregaciones: análisis de datos (estilo barroco)
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX monu: <http://www.monumentos.es/>
SELECT (COUNT (?subject) as ?monu) ?lugar ?label
WHERE {
{
?subject a monu:Sitio_de_interés.
?subject monu:Monumento_tiene_estilo monu:Barroco.
?subject monu:Está_en_Provincia ?lugar.
}
service <https://javiermurcia.tech/fuseki/lugares-espana/> {
?lugar rdfs:label ?label
filter (lang (?label) = 'es')
}
}
GROUP BY
?monu ?lugar ?label
order by desc ( ?monu)
LIMIT 100Lenguaje del código: PHP (php)
Aquí estás contando, agrupando y ordenando resultados: SPARQL también sirve para analítica.
Ejecutar consulta en vivo: Abrir demo
5. CONSTRUCT: crear nuevo conocimiento
PREFIX monu: <http://www.monumentos.es/>
PREFIX lugares: <http://www.monumentos.es/lugares/>
CONSTRUCT {
?nomhisto lugares:LugHistóricoÉpoca ?epoca .
}
WHERE {
?a a monu:Sitio_de_interés ;
monu:Esta_situado_en ?b ;
monu:Monumento_tiene_epoca ?epoca .
SERVICE <https://javiermurcia.tech/fuseki/Lugares-Historicos> {
?b lugares:Tiene_nombre_histórico ?nomhisto .
}
}limit 20Lenguaje del código: HTML, XML (xml)
No estás consultando datos: estás creando nuevos triples.
Este tipo de consultas permite inferir conocimiento sin necesidad de motores OWL complejos.
6. Caso complejo: genealogías + DBpedia
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX fami: <http://www.relacionesfamiliares/>
SELECT ?a ?aLabel
(SAMPLE (?pic) AS ?aejempic) (SAMPLE (?pic2) AS ?segudoejempic)(SAMPLE (?pic3) AS ?primoejempic) (SAMPLE (?pic4) AS ?PrimotercerPic) ?ParejaPrimo ?ParejaPrimolabel ?EjemfechaNacia ?fechaNaciParjeaAfines
?ParejaPrimoSegundo ?ParejaPrimoSegundolabel ?ParejaPrimoTercero ?ParejaPrimoTercero ?ParejaPrimoTercerolabel
WHERE {
{
?c <http://purl.org/dc/terms/subject> <http://es.dbpedia.org/resource/Categoría:Casa_de_Austria>.
?c rdfs:label ?aLabel.
service <https://javiermurcia.tech/fuseki/personas-con-hijos/> {
?a rdfs:label ?aLabel
filter (lang (?aLabel) = 'es')
optional {
?a <http://www.wikidata.org/prop/direct/P18> ?pic
}
OPTIONAL {
?a <http://www.wikidata.org/prop/direct/P569> ?EjemfechaNacia.
}
?a fami:esParejaDe ?ParejaPrimo .
?a <http://www.relacionesfamiliares/esPrimoDe> ?ParejaPrimo.
?ParejaPrimo rdfs:label ?ParejaPrimolabel.
filter (lang (?ParejaPrimolabel) = 'es')
OPTIONAL {
?ParejaPrimo <http://www.wikidata.org/prop/direct/P569> ?fechaNaciParjeaAfines.
}
optional {
?ParejaPrimo <http://www.wikidata.org/prop/direct/P18> ?pic3
}
}
}
union {
?c <http://purl.org/dc/terms/subject> <http://es.dbpedia.org/resource/Categoría:Casa_de_Austria>.
?c rdfs:label ?aLabel.
service <https://javiermurcia.tech/fuseki/personas-con-hijos/> {
?a rdfs:label ?aLabel
filter (lang (?aLabel) = 'es')
optional {
?a <http://www.wikidata.org/prop/direct/P18> ?pic
}
OPTIONAL {
?a <http://www.wikidata.org/prop/direct/P569> ?EjemfechaNacia.
}
?a fami:esParejaDe ?ParejaPrimoSegundo .
?a <http://www.relacionesfamiliares/esPrimoSegundoDe> ?ParejaPrimoSegundo.
?ParejaPrimoSegundo rdfs:label ?ParejaPrimoSegundolabel.
filter (lang (?ParejaPrimoSegundolabel) = 'es')
OPTIONAL {
?ParejaPrimoSegundo <http://www.wikidata.org/prop/direct/P569> ?fechaNaciParjeaAfines.
}
optional {
?ParejaPrimoSegundo <http://www.wikidata.org/prop/direct/P18> ?pic2
}
}
}
union {
?c <http://purl.org/dc/terms/subject> <http://es.dbpedia.org/resource/Categoría:Casa_de_Austria>.
?c rdfs:label ?aLabel.
service <https://javiermurcia.tech/fuseki/personas-con-hijos/> {
?a rdfs:label ?aLabel
filter (lang (?aLabel) = 'es')
optional {
?a <http://www.wikidata.org/prop/direct/P18> ?pic
}
OPTIONAL {
?a <http://www.wikidata.org/prop/direct/P569> ?EjemfechaNacia.
}
?a fami:esParejaDe ?ParejaPrimoTercero .
?a <http://www.relacionesfamiliares/esPrimoTerceroDe> ?ParejaPrimoTercero.
?ParejaPrimoTercero rdfs:label ?ParejaPrimoTercerolabel.
filter (lang (?ParejaPrimoTercerolabel) = 'es')
OPTIONAL {
?ParejaPrimoTercero <http://www.wikidata.org/prop/direct/P569> ?fechaNaciParjeaAfines.
}
optional {
?ParejaPrimoTercero <http://www.wikidata.org/prop/direct/P18> ?pic4
}
}
}
}group by ?a ?aLabel ?aejempic ?segudoejempic ?primoejempic ?PrimotercerPic ?ParejaPrimo ?ParejaPrimolabel ?EjemfechaNacia ?fechaNaciParjeaAfines
?ParejaPrimoSegundo ?ParejaPrimoSegundolabel ?ParejaPrimoTercero ?ParejaPrimoTercerolabel
LIMIT 100
Lenguaje del código: PHP (php)
En este ejemplo combinamos:
- DBpedia (categorías Wikipedia)
- Un grafo propio de relaciones familiares
Detectamos relaciones como:
- Primos.
- Primos segundos.
- Primos terceros.
Esto demuestra integración semántica real entre datasets.
Ejecutar consulta en vivo: Abrir demo
7. Caso avanzado: análisis geoespacial + demografía
PREFIX monu: <http://www.monumentos.es/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix lugares: <http://www.monumentos.es/lugares/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?provincia ?provinciaLabel (COUNT (?hospital) as ?Hospitales) (SAMPLE (?Ejempoblación) AS ?población) ?resultado ?point
WHERE {
{
?provincia a lugares:Provincia;
rdfs:label ?provinciaLabel;
<http://www.wikidata.org/prop/direct/P625> ?point.
optional {
?provincia <http://www.wikidata.org/prop/direct/P1082> ?Ejempoblación
}
filter(lang (?provinciaLabel) = 'es')
?hospital a lugares:Hospital;
<http://www.wikidata.org/prop/direct/P131> ?municipio.
?municipio lugares:Contenida_en_entidades_mayores ?provincia.
optional {
?provincia lugares:Contenida_en_entidades_mayores ?CCAA.
}
optional {
?CCAA <http://www.wikidata.org/prop/direct/P1082> ?Ejempoblación
}
}
union {
?provincia a lugares:Provincia;
rdfs:label ?provinciaLabel;
<http://www.wikidata.org/prop/direct/P625> ?point.
optional {
?provincia <http://www.wikidata.org/prop/direct/P1082> ?Ejempoblación
}
filter(lang (?provinciaLabel) = 'es')
?hospital a lugares:Hospital;
<http://www.wikidata.org/prop/direct/P131> ?algo.
?algo lugares:Está_contenido_en_Municipio ?municipio.
?municipio lugares:Esta_contenido_en_Provincia ?provincia.
optional {
?provincia lugares:Contenida_en_entidades_mayores ?CCAA.
}
optional {
?CCAA <http://www.wikidata.org/prop/direct/P1082> ?Ejempoblación
}
}
union {
?provincia a lugares:Provincia;
rdfs:label
?provinciaLabel;
<http://www.wikidata.org/prop/direct/P625> ?point.
optional {
?provincia <http://www.wikidata.org/prop/direct/P1082> ?Ejempoblación
}
filter(lang (?provinciaLabel) = 'es')
?hospital a lugares:Hospital;
<http://www.wikidata.org/prop/direct/P131> ?provincia.
}
bind ((?Ejempoblación/100000) as ?resultado)
}group by ?provincia ?provinciaLabel ?Hospitales ?población ?resultado ?point
LIMIT 100Lenguaje del código: PHP (php)
Calculamos ratios de hospitales por población usando datos geográficos y demográficos.
Esto es analítica avanzada sobre grafos.
Ejecutar consulta en vivo: Abrir demo
Conclusión
SPARQL no es solo un lenguaje de consulta:
- Análisis de grafos.
- Integración de datos.
- Construcción de conocimiento.
- Analítica avanzada.
Su valor real aparece con casos reales, no ejemplos teóricos.
Sobre mí
Trabajo en el desarrollo de grafos de conocimiento, integración semántica y análisis de datos RDF.
Puedes explorar mis demos y sus interfaces de descubrimiento aquí: Ver demos SPARQL

Deja una respuesta