Формирование запроса SPARQL

У меня есть данные RDF, и я хочу сформировать запрос SPARQL для извлечения записей, соответствующих определенному имени организма.

Просто к вашему сведению, я использовал RDF4J для создания записей RDF с использованием доступных данных JSONLD. У меня проблема с получением записей, которые соответствуют любому конкретному набору PropertyValue. Пример: все записи, имеющие организм как Equus caballus, или все записи, имеющие идентификатор представления как GSB-7331.

Любая помощь горячо приветствуется.

Записи данных такие:

@prefix schema: <http://schema.org/> .
@prefix obo: <http://purl.obolibrary.org/obo/> .
@prefix ebi-bsd: <https://www.ebi.ac.uk/biosamples/> .
@prefix biosamples: <http://identifiers.org/biosample/> .

biosamples:SAMEA104496657 a schema:DataRecord ;
schema:dateCreated "0002-10-15T00:00:00Z"^^schema:Date ;
schema:dateModified "2019-07-23T18:33:14.867Z"^^schema:Date ;
schema:identifier "SAMEA104496657" ;
schema:isPartOf ebi-bsd:samples ;
schema:mainEntity _:b0 .

ebi-bsd:samples a schema:Dataset .

_:b0 a schema:Sample , obo:OBI_0000747 ;
schema:additionalProperty _:b1 , _:b2 , _:b3 , _:b4 ;
schema:description "Blood samples N123" ;
schema:identifier "SAMEA104496657" ;
schema:name "N123" ;
schema:sameAs biosamples:SAMEA104496657 .

_:b1 a schema:PropertyValue ;
schema:name "organism" ;
schema:value "Equus caballus" ;
schema:valueReference obo:NCBITaxon_9796 .

obo:NCBITaxon_9796 a schema:DefinedTerm .

_:b2 a schema:PropertyValue ;
schema:name "submission description" ;
schema:value "ELOAD_294_samples" .

_:b3 a schema:PropertyValue ;
schema:name "submission identifier" ;
schema:value "GSB-7331" .

_:b4 a schema:PropertyValue ;
schema:name "submission title" ;
schema:value "ELOAD_294" .
@prefix schema: <http://schema.org/> .
@prefix obo: <http://purl.obolibrary.org/obo/> .
@prefix ebi-bsd: <https://www.ebi.ac.uk/biosamples/> .
@prefix biosamples: <http://identifiers.org/biosample/> .

biosamples:SAMEA104625758 a schema:DataRecord ;
schema:dateCreated "0014-06-07T00:00:00Z"^^schema:Date ;
schema:dateModified "2019-08-06T17:46:01.812Z"^^schema:Date ;
schema:identifier "SAMEA104625758" ;
schema:isPartOf ebi-bsd:samples ;
schema:mainEntity _:b0 .

ebi-bsd:samples a schema:Dataset .

_:b0 a schema:Sample , obo:OBI_0000747 ;
schema:additionalProperty _:b1 , _:b2 , _:b3 ;
schema:description "Colorectal Cancer Tumor Sequenced Samaple;      
schema:identifier "SAMEA104625758" ;
schema:name "P-0009062-T01-IM5" ;
schema:sameAs biosamples:SAMEA104625758 ;
schema:subjectOf "http://www.ebi.ac.uk/ena/data/view/SAMEA104625758" .

:b1 a schema:PropertyValue ;
schema:name "common name" ;
schema:value "Human" ;
schema:valueReference obo:NCBITaxon_9606 .

obo:NCBITaxon_9606 a schema:DefinedTerm .

_:b2 a schema:PropertyValue ;
schema:name "organism" ;
schema:value "Homo sapiens" ;
schema:valueReference obo:NCBITaxon_9606 .

_:b3 a schema:PropertyValue ;
schema:name "scientific name" ;
schema:value "Homo sapiens" ;
schema:valueReference obo:NCBITaxon_9606 .

Код, который я использую для создания данных RDF TURTLE, приведен ниже. Я загружаю образцы данных в формате JSONLD с сайта https://www.ebi.ac.uk/biosamples/samples/SAMN03177689.ldjson

import org.apache.commons.io.FileUtils;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParser;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Scanner;
import java.util.concurrent.Callable;

public class BioSchemasRdfGenerator implements Callable<Void> {
    private Logger log = LoggerFactory.getLogger(getClass());
    private static File file;
    private static long sampleCount = 0;
    private final URL url;

    public static void setFilePath(String filePath) {
        file = new File(filePath);
    }

    BioSchemasRdfGenerator(final URL url) {
        log.info("HANDLING " + url.toString() + " and the current sample count is: " + ++sampleCount);

        this.url = url;
    }

    @Override
    public Void call() throws Exception {
        requestHTTPAndHandle(this.url);

        return null;
    }

    private static void requestHTTPAndHandle(final URL url) throws Exception {
        final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        int response;

        try {
            conn.setRequestMethod("GET");
            conn.connect();
            response = conn.getResponseCode();

            if (response == 200) {
                handleSuccessResponses(url);
            }
        } catch (final Exception e) {
            throw new RuntimeException(e);
        } finally {
            conn.disconnect();
        }
    }

    private static void handleSuccessResponses(final URL url) {
        try (Scanner sc = new Scanner(url.openStream())) {
            final StringBuilder sb = new StringBuilder();

            while (sc.hasNext()) {
                sb.append(sc.nextLine());
            }

            try (InputStream in = new ByteArrayInputStream(sb.toString().getBytes(StandardCharsets.UTF_8))) {
                String dataAsRdf = readRdfToString(in);

                write(dataAsRdf);
            } catch (final Exception e) {
                throw new RuntimeException(e);
            }
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    @SuppressWarnings(value = "deprecation")
    private static void write(final String sampleData) throws Exception {
        FileUtils.writeStringToFile(file, sampleData, true);
    }

    /**
     * @param in a rdf input stream
     * @return a string representation
     */
    private static String readRdfToString(final InputStream in) {
        return graphToString(readRdfToGraph(in));
    }

    /**
     * @param inputStream an Input stream containing rdf data
     * @return a Graph representing the rdf in the input stream
     */
    private static Collection<Statement> readRdfToGraph(final InputStream inputStream) {
        try {
            final RDFParser rdfParser = Rio.createParser(RDFFormat.JSONLD);
            final StatementCollector collector = new StatementCollector();

            rdfParser.setRDFHandler(collector);
            rdfParser.parse(inputStream, "");

            return collector.getStatements();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Transforms a graph to a string.
     *
     * @param myGraph a sesame rdf graph
     * @return a rdf string
     */
    private static String graphToString(final Collection<Statement> myGraph) {
        final StringWriter out = new StringWriter();
        final TurtleWriterCustom turtleWriterCustom = new TurtleWriterCustom(out);

        return modifyIdentifier(writeRdfInTurtleFormat(myGraph, out, turtleWriterCustom));
    }

    private static String modifyIdentifier(String rdfString) {
        if (rdfString != null)
            rdfString = rdfString.replaceAll("biosample:", "");

        return rdfString;
    }

    private static String writeRdfInTurtleFormat(Collection<Statement> myGraph, StringWriter out, TurtleWriterCustom writer) {
        try {
            writer.startRDF();
            handleNamespaces(writer);

            for (Statement st : myGraph) {
                writer.handleStatement(st);
                //below line is commented: for short RDF
                //writer.writeValue(st.getObject(),O true);
            }

            writer.endRDF();
        } catch (final RDFHandlerException e) {
            throw new RuntimeException(e);
        }

        return out.getBuffer().toString();
    }

    private static void handleNamespaces(final TurtleWriterCustom writer) {
        writer.handleNamespace("schema", "http://schema.org/");
        writer.handleNamespace("obo", "http://purl.obolibrary.org/obo/");
        writer.handleNamespace("ebi-bsd", "https://www.ebi.ac.uk/biosamples/");
        writer.handleNamespace("biosamples", "http://identifiers.org/biosample/");
    }
}

person Dipayan Gupta    schedule 26.02.2020    source источник
comment
а что пробовали до сих пор?   -  person UninformedUser    schedule 26.02.2020
comment
Большое спасибо @UninformedUser. Я использовал библиотеку rdf4j для создания дампа RDF, и, поскольку я не очень хорошо знаком с форматом RDF, я боролся с запросами. Теперь предложенный вами запрос извлекает образцы с организмами, указанными в схеме: значение, но наряду с этим он также извлекает все другие образцы. Пример: если я использую организм в качестве ABC, он ничего не извлекает, но если я использую правильное имя, например, Equus caballus, он извлекает все образцы Equus caballus + остальные. Можем ли мы использовать какую-то фильтрацию, которая будет возвращать образцы только с организмом Equus caballus?   -  person Dipayan Gupta    schedule 27.02.2020
comment
не уверен что ты имеешь ввиду. Запрос должен возвращать все и только те записи, основной сущностью которых является некий организм с указанным именем. Пожалуйста, поделитесь данными, на которых запрос не работает.   -  person UninformedUser    schedule 27.02.2020
comment
@UninformedUser, если у меня есть две записи, одна с организмом как Homo Sapiens, а другая с организмом как Equus caballus, запрос -- select ?r {?r a schema:DataRecord ; схема: mainEntity [схема: дополнительное свойство [схема: имя организма ; schema:value Equus caballus] ] } возвращает обе записи, в идеале ожидая возвращения одной записи с Equus caballus в качестве организма. Фильтрация по названию организма не работает. Еще раз спасибо за помощь!   -  person Dipayan Gupta    schedule 27.02.2020
comment
этого никогда не может произойти с этим запросом, поэтому проблема в вашем наборе данных. Пожалуйста, покажите данные, могу поспорить, что вы сделали что-то не так во время генерации, например. повторное использование некоторых объектов или тому подобное. Поэтому, пожалуйста, покажите также другую запись данных, просто добавьте ее к своему вопросу, пожалуйста.   -  person UninformedUser    schedule 27.02.2020
comment
@UninformedUser Я отредактировал вопрос, чтобы добавить больше записей данных. Возможно, я сделал что-то не так в коде, который преобразовывал 18 миллионов записей JSON LD в RDF TURTLE. большое спасибо за вашу помощь в этом!   -  person Dipayan Gupta    schedule 27.02.2020
comment
хорошо, да, вы должны делать что-то не так. Посмотрите на свой набор данных, обе записи относятся к schema:mainEntity _:b0 . — так что все данные объединены в узле _:b0 — я не знаю, как вы создали набор данных, но вы не можете повторно использовать объекты во время генерации, если они обозначают разные объекты. Вы можете добавить генерацию кода или сначала начать двойную проверку   -  person UninformedUser    schedule 27.02.2020
comment
Большое спасибо за указатель. Да, вы правы @UninformedUser. Я пытаюсь изменить его, чтобы генерировать правильные данные RDF. Я также отредактировал свой вопрос и добавил код Java, который генерирует дамп RDF, на случай, если вы захотите взглянуть.   -  person Dipayan Gupta    schedule 28.02.2020


Ответы (1)


Ваш код выглядит намного сложнее, чем должен быть. Чтобы загрузить файл JSON-LD по удаленному URL-адресу в качестве модели RDF с использованием RDF4J, вы можете просто сделать это:

String file = "https://www.ebi.ac.uk/biosamples/samples/SAMN03177689.ldjson";
try (InputStream input = new URL(file).openStream()) {
    Model m = Rio.parse(input, file, RDFFormat.JSONLD);
}

Если вы затем захотите написать эту модель в синтаксисе Turtle, все, что вам нужно сделать, это следующее:

// replace System.out with your own outputstream if you want to write to file
Rio.write(m, System.out, RDFFormat.TURTLE); 

Если я запускаю это в вашем образце файла, я получаю следующее:

@prefix biosample: <http://identifiers.org/biosample/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix schema: <http://schema.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

biosample:SAMN03177689 a schema:DataRecord;
  schema:dateCreated "2014-12-12T06:54:48.957Z"^^schema:Date;
  schema:dateModified "2019-03-13T09:41:33.81Z"^^schema:Date;
  schema:identifier "biosample:SAMN03177689";
  schema:isPartOf <https://www.ebi.ac.uk/biosamples/samples>;
  schema:mainEntity <https://www.ebi.ac.uk/biosamples/samples/SAMN03177689> .

<https://www.ebi.ac.uk/biosamples/samples> a schema:Dataset .

<https://www.ebi.ac.uk/biosamples/samples/SAMN03177689> a <http://purl.obolibrary.org/obo/OBI_0000747>,
    schema:Sample;
  schema:additionalProperty _:genid-2e6f9d5c4cc34db8b5ab6e72e7857e31-b0 .

_:genid-2e6f9d5c4cc34db8b5ab6e72e7857e31-b0 a schema:PropertyValue;
  schema:name "INSDC center name";
  schema:value "FDA" .

  [snip]

Обратите внимание, что экземпляр schema:Sample здесь имеет фактический IRI в качестве идентификатора, а не пустой узел: <https://www.ebi.ac.uk/biosamples/samples/SAMN03177689>.

В вашем коде происходят странные вещи. Прежде всего есть этот метод modifyIdentifier. По какой-то причине он отсекает все вхождения biosample: с пустой строкой. Я не уверен, почему вы хотите это сделать (кажется плохой идеей манипулировать строковыми данными таким образом). Это также делает это таким образом, что вывод является недопустимым синтаксисом черепахи. Если в приведенном выше примере вы замените biosample: пустой строкой, вы получите в строке 1:

@prefix <http://identifiers.org/biosample/> .

что не является допустимым определением префикса (в нем отсутствует двоеточие после prefix). А дальше у вас будет

SAMN03177689 a schema:DataRecord;

Это недействительная ссылка IRI.

Тогда есть этот TurtleWriterCustom класс. Вы не показываете код для этого класса, но, учитывая его имя, я подозреваю, что он пытается выполнить дополнительную нестандартную настройку вывода и при этом искажает идентификаторы ваших образцов, каким-то образом заменяя их (идентичными) пустыми узлами. .

Честно говоря, я даже не уверен, почему вы вообще конвертируете JSON-LD в Turtle, потому что, если ваша цель — загрузить эти данные в базу данных RDF, чтобы вы могли выполнять запросы SPARQL, вы можете просто загрузить JSON-LD файл напрямую:

Repository repo = ...; // your RDF4J database
try (RepositoryConnection conn = repo.getConnection()) {
     conn.add(input, file, RDFFormat.JSONLD);

     // data added to database - you can now query. 
     String query = "prefix schema: <http://schema.org/> "
            + "select ?r {?r a schema:DataRecord ; "
            + "schema:mainEntity [schema:additionalProperty [schema:name \"organism\" ; schema:value \"Escherichia coli\"] ] }";

     conn.prepareTuplequery(query).evaluate().forEach(bs -> System.out.println(bs));
}

результат:

[r=http://identifiers.org/biosample/SAMN03177689]
person Jeen Broekstra    schedule 01.03.2020
comment
Спасибо @Джин. Я попробую это и дам вам знать. Большое спасибо за упрощение. - person Dipayan Gupta; 02.03.2020