Вікідані:Підручник із SPARQL
WDQS, Wikidata Query Service (Служба запитів Вікіданих) – це потужний інструмент для надання інформації про вміст Вікіданих. Цей підручник навчить вас, як користуватися WDQS. Див. також інтерактивний підручник від Wikimedia Israel.
Перш ніж писати свій власний запит SPARQL, подивіться {{Item documentation}}
або будь-який інший типовий шаблон запиту SPARQL і подивіться, чи ваш запит уже включено.
Перед тим, як почати
Хоча цей посібник може виглядати дуже довгим і складним, будь ласка, не дозволяйте цьому лякати вас! Просто вивчивши основи SPARQL, ви пройдете довгий шлях - навіть якщо ви перестанете читати після нашого першого запиту, ви вже зрозумієте достатньо, щоб створити багато цікавих запитів. Кожен розділ цього підручника дозволить вам писати ще потужніші запити.
Якщо ви ніколи раніше не чули про Вікідані, SPARQL або WDQS, ось коротке пояснення цих термінів:
- Вікідані – це база даних знань. Вона містить мільйони тверджень, наприклад, «столиця Канади — Оттава», «Мона Ліза намальована олійною фарбою на дереві тополі», або «золото має температуру плавлення 1064,18 градуса за Цельсієм».
- SPARQL – це мова для формулювання питань (запитів) до баз даних знань. Для правильної бази даних запит SPARQL міг би відповісти на такі запитання, як "яка найпопулярніша тональність у музиці?" або "який персонаж був зображений більшістю акторів?" або "який розподіл груп крові?" або "роботи яких авторів перейшли у суспільне надбання цього року?".
- WDQS, служба запитів Вікіданих, об’єднує їх разом: ви вводите запит SPARQL, служба запускає його з набором даних Вікіданих та показує вам результат.
Основи SPARQL
Простий запит SPARQL виглядає так:
SELECT ?a ?b ?c
WHERE
{
x y ?a.
m n ?b.
?b f ?c.
}
Пункт SELECT
перераховує змінні, які ви хочете повернути (змінні починаються із знака питання), а пункт WHERE
містить обмеження на них, переважно у формі триплетів. Уся інформація у Вікіданих (і аналогічних базах даних знань) зберігається у вигляді триплетів; коли ви запускаєте запит, служба запитів намагається заповнити змінні фактичними значеннями, так, як отримані триплети з'являються в базі знань, і повертає один результат для кожної комбінації змінних, які вона знаходить.
Триплет можна розглядати як дві вершини (псевдоніми — два вузли, два ресурси), з’єднані ребром (дугою, властивістю) всередині величезного орієнтованого (спрямованого) мультиграфа властивостей, який утворює Вікідані. Його можна читати як речення (тому він закінчується крапкою), з суб'єктом (підметом, subject), предикатом (присудком, predicate) і об'єктом (додатком, object):
SELECT ?fruit
WHERE
{
?fruit hasColor yellow.
?fruit tastes sour.
}
Результати для цього запиту можуть включати, наприклад, «лимон». У Вікіданих більшість властивостей є властивостями типу «має» («has»), тому запит можна читати навпаки:
SELECT ?fruit
WHERE
{
?fruit color yellow.
?fruit taste sour.
}
який читається як «?fruit
має колір 'yellow'» (не «?fruit
є колір 'yellow'» – майте це на увазі для таких пар властивостей, як «батько»/«дитина»!).
Однак це не дуже хороший приклад для WDQS. Смак суб’єктивний, тому Вікідані не мають властивостей для нього. Натомість давайте подумаємо про відношення між батьками та дітьми, які переважно однозначні.
Наш перший запит
Припустимо, ми хочемо скласти список усіх дітей барокового композитора Йоганна Себастьяна Баха. Використовуючи псевдоелементи, як у запитах вище, як би ви написали цей запит?
Сподіваємось, у вас є щось на зразок цього:
SELECT ?child
WHERE
{
# child "has parent" Bach
?child parent Bach.
# (примітка: все після ‘#’ є коментарем й ігнорується у WDQS.)
}
або цього
SELECT ?child
WHERE
{
# child "has father" Bach
?child father Bach.
}
або цього
SELECT ?child
WHERE
{
# Bach "has child" child
Bach child ?child.
}
The first two triples say that the ?child
must have the parent/father Bach; the third says that Bach must have the child ?child
. Let’s go with the second one for now.
So what remains to be done in order to turn this into a proper WDQS query? On Wikidata, items and properties are not identified by human-readable names like “father” (property) or “Bach” (item). (For good reason: “Johann Sebastian Bach” is also the name of a German painter, and “Bach” might also refer to the surname, the French commune, the Mercury crater, etc.) Instead, Wikidata items and properties are assigned an identifier. To find the identifier for an item, we search for the item and copy the Q-number of the result that sounds like it’s the item we’re looking for (based on the description, for example). To find the identifier for a property, we do the same, but search for “P:search term” instead of just “search term”, which limits the search to properties. This tells us that the famous composer Johann Sebastian Bach is Q1339, and the property to designate an item’s father is P:P22.
And last but not least, we need to include prefixes. For simple WDQS triples, items should be prefixed with wd:
, and properties with wdt:
. (But this only applies to fixed values – variables don’t get a prefix!)
Putting this together, we arrive at our first proper WDQS query:
SELECT ?child
WHERE
{
# ?child father Bach
?child wdt:P22 wd:Q1339.
}
Натисніть посилання "Виконати!", потім "Виконати запит" на сторінці WDQS. Що ви отримуєте?
child |
---|
wd:Q57225 |
wd:Q76428 |
… |
Well that’s disappointing. You just see the identifiers. You can click on them to see their Wikidata page (including a human-readable label), but isn’t there a better way to see the results?
Well, as it happens, there is! (Aren’t rhetorical questions great?) If you include the magic text
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
somewhere within the WHERE
clause, you get additional variables: For every variable ?foo
in your query, you now also have a variable ?fooLabel
, which contains the label of the item behind ?foo
. If you add this to the SELECT
clause, you get the item as well as its label:
SELECT ?child ?childLabel
WHERE
{
# ?child father Bach
?child wdt:P22 wd:Q1339.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Try running that query – you should see not only the item numbers, but also the names of the various children.
child | childLabel |
---|---|
wd:Q57225 | Johann Christoph Friedrich Bach |
wd:Q76428 | Carl Philipp Emanuel Bach |
… |
Автозавершення
That SERVICE
snippet looks tough to remember though, right? And going through the search function all the time while you’re writing the query is also tedious. Fortunately, WDQS offers a great solution to this: autocompletion. In the query.wikidata.org query editor, you can press Ctrl+Space (or Alt+Enter or Ctrl+Alt+Enter) at any point in the query and get suggestions for code that might be appropriate; select the right suggestion with the up/down arrow keys, and press Enter to select it.
For example, instead of writing out SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
every time, you can just type SERV
, hit Ctrl+Space, and the first suggestion will be that complete label service incantation, ready for use! Just hit Enter to accept it. (The formatting will be a bit different, but that doesn’t matter.)
And autocompletion can also search for you. If you type one of the Wikidata prefixes, like wd:
or wdt:
, and then just write text afterwards, Ctrl+Space will search for that text on Wikidata and suggest results. wd:
searches for items, wdt:
for properties. For example, instead of looking up the items for Johann Sebastian Bach (Q1339) and father (P22), you can just type wd:Bach
and wdt:fath
and then just select the right entry from the autocompletion. (This even works with spaces in the text, e.g. wd:Johann Sebastian Bach
.)
Удосконалені схеми триплетів
Отже, тепер ми побачили всіх дітей Йогана Себастьяна Баха - конкретніше: усі елементи з батьком Йоганном Себастьяном Бахом. Але Бах мав двох дружин, і тому в цих елементах є дві різні матері: що, якщо ми хочемо бачити дітей Йогана Себастьяна Баха зі своєю першою дружиною, Марією Барбарою Бах (Q57487)? Спробуйте записати цей запит, виходячи із зазначеного вище.
Зробили це? Добре, тоді до рішення! Найпростіший спосіб зробити це - додати другий триплет з цим обмеженням:
SELECT ?child ?childLabel
WHERE
{
?child wdt:P22 wd:Q1339.
?child wdt:P25 wd:Q57487.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
In English, this reads:
Child has father Johann Sebastian Bach. Child has mother Maria Barbara Bach.
That sounds a bit awkward, doesn’t it? In natural language, we’d abbreviate this:
Child has father Johann Sebastian Bach and mother Maria Barbara Bach.
In fact, it’s possible to express the same abbreviation in SPARQL as well: if you end a triple with a semicolon (;
) instead of a period, you can add another predicate-object pair. This allows us to abbreviate the above query to:
SELECT ?child ?childLabel
WHERE
{
?child wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
which has the same results, but less repetition in the query.
Now suppose that, out of those results, we’re interested only in those children who were also composers and pianists. The relevant properties and items are occupation (P106), composer (Q36834) and pianist (Q486748). Try updating the above query to add these restrictions!
Here’s my solution:
SELECT ?child ?childLabel
WHERE
{
?child 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]". }
}
This uses the ;
abbreviation two more times to add the two required occupations. But as you might notice, there’s still some repetition. This is as if we said:
Child has occupation composer and occupation pianist.
which we would usually abbreviate as:
Child has occupation composer and pianist.
And SPARQL has some syntax for that as well: just like a ;
allows you to append a predicate-object pair to a triple (reusing the subject), a ,
allows you to append another object to a triple (reusing both subject and predicate). With this, the query can be abbreviated to:
SELECT ?child ?childLabel
WHERE
{
?child wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834,
wd:Q486748.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Note: indentation and other whitespaces don’t actually matter – they just make it more readable. You can also write this as:
SELECT ?child ?childLabel
WHERE
{
?child wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834, wd:Q486748.
# both occupations in one line
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
or, rather less readable:
SELECT ?child ?childLabel
WHERE
{
?child wdt:P22 wd:Q1339;
wdt:P25 wd:Q57487;
wdt:P106 wd:Q36834,
wd:Q486748.
# no indentation; makes it hard to distinguish between ; and ,
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Luckily, the WDQS editor indents lines for you automatically, so you usually don’t have to worry about this.
Alright, let’s summarize here. We’ve seen that queries are structured like text. Each triple about a subject is terminated by a period. Multiple predicates about the same subject are separated by semicolons, and multiple objects for the same subject and predicate can be listed separated by commas.
SELECT ?s1 ?s2 ?s3
WHERE
{
?s1 p1 o1;
p2 o2;
p3 o31, o32, o33.
?s2 p4 o41, o42.
?s3 p5 o5;
p6 o6.
}
Now I want to introduce one more abbreviation that SPARQL offers. So if you’ll humor me for one more hypothetical scenario…
Suppose we’re not actually interested in Bach’s children. (Who knows, perhaps that’s actually true for you!) But we are interested in his grandchildren. (Hypothetically.) There’s one complication here: a grandchild may be related to Bach via the mother or the father. That’s two different properties, which is inconvenient. Instead, let’s flip the relation around: Wikidata also has a “child” property, P:P40, which points from parent to child and is gender-independent. With this information, can you write a query that returns Bach’s grandchildren?
Here’s my solution:
SELECT ?grandChild ?grandChildLabel
WHERE
{
wd:Q1339 wdt:P40 ?child.
?child wdt:P40 ?grandChild.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
In natural language, this reads:
Bach has a child
?child
.?child
has a child?grandChild
.
Once more, I propose that we abbreviate this English sentence, and then I want to show you how SPARQL supports a similar abbreviation. Observe how we actually don’t care about the child: we don’t use the variable except to talk about the grandchild. We could therefore abbreviate the sentence to:
Bach has as child someone who has a child
?grandChild
.
Instead of saying who Bach’s child is, we just say “someone”: we don’t care who it is. But we can refer back to them because we’ve said “someone who”: this starts a relative clause, and within that relative clause we can say things about “someone” (e.g., that they “have a child ?grandChild
”). In a way, “someone” is a variable, but a special one that’s only valid within this relative clause, and one that we don’t explicitly refer to (we say “someone who is this and does that”, not “someone who is this and someone who does that” – that’s two different “someone”s).
In SPARQL, this can be written as:
SELECT ?grandChild ?grandChildLabel
WHERE
{
wd:Q1339 wdt:P40 [ wdt:P40 ?grandChild ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
You can use a pair of brackets ([]
) in place of a variable, which acts as an anonymous variable. Inside the brackets, you can specify predicate-object pairs, just like after a ;
after a normal triple; the implicit subject is in this case the anonymous variable that the brackets represent. (Note: also just like after a ;
, you can add more predicate-object pairs with more semicolons, or more objects for the same predicate with commas.)
And that’s it for triple patterns! There’s more to SPARQL, but as we’re about to leave the parts of it that are strongly analogous to natural language, I’d like to summarize that relationship once more:
natural language | example | SPARQL | example |
---|---|---|---|
sentence | Juliet loves Romeo. | period | juliet loves romeo.
|
conjunction (clause) | Romeo loves Juliet and kills himself. | semicolon | romeo loves juliet; kills romeo.
|
conjunction (noun) | Romeo kills Tybalt and himself. | comma | romeo kills tybalt, romeo.
|
relative clause | Juliet loves someone who kills Tybalt. | brackets | juliet loves [ kills tybalt ].
|
Екземпляри й класи
Раніше я сказав, що більшість властивостей Wikidata відношення "має": "має" дитину, "має" батька, "має" професію. Але іноді (насправді, часто), вам також потрібно поговорити про те, що "є". Але існують фактично два види відношень:
- Звіяні вітром є фільмом.
- Фільм є мистецьким твором.
Звіяні вітром - це один конкретний фільм. Він має конкретного режисера (Віктор Флемінг), певну тривалість (238 хвилин), список акторів (Кларк Гейбл, Вів'єн Лі, ...) тощо.
"Фільм" - це загальне поняття. Фільми можуть мати режисерів, тривалості й акторів, але поняття "фільм" як таке не має жодного конкретного режисера, тривалості або акторів. І хоча фільм є мистецьким твором, а мистецький твір зазвичай має творця, у самому понятті "фільм" немає творця - його мають лише окремі "екземпляри" ("instances") цього поняття.
Ця різниця полягає в тому, що у Вікіданих є дві властивості для "є": instance of (P31) і subclass of (P279). Звіяні вітром - це окремий примірник класу "фільм"; клас "фільм" - це підклас (більш специфічний клас, спеціалізація) більш загального класу "витвір мистецтва".
Щоб допомогти вам зрозуміти різницю, ви можете спробувати використовувати два різних дієслова: "є" і "є свого роду". Якщо "є свого роду" твір (наприклад, фільм - це "є свого роду" "витвір мистецтва"), це означає, що ви говорите про підклас, спеціалізацію ширшого класу, і ви повинні використовувати subclass of (P279). Якщо "є свого роду" не працює (наприклад, вислів "Звіяні вітром "є свого роду" фільмом" не має сенсу), це означає, що ви говорите про конкретний екземпляр, і ви повинні використовувати instance of (P31).
So what does this mean for us when we’re writing SPARQL queries? When we want to search for “all works of art”, it’s not enough to search for all items that are directly instances of “work of art”:
SELECT ?work ?workLabel
WHERE
{
?work wdt:P31 wd:Q838948. # instance of work of art
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
As I’m writing this (October 2016), that query only returns 2,815 results – obviously, there are more works of art than that! The problem is that this misses items like Gone with the Wind, which is only an instance of “film”, not of “work of art”. “film” is a subclass of “work of art”, but we need to tell SPARQL to take that into account when searching.
One possible solution to this is the []
syntax we talked about: Gone with the Wind is an instance of some subclass of “work of art”. (For exercise, try writing that query!) But that still has problems:
- We’re no longer including items that are directly instances of work of art.
- We’re still missing items that are instances of some subclass of some other subclass of “work of art” – for example, Snow White and the Seven Dwarfs is an animated film, which is a film, which is a work of art. In this case, we need to follow two “subclass of” statements – but it might also be three, four, five, any number really.
The solution: ?item wdt:P31/wdt:P279* ?class
. This means that there’s one “instance of” and then any number of “subclass of” statements between the item and the class.
SELECT ?work ?workLabel
WHERE
{
?work wdt:P31/wdt:P279* wd:Q838948. # instance of any subclass of work of art
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
(I don’t recommend running that query. WDQS can handle it (just barely), but your browser might crash when trying to display the results because there are so many of them.)
Now you know how to search for all works of art, or all buildings, or all human settlements: the magic incantation wdt:P31/wdt:P279*
, along with the appropriate class. This uses some more SPARQL features that I haven’t explained yet, but quite honestly, this is almost the only relevant use of those features, so you don’t need to understand how it works in order to use WDQS effectively. If you want to know, I’ll explain it in a bit, but you can also just skip the next section and memorize or copy+paste wdt:P31/wdt:P279*
from here when you need it.
Шляхи властивостей
Шляхи властивостей - це спосіб дуже стислого записування шляху властивостей між двома елементами. Найпростіший шлях - це лише одна окрема властивість, яка утворює звичайний триплет:
?item wdt:P31 ?class.
Ви можете додати елементи шляху, використовуючи косу риску (/
).
?item wdt:P31/wdt:P279/wdt:P279 ?class.
Це еквівалентно одному з таких записів:
?item wdt:P31 ?temp1.
?temp1 wdt:P279 ?temp2.
?temp2 wdt:P279 ?class.
?item wdt:P31 [ wdt:P279 [ wdt:P279 ?class ] ].
Вправа: перепишіть попередній запит "онуки Баха" з використанням цього синтаксису.
Зірочка (*
) після елемента шляху означає “нуль або більше цих елементів".
?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 ...
In the special case where there is zero property in a path (no specific arc of relation: a NULL, "universal" property), then the subject node is directly connected to the object node in the graph, whatever the object node is, including itself. So that there is always a match. Thus, in SPARQL
, for instance in the case "zero something", ?a something* ?b
reduces to ?a ?b
, with no path between them, and ?a
takes directly the value of ?b
.
A plus (+
) is similar to an asterisk, but means “one or more of this element”. The following query finds all descendants of Bach:
SELECT ?descendant ?descendantLabel
WHERE
{
wd:Q1339 wdt:P40+ ?descendant.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
If we used an asterisk instead of a plus here, the query results would include Bach himself.
Знак питання (?
) подібний на зірочку або плюс, але означає "нуль або один цей елемент".
You can separate path elements with a vertical bar (|
) instead of a forward slash; this means “either-or”: the path might use either of those properties. (But not combined – an either-or path segment always matches a path of length one.)
You can also group path elements with parentheses (()
), and freely combine all these syntax elements (/|*+?
). This means that another way to find all descendants of Bach is:
SELECT ?descendant ?descendantLabel
WHERE
{
?descendant (wdt:P22|wdt:P25)+ wd:Q1339.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Instead of using the “child” property to go from Bach to his descendants, we use the “father” and “mother” properties to go from the descendants to Bach. The path might include two mothers and one father, or four fathers, or father-mother-mother-father, or any other combination. (Though, of course, Bach can’t be the mother of someone, so the last element will always be father.)
Кваліфікатори
(Спочатку гарна новина: у цьому розділі не вводиться додатковий синтаксис SPARQL - ура! Дихайте спокійно і розслабтеся, це має бути простіше простого, правильно?)
Наразі ми говорили лише про прості твердження: суб'єкт, властивість, об'єкт. Але твердження Вікіданих є чимось більшим: вони можуть також мати кваліфікатори та посилання. Наприклад, Мона Ліза (Q12418) має три твердження made from material (P186):
- oil paint (Q296955), основний матеріал;
- poplar wood (Q291034), з кваліфікатором applies to part (P518)painting support (Q861259) - це матеріал, на якому Мона Ліза була намальована;
- wood (Q287), з кваліфікаторами applies to part (P518)stretcher (Q1737943) та start time (P580) 1951 - це частина, яка пізніше була додана до картини.
Припустимо, ми хочемо знайти всі картини з їхньою поверхнею для малювання, тобто ті твердження made from material (P186) з кваліфікатором applies to part (P518)painting support (Q861259). Як ми це зробимо? Тут більше інформації, ніж можна представити в одному триплеті.
Відповідь: більше триплетів! (Практичне правило: Рішенням Вікіданих майже для всього є "більше елементів", а відповідним правилом WDQS є "більше триплетів". Посилання, точність чисел, значення з одиницями вимірювання, геокоординати тощо, все, що ми тут пропускаємо, також працює так само.) Досі ми використовували префікс wdt:
для наших триплетів тверджень, який вказує безпосередньо на об'єкт твердження. Але є ще один префікс p:
, який вказує не на об'єкт, а на вузол твердження (statement node). Тоді цей вузол є суб'єктом інших триплетів: префікс ps:
(для property statement, твердження властивості) вказує на об'єкт твердження, префікс pq:
(property qualifier, кваліфікатор властивості) - на кваліфікатори, а prov:wasDerivedFrom
вказує на вузли посилань (які ми наразі проігноруємо).
Це було багато абстрактного тексту. Ось конкретний приклад для Мони Лізи:
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)
Ми можемо це скоротити з допомогою синтаксису []
, замінивши змінні ?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
].
Чи можете ви використати ці знання, щоб написати запит для всіх картин з їхньою поверхнею для малювання?
Це мій розв'язок:
SELECT ?painting ?paintingLabel ?material ?materialLabel
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Спершу ми обмежуємо ?painting
всіма екземплярами painting (Q3305213) або її підкласів. Потім ми витягуємо матеріал з вузла твердження p:P186
, обмежуючи твердження до тих, що мають кваліфікатор applies to part (P518)painting support (Q861259).
ORDER
і LIMIT
We return to our regular scheduled program of more SPARQL features.
So far, we’ve only had queries where we were interested in all results. But it’s quite common to care only about a few results: those that are most extreme in some way – oldest, youngest, earliest, latest, highest population, lowest melting point, most children, most materials used, and so on. The common factor here is that the results are ranked in some way, and then we care about the first few results (those with the best rank).
This is controlled by two clauses, which are appended to the WHERE {}
block (after the braces, not inside!): ORDER BY
and LIMIT
.
ORDER BY something
sorts the results by something
. something
can be any expression – for now, the only kind of expression we know are simple variables (?something
), but we’ll see some other kinds later. This expression can also be wrapped in either ASC()
or DESC()
to specify the sorting order (ascending or descending). (If you don’t specify either, the default is ascending sort, so ASC(something)
is equivalent to just something
.)
LIMIT count
cuts off the result list at count
results, where count
is any natural number. For example, LIMIT 10
limits the query to ten results. LIMIT 1
only returns a single result.
(You can also use LIMIT
without ORDER BY
. In this case, the results aren’t sorted, so you don’t have any guarantee which results you’ll get. Which is fine if you happen to know that there’s only a certain number of results, or you’re just interested in some result, but don’t care about which one. In either case, adding the LIMIT
can significantly speed up the query, since WDQS can stop searching for results as soon as it’s found enough to fill the limit.)
Exercise time! Try to write a query that returns the ten most populous countries. (A country is a sovereign state (Q3624078), and the property for population is P:P1082.) You can start by searching for countries with their population, and then add the ORDER BY
and LIMIT
clauses.
Here’s my solution:
SELECT ?country ?countryLabel ?population
WHERE
{
?country wdt:P31/wdt:P279* wd:Q3624078;
wdt:P1082 ?population.
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
ORDER BY DESC(?population)
LIMIT 10
Note that if we want the most populous countries, we have to order by descending population, so that the first results will be the ones with the highest values.
Вправа
We’ve covered a lot of ground so far – I think it’s time for some exercises. (You can skip this section if you’re in a hurry.)
Книги Артура Конана Дойла
Write a query that returns all books by Sir Arthur Conan Doyle.
Hint |
---|
The relevant items and properties are: Arthur Conan Doyle (Q35610), author (P50). |
Example solution |
---|
SELECT ?book ?bookLabel
WHERE
{
?book wdt:P50 wd:Q35610.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Хімічні елементи
Write a query that returns all chemical elements with their element symbol and atomic number, in order of their atomic number.
Hint |
---|
The relevant items and properties are: chemical element (Q11344), element symbol (P246), atomic number (P1086). |
Example solution |
---|
SELECT ?element ?elementLabel ?symbol ?number
WHERE
{
?element wdt:P31 wd:Q11344;
wdt:P246 ?symbol;
wdt:P1086 ?number.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
ORDER BY ?number
|
Річки, що впадають у Міссісіпі
Write a query that returns all rivers that flow directly into the Mississippi River. (The main challenge is finding the correct property…)
Hint |
---|
The relevant items and properties are: Mississippi River (Q1497), mouth of the watercourse (P403). |
Example solution |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403 wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
Річки, що впадають у Міссісіпі II
Write a query that returns all rivers that flow into the Mississippi River, directly or indirectly.
Hint |
---|
This query is almost identical to the previous one. The difference is that this time you’ll need a path instead of a triple. (If you skipped the section about paths, skip this exercise too.) |
Example solution |
---|
SELECT ?river ?riverLabel
WHERE
{
?river wdt:P403+ wd:Q1497.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
|
OPTIONAL
In the exercises above, we had a query for all books by Sir Arthur Conan Doyle:
SELECT ?book ?bookLabel
WHERE
{
?book wdt:P50 wd:Q35610.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
But that’s a bit boring. There’s so much potential data about books, and we only show the label? Let’s try to craft a query that also includes the title (P1476), illustrator (P110), publisher (P123) and publication date (P577).
A first attempt might look like this:
SELECT <span lang="en" dir="ltr" class="mw-content-ltr">?book ?title ?illustratorLabel ?publisherLabel ?published</span>
WHERE
{
?book wdt:P50 wd:Q35610;
wdt:P1476 ?title;
wdt:P110 ?illustrator;
wdt:P123 ?publisher;
wdt:P577 ?published.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Run that query. As I’m writing this, it only returns two results – a bit meager! Why is that? We found over a hundred books earlier!
The reason is that to match this query, a potential result (a book) must match all the triples we listed: it must have a title, and an illustrator, and a publisher, and a publication date. If it has some of those properties, but not all of them, it won’t match. And that’s not what we want in this case: we primarily want a list of all the books – if additional data is available, we’d like to include it, but we don’t want that to limit our list of results.
The solution is to tell WDQS that those triples are optional:
SELECT <span lang="en" dir="ltr" class="mw-content-ltr">?book ?title ?illustratorLabel ?publisherLabel ?published</span>
WHERE
{
?book wdt:P50 wd:Q35610.
OPTIONAL { ?book wdt:P1476 ?title. }
OPTIONAL { ?book wdt:P110 ?illustrator. }
OPTIONAL { ?book wdt:P123 ?publisher. }
OPTIONAL { ?book wdt:P577 ?published. }
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
This gives us the additional variables (?title
, ?publisher
etc.) if the appropriate statement exists, but if the statement doesn’t exist, the result isn’t discarded – the variable simply isn’t set.
Note: it’s very important to use separate OPTIONAL
clauses here. If you put all the triples into a single clause, like here –
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". }
}
– you’ll notice that most of the results don’t include any extra information. This is because an optional clause with multiple triples only matches when all those triples can be satisfied. That is: if a book has a title, an illustrator, a publisher, and a publication date, then the optional clause matches, and those values are assigned to the appropriate variables. But if a book has, for example, a title but no illustrator, the entire optional clause doesn’t match, and although the result isn’t discarded, all four variables remain empty.
Вирази, FILTER
і BIND
This section might seem a bit less organized than the other ones, because it covers a fairly wide and diverse topic. The basic concept is that we would like to do something with the values that, so far, we’ve just selected and returned indiscriminately. And expressions are the way to express these operations on values. There are many kinds of expressions, and a lot of things you can do with them – but first, let’s start with the basics: data types.
Типи даних
Each value in SPARQL has a type, which tells you what kind of value it is and what you can do with it. The most important types are:
- item, like
wd:Q42
for Douglas Adams (Q42). - boolean, with the two possible values
true
andfalse
. Boolean values aren’t stored in statements, but many expressions return a boolean value, e.g.2 < 3
(true
) or"a" = "b"
(false
). - string, a piece of text. String literals are written in double quotes.
- monolingual text, a string with a language tag attached. In a literal, you can add the language tag after the string with an
@
sign, e.g."Douglas Adams"@en
. - numbers, either integers (
1
) or decimals (1.23
). - dates. Date literals can be written by adding
^^xsd:dateTime
(case sensitive –^^xsd:datetime
won’t work!) to an ISO 8601 date string:"2012-10-29"^^xsd:dateTime
.
Оператори
Доступні знайомі математичні оператори: +
, -
, *
, /
для додавання, віднімання, множення або ділення чисел, <
, >
, =
, <=
, >=
для їхнього порівняння. Перевірка нерівності ≠ записується так !=
. Порівняння також визначено для інших типів; наприклад, "abc" < "abd"
є істинним (лексичне порівняння), як і "2016-01-01"^^xsd:dateTime > "2015-12-31"^^xsd:dateTime
та wd:Q4653 != wd:Q283111
. Логічні умови можна комбінувати з &&
(логічне "і": a && b
є істинним, якщо і a
, і b
є істинними) та ||
(логічне "або": a || b
є істинним, якщо одна з умов (або обидві) a
та b
є істинними).
FILTER
Інформація Іноді швидшою альтернативою для FILTER
є MINUS
, див. приклад.
FILTER(condition).
- це пункт, який ви можете вставити у свій SPARQL-запит, щоб фільтрувати результати. У круглих дужках ви можете помістити будь-який вираз булевого типу, і використовувати лише ті результати, в яких вираз повертає true
.
For example, to get a list of all humans born in 2015, we first get all humans with their date of birth –
SELECT ?person ?personLabel ?dob
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?dob.
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
}
– and then filter that to only return the results where the year of the date of birth is 2015. There are two ways to do that: extract the year of the date with the YEAR
function, and test that it’s 2015 –
FILTER(YEAR(?dob) = 2015).
– or check that the date is between Jan. 1st (inclusive), 2015 and Jan. 1st, 2016 (exclusive):
FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).
I’d say that the first one is more straightforward, but it turns out the second one is much faster, so let’s use that:
SELECT ?person ?personLabel ?dob
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?dob.
FILTER("2015-01-01"^^xsd:dateTime <= ?dob && ?dob < "2016-01-01"^^xsd:dateTime).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Інше можливе використання FILTER
пов'язане з назвами. Служба назв дуже корисна, якщо ви просто хочете відобразити назву змінної. Але якщо ви хочете робити щось із назвою - наприклад: перевірити, чи починається вона з "Mr. " - ви виявите, що це не працює:
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. ")).
}
This query finds all instances of fictional human (Q15632617) and tests if their label starts with "Mr. "
(STRSTARTS
is short for “string starts [with]”; there’s also STRENDS
and CONTAINS
). The reason why this doesn’t work is that the label service adds its variables very late during query evaluation; at the point where we try to filter on ?humanLabel
, the label service hasn’t created that variable yet.
Fortunately, the label service isn’t the only way to get an item’s label. Labels are also stored as regular triples, using the predicate rdfs:label
. Of course, this means all labels, not just English ones; if we only want English labels, we’ll have to filter on the language of the label:
FILTER(LANG(?label) = "en").
Функція LANG
повертає мову одномовного рядка, і тут ми вибираємо лише ті назви, які задані англійською. Повний запит:
SELECT ?human ?label
WHERE
{
?human wdt:P31 wd:Q15632617;
rdfs:label ?label.
FILTER(LANG(?label) = "[AUTO_LANGUAGE]").
FILTER(STRSTARTS(?label, "Mr. ")).
}
We get the label with the ?human rdfs:label ?label
triple, restrict it to English labels, and then check if it starts with “Mr. ”.
One can also use FILTER with a regular expression. In the following example
SELECT ?item ?itemLabel ?bblid
WHERE {
?item wdt:P2580 ?bblid .
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en" }
FILTER(!REGEX(STR(?bblid), "[\\.q]"))
}
If the format constraint for an ID is [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,}$"))
}
It is possible to filter out specific elements like this
FILTER ( ?item not in ( wd:Q4115189,wd:Q13406268,wd:Q15397819 ) )
It is possible to filter and have elements that aren't filled:
FILTER ( NOT EXISTS { ?item wdt:P21 [] } )
BIND
, BOUND
, IF
Ці три функції часто використовуються спільно, тому я спочатку поясню їх, а потім покажу вам декілька прикладів.
Твердження BIND(expression AS ?variable).
може бути використане для присвоєння результату виразу змінній (зазвичай новій змінній, але також можна переписати наявну).
BOUND(?variable)
тестує, чи змінній задано значення і вона пов'язана з ним (повертає true
or false
). Це в основному корисно для змінних, які вводяться в пункті OPTIONAL
.
IF(condition,thenExpression,elseExpression)
вираховує thenExpression
, якщо condition
дорівнює true
і elseExpression
, якщо condition
дорівнює false
. Тобто IF(true, "так", "ні")
вираховує "так"
, а IF(false, "великий", "лячний")
вираховує "лячний"
.
BIND
можна використовувати для прив'язки результатів деякого розрахунку до нової змінної. Вона може бути проміжним результатом більшого розрахунку або просто безпосередньо результатом запиту. Наприклад, щоб отримати вік жертв смертної кари:
SELECT ?person ?personLabel ?age
WHERE
{
?person wdt:P31 wd:Q5;
wdt:P569 ?born;
wdt:P570 ?died;
wdt:P1196 wd:Q8454.
BIND(?died - ?born AS ?ageInDays).
BIND(?ageInDays/365.2425 AS ?ageInYears).
BIND(FLOOR(?ageInYears) AS ?age).
# або, як один вираз:
#BIND(FLOOR((?died - ?born)/365.2425) AS ?age).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
BIND
також можна використовувати для простої прив'язки постійних значень до змінних для покращення читабельності запиту. Наприклад, запит, який знаходить усіх священиків-жінок:
SELECT ?woman ?womanLabel
WHERE
{
?woman wdt:P31 wd:Q5;
wdt:P21 wd:Q6581072;
wdt:P106 wd:Q42603.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
можна переписати так:
SELECT ?woman ?womanLabel
WHERE
{
BIND(wdt:P31 AS ?instanceOf).
BIND(wd:Q5 AS ?human).
BIND(wdt:P21 AS ?sexOrGender).
BIND(wd:Q6581072 AS ?female).
BIND(wdt:P106 AS ?occupation).
BIND(wd:Q42603 AS ?priest).
?woman ?instanceOf ?human;
?sexOrGender ?female;
?occupation ?priest.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
Значна частина запиту, від ?woman
до ?priest.
, тепер, ймовірно, читабельніша. Однак, великий блок BIND
перед нею суттєво відволікає, тому цю техніку слід використовувати раціонально. (У користувацькому інтерфейсі WDQS ви також можете навести курсор миші на будь-який термін, наприклад, wd:Q123
або wdt:P123
і побачити назву й опис сутності, тому ?female
є читабельнішим, ніж wd:Q6581072
, якщо ви ігноруєте цю можливість.)
Вирази IF
часто використовуються в BOUND
як вираз. Наприклад, припустімо, що у вас є запит, який показує деяких людей, і замість того, щоб просто показати їхню назву, ви хочете показати їхній pseudonym (P742), якщо він є, та використати лише назву, якщо псевдонім не існує. Для цього ви виберете псевдонім у пункті OPTIONAL
(він повинен бути необов'язковим - ви не бажаєте викидати результати, які не мають псевдоніма), а потім використаєте BIND(IF(BOUND(…
, щоб вибрати псевдонім або назву.
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).
}
Інші властивості, які можуть бути використані таким способом, включають nickname (P1449), posthumous name (P1786), і taxon common name (P1843) - все, що має сенс деякого роду «резервного» (“fallback”).
Ви також можете поєднати BOUND
з FILTER
, щоб переконатися, що принаймні один із декількох блоків OPTIONAL
був виконаний. Наприклад, отримаймо всіх космонавтів, які літали на Місяць, а також членів Apollo 13 (Q182252) (досить близько, вірно?). Це обмеження не може бути виражене як єдиний шлях властивості, тому нам потрібна одна умова OPTIONAL
для "учасника деякої місячної місії", а інша для "члена Apollo 13". Але ми хочемо вибрати лише ті результати, в яких принаймні одна з цих умов є істинною.
SELECT ?astronaut ?astronautLabel
WHERE
{
?astronaut wdt:P31 wd:Q5;
wdt:P106 wd:Q11631.
OPTIONAL {
?astronaut wdt:P450 ?mission.
?mission wdt:P31 wd:Q495307.
}
OPTIONAL {
?astronaut wdt:P450 wd:Q182252.
BIND(wd:Q182252 AS ?mission).
}
FILTER(BOUND(?mission)).
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
COALESCE
Функцію COALESCE
можна використовувати як скорочення для шаблону BIND(IF(BOUND(?x), ?x, ?y) AS ?z).
для псевдонімів, що згадані вище: вона бере ряд виразів і повертає перший, який оцінює як "без помилки". Наприклад, вищезгаданий резервний "pseudonym"
BIND(IF(BOUND(?pseudonym),?pseudonym,?writerLabel) AS ?label).
можна записати стисліше так
BIND(COALESCE(?pseudonym, ?writerLabel) AS ?label).
а також легко додати ще одну резервну назву, якщо ?writerLabel
також не задано:
BIND(COALESCE(?pseudonym, ?writerLabel, "<no label>") AS ?label).
Групування
So far, all the queries we’ve seen were queries that found all items satisfying some conditions; in some cases, we also included extra statements on the item (paintings with materials, Arthur Conan Doyle books with title and illustrator).
But it’s very common that we don’t want a long list of all results. Instead, we might ask questions like this:
- How many paintings were painted on canvas / poplar wood / etc.?
- What is the highest population of each country’s cities?
- What is the total number of guns produced by each manufacturer?
- Who publishes, on average, the longest books?
Міське населення
Let’s look at the second question for now. It’s fairly simple to write a query that lists all cities along with their population and country, ordered by country:
SELECT ?country ?city ?population
WHERE
{
?city wdt:P31/wdt:P279* wd:Q515;
wdt:P17 ?country;
wdt:P1082 ?population.
}
ORDER BY ?country
(Note: that query returns a lot of results, which might cause trouble for your browser. You might want to add a LIMIT
clause.)
Since we’re ordering the results by country, all cities belonging to a country form one contiguous block in the results. To find the highest population within that block, we want to consider the block as a group, and aggregate all the individual population values into one value: the maximum. This is done with a GROUP BY
clause below the WHERE
block, and an aggregate function (MAX
) in the SELECT
clause.
SELECT ?country (MAX(?population) AS ?maxPopulation)
WHERE
{
?city wdt:P31/wdt:P279* wd:Q515;
wdt:P17 ?country;
wdt:P1082 ?population.
}
GROUP BY ?country
We’ve replaced the ORDER BY
with a GROUP BY
. The effect of this is that all results with the same ?country
are now grouped together into a single result. This means that we have to change the SELECT
clause as well. If we kept the old clause SELECT ?country ?city ?population
, which ?city
and ?population
would be returned? Remember, there are many results in this one result; they all have the same ?country
, so we can select that, but since they can all have a different ?city
and ?population
, we have to tell WDQS which of those values to select. That’s the job of the aggregate function. In this case, we’ve used MAX
: out of all the ?population
values, we select the maximum one for the group result. (We also have to give that value a new name with the AS
construct, but that’s just a minor detail.)
This is the general pattern for writing group queries: write a normal query that returns the data you want (not grouped, with many results per “group”), then add a GROUP BY
clause and add an aggregate function to all the non-grouped variables in the SELECT
clause.
Матеріал для картин
Let’s try it out with another question: How many paintings were painted on each material? First, write a query that just returns all paintings along with their painting material. (Take care to only use those made from material (P186) statements with an applies to part (P518)painting support (Q861259) qualifier.)
SELECT ?material ?painting
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
Next, add a GROUP BY
clause on the ?material
, and then an aggregate function on the other selected variable (?painting
). In this case, we are interested in the number of paintings; the aggregate function for that is COUNT
.
SELECT ?material (COUNT(?painting) AS ?count)
WHERE
{
?painting wdt:P31/wdt:P279* wd:Q3305213;
p:P186 [ ps:P186 ?material; pq:P518 wd:Q861259 ].
}
GROUP BY ?material
One problem with this is that we don’t have the label for the materials, so the results are a bit inconvenient to interpret. If we just add the label variable, we’ll get an error:
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting 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
Bad aggregate
"Bad aggregate" - це повідомлення про помилку, яке ви, ймовірно, побачите під час роботи з груповими запитами; воно означає, що для однієї з вибраних змінних потрібна агрегатна функція, але її немає, або вона має агрегатну функцію, але не повинна мати її. У цьому разі WDQS вважає, що можуть бути декілька ?materialLabel
для ?material
(хоча ми знаємо, що це не може статися), і тому він скаржиться на те, що ви не вказали агрегатну функцію для цієї змінної.
Одним із рішень є групування за декількома змінними. Якщо ви вкажете декілька змінних у пункті GROUP BY
, для кожної комбінації цих змінних є один результат, і ви можете вибрати всі ці змінні без агрегатної функції. У цьому разі ми будемо групувати як ?material
, так і ?materialLabel
.
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting 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
Ми майже завершили із запитами - ще лиш одне вдосконалення: ми хочемо побачити спершу найчастіше використовувані матеріали. На щастя, нам дозволяється використовувати нові, агреговані змінні у пункті SELECT
(тут ?count
) у пункті ORDER BY
, так що це дуже просто зробити:
SELECT ?material ?materialLabel (COUNT(?painting) AS ?count)
WHERE
{
?painting 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(?count)
Як вправу, зробімо також інші запити.
Вогнепальна зброя за виробником
Яка загальна кількість зброї, виробленої кожним виробником?
Hint |
---|
The relevant items and properties are: firearm (Q12796), manufacturer (P176), total produced (P1092). |
Example solution |
---|
SELECT ?manufacturer ?manufacturerLabel (SUM(?produced) AS ?totalProduced)
WHERE
{
?model wdt:P31?/wdt:P279* wd:Q12796;
wdt:P176 ?manufacturer;
wdt:P1092 ?produced.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?manufacturer ?manufacturerLabel
ORDER BY DESC(?produced)
|
Видавці за кількістю сторінок
What is the average (function: AVG
) number of pages of books by each publisher?
Hint |
---|
The relevant items and properties are: publisher (P123), number of pages (P1104). |
Example solution |
---|
SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
?book wdt:P123 ?publisher;
wdt:P1104 ?pages.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
ORDER BY DESC(?avgPages)
|
HAVING
Невелике доповнення до останнього запиту – якщо ви глянете на результати, то можете зауважити, що верхній результат має надзвичайно великий середній показник, що перевищує в десять разів показник другого місця. Трохи досліджень показує, що це тому, що видавець (UTET (Q4002388)) опублікував лише одну книгу з твердженням number of pages (P1104), Grande dizionario della lingua italiana (Q3775610), що трохи спотворює результати. Щоб видалити такі відхилення, ми можемо спробувати вибрати лише тих видавців, які опублікували принаймні дві книги з твердженнями number of pages (P1104) на Вікіданих.
Як ми це робимо? Зазвичай ми обмежуємо результати за допомогою пункту FILTER
, але в цьому разі ми хочемо обмежитися групою (кількістю книг), а не окремим результатом. Це робиться за допомогою пункту HAVING
, який може бути розміщений безпосередньо після пункту GROUP BY
і приймає вираз так само, як FILTER
:
SELECT ?publisher ?publisherLabel (AVG(?pages) AS ?avgPages)
WHERE
{
?book wdt:P123 ?publisher;
wdt:P1104 ?pages.
SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE]". }
}
GROUP BY ?publisher ?publisherLabel
HAVING(COUNT(?book) > 1)
ORDER BY DESC(?avgPages)
Огляд агрегатних функцій
Ось короткий огляд доступних агрегатних функцій:
COUNT
: the number of elements. You can also writeCOUNT(*)
to simply count all results.SUM
,AVG
: the sum or average of all elements, respectively. If the elements aren’t numbers, you’ll get weird results.MIN
,MAX
: the minimum or maximum value of all elements, respectively. This works for all value types; numbers are sorted numerically, strings and other types lexically.SAMPLE
: any element. This is occasionally useful if you know there’s only one result, or if you don’t care which one is returned.GROUP_CONCAT
: concatenates all elements. Useful for example if you want only one result for an item but you want to include informations for a property that may have several statements for this item, such as the occupations of a person. The different occupations may be regrouped and concatenated to appear all in only one variable instead of several lines in the results. If you’re curious, you can look it up in the SPARQL specification.
Крім того, ви можете додати модифікатор DISTINCT
для будь-якої з цих функцій, щоб виключити повторювані результати. Наприклад, якщо є два результати, але вони мають однакові значення в ?var
, то COUNT(?var)
поверне 2
, але COUNT(DISTINCT ?var)
поверне лише 1
. Вам часто доведеться використовувати DISTINCT
, коли ваш запит може повернути ту ж саму групу декілька разів - це може статися, якщо, наприклад, ви використовуєте ?item wdt:P31/wdt:P279* ?class
, і існує декілька шляхів від ?item
до ?class
: ви отримаєте новий результат для кожного з цих шляхів, навіть якщо всі значення в результаті ідентичні. (Якщо ви не групуєте, ви також можете виключити ці дубльовані результати, запустивши запит за допомогою SELECT DISTINCT
, а не просто SELECT
.)
wikibase:Назви й агрегації
Запит, подібний до наведеного нижче, який шукає у Вікіданих усіх науковців із більш ніж двома країнами громадянства, не показує назви цих країн у стовпці ?citizenships:
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)
Щоб показати ?citizenships, явно вкажіть ?personLabel і ?citizenshipLabel у виклику служби wikibase:label
так:
SERVICE wikibase:label {
bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en".
?citizenship rdfs:label ?citizenshipLabel .
?person rdfs:label ?personLabel .
}
Цей запит працює належним чином:
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)
VALUES
One can select items based on a list of items:
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". }
}
One can also select based on a list of values of a specific property:
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". }
}
VALUES can also do more and build enumerations of values possible for a couple (or a tuple) of variables. For example say you want to use (known) custom labels for the persons enumerated in the first « value » example. It’s then possible to use a « values » clause such as VALUES (?item ?customItemLabel) { (wd:Q937 "Einstein") (wd:Q1339 "Bach") }
which ensures that whenever ?item
has value wd:Q937
in a result, ?customItemLabel
own value is Einstein
and whenever ?item
has value wd:Q1339
, ?customItemLabel
’s value is 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". }
}
А далі…
This guide ends here. SPARQL doesn’t: there’s still a lot that I haven’t shown you – I never promised this was going to be a complete guide! If you got this far, you already know a lot about WDQS and should be able to write some very powerful queries. But if you want to learn even more, here are some things you can look at:
- Підзапити. Ви додаєте ще один цілий запит у фігурні дужки (
{ SELECT ... WHERE { ... } LIMIT 10 }
), а результати відображаються у зовнішньому запиті. (Якщо ви знайомі з SQL, вам доведеться трохи переосмислити концепцію – підзапити SPARQL є суто "знизу-вверх" і не можуть використовувати значення з зовнішнього запиту, як, наприклад, "корельовані підзапити" SQL.) MINUS
дає змогу вибрати результати, які не відповідають певному шаблону графа (graph pattern).FILTER NOT EXISTS
в основному еквівалентний (див. специфікацію SPARQL для прикладу, де вони відрізняються), але, принаймні, на WDQS – зазвичай виконується повільніше.
Основною довідкою для цієї та інших тем є специфікація SPARQL.
Also, you can take a look at SPARQL tutorial on Wikibooks and this tutorial by data.world.
And of course, there are some parts of Wikidata still missing as well, such as references, numeric precision (100±2.5), values with units (two kilograms), geocoordinates, sitelinks, statements on properties, and more. You can see how those are modeled as triples under mw:Wikibase/Indexing/RDF Dump Format.