Graphql-engine: Computed fields for relationship values

Created on 4 May 2020  路  7Comments  路  Source: hasura/graphql-engine

What I am attempting is to create a computed field that sums a value from table A with another value from table B, where A and B has a relationship. I have followed your example in the docs (https://hasura.io/docs/1.0/graphql/manual/schema/computed-fields.html#scalar-computed-fields) and succeded with summing two values for the same table, but not succeded with adding another nested value (nested in a GraphQL sense).

This is how I defined my current, working function:

CREATE FUNCTION avg_price(product_row product)
RETURNS int AS $$
  SELECT product_row.start_price + product_row.price
$$ LANGUAGE sql STABLE;

Now, I have a one-to-many relationship from product to product_deal and I would like to integrate a product_deal.price into my formula. When I do a normal query on product I can access the field product.deal (due to the relationship). I am imagining something like this for the computed value:

CREATE FUNCTION avg_price(product_row product)
RETURNS int AS $$
  SELECT product_row.start_price + (product_row.price || product_row.deal.price)
$$ LANGUAGE sql STABLE;

I am not a database expert so I guess my conditional clause is faulty, but hopefully you get the general idea. The problem is that I don't think that I can access this relational value. Is it even doable at this point? If so how can it be done?

question

All 7 comments

Hi @oskery

You can get the relationship row in your function. E.g

CREATE FUNCTION avg_price(product_row product)
RETURNS int AS $$
DECLARE deal_row deal;
BEGIN
  -- get the related row into a variable
  SELECT * INTO deal_row FROM deal where deal.product_id = product_row.id; 
  SELECT product_row.start_price + (product_row.price || deal_row.price)
END;
$$ LANGUAGE plpgsql STABLE;

Note that you will have to convert this into a plpgsql function as it is declaring a variable. (Also, please check for any syntactic errors)

Thank you @tirumaraiselvan, this seems to be what I am looking for! I simplified the function in the question and now I have issues with my more complicated version. I would really appreciate your help or input! This is my complete function, trying to do what you suggested:

CREATE FUNCTION avg_price(product_row product)
RETURNS real AS $$
DECLARE deal_row product_deal;
BEGIN
  SELECT * INTO deal_row FROM product_deal WHERE product_deal.product_id = product_row.id AND product_deal.price IS NOT NULL;
  IF deal_row IS NOT NULL THEN
    SELECT product_row.start_price + (product_row.price * (12-deal_row.duration) + deal_row.price * deal_row.duration)/12.0;
  ELSE 
    SELECT product_row.start_price + product_row.price;
  END IF;
END;
$$ LANGUAGE plpgsql STABLE;

So in my database there exists products without deals, therefore I used the IF statement. There are also deals that are not price-deals, hence the additional WHERE condition. I think I am doing something wrong though, I can CREATE the above function but get an error when querying it:

"error": {
  "exec_status": "FatalError",
  "hint": "If you want to discard the results of a SELECT, use PERFORM instead.",
  "message": "query has no destination for result data",
  "status_code": "42601",
  "description": null
},

If I use PERFORM, as suggested, it gives me an error about the function being VOLATILE but I think the hasura functions for computed fields can only be STABLE or IMMUTABLE?

Hi @oskery

Turns out in plpgsql, you need to RETURN a value : https://www.postgresql.org/docs/current/plpgsql-control-structures.html#PLPGSQL-STATEMENTS-RETURNING

Try:

CREATE FUNCTION avg_price(product_row product)
RETURNS real AS $$
DECLARE deal_row product_deal;
BEGIN
  SELECT * INTO deal_row FROM product_deal WHERE product_deal.product_id = product_row.id AND product_deal.price IS NOT NULL;
  IF deal_row IS NOT NULL THEN
    RETURN product_row.start_price + (product_row.price * (12-deal_row.duration) + deal_row.price * deal_row.duration)/12.0;
  ELSE 
    RETURN product_row.start_price + product_row.price;
  END IF;
END;
$$ LANGUAGE plpgsql STABLE;

Thanks alot @tirumaraiselvan!

It now executes but avg_price is always the normal price. It seems as though the variable deal_row is always NULL and the ELSE clause is always executed, even though the WHERE clause should be true?:

 "data": {
    "product": [
      {
        "price": 95,
        "avg_price": 95,
        "deal": [
          {
            "price": 47.5,
            "product_id": 5056
          }
        ],
        "id": 5056
      }
    ]
}

I modified the IF-statments part to debug and it never returns 1.0:

IF deal_row IS NOT NULL THEN
  RETURN 1.0;
ELSE 
  RETURN product_row.start_price + product_row.price;
END IF;

@oskery Looking at https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW ,I think you cannot check for NULL value because it is a record type. You are probably getting something like (null, null, null) (pls check this as even I am not sure).

Try to check the FOUND variable instead:

IF FOUND THEN
  RETURN 1.0;
ELSE 
  RETURN product_row.start_price + product_row.price;
END IF;

I would also encourage you to explore the docs above and ask PG related questions in our community channel.

Thanks @tirumaraiselvan,

I apologize for asking somewhat un-hasura-related questions, but I have been struggling with finding answers and you were very helpful. I will definitely use the discord channel for PG related questions in the future, didn't know about it!

I saw the "FOUND" keyword in a similar question but I assumed it was a variable. Changing the "IS NOT NULL" to "FOUND" did indeed work! Thank you very much for your help. Closing the issue.

@oskery No problem. Happy to help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bogdansoare picture bogdansoare  路  3Comments

coco98 picture coco98  路  3Comments

egislook picture egislook  路  3Comments

sachaarbonel picture sachaarbonel  路  3Comments

hooopo picture hooopo  路  3Comments