Id required for alias updates?

I’m trying to use aliases to update many records at once.

Unfortunately I get the following error:

{
  "data": null,
  "errors": [
    {
      "message": "The request is invalid.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "FLWS_alias"
      ],
      "code": "ValidationError",
      "details": {
        "id": "id should be specified"
      }
    }
  ]
}

I don’t know the id’s ahead of time and I don’t want to waste a request to fetch all the ids and then loop over what will be around ~3k records. To find the intersections or map it. It’s wasteful.

The ticker column in this Table is unique. So it should be able to find the record to update based on that. Just like it does in a normal update without having to know the ID as well.

The test mutation I’m trying to run:

mutation {
    FLWS_alias: aIReportUpdate(data: {
        ticker: "FLWS",
        scores: "{\"ai_soundness\":1,\"ai_stewardship\":1,\"ai_profitability\":0.428571,\"ai_growth\":0.5,\"ai_value\":-0.818182,\"ai_reward\":0.212867,\"ai_safety\":0.241133,\"ai_score\":-0.0871333}",
        price: 13.74,
        date: "2019-10-29",
        aIScore: -0.0871333
        }) { id }
XXII_alias: aIReportUpdate(data: {
        ticker: "XXII",
        scores: "{\"ai_soundness\":0.272727,\"ai_stewardship\":0,\"ai_profitability\":-0.714286,\"ai_growth\":-0.25,\"ai_value\":-0.818182,\"ai_reward\":-0.724533,\"ai_safety\":-0.949633,\"ai_score\":-1}",
        price: 2.03,
        date: "2019-10-29",
        aIScore: -1
        }) { id }
DDD_alias: aIReportUpdate(data: {
        ticker: "DDD",
        scores: "{\"ai_soundness\":0.636364,\"ai_stewardship\":0.25,\"ai_profitability\":-0.714286,\"ai_growth\":-0.5,\"ai_value\":-0.818182,\"ai_reward\":-0.1454,\"ai_safety\":-0.371667,\"ai_score\":-0.7954}",
        price: 8.93,
        date: "2019-10-29",
        aIScore: -0.7954
        }) { ticker }
  }

This is my schema:

Ticker should be unique and searchable for a single record, so ID shouldn’t be needed.

I tried using filter to get around this, but looks like that doesn’t work with update?

FLWS_alias: aIReportUpdate(filter: { ticker: { equals: "FLWS" }}, data: { ... }) { ticker }

PS. Also when using aliases in mutation. I would expect there to be multiple errors. This is only throwing 1 error for the first thing that goes wrong. I would expect it to throw 1 error for each mutation.

Looks like the other mutations weren’t even attempted.

Hey @MarkLyck! I just ran this test and it worked.

mutation {
  burger: dishUpdate(
    filter: {
      name: "Burger"
    },
    data: {
      price: 10.00
    }
  ) { ...dishTest }
  
  fries: dishUpdate(
    filter: {
      name: "Fries"
    },
    data: {
      price: 3.00
    }
  ) { ...dishTest }
}

fragment dishTest on Dish {
  name
  price
}

The filters used on Update’s are different that those used in List queries. Essentially, the predicates work differently since you are matching a unique record as opposed to list of matching records.

I think that it will work fine if you update it to the following.

FLWS_alias: aIReportUpdate(
  filter: {
    ticker: "FLWS"
  }, 
  data: { ... }
) { ticker }
1 Like

Thanks @sebastian.scholl! that solved part of it.

Unfortunately it only gives 1 error mentioning the first failed part of the query.

Which means I can’t really use this without having to loop over all 3k records to check if they already exist or not first :frowning:

Yeah, aliases simply send all the queries in one request, thought it will return an error after the first failed update.

If you want, there is a sequence that you can follow for now that would be appropriate to handle this.

Aliases are handled in order (top to bottom). So, if you look at the mutation below, the updates are going to be run in the order of dish1, dish2, dish3, etc…

mutation {
  dish1: dishUpdate(
    filter: {
      name: "Burger"
    },
    data: {
      price: 10.00
    }
  ) { ...dishTest }
  dish2: dishUpdate(
    filter: {
      name: "NOT_FOOD"
    },
    data: {
      price: 10.00
    }
  ) { ...dishTest }
  dish3: dishUpdate(
    filter: {
      name: "Shake"
    },
    data: {
      price: 3.00
    }
  ) { ...dishTest }
}

fragment dishTest on Dish {
  name
  price
}

Now, when the first update fails, the error message returned will include errors.path which contains the name of the failed alias. Again, looking at the mutation above, dish2 is not a dish, which means that the mutation would fail when updating dish2 and return errors.path = ["dish2"].

Knowing this, we know that dish1 was successfully updated and all we need to handle is dish2 and dish3. Thus, we can rebuild our mutation, only re-adding aliases past the failed record.

When handling 3000+ records, it’s very likely that the first 1500 records could update before there is an issue. If you use aliases sequentially, you’ll know that aliases 1-1500 are all updated and all that needs to be done is the failing update be corrected/omitted before resubmitting the remainder.

1 Like

I think in almost if not all scenarios it would be preferred if it tried to run all of the aliases no matter what.

In this case. You would have to write a very inefficient loop to continue from the error state.

Basically the updates are looping through all updates, and when the first one fail you get the index of the one that failed. You then have to restart the loop at that index +1 and try to do a new network request.

If you are updating a lot of records (like e.g. I’m trying to do) and let’s say 300 of them are failing (for whatever reason). I would end up having to restart my loop 300 times, and be making 300 network requests instead of just 2.

If instead it ran all of them, told me which 300 aliases failed. I could then just loop over the errors once and check if they failed because of a missing entity in the table.

Then do 1 single createMany request for the new list.

It would be much more performant and helpful than just having the whole thing fall over at the first error.

The other alternative, is also wasteful where you have to fetch all current records and compare them, which is better than this looping, but still unnecessary overhead.

I can’t think of any scenario using aliases where I wouldn’t want it to run all of them regardless of outcome.

I totally get it. It’s not the ideal scenario, though is what works today.

I would argue though that the querying of current records is not that wasteful/non-performant. We know that you are going to at least have to do 2 API calls (1 to send all the updates, get back all the failed aliases, and then createMany for all of those that got returned). That said, by querying the existing records and organizing them ahead of time, you can also accomplish it in 2 API calls (1 to query tickers, another to send all updates and creates together).

For example, this mutation would run all the updates and the create many in one 1 API request.

mutation {
  burger1: dishUpdate(
    filter: {
      name: "Burger"
    },
    data: {
      price: 5.00
    }
  ) { ...dishTest }
  
  fries2: dishUpdate(
    filter: {
      name: "Fries"
    },
    data: {
      price: 0.99
    }
  ) { ...dishTest }
  
  newDishes: dishCreateMany(data: [
    { name: "Hummus" },
    { name: "Carrots" },
    { name: "Impossible Bacon" }
  ]) { count }
}

fragment dishTest on Dish {
  name
  price
}

Just for the sake of it, I ran a quick benchmarking test to see how sets of 15,000 and 20,000 ticker strings/objects would be handled performance wise for sorting

var start = new Date()
var hrstart = process.hrtime()
var simulateTime = 5

function createTickers(count, type) {
    return [...Array(count).keys()].map(n => { 
        return type === 'object' ? { name: `ticker-${n}` } : `ticker-${n}`
    })
}

var tickersOld = createTickers(15000)
var tickersNew = createTickers(20000, 'object')

setTimeout(function (argument) {
    // execution time simulated with setTimeout function
    var end = new Date() - start, hrend = process.hrtime(hrstart)

    // BENCHMARK CODE STARTS HERE
    var toUpdate = [];
    var toCreate = [];

    // test code here
    tickersNew.forEach(o => tickersOld.includes(o.name) ? toUpdate.push(o) : toCreate.push(o));

    console.log("To Update: ", toUpdate.length, "\nTo Create: ", toCreate.length)
    // BENCHMARK CODE ENDS HERE

    // Bench marks
    console.info('Execution time (hr): %ds %dms', hrend[0], hrend[1] / 1000000)
}, simulateTime)

// MY RESULTS
To Update:  15000
To Create:  5000
Execution time (hr): 0s 18.518757ms
2 Likes