J'écris actuellement un service qui crée de nouveaux éléments (données) par entrée utilisateur. Pour enregistrer ces éléments dans un RDF Graph Store (utilisant actuellement Sesame via Sparql 1.1), je dois ajouter un URI sujet aux données. Mon approche consiste à utiliser un nombre incrémenté pour chaque nouvel élément. Donc, par exemple :
<http://example.org/item/15> dct:title "Example Title" . <http://example.org/item/16> dct:title "Other Item" .
Quelle est la meilleure approche pour obtenir un nombre incrémenté pour les nouveaux éléments (comme l'incémentation automatique dans MySQL / MongoDB) via Sparql? Ou pour émettre des données et le point de terminaison crée automatiquement un URI par un modèle (comme cela est fait pour les nœuds vides). Mais je ne veux pas utiliser de nœuds vides comme sujets pour ces éléments. Existe-t-il une meilleure solution que d'utiliser un nombre incrémenté? Mes utilisateurs ne se soucient pas de l'URI ... et je ne veux pas gérer les collisions comme celles créées en hachant les données et en utilisant le hachage dans le cadre du sujet.
3 Réponses :
En supposant que la classe de vos éléments est http://example.org/ontology/Example
, la requête devient la suivante. Remarque: les éléments doivent être insérés un par un, car un seul nouvel URI est calculé à chaque transaction.
PREFIX dct: <http://purl.org/dc/terms/> INSERT { GRAPH <http://example.com> { ?id dct:title "Example Title" ; a <http://example.org/ontology/Example> . } . } WHERE { SELECT ?id WHERE { { SELECT (count(*) AS ?c) WHERE { GRAPH <http://example.com> { ?s a <http://example.org/ontology/Example> } } } BIND(IRI(CONCAT("http://example.org/item/", STR(?c))) AS ?id) } }
(Testé avec GraphDB 8.4.0 en utilisant RDF4J 2.2.2)
C'est assez intelligent. Cela ne fonctionnera pas si les ressources sont supprimées et créées.
Risque également de collisions si l'exécution de transactions en dessous du niveau d'isolement SERIALIZABLE.
Cette approche est intelligente, en effet. S'il est implémenté comme dans la réponse, le client n'a aucune connaissance de la ressource créée (car sparql ne répond que par ~ "Success"). Malheureusement, il n'y a pas de prise en charge des requêtes Select-Update dans sparql, ce qui nécessite de diviser cette requête en deux requêtes.
Vous avez dit que vous étiez ouvert à d'autres options qu'un nombre auto-incrémenté. Une bonne alternative consiste à utiliser des UUID .
Si vous ne vous souciez pas du tout de quoi l'URI ressemble à, vous pouvez utiliser le UUID
fonction:
INSERT { ?uri dct:title "Example Title" } WHERE { BIND (IRI(CONCAT("http://example.org/item/", strUUID())) AS ?uri) }
Cela générera des URI comme
.
Si vous préférez avoir des URI HTTP dans votre propre espace de noms, vous pouvez utiliser strUUID
:
INSERT { ?uri dct:title "Example Title" } WHERE { BIND (UUID() AS ?uri) }
Cela générera des URI comme http://example.org/item /73cd4307-8a99-4691-a608-b5bda64fb6c1
.
UUIDs sont plutôt bons. Le risque de collision est négligeable. Les fonctions font partie du standard SPARQL. Le seul inconvénient est qu'ils sont longs et laids.
Pour réduire la longueur, vous pouvez utiliser un hachage par-dessus. Par exemple. BIND (IRI (CONCAT ("http://example.org/item/", MD5 (STRUUID ()))) comme? uri)
. Pas vraiment plus jolie, mais la chaîne résultante sera plus courte. Le risque de collision est encore à peu près négligeable.
Si vous maintenez un compteur désigné pendant les mises à jour, alors quelque chose du genre le fera,
insérez d'abord un compteur dans votre ensemble de données
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> delete { #remove the old value of the counter graph <urn:counters> {<urn:Example> <urn:count> ?old} } insert { #update the new value of the counter graph <urn:counters> {<urn:Example> <urn:count> ?new} # add your new data using the constructed IRI GRAPH <http://example.com> { ?id dct:title "Example Title" ; a <http://example.org/ontology/Example> . } . } where { # retrieve the counter graph <urn:counters> {<urn:Example> <urn:count> ?old} # compute the new value bind(?old+1 as ?new) #construct the IRI bind(IRI(concat("http://example.org/item/", str(?old))) as ?id) }
puis une mise à jour typique devrait ressembler à:
insert data { graph <urn:counters> {<urn:Example> <urn:count> 1 } }
Cette approche est bonne, au cas où elle serait divisée en trois demandes. Un obtenant le compteur actuel (il est donc disponible dans le client demandeur), un incrémentant le compteur et un insérant des données avec le compteur demandé. S'il est implémenté comme dans la réponse, le client n'est pas en mesure de savoir quelle ressource a été créée et de la traiter davantage (car sparql ne répond que par ~ "Success"). C'est vraiment un inconvénient de sparql (pas de prise en charge des requêtes Select-Update).
@Roy divisant la mise à jour en 3 différents simples freine son atomicité. Pour le problème principal, à savoir avoir des moyens d'identifier l'ID créé après la mise à jour, il existe toujours une solution de contournement. Par exemple, si vous gérez une sorte d'ID de demande externe unique côté client, vous pouvez les associer aux ID créés et les utiliser par la suite pour trouver ce qui a été ajouté. Vous pouvez transmettre la valeur de l'ID de demande dans le cadre de la mise à jour, par exemple inclure dans la clause where
VALUES? rq_id {"abscedf"}
et ajouter une instruction comme ? id
.
Ce que vous décrivez est une solution de contournement, c'est vrai. Mais cela ne semble pas correspondre aux paradigmes actuels de l'architecture Web. Par exemple. il n'est pas utilisable pour la réponse UUID et il n'est pas utilisable dans le cas où plusieurs clients auraient accès au magasin - comme en augmentant un client de magasin (service). L'alternative d'avoir un service de création d'identifiant tiers est comme casser un papillon sur une roue ... ainsi que garder une trace de tous les sujets d'une classe et de les différencier lors de l'insertion.
@Roy, la version antérieure de Sparql 1.1 est apparue il y a près de 10 ans, il est donc impossible de s'attendre à ce qu'elle soit à jour avec les architectures Web modernes. Dans le cas où vous avez un schéma fixe, une façon de résoudre le problème consiste soit à créer un service GraphQL léger interagissant avec le magasin RDF qui correspond à vos besoins particuliers et ainsi à l'exposer à vos clients, soit à attendre qu'un tiers (fournisseur ou communauté) fournissent une solution solide et utilisable à votre problème.