A quick question on format for App Tables function versus SQL Function

I’m just looking on the Anvil tables query examples, and just wanted to clarify (now I’ve imported all my sql tables to anvil tables) how the functions might differ?

So my SQL Query is this


@anvil.server.callable
def daysQuery (currentdays):
  conn = connect()
  with conn.cursor() as cur:
    cur.execute("SELECT DaysID, Days, DaysPercent FROM days ")

I’ve now replaced it with this:

@anvil.server.callable
def get_all_days(currentdays):
  return app_tables.days.search()

And a joined query

@anvil.server.callable
def locationQuery (currentcountry):
  conn = connect()
  with conn.cursor() as cur:
    cur.execute("SELECT location.LocationID, location.Location,location.LocationPercent FROM location INNER JOIN locationdetail ON (locationdetail.LocationID = location.LocationID)")
  return cur.fetchall()

to this:


@anvil.server.callable
def get_all_location_matches(currentcountry):
  location.location == locationdetail.location
  return app_tables.location.search()

I’m not sure thats right,but would appreciate some guidance

There’s no real joined query in Anvil data tables, but the way the linking fields work you generally don’t need them.

Your SQL is doing an INNER JOIN, but you’re not selecting any fields from the joined table, so I’m not sure why the join is there. So rather than try to convert that directly, I’ll make some assumptions and work from there:

  1. You have a location table and a locationdetail table

  2. The locationdetail table has a linking field named location that links to a single row in the location table

  3. You want to pull all the location detail rows for a single location

  4. On the client you have location rows that the user is choosing from for the query and are passing a full location row as the currentlocation parameter

@anvil.server.callable
def get_all_location_matches(currentlocation):
  return app_tables.locationdetail.search(location=currentlocation)
2 Likes

Thanks yes you are right, there is a where clause there but I was just trying to figure out how the join looks.
so on the client side how would this look as amended

for the location with the join I have:-
self.repeating_panel_location.items = anvil.server.call('get_all_location_matches', self.text_box_location.text)

for the other tables where I dont have a join in them I have:-

self.repeating_panel_days.items = anvil.server.call('get_all_days', self.text_box_days.text)

Though on the ages one I have x as represented by {ages} number of ages so had to do this

self.repeating_panel_ages.items = anvil.server.call('get_all_ages', "{ages}".format(ages=ages))

What should this server function return? A list of matching rows from the location table? Or from the locationdetail table?

If it’s from the locationdetail table then the text you pass in must be converted to a location row before you search the locationdetail table:

@anvil.server.callable
def get_all_location_matches(currentlocation):
  currentlocation = app_tables.location.get(???=currentlocation)
  return app_tables.locationdetail.search(location=currentlocation)

Where you replace ??? with whatever field name holds the value you’re trying to search.

1 Like

So, locationdetail contains countries and cities.

The user will input - say spain as the country they are visiting, then the locationdetail keyfield of location will find the value in location of Europe the actual premium rating is done on Europe, so this is the row I need to capture in order to get the correct rating.

Just to confirm, there are only 3 fields on the location table

Europe
Worldwide (Excluding USA and Australia)
Worldwide

one of these 3 will show on the linked field in the locationdetail table. We dont know until the user chooses the country, which of those 3 it would be. So that will only be known from the client side.

I got it done in MysQL with this full query, just to add I split the query into the below on the serverside

@anvil.server.callable
def locationQuery (currentcountry):
  conn = connect()
  with conn.cursor() as cur:
    cur.execute("SELECT location.LocationID, location.Location,location.LocationPercent FROM location INNER JOIN locationdetail ON (locationdetail.LocationID = location.LocationID)WHERE locationdetail.LocationDetailCountry = '{currentcountry}' ORDER BY locationdetail.LocationDetailCountry LIMIT 1".format(currentcountry=currentcountry))
  return cur.fetchall()

The original function was on the quotation client code on a submit button click event


anvil.server.call(‘locationQuery’, self.text_box_location.text)

You’d just do the search on locationdetail, then:

app_tables.locationdetail.search(LocationDetailCountry=currentcountry)

That would give you all locationdetail rows that matched the currentcountry. If there will only be one, then you’d use get instead of search.

Once you have a locationdetail row, you access the location field in it to get to the location linking row (including all the fields in that location row).

2 Likes

So I am still having trouble with 2 of the functions

This is the code on the server side

@anvil.server.callable
def get_all_ages(currentage):
   return app_tables.age.search(Age=currentage)

@anvil.server.callable
def get_all_location_matches(currentcountry):
  return app_tables.locationdetail.search(LocationDetailCountry=currentcountry)

This is the code on the client side

self.repeating_panel_2.items = anvil.server.call('get_all_ages', "{ages}".format(ages=ages))
 self.repeating_panel_location.items = anvil.server.call('get_all_location_matches', self.location)

I did a print output to show what each of the arguments pull out

the self.ageall prints (30, 40, 50, 60)
and the self.location prints spain

the repeating panel for location, prints nothing at all but Im not getting errors on it. on get_all_ages I am getting anvil.tables.TableError: Column 'Age' can only be searched with a number (not a string) in table 'age'

Both of my original SQL queries worked but the server side on ages was an SQL IN query

That’s the easy one. Your Age field is a number, you can’t search it using a string. Pass a number to your server function instead of a string. If ages is already a number, don’t format it:

anvil.server.call('get_all_ages', ages)

If age isn’t a number already, convert it to a number before passing it to your server function.

Without seeing more context, it’s hard to say about the other. Can you share a clone link?

1 Like

Hi

yes here is the clone link the pages with code on are Quotation on the client side, and Queries on the server side. Theres not much else on it though other than blank forms.

https://anvil.works/build#clone:ALUYX6V7HUSUEZJX=OI5UR3NYFZKBXWJX5PTFQM7C

You’ve got references to a global variable ages in there that should (probably) be self.ages. I made that change in my copy so it would get to the server calls

Then you have this line:

self.repeating_panel_2.items = anvil.server.call('get_all_ages', int(self.ages))

self.ages (and ages before it) is a list. You can’t convert a list to an int. Your server function is clearly expecting a single int, not a list of ints.

It looks like what you’re trying to do is to get the information for all the ages they input, and display that in the repeating_panel_2.

The most straightforward way of doing that is on the server, with something along these lines (pass self.ages to the server as a list, don’t convert it to anything):

@anvil.server.callable
def get_all_ages(ages):
    results = []

    for age in ages:
        row = app_tables.age.get(Age=age)

        if row:
            results.append(row)

    return results

There’s probably a shorter way of doing that using queries (Querying Data Tables), but I’m not a query expert.

1 Like

I’m still not getting it working, so I’m going to walk away from it for the rest of the evening and pick it up tomorrow, its probably beacause I’m tired.

Thank you so much for all your help, its way over what I can expect, I do appreciate it.

So I have tried everything suggested but nothing is working so far. It seems any_of should produce the same as an SQL IN Statement

The code below will produce a result if only 1 argument is given, if more than one I get TypeError: float() argument must be a string or a number, not 'list'

@anvil.server.callable
def get_ages_again(ages):
  return app_tables.age.search(Age=(q.any_of(float(ages))))

I tried this variation

def get_ages_again(ages):
  result = app_tables.age.search()
  if ages:
    result = (
    x for x in result
    if ages in x('Age')
    )
  return result

and got this error
anvil.server.SerializationError: Cannot serialize return value from function. Cannot serialize <class 'generator'> object at msg['response']

I left the client side the same as when I was using the SQL If statement, because that worked before and its only where I am storing the tables that has changed.

I also still have not been able to get the table join location one to work in the tables in Anvil format.

@anvil.server.callable
def get_all_location_details(currentcountry):
  return app_tables.locationdetail.search(LocationDetailCountry=currentcountry)

I have looked on the Anvil Docs and also the Tutorial Storing Data in Tables, I have cloned the App Query Example and there are no examples in there of dealing with a result that is a list or how linked tables work as you would a Join. If there is a resource out there I can use I would really appreciate a link to it, since I will likely need to make use of this on other queries.

In your locationdetail search, remember that strings search case sensitive by default. To get a case insensitive search, you need to use the query operators:

@anvil.server.callable
def get_all_location_details(location):
  return app_tables.locationdetail.search(LocationDetailCountry=q.ilike(location))

That returns rows for me in your app.

In your any_of query, you have two issues. One is that you are trying to convert a list into a float. That will never work, as the error message says.

The second is that any_of wants a series of arguments, but you have a list. You can unpack the list into separate arguments:

@anvil.server.callable
def get_all_ages(ages):
  return app_tables.age.search(Age=(q.any_of(*ages)))
2 Likes

Hi thanks for that on the age query, I missed the * !!!OMG I cannot believe it was that simple, I almost had it.

I also figured out my issue with the locationdetail and it wasn’t the query, my import only did up to 9900 lines, I’m not sure if its the import or the fact we are on a free “education” plan thats limited it.

There is only one more issue with that query, I need to limit the result to 1, obviously in SQL I can put the limit on the Select but what about in this query.

In general, there are two ways to get a single row out of a query.

The first is to use get instead of search. That’s appropriate if you’re searching on a unique field and only expect to get one row out the query.

The second is to pull the first entry out of the search results, e.g.:

results = app_tables.locationdetail.search(LocationDetailCountry=q.ilike(location))
return results[0]

I always question that second method, though, it seems to me that the query itself is not being used right if you’re getting multiple rows but only care about the first. In SQL, there aren’t that many times I’d use a LIMIT 1, especially without an ORDER BY clause.

2 Likes

In this case I would use the results method, because the table I am using splits countries down into cities. We arent using cities at this stage, so we only need to know the country for rating purposes, e.g spain has a lot of cities, but all of spain is in Europe.

I’m conitnuing the import now for the locations as I expect all it was, was that it timed out.

So does this image look correct if I am converting it to Anvil, or does some of this have to be done in the clientside? Ignoring the global part

Your days table looks like the Days field in it is unique. So the basic query, assuming that you’re using a number format text box and passing the value from it into the currentdays parameter in a server function, would be:

row = app_tables.days.get(Days=currentdays)

You’d return that row back to the client, and then use row[‘DaysPercent’] to get the percentage out of it.

There’s no need for a search and a for loop, since the query will never return more than one row.

That makes sense, the 30 second server call time limit probably hit. It might make sense to have each server call do a different subset of the import (e.g. all the countries starting with A, then with B, etc).

2 Likes

I probably didnt explain it well in the image, so would that query translate ok to this?

@anvil.server.callable
def new_days_query(currentdays,dayspercent,daystoadd):
  return app_tables.days.search(
    Days=currentdays,
    DaysPercent=dayspercent, 
    daystoadd = (anvil.server.call('baseprice') * dayspercent) * anvil.server.call('trav'))

Where both baseprice and trav are other variables depending on inputs

Your days table has two fields in it: Days, and DaysPercent.

A query on a table may only include keyword arguments for fields that are in it. daystoadd is not a field in the table, so you may not use it as a keyword argument in the search.

Your original SQL query from the image was always getting one row out, using a WHERE Days=currentdays

The matching Anvil query is app_tables.days.get(Days=currentdays)

I’m not sure what task you’re trying to do with new_days_query. If it’s just to return a row from the days table, you don’t need anything but the currentdays parameter. If you’re trying to do something else, then a simple query isn’t going to work.

1 Like