How to define advanced custom rule in GraphDb ruleset - rule-engine

Let's start from the begin.
There are some persons stored in graph database with predicates like birthDate, name, etc.
I'm currently trying to write custom rule which add some new fact for persons older than e.g. 50 years.
So to achieve that two steps are needed: calculate age and filter age.
SPARQL query could look like below:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX drl: <http://...>
SELECT DISTINCT ?person ?age
WHERE {
?person rdf:type drl:Person.
?person drl:Person.birthDate/drl:value ?birthDate;
BIND(year(now()) - year(?birthDate) as ?age)
FILTER (?age > 50)
}
But how to write such rule?
According to https://graphdb.ontotext.com/documentation/standard/reasoning.html#entailment-rules
The syntax of a rule definition is as follows:
Id: <rule_name>
<premises> <optional_constraints>
-------------------------------
<consequences> <optional_constraints>
So is it possible to use BIND and FILTER keywords inside <premises> or <optional_constraints>?
As I suppose the answear is: NOT :(
Maybe there is anoter way to have such rule?
Seems that SPARQL SPIN functions and magic predicates are not an option for GraphDB?

When you say, "which add some new fact for persons older than e.g. 50 years." I interpret that to mean
You want to construct new triples and add them to the triplestore
You want to detect the new triples with a query like the one you show
Let's say you wish to add a concept of "Retirement Pension Account" to any Person over 60 ( I would say 50 like you, but I am 57 and not ready to retire so I'll make it 60 ;-).
If you have Person data that matches, you can construct triples with SPARQL:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX drl: <http://example.org/drl/>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
CONSTRUCT
{
?person drl:beneficiaryOf ?acct .
?acct a drl:RetirementAccount ;
drl:hasId ?acctId ;
drl:hasBalance ?balance .
}
WHERE {
?person rdf:type drl:Person ;
drl:birthDate/drl:value ?birthDate;
BIND(year(now()) - year(?birthDate) as ?age)
FILTER (?age > 50)
BIND(IRI(CONCAT(STR(drl:),"_Account_",STRUUID())) AS ?acct)
BIND(IRI(CONCAT(STR(drl:),"_AccountID_",STRUUID())) AS ?acctId)
BIND(STRDT("10.0",xsd:double) AS ?balance)
}
Is this the kind of thing you want to do?

Related

I am having a hard time joining between unrelated domains in JPA

I am experiencing various things while studying JPA, but I am too unfamiliar with it, so I would like to get some advice.
The parts I got stuck in during my study were grouped into three main categories. Could you please take a look at the code below?
#Repository
public interface TestRepository extends JpaRepository<TestEntity,Long> {
#Query(" SELECT
, A.test1
, A.test2
, B.test1
, B.test2
FROM TEST_TABLE1 A
LEFT JOIN TEST_TABLE2 B
ON A.test_no = B.test_no
WHERE A.test3 = ?1 # Here's the first question
if(VO.test4 is not null) AND B.test4 = ?2") # Here's the second question
List<Object[] # Here's the third question> getTestList(VO);
}
First, is it possible to extract test3 from the VO received when using native sql?
Usually, String test1 is used like this, but I wonder if there is any other way other than this.
Second, if extracting is possible in VO, can you add a query in #QUERY depending on whether Test4 is valued or not?
Thirdly, if I use List<Object[]>, can the result of executing a query that is not in the already created entity (eg, test1 in TEST_TABLE2, which is not in the entity of TEST_TABLE1) can be included?,
First, is it possible to extract test3 from the VO received when using native sql? Usually, String test1 is used like this, but I wonder if there is any other way other than this.
Yes, it is possible.
You must use, eg where :#{[0].test3} is equals vo.test3
[0] is position the first param, past for method annotated with #Query
#Query(value = "SELECT a.test1, a.test2, b.test1, b.test2
FROM test_table1 a
LEFT JOIN test_table2 b ON a.test_no = b.test_no
WHERE a.test3 = :#{[0].test3}", nativeQuery = true)
List<Object[]> getList(VO);
Second, if extracting is possible in VO, can you add a query in #QUERY depending on whether Test4 is valued or not?
You can use a trick eg:
SELECT ... FROM table a
LEFT JOIN table b ON a.id = b.id
WHERE a.test3 = :#{[0].test3}
AND (:#{[0].test4} IS NOT NULL AND b.test4 = :#{[0].test4})
Thirdly, if I use List<Object[]>, can the result of executing a query that is not in the already created entity (eg, test1 in TEST_TABLE2, which is not in the entity of TEST_TABLE1) can be included?
Sorry, but I not understand the third question.
Maybe this tutorial will help you: https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions

idh_hist query is very slow while searching with date

I am trying to write a query to search thru MFG/PRO invoice table 'idh_hist' for specific date range. It is running very slow when added the date condition. But when I put off the date condition, it is very fast. Can you please suggest ways to write a query on idh_hist that runs reasonably faster with conditions.
Following is my query:
for each idh_hist no-lock where idh_domain = "d0002"
and idh_due_date = TODAY:
/* display code here... */
end.
Thanks in advance!
Database Index:
Flags Index Name Cnt Field Name
----- --------------------- ---- ---------------------
idh_fsm_type 4 + idh_domain
+ idh_fsm_type
+ idh_nbr
+ idh_line
pu idh_invln 4 + idh_domain
+ idh_inv_nbr
+ idh_nbr
+ idh_line
idh_part 4 + idh_domain
+ idh_part
+ idh_inv_nbr
+ idh_line
u oid_idh_hist 1 + oid_idh_hist
You do not appear to have an index that uses idh_due_date. You will need to add such an index.
The 4gl uses rules to select indexes based on the WHERE clause. The most important rule is that leading components of the index which have equality matches will be used.
The query you have shown only has one such match on idh_domain. So then tie breaker rules are applied. This will result in the idh_invln index being chosen.
As it is, to satisfy your query all records that match the "idh_domain" field need to be searched. (If you only have one domain that means that you are doing a table scan.)
You probably want to add an index on idh_domain and idh_due_date. That would be a perfect match for your query.

SPARQL Join two graphs : value precedence?

I have two graphs that contain values for enrollment in clinical trials. Each graph has incomplete records for enrollment so I want to combine the graphs to get a more complete listing of the enrollment values.
The KMD graph should take precedence. If enrollment is present in both the KMD graph and the KCTGOV graph, use the value from KMD. If enrollment is missing in KMD, use the enrollment value from KCTGOV.
I am getting close with the query below: I bring in the enrollment values from each graph by successfully joining on the value of ?nctId. How do I then create a result for ?enrollment that is from KMD when present in that graph or comes from KCTGOV when the value is missing in KMD? The code below creates separate enrollment columns named enrollKMD and enrollKCT. I need a merge of those columns.
Suggestions greatly appreciated!
PREFIX kmd: <http://www.example.org/kmd/>
PREFIX lct: <http://data.linkedct.org/vocab/resource/>
SELECT *
FROM NAMED <http://localhost:8890/KMD>
FROM NAMED <http://localhost:8890/KCTGOV>
WHERE
{
GRAPH <http://localhost:8890/KMD>
{
?obs a kmd:Study ;
kmd:hasOrgId ?orgId .
OPTIONAL
{
?obs kmd:hasNctId ?nctIdURI .
}
OPTIONAL {?obs kmd:hasEnrollment ?enrollkmd.}
# Create STR of NCTID for merge
BIND(strafter(str(?nctIdURI), "kmd/") AS ?nctId )
}
OPTIONAL
{
GRAPH <http://localhost:8890/KCTGOV>
{
OPTIONAL{ ?govNctIdURI lct:enrollment ?enrollKCT.}
# Create STR of NCTID for merge
BIND(UCASE(strafter(str(?govNctIdURI), "trial/")) AS ?nctId )
}
}
}ORDER BY ?orgId
You can do this with an IF operation, like so:
select (if(bound(?enrollkmd), ?enrollkmd, ?enrollKCT) as ?enrollment)
where ...
The IF operator checks if ?enrollkmd is bound to a value, if so, it returns that value, otherwise it returns the value of ?enrollKCT. The outcome of the operator is then bound to the ?enrollment variable in your query result.
Of course, since you are no longer using the wildcard-select ('*'), you will now need to explicitly add all variables you want returned. So the full select-clause will become something like this:
select ?obs ?orgId ?nctId (if(bound(?enrollkmd), ?enrollkmd, ?enrollKCT) as ?enrollment)
adapt to taste.

yesql - named parameter for column name

I have a simple update statement:
-- name: add-response!
UPDATE survey
SET :question = :response
WHERE caseid = :caseid
And I invoke it like this:
(add-response! db-spec "q1" 2 1001)
However, yesql doesn't like using a string as a parameter for the column - it translates "q1" to 'q1', which isn't valid postgres syntax.
"BatchUpdateException Batch entry 0 UPDATE survey SET 'q1' = 2
WHERE caseid = 1001 was aborted."
Is there a way to make this work? I've tried using the question name as a symbol: 'q1. That doesn't work because:
"PSQLException Can't infer the SQL type to use for an instance of
clojure.lang.Symbol."
I've had same problem some time ago with yesql, so I investigated its source code. I turns out that yesql converts query like
UPDATE survey SET :question = :response WHERE caseid = :caseid
to
["UPDATE survey SET ? = ? WHERE caseid = ?" question response caseid]
and feeds it to clojure.java.jdbc/query. So this is just a prepared statement. According to this StackOverflow question there is no way to pass column names as parameters to DB query. That actually makes sence, because one of purposes of prepared statements is to force values to be always treated as values and thus protect you from SQL injections or similar issues.
I your case, you could use clojure.java.jdbc/update! as it clearly allows parameterized colum names:
(:require [clojure.java.jdbc :as j])
(j/update! db-spec :survey
{"q1" 2}
["caseid = ?" 1001])
Hope that helps. Cheers!

MongoDB - Contains (LIKE) query on concatenated field

I am new in MongoDB.
I am programming an application with spring data and mongodb and I have one class with two fields: firstname and lastname.
I need one query for documents that contain one string in the full name (firstname + lastname).
For example: firstname = "Hansen David", lastname = "Gonzalez Sastoque" and I have a query to find David Gonzalez. In this example I expect there to be a match.
Concatenate two strings solves it but I don't know how to perform this.
Create a new array field (call it names) in the document and in that array put each name split by space. In your example the array would have the following contents:
hansen
david
gonzalez
sastoque
(make them all lower case to prevent case insensitivity issues)
Before you do your query, convert your input to lower case and split it by spaces as well.
Now, you can use the $all operator to achieve your objective:
db.persons.find( { names: { $all: [ "david", "gonzalez" ] } } );
You can use $where modifier in your queries:
db.users.findOne({$where: %JavaScript to match the document%})
In your case it may look like this:
db.users.findOne({$where: "this.firstname + ' ' + this.lastname == 'Gonzalez Sastoque'"})
or this:
db.users.findOne({$where: "this.firstname.match(/Gonzalez/) && this.lastname.match(/Sastoque/)"})
My last example does exactly what you want.
Update: Try following code:
db.users.findOne({$where: "(this.firstname + ' ' + this.lastname).match('David Gonzalez'.replace(' ', '( .*)? '))"})
You should split your full name into a first name and a last name, then do your query on both fields, using the appropriate MongoDB query selectors.