SPARQL explicado con ejemplos reales (y ejecutables)

SPARQL explicado con ejemplos

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

Comentarios

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *