Hugo Koopmans
Hugo Koopmans

Reputation: 1369

search map based on key wildcard

I have the following map of maps:

<map:map xmlns:map="http://marklogic.com/xdmp/map" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<map:entry key="101201">
<map:value>
"content"
</map:value>
</map:entry>
... more maps ...

Now I would like to search/filter the map based on the key using a wildcard.

Actually I want to filter based on the first 4 characters of the key="101201" so key="1012**".

Question: Give me all maps that have a key that is matching '1012*' ...

Can that be done efficiently?

tx for your time

hugo

Upvotes: 0

Views: 230

Answers (3)

joemfb
joemfb

Reputation: 3056

There are no indexes of in-memory maps (or nodes, for that matter). However, you can persist the XML serialization of the map in the database, and than resolve a query using indexes. To save the map XML:

let $map := map:new(( map:entry("test", "1, 2") ))
return xdmp:document-insert("/map.xml", document { $map })

You can use cts:element-attribute-value-query with the wildcard option, and no additional configuration:

cts:search(/map:map/map:entry,
  cts:element-attribute-value-query(
    xs:QName("map:entry"), xs:QName("key"), "1012*", ("wildcarded")))

Or, you can create an element-attribute range index, and use cts:element-attribute-value-match:

cts:element-attribute-value-match(
  xs:QName("map:entry"), xs:QName("key"), "1012*")

Upvotes: 1

mblakele
mblakele

Reputation: 7842

Can that be done efficiently? Yes. There won't be any I/O or network traffic, just CPU.

map:keys($map)[starts-with(., '1012')]

map:get($map, map:keys($map)[starts-with(., '1012')])

However if this is a common use case then you might want to build a map of maps. One map would have four-digit keys, and each entry would contain a map of six digit keys that start with the same prefix. That way you could get your four-digit matches with one map:get, and six-digit matches with substring and two calls to map:get.

map:get($m, substring($id, 1, 4))

map:get(map:get($m, substring($id, 1, 4)), $id)

It's up to you to decide which use-case to optimize for.

Upvotes: 1

Dave Cassel
Dave Cassel

Reputation: 8422

There are no indexes involved here, which limits the tricks you can do. This would work, but I'm not sure how it would perform at scale:

let $map-of-maps :=
  map:new((
    map:entry('10000', map:new((map:entry('key1', 'value1'), map:entry('key2', 'value2')))),
    map:entry('20000', map:new((map:entry('key3', 'value3'), map:entry('key4', 'value4'))))
  ))
let $keys := 
  for $key in map:keys($map-of-maps) 
  where fn:matches($key, '1000*') 
  return $key
return $keys

Upvotes: 0

Related Questions