0

I'm building a fastAPI app and I have a complicated query that I'm trying to avoid doing as multiple individual queries where I concat the results. I have the following tables that all have foreign keys:

CHANGE_LOG: change_id | original (FK ROSTER.shift_id) | new (FK ROSTER.shift_id) | change_type (FK CONFIG_CHANGE_TYPES)

ROSTER: shift_id | shift_type (FK CONFIG_SHIFT_TYPES) | shift_start | shift_end | user_id (FK USERS)

CONFIG_CHANGE_TYPES: change_type_id | change_type_name

CONFIG_SHIFT_TYPES: shift_type_id | shift_type_name

USERS: user_id | user_name

FK= Foreign Key

I need to return the following information: user_name, change_type_name, and shift_start shift_end and shift_type_name for those whose shift_id matches the original or new in the CHANGE_LOG row.

The catch is that the CHANGE_LOG table might have both original and new, only an original but no new, or only a new but no original. But as the user can select a few options from drop down boxes before submitting the request, I also need to be able to include a filter to single out:

  1. just one user, or all users
  2. any change_type, or a group of change_types

The issue is that I can't find a way to get the user_name guaranteed for each row without inspecting it afterwards because I don't know if the new or original exist or are set to null.

Is there a way in SQLalchemy to have an optional filter in the query where I can say if the original exists use that to get the user_id, but if not then use the new to get the user_id. Also, if i have a query that definitely finds those with original and new shifts, it will never find those with only one of them as the criteria will never match.

I've also read this and similar ones, and while they'll resolve the issue of conditionally setting some of the filters, it doesn't get around the issue of part nulls returning nothing at all, rather than half the data. This one seems to solve that problem, but I have no idea how to implement it.

I know it's complicated, so let me know if I've done a poor job of explaining the question.

Fonty
  • 239
  • 2
  • 11
  • I think this can/will be solved using `outerjoin`, but it's a pain to figure out the syntax. Any pointers to a 5 table join out there? – Fonty Jul 06 '20 at 17:17

1 Answers1

0

Sorted. The solution was to use the outerjoin option. I'm sure the syntax can be more elegant than my solution if I properly engage in adding relationships when defining each class, but what I end up with is explicit and I think it makes it easier to read... at least for me.

Since I'm using a few tables more than once in the same query for different information, it was important to alias those, otherwise I ended up with a conflict (which 'user_id' did you want - it's not clear). For those playing at home, here's my general solution:

new=aliased(ROSTER)
original=aliased(ROSTER)
o_name=aliased(CONFIG_SHIFT_TYPES)
n_name=aliased(CONFIG_SHIFT_TYPES)

pd.read_sql(
  db.query(
    CHANGE_LOG.change_id,
    CHANGE_LOG.created,
    CONFIG_CHANGE_TYPES.change_name,
    o_name.shift_name.label('original_type'),
    n_name.shift_name.label('new_type'),
    OPERATORS.operator_name
  )
  .outerjoin(original, original.shift_id==CHANGE_LOG.original_shift)
  .outerjoin(new, new.shift_id==CHANGE_LOG.new_shift)
  .outerjoin (CONFIG_CHANGE_TYPES,CONFIG_CHANGE_TYPES.change_id==CHANGE_LOG.change_type)
  .outerjoin(CONFIG_SHIFT_TYPES, CONFIG_SHIFT_TYPES.shift_id==new.roster_shift_id)
  .outerjoin(o_name, o_name.shift_id==original.roster_shift_id)
  .outerjoin(n_name, n_name.shift_id==new.roster_shift_id)
  .outerjoin(USERS, or_(USERS.operator_id==original.user_id, USERS.user_id==new.user_id)
  ).statement, engine)
Fonty
  • 239
  • 2
  • 11