functionh.makeAndRunQuery(player)localquery={tables={'MatchSchedule=MS','MatchSchedule__InterviewWith=MSInt','PlayerRedirects=PRMS','MatchScheduleGame=MSG','MatchScheduleGame__InterviewWith=MSGInt','PlayerRedirects=PRMSG','Tournaments=T',},join={'MS._ID=MSInt._rowID','MSInt._value=PRMS.AllName','MS.UniqueMatch=MSG.UniqueMatch','MSG._ID=MSGInt._rowID','MSGInt._value=PRMSG.AllName','MS.OverviewPage=T.OverviewPage',},fields={'T.Name=Tournament','MS.VodInterview=MSInterview','MSG.VodInterview=MSGInterview','MS.Winner=MSWinner[number]','MS.DateTime_UTC=Date','MS.Team1 [team]','MS.Team2 [team]','MSG.Blue [team]','MSG.Red [team]','MSG.Winner=MSGWinner',},where={('PRMS.OverviewPage="%s" OR PRMSG.OverviewPage="%s"'):format(player,player)},orderBy='MS.DateTime_UTC DESC',groupBy='MSInterview,MSGInterview',}returnutil_cargo.queryAndCast(query)end
What’s going on? Once again, we have an OR in the where condition, and two copies of PlayerRedirects, one from MatchSchedule and MatchScheduleGame.
This setup is similar to the first query we looked at, but there is one big confounding factor: the groupBy. We still don’t ever have to splice data together - that is, there will never be a case when some of the columns in a row are coming from union query 1 and some of the columns in that same row are coming from union query 2 - but we WILL have to handle “merge conflicts”, where one row shows up in both of our data sets and we want to see it only a single time.
New Cargo wrapper application logic
Before, the end of my queryAndCast function looked like this:
Now, to accommodate checking for duplicates at the same time as merging, I added a new function to Module:TableUtil:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
functionp.uniqueKeyMergeArrays(key,tbl1,...)-- tbl1 is modified to include the elements of tbl2 appended to the end. Order is preserved.-- skips rows in later tables that have repeat values of a key.-- useful when unioning Cargo tables with a net groupBy.ifnottbl1thentbl1={}endlocalnewTables={...}localseenKeys={}for_,tbl2inipairs(newTables)dofor_,rowinipairs(tbl2)doifnotseenKeys[row[key]]thenseenKeys[row[key]]=truetbl1[#tbl1+1]=rowendendendreturntbl1end
And here is my new queryAndCast function, which also updates the syntax to require only one layer of nesting as opposed to two, like I mentioned I wanted to change last time:
functionp.queryAndCast(query)ifnotquery.unionthenreturnh.queryAndCastOne(query)end-- h.queryAndCastOne will clone the table so this can be in placelocalresult=util_map.arrayInPlace(query,h.queryAndCastOne)ifquery.uniqueKeythen-- currently query.uniqueKey MUST be a single constant key-- use CONCAT in the query as needed-- remember you STILL must groupBy your first result!!!result=util_table.uniqueKeyMergeArrays(query.uniqueKey,unpack(result))elseresult=util_table.mergeArrays(unpack(result))endifquery.sortKeythenutil_sort.tablesByKeys(result,query.sortKey,query.sortOrder)endreturnresultend
The reason for that warning about using a groupBy on the first result is that I very deliberately do NOT check for uniqueness of keys within the first table in uniqueKeyMergeArrays, and instead only between members of later tables and the first table. The reason for this omission is that I’d rather let SQL handle getting rid of duplicates and process less data in Lua.
One additional wrapper note
As it turns out, this wrapper is missing one last clause:
1
2
3
4
5
-- we normally already added an index, but it is completely untrustworthy now-- so just overwrite it entirelyfori,rowinipairs(result)dorow.index=iend
When I cast values in my Cargo wrapper, I provide a parameter row.index; however, when taking the union of distinct queries, this index becomes useless, and so I need to go back over the entire thing and re-index from scratch.
In this case, I was able to choose an ordering to the tables & join that works regardless of which table - MatchSchedule or MatchScheduleGame - I’m getting the interview from. That possibility makes the setup about 10 lines of code shorter than it would otherwise be. It also pretty much removes the need to use LuaClassSystem at all, but that’s ok; I hadn’t realized I’d be able to do this until I’d already set up this much structure, and at that point it wasn’t worth undoing it.
You can also see how much neater the syntax looks with only one level of nesting needed: taking advantage of Lua’s equivalence between dicts and lists, I’m now just mapping over ipairs(query) instead of ipairs(query.union) after checking that union is true.
And in keeping with my decision to use sortKey & sortOrder as a distinct term from orderBy before, I’m using uniqueKey instead of groupBy as the outer param for what is essentially my application-logic-level groupBy param for the union query.
Conclusion
This post concludes the short series! We saw how a feature evolved slowly over time:
Union support was originally added with a verbose syntax, and no uniqueKey for merging the queries.
Grouping support was added later.
I then improved the syntax (this was ok because it was deployed in only 2 places at the time, and on only one wiki).
Finally, I added the index fix.
The last three steps were condensed into this one post, but in reality they were done over the span of about a month.
We also saw a case of an edit where I didn’t have to use this module at all - just because you have a tool doesn’t mean you should rule out making easier fixes when approaching a problem (not everything is a nail!). And I’ve mentioned a couple times the possibility of “splicing” results from separate queries together into a single row - but this is computational complexity that I want to avoid forever and never support if possible.
My Cargo wrapper has been an evolving product over the course of years, and I’m sure its union support will continue to be improved in the future as well. The important thing is to continue to keep the interface clean, understandable, and backwards-compatible.
Share on
WRITTEN BY
River
River is a developer most at home in MediaWiki and known for building Leaguepedia. She likes cats.