Xquery/eXist-db - iterating over a collection +/- 3 positions from current document - xquery

In eXist-db I have hundreds of documents in /db/apps/foo/resources/documents like so:
...
BNF9992-J305-1.xml
BNF9992-J305-5.xml
BNF9992-J308-9.xml
BNF9992-J310-8.xml
BNF9992-J311-1.xml
BNF9992-J312-6.xml
BNF9992-J312-7.xml
BNF9992-J315-9.xml
BNF9992-J316-2.xml
BNF9992-J317-2.xml
BNF9992-J319-3.xml
...
Imagine I want to present to the user a list of 3 documents appearing before and after a specific document (based on alpha-numeric sort). So, my 'current document' is BNF9992-J312-7.xml, and I want to show the user something like:
BNF9992-J310-8.xml
BNF9992-J311-1.xml
BNF9992-J312-6.xml
BNF9992-J312-7.xml (current document)
BNF9992-J315-9.xml
BNF9992-J316-2.xml
BNF9992-J317-2.xml
Is there a function/method in Xquery 3.1 for iterating up/down a list of documents once they've been retrieved. The most I've been able to do is a simple retrieval of document names from a collection:
for $resource in collection("/db/apps/foo/resources/documents")
let $uri := base-uri($resource)
return util:unescape-uri(replace($uri, ".+/(.+)$","$1"), "UTF-8")
But I don't know how to iterate up and down the list from a given document.
Perhaps writing the list into nodes and applying a formula to node ordinals?
Many thanks.

If this were a list of strings $list, and the "current string" is $s, then I would do
let $i := index-of($list, $s)
return subsequence($list, $i - 3, 7)
I'm not sure whether the fact that you have a list of documents (rather than strings) changes this.

Related

Create a perl hash from a db select

Having some trouble understanding how to create a Perl hash from a DB select statement.
$sth=$dbh->prepare(qq{select authorid,titleid,title,pubyear from books});
$sth->execute() or die DBI->errstr;
while(#records=$sth->fetchrow_array()) {
%Books = (%Books,AuthorID=> $records[0]);
%Books = (%Books,TitleID=> $records[1]);
%Books = (%Books,Title=> $records[2]);
%Books = (%Books,PubYear=> $records[3]);
print qq{$records[0]\n}
print qq{\t$records[1]\n};
print qq{\t$records[2]\n};
print qq{\t$records[3]\n};
}
$sth->finish();
while(($key,$value) = each(%Books)) {
print qq{$key --> $value\n};
}
The print statements work in the first while loop, but I only get the last result in the second key,value loop.
What am I doing wrong here. I'm sure it's something simple. Many thanks.
OP needs better specify the question and do some reading on DBI module.
DBI module has a call for fetchall_hashref perhaps OP could put it to some use.
In the shown code an assignment of a record to a hash with the same keys overwrites the previous one, row after row, and the last one remains. Instead, they should be accumulated in a suitable data structure.
Since there are a fair number of rows (351 we are told) one option is a top-level array, with hashrefs for each book
my #all_books;
while (my #records = $sth->fetchrow_array()) {
my %book;
#book{qw(AuthorID TitleID Title PubYear)} = #records;
push #all_books, \%book;
}
Now we have an array of books, each indexed by the four parameters.
This uses a hash slice to assign multiple key-value pairs to a hash.
Another option is a top-level hash with keys for the four book-related parameters, each having for a value an arrayref with entries from all records
my %books;
while (my #records = $sth->fetchrow_array()) {
push #{$books{AuthorID}}, $records[0];
push #{$books{TitleID}}, $records[1];
...
}
Now one can go through authors/titles/etc, and readily recover the other parameters for each.
Adding some checks is always a good idea when reading from a database.

Update dictionary key inside list using map function -Python

I have a dictionary of phone numbers where number is Key and country is value. I want to update the key and add country code based on value country. I tried to use the map function for this:
print('**Exmaple: Update phone book to add Country code using map function** ')
user=[{'952-201-3787':'US'},{'952-201-5984':'US'},{'9871299':'BD'},{'01632 960513':'UK'}]
#A function that takes a dictionary as arg, not list. List is the outer part
def add_Country_Code(aDict):
for k,v in aDict.items():
if(v == 'US'):
aDict[( '1+'+k)]=aDict.pop(k)
if(v == 'UK'):
aDict[( '044+'+k)]=aDict.pop(k)
if (v == 'BD'):
aDict[('001+'+k)] =aDict.pop(k)
return aDict
new_user=list(map(add_Country_Code,user))
print(new_user)
This works partially when I run, output below :
[{'1+952-201-3787': 'US'}, {'1+1+1+952-201-5984': 'US'}, {'001+9871299': 'BD'}, {'044+01632 960513': 'UK'}]
Notice the 2nd US number has 2 additional 1s'. What is causing that?How to fix? Thanks a lot.
Issue
You are mutating a dict while iterating it. Don't do this. The Pythonic convention would be:
Make a new_dict = {}
While iterating the input a_dict, assign new items to new_dict.
Return the new_dict
IOW, create new things, rather than change old things - likely the source of your woes.
Some notes
Use lowercase with underscores when defining variable names (see PEP 8).
Lookup values rather than change the input dict, e.g. a_dict[k] vs. a_dict.pop(k)
Indent the correct number of spaces (see PEP 8)

cts search to test if the element is not available

Below is the XML structure where I want to get the entries for which element co:isbn is not available:-
<tr:trackingRecord xmlns:tr="https://www.mla.org/Schema/Tracking/tr"
xmlns:co="https://www.mla.org/Schema/commonModule/co"
xmlns:r="http://www.rsuitecms.com/rsuite/ns/metadata">
<tr:journal>
<tr:trackingDetails>
<tr:entry>
<co:trackingEntryID>2015323313</co:trackingEntryID>
<co:publicationDate>2015</co:publicationDate>
<co:volume>21</co:volume>
</tr:entry>
<tr:entry>
<co:trackingEntryID>2015323314</co:trackingEntryID>
<co:publicationDate>2015</co:publicationDate>
<co:isbn>
<co:entry>NA</co:entry>
<co:value>1234567890128</co:value>
</co:isbn>
</tr:entry>
<tr:entry>
<co:trackingEntryID>2015323315</co:trackingEntryID>
<co:publicationDate>2015</co:publicationDate>
<co:volume>21</co:volume>
<co:isbn></co:isbn>
</tr:entry>
<tr:entry>
<co:trackingEntryID>2015323316</co:trackingEntryID>
<co:publicationDate>2015</co:publicationDate>
<co:volume>21</co:volume>
</tr:entry>
</tr:trackingDetails>
</tr:journal>
</tr:trackingRecord>
Please suggest the cts:query for the same.
If you can edit xml structure, add one attribute in entry element, like
<tr:entry isbnPresent="yes"> for isbn present,
<tr:entry isbnPresent="no"> for isbn absent
and based on these field fire search with,
cts:element-attribute-value
on it.
OR
without editing schema, try like, ,
for $i in cts:search(//tr:entry,"2015")
return if(fn:exists($i//co:isbn)) then () else $i

CouchDB View with 2 Keys

I am looking for a general solution to a problem with couchdb views.
For example, have a view result like this:
{"total_rows":4,"offset":0,"rows":[
{"id":"1","key":["imported","1"],"value":null},
{"id":"2","key":["imported","2"],"value":null},
{"id":"3","key":["imported","3"],"value":null},
{"id":"4","key":["mapped","4"],"value":null},
{"id":"5,"key":["mapped","5"],"value":null}
]
1) If I want to select only "imported" documents I would use this:
view?startkey=["imported"]&endkey=["imported",{}]
2) If I want to select all imported documents with an higher id then 2:
view?startkey=["imported",2]&endkey=["imported",{}]
3) If I want to select all imported documents with an id between 2 and 4:
view?startkey=["imported",2]&endkey=["imported",4]
My Questtion is: How can I select all Rows with an id between 2 and 4?
You can try to extend the solution above, but prepend keys with a kind of "emit index" flag like this:
map: function (doc) {
emit ([0, doc.number, doc.category]); // direct order
emit ([1, doc.category, doc.number]); // reverse order
}
so you will be able to request them with
view?startkey=[0, 2]&endkey=[0, 4, {}]
or
view?startkey=[1, 'imported', 2]&endkey=[1, 'imported', 4]
But 2 different views will be better anyway.
I ran into the same problem a little while ago so I'll explain my solution. Inside of any map function you can have multiple emit() calls. A map function in your case might look like:
function(doc) {
emit([doc.number, doc.category], null);
emit([doc.category, doc.number], null);
}
You can also use ?include_docs=true to get the documents back from any of your queries. Then your query to get back rows 2 to 4 would be
view?startkey=[2]&endkey=[4,{}]
You can view the rules for sorting at CouchDB View Collation

xquery order by date Time

Say I have the following documents in my database: a_doc1, a_doc2, b_doc1, and b_doc2
All these documents are of the following format
<doc>
....
<updatedTime>2011-02-07T14:41:02.133-05:00</updatedTime>
....
</doc
>
The value of the "updatedTime" element is inserted when the document is created using the fn:current-dateTime()
Now I am trying to do the following:
find all documents whose name starts with "a_"
order these documents by their <updatedTime> element in descending order
Return the first document name from the descending order
I tried the following:
for $doc_name in db:list()
where fn:starts-with($doc_name, 'a_')
order by xs:dateTime(doc($doc_name)/updatedTime) descending
return $doc_name
Say "a_doc1" is created at "2011-02-07T14:40:00.78-05:00" and "a_doc2" is created at "2011-02-07T14:41:02.133-05:00", the desired output is a_doc2. In short the document name(starting with a_) of the most recent document created must be returned.
When I try my sample code, the output returned is : [a_doc1, a_doc2].
The expected output is: [a_doc2, a_doc1].
Thanks,
Sony
order by dateTime(doc($doc_name)/updatedTime/text())
Instead of this use:
order by xs:dateTime(doc($doc_name)/updatedTime)
you should use (xs: http://www.w3.org/2001/XMLSchema):
xs:dateTime($arg as xs:anyAtomicType?) as xs:dateTime?
Example:
xs:dateTime("1999-12-31T12:00:00")
instead of (fn: http://www.w3.org/2005/xpath-functions)
fn:dateTime($arg1 as xs:date?, $arg2 as xs:time?) as xs:dateTime?
Example:
fn:dateTime(xs:date("1999-12-31"), xs:time("12:00:00"))

Resources