Wikidata:Tutorial SPARQL

This page is a translated version of the page Wikidata:SPARQL tutorial and the translation is 96% complete.
Outdated translations are marked like this.

WDQS, Wikidata Query Service, é uma ferramenta poderosa para proporcionar informação sobre o conteúdo do Wikidata. Esta guia lhe ensinará como usá-la.

Before writing your own SPARQL query, look at {{Item documentation}} or any other generic SPARQL query template and see if your query is already included.

Antes de começar

Esse guia pode parecer longo e intimidar, mas não se assuste! Só o básico de SPARQL já é suficiente para fazer muita coisa - mesmo que a única coisa que voce leia seja #Our first query voce já vai conseguir construir diversas buscas interessantes. Cada seção do tutorial vai te empoderar para escrever buscas cada vez mais poderosas.

Se voce nunca ouviu falar de Wikidata, SPARQL, ou WDQS antes, aqui vai uma explicação breve desses termos:

O básico de SPARQL

Uma busca SPARQL simples se parece com isso:

SELECT ?a ?b ?c
WHERE
{
  x y ?a.
  m n ?b.
  ?b f ?c.
}

A parte SELECT lista as variáveis que voce quer que sejam retornadas na busca (as variáveis começam com um ponto de interrogação), e o WHERE contém as restrições da busca, representadas em triplas (declarações com sujeito, propriedade e valor). Toda a informação no Wikidata (e bases similares) é guardada na forma de triplas; quando a busca é feita, o WDQS vai (tentar) preencher as variáveis com valores reais que estejam nas triplas da base. O programa retorna, então, um resultado para cada combinação que ele encontra.

Uma tripla pode ser lida como uma frase (e por isso termina com um ponto) com um "sujeito", um "predicado" e um "objeto".

SELECT ?fruta
WHERE
{
  ?fruta temCor amarelo.
 ?fruta temGosto azedo.
}

Os resultados dessa busca podem incluir, por exemplo, "Limão amarelo". Em Wikidata, a maioria das propriedades são do tipo "tem", então a busca pode ser escrita mais como:

SELECT ?fruta
WHERE
{
  ?fruta cor amarelo.
 ?fruta gosto azedo.
}

que le-se “?fruta tem cor ‘amarelo’” (não?fruta é da" cor' ‘amarelo’” – guarde essa ideia para pares de propriedade como “pai”/“filho”!).

Contudo, esse não é um bom exemplo pro WDQR. Gosto é subjetivo, então wikidata não tem uma propriedade para isso. Em vez disso, vamos pensar em relações de pai/filho, que não tem tanta ambiguidade.

Nossa primeira busca

Suponha que queremos listar toda a prole do compositor barroco Johann Sebastian Bach. Usando pseudo elementos como nos exemplos acima, como voce escreveria essa busca?

Talvez voce tenha feito algo assim:

SELECT ?criança
WHERE
{
  #  ?criança "tem progenitor" Bach
  ?criança progenitor Bach.
  # (note: tudo após um 'H' é um comentário que é ignorado pelo WDQS.)
}

ou assim,

SELECT ?criança
WHERE
{
  # ?criança "tem pai" Bach 
  ?criança pai Bach. 
}

ou assim,

SELECT ?criança
WHERE
{
  #  Bach "tem filho" criança
  Bach filho ?criança
}

As primeiras duas triplas falam que a ?criança deve ter como progenitor/pai o Bach, a terceira fala que Bach deve ter o filho ?criança. Vamos usar a segunda opção por agora.

Bem, o que precisamos fazer para converter isso numa busca do WDQS? No Wikidata, items e propriedades não são identificadas por termos de línguas humanas como "pai" (propriedade) ou "Bach" (item). (Há uma boa razão: “Johann Sebastian Bach” também é o nome de um Pintor alemão, e “Bach” pode também se referir ao sobrenome em si, a comuna francesa, a cratera de Mercúrio, etc.)

Em vez disso, itens e propriedades no Wikidata recebem um identificador. Para encontrar o identificador para um item, fazemos uma busca pelo item e copiamos o Q-id do resultado que parece ser o que estamos procurando (pela descrição, por exemplo). Para encontrar o identificador para um propriedade, fazemos o mesmo, mas buscando por "P:termo para busca" em vez de "termo para busca". Fazendo isso, descobrimos que o identificador para o famoso compositor Johann Sebastian Bach é Q1339, e a propriedade para indica o pai de algum item é P:P22.

E por fim, precisamos adicionar prefixos. Para triplas WDQS simples, cada item é prefixado com wd:, and properties with wdt:. (mas isso se aplica apenas a valores fixos, variáveis não ganham um prefixo!)

Juntando tudo, chegamos ao código para nossa primeira busca WDQS propriamente dita:

SELECT ?criança
WHERE
{
# ?criança pai Bach.
  ?criança wdt:P22 wd:Q1339.
}
Try it!

Clique no link dizendo "Try it" e depois clique em "Run" na página do WDQS. O que voce achou?

criança
wd:Q57225
wd:Q76428

Hm, isso foi triste. Voce apenas viu os identificadores. Voce pode clicar neles para ver as páginas (com rótulos que humanos podem ler), mas não há algum jeito melhor de ver os resultados?

Bem, tem, claro! (Perguntas retóricas não são o máximo?) Voce precisa incluir o texto mágico em ingles

SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }

dentro de WHERE, voce ganha variáveir adicionais: para cada variável ?foo in your query, you now also have a variable fooLabel , que contém o rótulo (em ingles, "label") do item recuperado por ?foo . Se voce adicionar isso ao SELECT , voce ganha o item e também seu rótulo:

SELECT ?criança ?criançaLabel
WHERE
{
# ?criança pai Bach
  ?criança wdt:P22 wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Tente rodar essa busca – voce deve ver não somente os números, mas também os nomes de várias crianças.

criança criançaLabel
wd:Q57225 Johann Christoph Friedrich Bach
wd:Q76428 Carl Philipp Emanuel Bach

Autocompletar

Esse código em SERVICE parece ser difícil pra ficar lembrando, né? E buscar por identificadores o tempo todo é também um tanto tedioso. Para nossa sorte, o WDQS oferece uma solução excelente: o "autocompletar". No query.wikidata.org editor de texto, voce pode apertar Ctrl+Space (ou Alt+Enter ou Ctrl+Alt+Enter) em qualquer ponto da busca e obter sugestões para qual código pode ser apropriado. Selecione a sugestão apropriada com as setas para cima / para baixo e aperte Enter para selecionar.

Por exemplo, em vez de escrever SERVICE wikibase:label { bd:serviceParam wikibase:language "en".} every time, you can just type SERV , hit Ctrl+Space, e a primeira sugestão que aparece vai fazer a mágica dos rótulos, pronta pra uso!. Aperte Enter para aceitar. (A formatação vai ser um pouco diferente, mas não importa.)

A função de autocompletar pode também fazer buscas. Após digitar um prefixo como wd: ou wdt:, voce pode começar a digitar o termo de interesse e apertar Ctrl+Espaço para ver algumas sugestõef. wd: busca por itens, wdt: busca propriedades. Por exemplo, em vez de buscar no Wikidata por Johann Sebastian Bach (Q1339) e father (P22), é possível digitar wd:Bach e wdt:fath e selecionar a entidade certa pelo auto-completador. (Isso funciona até com espaços no texto, por exemplo: wd:Johann Sebastian Bach.)

Padrões avançados de triplas

Então agora já vimos todos os filhos de Johann Sebastian Bach - mais precisamente: todos os itens com o pai Johann Sebastian Bach. Mas o Bach teve duas esposas, e então esses itens tem 2 mães diferent]es. E se quiremos identificar apenas os filhos de Johann Sebastian Bach com sua primeira mulher, Maria Barbara Bach (Q57487)? Tente escrever essa busca com base na busca de cima.

Fez isso? Beleza, então vamos para a solução! O jeito mais simples de fazer isso é adicionar uma sugunda tripla com essa restrição.

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339.
  ?criança wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Em língua humana, temos:

criança tem como pai Johann Sebastian Bach.

criança tem como mãe Maria Barbara Bach.

Isso soa meio estranho, né? Normalmente, abreviaríamos isso:

A criança tem como pai Johann Sebastian Bach e como mãe Maria Barbabara Bach.

Na verdade, podemos expressar isso em SPARQL de forma parecida: terminando uma tripla com um semicolon (;) em vez de um ponto final, dá para adicionar outro par predicado-objeto. Isso permite uma abreviação da busca acima para:

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

que fornece os mesmos resultados, mas a busca em si é menor repetitiva.

Agora, suponha que, desses resultado, estivéssemos interessados apenas nos filhos que eram também compositores e pianistas. As propriedades e itens relevantes são occupation (P106), composer (Q36834) e Malaya Medyanka (Q486738). Tente atualizar a busca acima para adicionar essas restrições também!

Aqui a minha solução:

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834;
         wdt:P106 wd:Q486748.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Ela usa a abreviação com ; mais duas vezes para adicionar as duas ocupações. Dá para notar que ainda tem um pouco de repetição. É como se disséssemos:

A criança tem como ocupação compositor e como ocupação pianista.

que normalmente abreviaríamos como:

Criança tem como ocupação compositora e pianista.

E o SPARQL tem algumas sintaxes para isso também: do mesmo jeito que o ; te permite adicionar um novo par predicado-objeto (reutilizando o sujeito), um , te permite adicionar outro objeto (reutilizando tando o sujeito quanto o predicado). Com isso, a busca pode ser abreviada para:

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834,
                  wd:Q486748.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Note: indentação e espaços adicionais não importam pro computador, eles só deixam o código mais legível para humanos. Também dá para escrever isso como:

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339;
         wdt:P25 wd:Q57487;
         wdt:P106 wd:Q36834, wd:Q486748.
  # com ambas ocupações em uma linha
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

ou, de forma menos legível:

SELECT ?criança ?criançaLabel
WHERE
{
  ?criança wdt:P22 wd:Q1339;
  wdt:P25 wd:Q57487;
  wdt:P106 wd:Q36834,
  wd:Q486748.
  # sem indentação fica difícil distinguir entre ; e ,
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Para nossa alegria, o editor do WDQS indenta as linhas automaticamente, então normalmente não precisamos nos preocupar com isso.

Beleza, vamos resumir. Vimos que as buscas são estruturadas como texto. Cada tripla sobre um sujeito termina com um ponto final. Predicados múltiplos sobre o mesmo sujeito são separados por ponto-e-vírgula, e múltiplos objetos podem ser separados por vírgulas.

SELECT ?s1 ?s2 ?s3
WHERE
{
  ?s1 p1 o1;
      p2 o2;
      p3 o31, o32, o33.
  ?s2 p4 o41, o42.
  ?s3 p5 o5;
      p6 o6.
}

Agora eu gostaria de apresentar mais um atalho do SPARQL. Mais um cenário hipotético aqui:

Suponha que a gente não ligue para os filhos de Bach. (Como bem pode ser o caso!) Estamos interessados em seus netos, na verdade. Há uma complicação aqui: um neto pode ter relacionado a Bach tanto por sua mãe quanto por seu pai. Isso são duas propriedades diferentes, o que é inconveniente. Em vez disso, vamos inverter a perspectiva: há também a propriedade P:P40 no Wikidata, que aponta de um pai/mãe para seus filhos e filhas e é independente de generos. Com essa informação, será que consegues escrever uma busca para encontrar os netos e netas de Bach?

Aqui a minha solução:

SELECT ?neto ?netoLabel
WHERE
{
  wd:Q1339 wdt:P40 ?criança.
  ?criança wdt:P40 ?neto.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Em linguagem natural, isso fica:

Bach tem uma criança ?crianca .

?criança tem uma criança ?neto

Uma vez mais, vou usar os atalhos em linguagem natural, e quero te mostrar como fazer em SPARQL. Observe como não ligamos para os filhos de Bach de fato, só usamos a variável para falar dos netos. Poderíamos abreviar a frase para:

Bach tem um filho alguém que tem um filho ?neto

Em vez de falar quem é a criança de "Bach", falamos simplesmente "alguém": nós não ligamos quem seja. Mas podemos nos referir a elas como "alguém que": isso começa uma cláusula relativa, e dentro dessa cláusula podemos dizer coisas sobre esse "alguém" (ex: que esse alguém "tem criança ?neto "). De certa forma, "alguém" é uma variável, mas de um tipo especial, que só é válido nessa cláusula relativa, e que não é referenciada de forma explícita.

Em SPARQL, podedos escrever isso como:

SELECT ?neto ?netoLabel
WHERE
{
  wd:Q1339 wdt:P40 [ wdt:P40 ?neto ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Podemos usar parenteses quadrados ([]) no lugar de uma variável, que eles agem como uma variável anonima. Dentro dos parentese, podemos especificar pares de predicados-objetos, da mesma forma que fazemos após um ; em uma tripla normal; o sujeito implícito neste caso é a variável anonima que os parenteses representam. (Note: da mesma forma que depois de um  ;</code, voce pode adicionar mais pares predicado-objeto com mais semivirgulas ou mais objetos para o mesmo predicado com vírgulas.)

E é isso é tudo sobre padrões de triplas! Há (bem) mais no mundo do SPARQL, mas como estamos prestes a deixar as partes que tem ligação forte com linguagem natural, eu gostaria de fazer um resumo das relações:

linguagem natural exemplo SPARQL exemplo
frase Julieta ama Romeu. ponto final julieta ama romeu.
conjunção (cláusula) Romeu ama Julieta e mata a si mesmo. ponto e vírgula romeu ama julieta; mata romeu.
conjunção (nome) Romeu mata Tybalt e a si mesmo. vírgula romeu mata tybalt, romeu.
cláusula relativa Julieta ama alguém que mata Tybalt. Colchetes julieta ama [ mata tybalt ].

Instancias e classes

Antes, eu disse que a maioria das propriedades aqui no Wikidata são propriedades de "ter": "tem" filho, "tem" pai, "tem" como ocupação. Mas em alguns casos (na real, frequentemente), voce vai precisar falar de algo que "é". Mas há, de fato, dois tipos de relação aí:

  • "E o Vento Levou" é um filme.
  • Um filme é um trabalho de arte.

"E o Vento Levou" é um filme em particular. Tem um diretor específico (Victor Fleming), uma duração específica (238 minutos), uma lista de atores (Clark Gable, Vivien Leigh,...) e por aí vai.

"Filme" é um conceito mais geral. Filmes podem ter diretores, durações e elenco, mas o conceito "filme" como tal não tem nenhum diretor, duração ou elenco, Apesar de um filme ser um trabalho de arte, e trabalhos de arte normalmente terem criadores, o conceito de "filme" em si não tem um criador - somente "instancias" específicas desse conceito tem.

A diferença é a razão por que há duas propriedades para a noção de "é" no Wikidata: instance of (P31) e subclass of (P279). "E o Vento Levou" é uma instancia particular da classe "filme"; a classe "filme" é uma subclasse (classe mais específica; especialização) da classe mais geral "trabalho de arte".

Para ajudar a entender a diferença, voce pode tentar usar dois verbos diferentes: "é um" e "é um tipo de". Se "é um tipo de" cabe na frase (ex: um filme "é um tipo de" trabalho de arte), é uma indicação que há uma relação de subclasse, uma especialização de uma classe superior e que voce deveria utiliza subclass of (P279). Se "é um tipo de" não funciona direito (ex: "E o Vento Levou" "é um tipo de" filme não faz sentido), é provável que voce esteja falando de uma instancia particular, e então é melhor usar instance of (P31).

Então, o que isso significa para nós enquanto escrevemos buscas SPARQL? Bem, se quisermos buscar "todos os trabalhos de arte", não basta buscar todos os itens que são instancias diretas de "trabalho de arte":

SELECT ?trabalho ?trabalhoLabel
WHERE
{
  ?trabalho wdt:P31 wd:Q838948. # instancia de  trabalho de arte
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

No momento de agora, essa busca só retorna 2815 resultados - claro, há muito mais trabalhos de arte que isso! O problema é que essa busca não está vendo itens como "E o Vento Levou", que é apenas uma instancia de "filme", não de "trabalho de arte". "filme" é uma subclasse de "trabalho de arte", mas nós precisamos falar pro SPARQL levar isso em consideração enquanto fazemos buscas.

Uma solução para isso é a sintaxe [] que falamos antes: "E o Vento Levou" é uma instancia de alguma subclasse de "trabalho de arte". (Para exercitar, tente escrever essa busca!) Mas essa abordagem ainda tem problemas:

  1. Não estamos mais incluindo os items que são instancias diretas de trabalho de arte.
  2. Ainda faltam itens que são subclasses de alguma outra subclasse de "trabalho de arte" - por exemplo, "A Branca de Neve e os Sete Anões" é um filme animado, que é um filme, que é um trabalho de arte. Nesse caso, precisamos seguir 2 declarações de "subclasse de" - mas podem ser necessárias 3, 4, 5 ou qualquer número, na verdade.

A solução: ?item wdt:P31/wdt:P279* ?classe . Isso signiva que há uma "instancia de" e, então, qualquer número de declarações de "subclasse de" entre o item e a classe.

SELECT ?trabalho ?trabalhoLabel
WHERE
{
  ?trabalho wdt:P31/wdt:P279* wd:Q838948. # instancia de qualquer subclasse de um trabalho de arte
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

(Não recomendo que rode essa busca. O WDQS consegue lidar com ela (por pouco), mas seu browser pode travar na hora de mostrar o resultado, pois são muitos)

Agora voce sabe como buscar por todos os trabalhos de arte, ou todos os prédios, ou todas as doenças: a encantação wdt:P31/wdt:P279* , além da classe apropriada.Isso usa alguns detalhes de SPARQL que não expliquei ainda, mas esse é praticamente o único uso relevante dessas coisas então não é necessário ter um conhecimento profundo disso para usar o WDQS.

Caminhos de propriedades

Or caminhos de propriedades são um jeito conciso de escrever um caminho de propriedades entre dois items. O caminho mais simples é só uma propriedade, que forma uma tripla comum:

?item wdt:P31 ?class.

É possível adicionar caminhos um uma barra (/).

?item wdt:P31/wdt:P279/wdt:P279 ?class.

Isso é o equivalete a qualquer um dos seguintes:

?item wdt:P31 ?temp1.
?temp1 wdt:P279 ?temp2.
?temp2 wdt:P279 ?class.
?item wdt:P31 [ wdt:P279 [ wdt:P279 ?class ] ].

Exercício: reescreva a busca dos "netos de Bach" que fizemos antes usando essa sintaxe.

Um asterisco (*) depois de um caminho significa "zero" ou mais desse elemento".

?item wdt:P31/wdt:P279* ?class.
# means:
?item wdt:P31 ?class
# or
?item wdt:P31/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279 ?class
# or
?item wdt:P31/wdt:P279/wdt:P279/wdt:P279 ?class
# or ...

Caso não haja outros elementos no caminho, ?a algo* ?b significa que ?b pode também simplesmente ser ?a diretamente,sem caminho entre eles.

Um mais (+) é parecido com um asterisco, mas significa "um ou mais desse elemento". A busca a seguir encontra todos os descendentes de Bach:

SELECT ?descendente ?descendenteLabel
WHERE
{
  wd:Q1339 wdt:P40+ ?descendente.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Se nós usássemos um asterisco em vez de um mais aqui, a busca incluiria também o Bach.

Um ponto de interrogação (?) é semelhante a um asterisco ou um mais, mas significa "zero ou um desse elemento".

Voce pode separar elementos de cainho com uma barra vertical ("|) em vez de uma barra; ele significa "somente uma": o caminho pode usar uma das duas propriedades. (Mas não de forma combinada, esse tipo de caminho sempre faz um padrão de tamanho 1)

Voce pode também agrupar os elementos de caminho com parenteses (()), e combinar livremente todos ess elementos (/|*+?). Isso significa que um outro jeito de achar todos os descendentes de Bach:

SELECT ?descendente ?descendenteLabel
WHERE
{
  ?descendente (wdt:P22|wdt:P25)+ wd:Q1339.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Em vez de usar uma propriedade "tem filho" para ir do Bach para seus descendentes, poderíamos usar as propriedades "pai" e "mãe"para ir dos descendentes até Bach. O caminho pode incluir duas mães e um pai, ou quatro pais, ou pai-mãe-mãe-pai, ou qualquer outra combinação. (Apesar disso, Bach não pode ser a mãe de alguém, então o último elemento sera sempre pai.)

Qualificadores

(Primeiro, boas notícias: essa seção não introduz nenhuma sintaxe SPARQL nova - eba! Respire e relaxe, isso deve ser moleza. Certo?)

Até então, só falamos sobre declarações simples: sujeito, propriedade, objeto. Mas declarações no Wikidata são mais que isso: eles podem também ter qualificadores e referencias. Por exemplo, a Mona Lisa (Q12418) tem 3 declarações made from material (P186):

  1. oil paint (Q296955), o material principal;
  2. poplar wood (Q291034), com o qualificador applies to part (P518)painting support (Q861259) - esse é o material "sobre" o qual a Mona Lisa foi pintada; e
  3. wood (Q287), com os qualificadores Betta edithae (Q518)P1737943{{{3}}} e start time (P580) 1951 - essa é uma parte que foi adicionada ao quadro depois.

Imagine que queiramos achar todas as pinturas com suas superfícies de pintura, ou sejam as declarações made from material (P186) com um qualificador applies to part (P518)painting support (Q861259). Como fazemos isso? Isso é mais informação do que cabe em uma tripla individual.

A resposta é: mais triplas! (Regra de ouro: a solução do Wikidata para quase tudo é "mais itens" e a regra correspondente do WDQS é "mais triplas". Referencias, precisão numérica, valores com unidades, geocoordenadas, etc., tudo que estamos pulando também funcionam desse jeito.) Até então, usamos o prefixo wdt: para nossas triplas, que aponta diretamente ao objeto da declaração. Mas há um outro prefixo: p: , que aponta não ao objeto, mas ao "nó da declaração". Esse nó é, então, o sujeito de outras triplas: o prefixo ps: (p de propriedade e s de "statement", declaração em ingles) aponta para o objeto da declaração, o prefixo pq: (q de qualificador) aponta para os qualificadores, e prov:wasDerivedFrom que aponta para nós de referencias, que vamos ignorar por agora).

Isso foi bastante abstrato. Vamos para um exemplo concreto da Mona Lisa:

wd:Q12418 p:P186 ?statement1.    # Mona Lisa: material used: ?statement1
?statement1 ps:P186 wd:Q296955.  # value: oil paint

wd:Q12418 p:P186 ?statement2.    # Mona Lisa: material used: ?statement2
?statement2 ps:P186 wd:Q291034.  # value: poplar wood
?statement2 pq:P518 wd:Q861259.  # qualifier: applies to part: painting surface

wd:Q12418 p:P186 ?statement3.    # Mona Lisa: material used: ?statement3
?statement3 ps:P186 wd:Q287.     # value: wood
?statement3 pq:P518 wd:Q1737943. # qualifier: applies to part: stretcher bar
?statement3 pq:P580 1951.        # qualifier: start time: 1951 (pseudo-syntax)

Podemos abreviar isso bastante com a sintaxe [] , substituindo as variáveis ?statement :

wd:Q12418 p:P186 [ ps:P186 wd:Q296955 ].

wd:Q12418 p:P186 [
            ps:P186 wd:Q291034;
            pq:P518 wd:Q861259
          ].

wd:Q12418 p:P186 [
            ps:P186 wd:Q287;
            pq:P518 wd:Q1737943;
            pq:P580 1951
          ].

Será que voce consegue usar esse aprendizado para escrever uma busca por todas as pinturas com suas superfícies de pintura?

Aqui a minha solução:

SELECT ?pintura ?pinturaLabel ?material ?materialLabel
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Primeiro, limitamos ?pintura a todas as instancias de painting (Q3305213) ou uma subclasse disso. Então, extraímos o material do nó de declaração p:P186 limitando as declarações a somente aquelas que tem um qualificador applies to part (P518)painting support (Q861259).

ORDER e LIMIT

Voltamos ao nosso programa regular com de sintaxe de SPARQL.

Até então, nossas buscas foram voltadas a todos os resultados. Mas é comum que queiramos apenas uma parte dos resultados: aqueles que são mais extremos em alguma forma - mais velhos, mais novos, primeiros, últimos, maior população, ponto de fusão mais baixo, mais filhos, mais materiais usados, e por aí vai. O fator comum é que os resultados são ordenados de alguma forma, e, então vamos nos importar apenas com os primeiros (os com melhor "ranking")

Isso é controlado por duas cláusulas, que são adicionadas ao bloco WHERE {} (após as chaves, não dentro!): ORDER BY e LIMIT/

ORDER BY algo ordena os resultados por algo.algo pode ser qualquer expressão - por agora, o único tipo de expressão que conhecemos são variáveis simples (?algo), mas vamos ver alguns outros tipos depois. Essa expressão pode ser embrulhada em tanto ASC() ou DESC() para especificar a orientação do ordenamento (ASCendente ou DESCendente). (Se nenhum for especificado, o padrão é o ordenamento ascendente, então ASC(algo) é equivalente a apenas algo.)

LIMIT contagem cuts off the result list até a contagem, onced contagem é qualquer número natural. Por exemplo, LIMIT 10 limita a busca aos dez primeiros resultados. LIMIT 1 retorna somente um resultado.

(Voce também pode usar LIMIT sem o ORDER BY. Nesse caso, os resultados não são ordenados, então não há garantia de qual resultado virá. Não há problema, caso voce queira apenas alguns resultados certos, e não importa muito quais. Em ambos os casos, adicionar o LIMIT pode acelerar bastante a busca, já que o WDQS pode parar de buscar assim que encontrar resultados suficientes para atingir o limite.)

Hora de praticar! Tente escrever uma busca que retorne os dez países mais populosos. (Um país é um sovereign state (Q3624078), e a propriedade para a população é P:P1082.) Voce pode começar procurando países e suas populações, aí adicionar as cláusulas ORDER BY e LIMIT.

Aqui a minha solução:

SELECT ?pais ?paisLabel ?populaçao
WHERE
{
  ?pais wdt:P31/wdt:P279* wd:Q3624078;
           wdt:P1082 ?populacao.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 10
Try it!

Note que se quiséssemos os países mais populosos, nós teríamos que ordenar por população "descendente", de forma que os primeiros resultados serão aqueles com os maiores valores.

Exercício

Nós fizemos bastante até agora - acho que está na hora de alguns exercícios. (Pode pular essa seção se estiver com pressa.)

Livros de Arthur Conan Doyle

Escreva uma busca que retorna todos os livros de Sir Arthur Conan Doyle.

Elementos químicos

Escreva uma busca que retorna todos os elementos químicos com seus símbolos e números atomicos, por ordem de número atomico.

Rios que desaguam no Rio Mississippi

Escreva uma busca que retorne todos os rios que fluem diretamente no Rio Mississippi. (O maior desafio é achar a propriedade correta...)

Rios que desaguam no Mississippi II

Escreva uma busca que retorne todos os rios que fluem no Rio Mississippi, direta ou indiretamente.

OPTIONAL

Nos exercícios acima, tivemos uma busca por todos os buscos de Sir Arthur Conan Doyle.

SELECT ?book ?bookLabel
WHERE
{
  ?book wdt:P50 wd:Q35610.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Mas isso é um pouco chato. Tem tantos dados sobre livros e só mostramos o rótulo? Vamos fazer uma busca que também incluam title (P1476), illustrator (P110), publisher (P123) e publication date (P577).

Uma primeira tentativa pode ser assim:

SELECT ?livro ?titulo ?ilustradorLabel ?editoraLabel ?publicado
WHERE
{
  ?livro wdt:P50 wd:Q35610;
        wdt:P1476 ?titulo;
        wdt:P110 ?ilustrador;
        wdt:P123 ?editora;
        wdt:P577 ?publicado.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Rode essa busca. Agora, enquanto escrevo, eu encontro só 2 resultados - um pouco ralo! Por que é isso? Nós encontramos mais de 100 livros antes!

A razão é que para um resultado ser recuperado, um item em potencial (um livro) deve ter todas as triplas que listamos: ele precisa ter um título, e um ilustrador, e uma editora, e uma data de publicação. Se ele tiver algumas dessas propriedades, mas não todas, não vai ser recuperado. E não é isso que queremos agora: nós queremos todos os livros, e se mais dados estiverem disponíveis, queremos também, mas não queremos restringir nossa lista de resultados.

A solução é dizer para o WDQS que essas triplas são opcionais:

SELECT ?livro ?titulo ?ilustradorLabel ?editoraLabel ?publicado
WHERE
{
  ?livro wdt:P50 wd:Q35610.
  OPTIONAL { ?livro wdt:P1476 ?titulo. }
  OPTIONAL { ?livro wdt:P110 ?ilustrador. }
  OPTIONAL { ?livro wdt:P123 ?editora. }
  OPTIONAL { ?livro wdt:P577 ?publicado. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

Isso nos dá as variáveis adicionais(?titulo, ?publisher etc.) caso a declaração exista, mas caso não exista, o resultado não é descartado - a variável simplesmente não é adicionada.

Nota: é muito importante usar cláusulas OPTIONAL de forma independente. Se colocar todas as triplas dentro de uma cláusula única, como aqui -

SELECT ?book ?title ?illustratorLabel ?publisherLabel ?published
WHERE
{
  ?book wdt:P50 wd:Q35610.
  OPTIONAL {
    ?book wdt:P1476 ?title;
          wdt:P110 ?illustrator;
          wdt:P123 ?publisher;
          wdt:P577 ?published.
  }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
Try it!

- voce vai notar que a maioria dos resultados não mostra nenhuma informação extra. Isso é por que uma cláusula opcional com múltiplas triplas só vai retornar um resultado se todas as triplas forem encontradas. Mas se um livro tiver, por exemplo, um título, mas não tiver um ilustrador, a cláusula não retorna resultado. Apesar do resultado não ser descartado, as quatro variáveis ficam sem valor.

Expressões, FILTER e BIND

Essa seção pode parecer um pouco menos organizada que as outras, pois ela cobre um tema bem amplo e diverso. O conceito básico é que nós queremos fazer algo com os valores que, até agora, só selecionamos e retornamos de forma indiscriminada. E "expressões" são o jeito de expressar operações nesses valores. Há muitos tipos de expressões, e muitas coisas que podemos fazer com elas, mas vamos começar com o básico: tipos de dados:

Tipo de dados

Cada valor de SPARQL tem um tipo, que te fala que classe de valor ele é, e o que pode fazer com isso. Os tipos mais importantes são:

  • item, like wd:Q42 para Douglas Adams (Q42).
  • boolean, com dois valores possíveis: true(verdadeiro) e false(falso). Valores booleanos não são guardados em decalarações, mas muitas expressões retornam um valor booleano, 2 < 3 ou "a" = "b" (false).
  • string, um pedaço de texto. "Strings" são valores literais, escritos em meio a aspas duplas.
  • texto monolingual, uma "string" com uma etiqueta de linguagem anexada. Em um literal, é possível adicionar uma etiqueta de linguagem depois da "string" usando o símbolo de @, por exemplo: "Douglas Adams"@en.
  • números, tanto inteiros (1) ou decimais (1.23).
  • datas. Literais de datas podem ser escritos adicionando ^^xsd:dateTime (sensível a capitalização - "^^xsd:datetime não vai funcionar!) a uma "string" de data no formato ISO 8061: "2012-10-29"^^xsd:dateTime.

Operadores

Os operadores matemáticos familiares estão disponíveis: +, -, *, / para adicionar, subtrair, multiplicar ou dividir números, e <, >, =, <=, >= para compará-los. O teste de inequalidade ≠ é escrito !=. As comparações também funcionam para outros tipos de dados; por exemplo, "abc" < "abd" é verdadeiro (comparação lexical),assim como "2016-01-01"^^xsd:dateTime > "2015-12-31"^^xsd:dateTime e wd:Q4653 != wd:Q283111. E condições booleanas podem ser combinadas com && ("e" lógico: a && b é verdadeiro se ambos a e b são verdadeiros) e || ("ou" lógico: a || b é verdadeiro se algum (ou ambos) a ou b for verdadeiro.

FILTER

  Info Para uma alternativa que é algumas vezes mais rápida que o FILTER, voce pode olhar também para a cláusula de MINUS, veja exemplo.

FILTER(condição). é uma cláusula que pode ser inserida na busca SPARQL para, bem, filtrar os resultados. Dentro dos parenteses voce pode colocar qualquer expressão do tipo booleano, e apenas os resultados para os quais a expressão retorna verdadeiro são utilizados.

Por exemplo, para pegar uma lista de todos os humanos nascidos em 2015, primeiro vamos listar todos os humanos com suas datas de nascimento -

SELECT ?person ?personLabel ?dob
WHERE
{
  ?person wdt:P31 wd:Q5;
          wdt:P569 ?dob.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". } 
}

- e aí filtrar por retornar só os resultados nos quais o ano da data de nascimento é 2015. Só há dois jeitos de fazer isso: extrair o ano da data com a função YEAR e testando se o ano é de fato 2015 -

FILTER(YEAR(?dob) = 2015).

- ou verificando que a data é entre primeiro de Janeiro de 2015 (incluindo esse dia) e primeiro de Janeiro de 2016 (excluindo esse dia):

FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).

Eu diria que o primeiro é mais direto, mas acontece que a segunda opção é muito mais rápida, então vamos usá-la:

SELECT ?pessoa ?pessoaLabel ?anoDeNascimento
WHERE
{
  ?pessoa wdt:P31 wd:Q5;
          wdt:P569 ?dataDeNascimento.
  FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dataDeNascimento < "2016-01-01"^^xsd:dateTime).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". } 
}
Try it!

Outra possibilidade de uso de FILTER é relacionado aos rótulos. O serviço de rótulos ("labels"), via SERVICE , é muito útil caso só queira mostrar o rótulo de uma variável. Mas caso queira fazer algo com o rótulo - por exemplo: verificar se ele começã com "Mr." - voce vai ver que ele não funciona:

SELECT ?human ?humanLabel
WHERE
{
  ?human wdt:P31 wd:Q15632617.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
  #This FILTER does not work!
  FILTER(STRSTARTS(?humanLabel, "Mr. ")).
}
Try it!

Essa busca encontra todas as instancias de fictional human (Q15632617) e testa se o rótulo começa com "Mr." (STRSTARTS é uma abreviação de "string starts with" (ingles para "string começa com"); também tem STRENDS para o fim da "string" e CONTAINS). A razão por que isso não funciona é que o serviço de rótulo adiciona as variáveis tardiamente durante o processamento da busca; no momento que a gente tenta filtrar ?humanoLabel, o serviço ainda não criou essa variável, ela ainda não existe.

Felizmente, o serviço de rótulos não é o único jeito de recuperar o rótulo de um item. Os rótulos também são salvos como triplas comuns usando o predicado rdfs:label. Claro, isso significa todos rótulos, não apenas os de língua inglesa! Se quisermos apenas os rótulos na língua inglesa, nós teremos que fitrar pela linguagem do rótulo:

FILTER(LANG(?label) = "en").

A função LANG retorna a linguagem de uma "string" monolingual, e aqui selecionadmos apenas os rótulos na língua inglesa. A busca completa é:

SELECT ?humano ?rotulo
WHERE
{
  ?humano wdt:P31 wd:Q15632617;
         rdfs:label ?rotulo.
  FILTER(LANG(?label) = "[AUTO_LANGUAGE]").
  FILTER(STRSTARTS(?label, "Mr. ")).
}
Try it!

Nós pegamos o rótulo com ?humano rdfs:label ?rotulo, restringimos ao ingles e verificamos se começa com "Mr.".

É também possível usar o FILTER com uma expressão regular. No exemplo a seguir

SELECT ?item ?itemLabel ?bblid
WHERE {  
    ?item wdt:P2580 ?bblid .
    SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }  
    FILTER(!REGEX(STR(?bblid), "[\\.q]")) 
}
Try it!

Se a restrição de formato para um identificador é [A-Za-z][-.0-9A-Za-z]{1,}:

SELECT ?item ?itemLabel ?bblid
WHERE {  
    ?item wdt:P2580 ?bblid .
    SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }  
    FILTER(!REGEX(STR(?bblid), "^[A-Za-z][-.0-9A-Za-z]{1,}$"))
}
Try it!

É possível filtrar elementos específicos, como esse

FILTER ( ?item not in ( wd:Q4115189,wd:Q13406268,wd:Q15397819 ) )

É também possível filtrar e obter os elementos que não estão preenchidos:

FILTER ( NOT EXISTS { ?item  wdt:P21 [] } )


BIND, BOUND, IF

Esses tres pedaços de sintaxe são frequentemente usados de forma conjunta, então vou explicar os tres de uma vez e depois mostrar alguns exemplos.

Uma cláusula BIND(expressão AS ?variável). pode ser usada para atribuir o resultado de uma expressão a uma variável (normalmente uma nova variável, mas também se pode sobrescrever uma variável pré-existente).

BOUND(?variável) testa se uma variável já está conectada a um valor (e retorna verdadeiro ou falso. É útil principalmente para variáveis introduzidas em uma cláusula de OPTIONAL.

IF(condição, expressãoSeSim, expressãoSeNão)BOUND(?variável) testa se uma variável j retorna expressãoSeSim se condição retorna true, e retorna expressãoSeNão caso condição seja false. Isso é, IF(true,"sim", "não") retorna "sim", e IF(false, "ótimo", "péssimo") retorna "péssimo".

BIND pode ser usado para conectar os resultados de algum cálculo a uma variável nova. Isso pode ser um resultado intermediário de um cálculo maior, ou apenas diretamento o resultado da busca. Por exemplo, para pegar a idade das vítimas de pena de morte:

SELECT ?pessoa ?pessoaLabel ?idade
WHERE
{
  ?pessoa wdt:P31 wd:Q5;
          wdt:P569 ?nascido;
          wdt:P570 ?morreu;
          wdt:P1196 wd:Q8454.
  BIND(?morreu - ?nascido AS ?idadeEmDias).
  BIND(?idadeEmDias/365.2425 AS ?idadeEmAnos).
  BIND(FLOOR(?idadeEmAnos) AS ?idade).
  # ou, como uma expressão:
  #BIND(FLOOR((?morreu - ?nascido)/365.2425) AS ?idade).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

BIND pode também ser usado para conectar valores constantes a variáveis para aumentar a legibilidade. Por exemplo, uma busca que retorna todos as mulheres que são ordenadas como "padres":

SELECT ?mulher ?mulherLabel
WHERE
{
  ?mulher wdt:P31 wd:Q5;
         wdt:P21 wd:Q6581072;
         wdt:P106 wd:Q42603.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

pode ser reescrita desse jeito:

SELECT ?mulher ?mulherLabel
WHERE
{
  BIND(wdt:P31 AS ?instanciaDe).
  BIND(wd:Q5 AS ?humano).
  BIND(wdt:P21 AS ?sexoOuGenero).
  BIND(wd:Q6581072 AS ?feminino).
  BIND(wdt:P106 AS ?ocupação).
  BIND(wd:Q42603 AS ?padre).
  ?mulher ?instanciaDe ?humano;
         ?sexoOuGenero ?feminino;
         ?ocupação ?padre.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

A parte significativa da busca, de ?mulher até ?padre, é agora provavelmente mais legível. Contudo, o grande bloco de BIND é um tanto quanto chamativo, então essa técnica deve ser usada com cautela. (Na interface do WDQS, voce pode também passa o mouse sobre qualquer termo, como wd:Q123 ou <wdt:P123 e ver o rótulo e a descrição para a entidade, então ?feminino é somente mais legível que wd:Q6581072 caso voce ignore essa ferramenta.)

Expressões IF são normalmente usadas com expressões de condicões construídas com BOUND. Por exemplo, pense numa busca que mostra aluguns humanos, e em vez de mostrar apenas o rótulo, voce gostaria de mostrar seus pseudonym (P742) caso eles tenham um, e só usar o rótulo caso o pseudonimo não exista. Para isso, voce escolhe o pseudonimo em uma cláusula OPTIONAL (é necessário que seja opcional - voce não quer jogar fora os resultados que não tenham pseudonimo), e entãou usar BIND(IF(BOUND(... para selecionar ou o pseudonimo ou o rótulo.

SELECT ?writer ?label
WHERE
{
  # French writer born in the second half of the 18th century
  ?writer wdt:P31 wd:Q5;
          wdt:P27 wd:Q142;
          wdt:P106 wd:Q36180;
          wdt:P569 ?dob.
  FILTER("1751-01-01"^^xsd:dateTime <= ?dob && ?dob < "1801-01-01"^^xsd:dateTime).
  # get the English label
  ?writer rdfs:label ?writerLabel.
  FILTER(LANG(?writerLabel) = "en").
  # get the pseudonym, if it exists
  OPTIONAL { ?writer wdt:P742 ?pseudonym. }
  # bind the pseudonym, or if it doesn’t exist the English label, as ?label
  BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).
}
Try it!

Outras propriedades que podem ser usadas desse jeito incluem nickname (P1449), posthumous name (P1786), e taxon common name (P1843) – qualquer coisa que faça sentido ter um "plano B".

Voce também pode combinar BOUND com FILTER para garantir que pelo menos um de múltipos blocos OPTIONAL encontraram resultado. Por exemplo, peguemos todos os astronautas que foram para a lua, assim como os membros de Apollo 13 (Q182252) (perto o suficiente, né?). Essa restrição não pode ser expressa com um único caminho de propriedades, então precisamos de um OPTIONAL para "membro de alguma missão lunar" e outra para "membro da Apollo 13". Mas nós só queremos selecionar os resultados nos quais pelo menos uma dessas condições é verdadeira.

SELECT ?astronauta ?astronautaLabel
WHERE
{
  ?astronauta wdt:P31 wd:Q5;
             wdt:P106 wd:Q11631.
  OPTIONAL {
    ?astronauta wdt:P450 ?missao.
    ?missao wdt:P31 wd:Q495307.
  }
  OPTIONAL {
    ?astronauta wdt:P450 wd:Q182252.
    BIND(wd:Q182252 AS ?missao).
  }
  FILTER(BOUND(?missao)).
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try it!

COALESCE

A função COALESCE pode ser usada como uma abreviação de BIND(IF(BOUND(?x),?x,?y) AS ?z). mencionado anteriormente: ela recebe várias expressões e retorna a primeira que avalia sem erro. Por exemplo, o "plano B" do pseudonimo acima:

BIND(IF(BOUND(?pseudonimo),?pseudonimo,?escritorLabel) AS ?rotulo).

pode ser escrito mais concisamente como:

BIND(COALESCE(?pseudonimo, ?escritorLabel) AS ?rotulo).

e também é fácil adicionar um "plano C" caso o ?escritorLabel também não seja definido:

BIND(COALESCE(?pseudonimo, ?escritorLabel, "<no label>") AS ?rotulo).

Agrupamento

Até então, todas as buscas que vimos foram para encontrar todos os itens que satisfazem certas condições; em alguns casos, incluímos também declaraçoes extras no item (como pinturas com materiais, livros de Arthur Conan Doyle com título e ilustrador, etc).

Mas é bem comum que não queiramos uma lista longa de todos os resultados. Podemos querer fazer perguntas como:

  • Quantas pinturas foram feitas em telas de um certo tipo?
  • Qual é a maior população de cidades em cada país?
  • Qual é o número total de armas produzidas por cada fabricante?
  • Quem publica, na média, os livros mais longos?

População de cidades

Vamos começar pela segunda questão. É bem simples escrever uma busca que lista todas as cidades com suas população e país, ordenado por país:

SELECT ?país ?cidade ?população
WHERE
{
  ?cidade wdt:P31/wdt:P279* wd:Q515;
        wdt:P17 ?país;
        wdt:P1082 ?população.
}
ORDER BY ?país
Try it!

(Note: essa busca retorna muitos resultados, o que pode dar problemas no seu browser. Pode ser prudente adicionar uma cláusula de LIMIT.)

Como estamos ordenando os resultados por país, todas as cidades de um mesmo país formarão um bloco contíguo nos resultados. Para achar a maior população dentrao daquele bloco, nós vamos considerar aquele bloco como uma grupo e agregar todas as populações individuais em um valor só: o máximo. Isso é feito com um cláusula GROUP BY abaixo do bloco de WHERE, e uma função agregativa (MAX) na cláusula SELECT.

SELECT ?país (MAX(?população) AS ?maxPopulação)
WHERE
{
  ?cidade wdt:P31/wdt:P279* wd:Q515;
        wdt:P17 ?país;
        wdt:P1082 ?população.
}
GROUP BY ?país
Try it!

Nós trocamos o ORDER BY por um GROUP BY. O efeito disso é que todos os resultados com o mesmo ?país são agora agrupados em um resultado único. Isso significa que temos que mudar a cláusula de SELECT também. Se mantivermos a cláusula antiga SELECT ?país ?cidade ?população, qual ?cidade e ?população seriam retornados? Lembre-se há muitos resultados dentro do resultado único; eles todos tem o mesmo ?país, então nós podemos selecionar isso, mas como eles todos tem diferentes valores para ?cidade e ?população, então temos que dizer ao WDQR qual desses valores a selcionar. Esse é o trabalho da "função agregativa". Nesse caso, nós usamos MAX: dos valores de ?população, nós selecionamos o valor máximo dentro do grupo. (Nós também temos que dar a esse valor um novo name usando o construto AS, mas isso é detalhe.)

Esse é o padråo geral para escrever buscas de grupo: escreva uma busca normal que retorna os dados desejados (não agrupados, com muitos resultados por "grupo"), e então adicionar um cláusula de GROUP BY e uma função agregativa para as variáveis não agrupadas na cláusula de SELECT.

Materiais de pintura

Vamos experimentar com uma nova pergunta: Quantas pinturas foram pintadas em cada tipo de material? Primeiro, escreva uma busca que, simplesmente, retorne todas as pinturas com seus materiais de pintura. (Tome cuidado para usar apenas as declarações made from material (P186) com um qualificador applies to part (P518)painting support (Q861259).)

SELECT ?material ?pintura
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
Try it!

Então adicione uma cláusula GROUP BY para o ?material e uma função agregativa na outra variável selecionada (?pintura). Nesse caso, estamos interessados no número de pinturas; a função agregativa para isso é COUNT.

SELECT ?material (COUNT(?pintura) AS ?contagem)
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
GROUP BY ?material
Try it!

Um problema com isso é que não temos os rótulos para os materiais, então or resultados são um pouco inconvenientes. Se adicionarmos somente o rótulo, nós vamos ter um erro:

SELECT ?material ?materialLabel (COUNT(?pintura) AS ?contagem)
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material
Try it!

Bad aggregate

"Bad aggregate" (agregado ruim) é uma mensagem de erro que voce vai provavelmente ver bastante quando fazendo buscas de grupo; significa que uma das variáveis selecionadas precisa de uma função agregativa mas não tem, ou que tem uma, mas não era para ter. Nesse caso, o WDQS pensa que possam haver múltiplos ?materialLabel por cada ?material (apesar de sabermos que isso não pode acontecer), e então ele reclama que não há uma função agregativa para essa variável.

Uma solução é agrupar múltiplas variáveis. Caso liste várias variáveis no GROUP BY, há apenas um resultado para cada combinação dessas variáveis, e dá para selecionar sem uma função agregativa. Nesse caso, vamos agrupar tanto ?material e ?materialLabel.

SELECT ?material ?materialLabel (COUNT(?pintura) AS ?contagem)
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
Try it!

Nós já quase terminamos essa busca - só mais uma melhoria: nós gostaríamos de ver os materiais mais usados primeiro. Felizmente, podemos usar as novas variáveis agregadas na cláusula de SELECT (aqui, ?contagem) em uma cláusula ORDER BY, então é bem simples:

SELECT ?material ?materialLabel (COUNT(?pintura) AS ?contagem)
WHERE
{
  ?pintura wdt:P31/wdt:P279* wd:Q3305213;
            p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?material ?materialLabel
ORDER BY DESC(?contagem)
Try it!

Como exercício, vamos fazer outras buscas também.

Armas por fabricante

Qual é o número total de armas produzidas por cada fabricante?

= Editoras pelo número de páginas

Qual é a média (função: AVG) de número de páginas dos livros de cada editora?

HAVING

Um pequeno adendo para essa última busca - se olhar para os resultados, vai perceber que o resultado do topo tem uma média absurdamente enorme, mais de dez vezes o segundo colocado. Uma investigação rápida revela que é por que a editora (4"002388) só publicou um livro com uma declaração number of pages (P1104), Grande dizionario della lingua italiana (Q3775610), que desvia um pouco os resultados. Para remover uns outliers como esse, nós podemos tentar selecionar somente editoras que publicaram ao menos dois livros com declarações number of pages (P1104) no Wikidata.

Como fazemos isso? Normalmente, nós restringimos os resultados com uma cláusula FILTER, mas nesse caso, nós queremos restringir baseado no grupo (no número de grupos), não nenhum resultado individual. Isso pode ser feito com uma cláusula HAVING, que pode ser colocada logo após uma cláusula GROUP BY e recebe uma expressão bem parecida com o FILTER:

SELECT ?editora ?editoraLabel (AVG(?páginas) AS ?médiaPáginas)
WHERE
{
  ?livro wdt:P123 ?editora;
        wdt:P1104 ?páginas.
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?editora ?editoraLabel
HAVING(COUNT(?livro) > 1)
ORDER BY DESC(?médiaPáginas)
Try it!

Resumo das funções agregativas

Aqui vai um resumo pequeno das funções agregativas disponíveis:

  • COUNT: conta o número de elementos. Também dá para escrever COUNT(*) para simplesmente contar todos os resultados.
  • SUM, AVG: a soma ou média de todos os elemento, respectivamente. Se os elementos não forem números, os resultados serão esquisitos.
  • MIN, MAX: o valor mínimo e o valor máximo entre todos os elementos, respectivamente. Isso funciona para todos os tipos de valores; números são orientados numericamente, "strings" e outros tipos, lexicamente.
  • SAMPLE: qualquer elemento. Isso é útil de vez em quando, quando há um resultado só ou quando tanto faz qual resultado vai ser retornado.
  • GROUP_CONCAT: concatena todos os elementos. Útil, por exemplo, caso queira só um para um item, mas queira incluir todas as informações para uma propriedade específica que pode ter vários valores, como as ocupações de uma pessoa. As diferentes ocupações podem ser reagrupadas e concatenadas para aparecerem todas como uma variável, em vez de diversas linhas no resultado. Se estiver com curiosidade, pode procurar na especificação SPARQL.

Além disso, é possível adicionar um modificador DISTINCT para cada uma dessas funções para eliminar resultados duplicados. Por exemplo, caso dois resultados tenham o mesmo valor para ?var, então COUNT(?var) vai retornar 2, mas o COUNT(DISTINCT ?var) retornaà apenas 1. De vez em quando, voce terá que usar DISTINCT quando sua busca retornar o mesmo item múltiplas vezes - isso pode acontecer se, por exemplo, voce usar ?item wdt:P31/wdt:P279* ?class, e há múltipos caminhos do ?item até a ?class: voce vai receber um resultado novo para cada um dos caminhos, apesar de todos os valores no resultado serem identicos. (Se não tivesse agrupando, voce poderia também eliminar todos os resultados duplicados começando a busca com SELECT DISTINCT em vez de apenas SELECT.)

wikibase:Label e "bugs" de agregação

A query such as the following, which searches all academic persons with more than two countries of citizenships in Wikidata, does not show the names of those countries in the ?citizenships column:

select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
  # find all academics
  ?person wdt:P106 wd:Q3400985 ;   
          wdt:P27  ?citizenship .
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
} group by ?person ?personLabel having (count(?citizenship) > 2)
Try it!

To show the ?citizenships, explicitly name the ?personLabel and ?citizenshipLabel in the wikibase:label service call like this:

  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". 
    ?citizenship rdfs:label ?citizenshipLabel .
    ?person      rdfs:label ?personLabel .
  }

The following query works as expected:

select ?person ?personLabel (group_concat(?citizenshipLabel;separator="/") as ?citizenships) {
  ?person wdt:P106 wd:Q3400985 ;
          wdt:P27  ?citizenship .
  SERVICE wikibase:label { 
    bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". 
    ?citizenship rdfs:label ?citizenshipLabel .
    ?person      rdfs:label ?personLabel .
  }
} group by ?person ?personLabel having (count(?citizenship) > 2)
Try it!

VALUES

É possível selecionar itens baseados numa lista de itens:

SELECT ?item ?itemLabel ?mother ?motherLabel WHERE {
  VALUES ?item { wd:Q937 wd:Q1339 }
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

Também é possível selecionar com base em uma lista de valores de uma propriedade específica:

SELECT ?item ?itemLabel ?mother ?motherLabel ?ISNI WHERE {
  VALUES ?ISNI { "000000012281955X" "0000000122764157" }
  ?item wdt:P213 ?ISNI.
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

VALUES podem também fazer mais e criar enumerações de valores possíveis para um par (ou uma tupla) de variáveis. Por exemplo, digamos que você queira usar rótulos personalizados conhecidos no primeiro exemplo de uso de VALUE, é então possível usar uma cláusula de VALUES como VALUES (?item ?customItemLabel) {(wd:Q937 "Einstein") (wd:Q1339 "Bach") } que garante que sempre quando um ?item tiver valor wd:Q937 em um resultado, o valor personalizado de ?customItemLabel vai ser Eiestein e quando ?item tiver valor code>wd:Q1339, ?customItemLabel será Bach.

SELECT ?item ?customItemLabel ?mother ?motherLabel WHERE {
  VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") }
  OPTIONAL { ?item wdt:P25 ?mother. }
  SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". }
}
Try it!

E além...

Este guia acaba aqui. SPARQL não: ainda tem bastante que não mostramos - nunca prometi que seria um guia completo! Se você chegou até aqui, você já sabe muito sobre o WDQS e deve ser capaz de escrever buscas poderosíssimas. Mas se você quiser aprender ainda mais, aqui estão algumas coisas que você pode olhar

  • Sub-buscas. Você pode adicionar outra busca interia entre parênteses ({SELECT ... WHERE { ... } LIMIT 10 } ), e os resultados são visívelis na busca exterior. (Se você conhecer bem SQL, você terá que repensar o conceito um pouco - as sub-buscas SPARQL são puramente "bottom-up" (de baixo pra cima) e não podem usar valores da busca externa, da forma como as "sub-buscas correlaciondas" do SQL podem.)
  • MINUS deixa você escolher resultados que não batem com algum padrão de grafo. FILTER NOT EXISTS é praticamente equivalente (veja a especificação SPARQL para um exemplo de quando há diferença), mas - pelo menos no WDQS - é bem mais lento.

Sua referência principal para esse e outros tópicos é a especificação SPARQL.

Você também pode olhar para o tutorial de SPARQL em inglês no Wiki livros e esse tutorial pelo data.world.

E, claro, ainda há algumas partes de Wikidata que não cobrimos, como referências, precisão numérica (100±2.5), valores com unidades (dois quilogramas), geocoordenadas, ligaçṍes para outros sites, declarações em propriedades, e mais. Você pode ver como eles são modelados como tripla em mw:Wikibase/Indexing/RDF Dump Format.

Veja também