7

I am having a hard time finding out if I can check multiple key values at once with TinyDB. Currently I can check multiple fields by using and in an if statement like this:

def check_table(FNAME="NULL", LNAME="NULL", MNAME="NULL"):
    if (HHK_STAFF.search(Query().FNAME == FNAME)) != [] \
    and (HHK_STAFF.search(Query().MNAME == MNAME)) != [] \
    and (HHK_STAFF.search(Query().LNAME == LNAME)) != []:
        print(HHK_STAFF.search(Query().FNAME == FNAME)[0])
    else:
        print("user does not exist")

check_table(FNAME="Some", MNAME="Random", LNAME="Person")

It does what I want however it seams bulky. I am hoping there is something built into TinyDB that can perform the same function without the need for many and statements.

I am trying to do something akin to:

HHK_STAFF.search(where(("FNAME", "MNAME", "LNAME")) == (FNAME, MNAME, LNAME)))

Question:

  1. Is anyone aware of a way to Query() the table for multiple key values instead of just one at a time?

  2. Can you list a link to this information? I have had a hard time locating this bit of info if it exist.

Here is a simple version of what I am ding with TinyDB:

from tinydb import TinyDB, Query


#~~~~~~~~~~~~~~~~~~~~~~< CURRENT DBPATH >~~~~~~~~~~~~~~~~~~~~~~
DB = TinyDB('./DB_PATH/HHK_DB.json')
#~~~~~~~~~~~~~~~~~~~~~~< CURRENT TABLES >~~~~~~~~~~~~~~~~~~~~~~
HHK_STAFF = DB.table("HHK_STAFF")

HHK_STAFF.insert({'EMPLOYEE_ID':'00000001', 'FNAME': 'Some', 'LNAME':'Person', 'MNAME':'Random'})


def check_table(FNAME="NULL", LNAME="NULL", MNAME="NULL"):
    if (HHK_STAFF.search(Query().FNAME == FNAME)) != [] \
    and (HHK_STAFF.search(Query().MNAME == MNAME)) != [] \
    and (HHK_STAFF.search(Query().LNAME == LNAME)) != []:
        print(HHK_STAFF.search(Query().FNAME == FNAME)[0])
    else:
        print("user does not exist")

check_table(FNAME="Some", MNAME="Random", LNAME="Person")

Results:

{'EMPLOYEE_ID': '00000001', 'FNAME': 'Some', 'LNAME': 'Person', 'MNAME': 'Random'}
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79

2 Answers2

4

You can use the python builtin all to achieve a shorter, more flexible work-alike to your sample code:

def check_table2(**query):
    if all(HHK_STAFF.search(getattr(Query(), k) == v) 
           for k, v in query.items()):
        print(HHK_STAFF.search(Query().FNAME == query['FNAME'])[0])
    else:
        print("user does not exist")

But you should be sure this is actually what you want. The English equivalent would be,

"If someone with this last name exists, and someone with this first name exists, and someone with this middle name exists, regardless of whether they're all the same someone, return the person with the matching first name."

In other words, If I now add another person to your database

HHK_STAFF.insert({'EMPLOYEE_ID':'00000002', 
                  'FNAME': 'Anne', 
                  'LNAME':'Person',
                  'MNAME':'Other'})

Your function above will return something for the query

check_table(FNAME="Some", MNAME="Other", LNAME="Person")

Even though Some's middle name is "Random" not "Other", just because somebody else exists on the system whose middle name is "Other"

You may instead want to take advantage of Query's overridden __and__ operator to find a single person that has all the names you're querying:

q = Query()
HHK_STAFF.search((q.FNAME=="Anne") & 
                 (q.MNAME=="Other") & 
                 (q.LNAME=="Person"))

Or, for something using key-value like I did above, using functools.reduce:

from functools import reduce

def user_matching_all(**query):
    q = Query()
    out = HHK_STAFF.search(reduce(lambda x, y: x & y,
                                  [getattr(q, k) == v
                                   for k, v in query.items()]))
    if out:
        print(out)
    else:
        print("user does not exist")
Dan
  • 4,312
  • 16
  • 28
  • You first example is not what I want. I need an exact match that matches ever field I specify regardless of how many fields I want to cross reference. – Mike - SMT Apr 13 '18 at 18:56
  • I figured, hence the rest of my answer. – Dan Apr 13 '18 at 18:58
  • Just realized that your 2nd example is what I am looking for. For some reason I misread the example to be more like my original example in my question. – Mike - SMT Apr 13 '18 at 19:14
4

According to Advanced Usage — TinyDB 3.8.1.post1 documentation, a logical AND would look like this:

q = Query()
HHK_STAFF.search((q.FNAME == FNAME) & (q.MNAME == MNAME) & (q.LNAME == LNAME))

According to git blame of tinydb/queries.py, it's been available always, since the very first release.

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • Wow somehow I missed that section. I looked over that page a dozen times. I will test this example when I get a chance but it does appear to be exactly what I am looking for. Thanks. – Mike - SMT Apr 13 '18 at 19:05
  • @Mike-SMT - In the interest of fairness, you claimed that Dan's second example is not quite what you're looking for while it is virtually identical to this answer, where you claim that it is. Also, beware that under the hood this is doing almost the same thing as your own `check_table()` function but that's what you get with a horribly inefficient 'db' system like the TinyDB. – zwer Apr 13 '18 at 19:11
  • Thanks for pointing that out. I read it to be more like my example of what I original was doing. Now that I look at it again I can see it is the same as your example. I am having an off day I guess. – Mike - SMT Apr 13 '18 at 19:13
  • Is there a way to apply the bonus to more than one answer? I feel kinda bad for not providing to Dan's answer now. – Mike - SMT Apr 13 '18 at 19:15
  • 1
    Well where his answer is similar to your own I think yours is still the better answer because it uses a single `query()` and the documentation has stated it can cause strange issues when using multiple `query()`'s – Mike - SMT Apr 13 '18 at 19:17
  • 1
    I fixed the reduntant `Query()` calls in mine for posterity. I'd still say this one deserves to be accepted, because ivan found the actual source code and a documentation link (which I only edited into mine later). – Dan Apr 13 '18 at 20:10