关于数据建模的说明
所以首先你确定当你谈论列表时你一定是指RDF列表吗?区别很重要,因为它改变了数据的形状以及你完成事情的方式。
RDF 列表是将值连接在一起的空白节点的有序序列,例如
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix : <http://example.org/> .
:root :values [ rdf:first "a" ;
rdf:rest [ rdf:first "b" ;
rdf:rest [ rdf:first "c" ;
rdf:reset rdf:nil ] ] ] .
正如你所看到的,它在三元组方面有很多开销,我们当然可以像这样简化 Turtle 中的语法:
@prefix : <http://example.org/> .
:root :value ( "a" "b" "c" ) .
这等效于第一个示例,它只是隐藏 Turtle 解析器将创建的显式三元组将遇到此语法。dotNetRDF 中包含的列表扩展专门用于处理 RDF 列表。
而您所说的列表可能只是与属性相关联的一组值,例如
@prefix : <http://example.org/> .
:root :value "a" ;
:value "b" ;
:value "c" .
正如你所看到的,这实际上只是陈述了几个三元组,每个三元组都说明了属性的值。在 Turtle 中,我们可以使用语法进一步简化这一点,,
以避免重复谓词:
@prefix : <http://example.org/> .
:root :value "a" , "b" , "c" .
这种方法的缺点是,由于 RDF 图是三元组的无序集,因此不能保留值的顺序或重复值。如果您需要订购或重复,那么您将需要使用 RDF 列表方法。
我的其余答案将展示如何使用任何一种数据建模方法来做事。
插入值列表
你如何做到这一点取决于你是否想要一个 RDF 列表或只是一些值,你是完全正确的 dotNetRDF 在处理构建参数化 SPARQL 时没有任何内置支持来处理此类事情。
如果您想要一个 RDF 列表,那么您需要编写您的模板,以便它可以获取必要数量的项目,例如
paramString.CommandText =
@"INSERT DATA
{
data:person_1 app:nicknames [ rdf:first @nick1 ;
rdf:rest [ rdf:first @nick2 ;
rdf:rest rdf:nil ] ] .
}";
paramString.SetParameter("nick1", "Rob");
paramString.SetParameter("nick2", "Bob");
而且您显然可以根据需要扩展此模式以处理较短/较长的列表。显然,这需要用户做很多工作,所以如果这是您需要的,那么我们当然可以考虑在未来的版本中为用户添加一个功能。
如果您只是插入几个值,您可以使用单个三元组模板并简单地依次插入每个参数并执行它,例如
paramString.CommandText =
@"INSERT DATA
{
data:person_1 app:nicknames @nickname.
}";
foreach (String nick : nicknames)
{
paramString.SetParameter("nickname", nick);
// Execute the update
}
或者你可以改变你的模板,让每个昵称都有一个三元组,这里我,
再次使用语法来避免重复主语和谓语:
paramString.CommandText =
@"INSERT DATA
{
data:person_1 app:nicknames @nick1 , @nick2 .
}";
paramString.SetParameter("nick1", "Rob");
paramString.SetParameter("nick2", "Bob");
与 RDF 列表方法一样,您可以根据需要将此模式扩展到更多/更少的列表项。同样,如果这是您希望 dotNetRDF 为您做的事情,我们可以考虑在未来的版本中添加它。
检查两个列表是否相交
对于 RDF 列表方法:
queryString.CommandText =
@"SELECT ?personWithSameNickname WHERE {
data:person_1 app:nicknames [ rdf:rest*/rdf:first ?nicknames ].
?personWithSameNickname app:nicknames [ rdf:rest*/rdf:first ?nicknames ] .
FILTER(!SAMETERM(data:person_1, ?personWithSameNickname))
}";
本质上,您只需为起始节点选择所有昵称,然后对所有人执行相同操作,并依靠 SPARQL 连接语义来找到我们的交集。
请注意使用rdf:rest*/rdf:first
属性路径遍历 RDF 列表的所有值节点以提取实际昵称。此外,由于起始节点将与自身相交,我们使用 a!SAMETERM(data:person_1, ?personWithSameNickname)
来FILTER
消除自身的匹配,但是如果您希望避免FILTER
如果您只是使用多重三元法,则查询会更简单:
queryString.CommandText =
@"SELECT ?personWithSameNickname WHERE {
data:person_1 app:nicknames ?nicknames .
?personWithSameNickname app:nicknames ?nicknames .
FILTER(!SAMETERM(data:person_1, ?personWithSameNickname))
}";
再次简单地为您的起始节点选择所有昵称,然后对所有人执行相同操作,并依靠 SPARQL 连接语义来找到我们的交集。
现在,如果您想按交叉点的数量对人员进行排名,那么我们可以使用GROUP BY
and来执行此操作,ORDER BY
并且可以将其添加到查询的任一变体中。我将使用第二种变体,因为基本查询更简单:
queryString.CommandText =
@"SELECT ?personWithSameNickname (COUNT(?nicknames) AS ?matches) WHERE {
data:person_1 app:nicknames ?nicknames .
?personWithSameNickname app:nicknames ?nicknames .
FILTER(!SAMETERM(data:person_1, ?personWithSameNickname))
}
GROUP BY ?personWithSameNickname
ORDER BY DESC(?matches)";
因此,首先我们向 中添加一个聚合SELECT
,特别是我们要计算昵称的数量。然后,我们还需要GROUP BY
在?personWithSameNickname
变量上添加一个,因为我们希望为每个具有相交昵称的人创建一个组。这也意味着将为每个组计算我们的聚合,以便我们可以使用ORDER BY
降序排列匹配。