samedi 11 juin 2016

PostgreSQL: SQL JOIN with "Lookahead" Condition in ON or WHERE Clause

I am not sure how best to describe the problem I have, but it feels very much like an SQL query need for a lookahead condition such as those in a regular expression :). Pardon my verbosity as I try to find a way to express this problem.

I have a relatively complex schema of medical services, patient info, patient payment types (including insurances, workers comp info, etc), and medical sales commission earnings. Naturally there are sales reporting tools. What follows is a very oversimplified version of a query involving varying JOIN conditional cases. Most of this query is just context for the clause my question focuses on, the 2nd-to-last WHERE condition (which defines JOIN conditions):

SELECT vendor_services.*, patients.*, payments.*, vendor_products.*, products.*, vendor_product_commissions.*, vendor_commissions.*, commission_earners.*, users.*
    FROM vendor_services
     JOIN patients ON patients.vendor_id = vendor_services.vendor_id
     JOIN payments ON payments.patient_id = patiends.id
     JOIN payment_types ON payment_types.id = payments.payment_type_id
     JOIN vendor_products ON vendor_products.id = vendor_services.vendor_product_id
     JOIN products ON products.id = vendor_products.product_id
     JOIN vendor_product_commissions ON vendor_product_commissions.vendor_product_id = vendor_products.id
     JOIN vendor_commissions ON vendor_commissions.id = vendor_product_commissions.vendor_commissions.id
     LEFT JOIN commission_earners ON commission_earners.id = vendor_commissions.commission_earners_id
     JOIN users ON commission_earners.user_id = users.id
    WHERE
     vendor_services.state != 'In Progress'
     AND
      vendor_services.date BETWEEN :datetime_1 AND :datetime_2
     AND
      vendor_commissions.start_date > :datetime_1
     AND
      vendor_commissions.end_date < :datetime_2
     AND
      vendor_product_commissions.payment_type = payment_types.type
     AND
      payments.transaction_type = 'Paid'
    GROUP BY
      ....

Again, this is very oversimplified: the SELECT clause is far more complex, as are the GROUP BY and ORDER clauses, performing CASE statements and aggregate calculations, etc. I have left out many other tables which represent other systems within the overall application, and focused just on the data and clauses that are relevant. My question is in regards to a needed change to this particular WHERE condition regarding the following JOIN:

WHERE ... AND vendor_product_commissions.payment_type = payment_types.type

There has been an introduction of a new possible vendor_product_commissions.payment_type value that is not a member of the payment_types.type values. With the SQL query exactly as is, it no longer selects rows in most cases, as much of the LEFT table will be using this new value. When adding an OR clause, then duplicate rows are selected when only one row should be selected:

WHERE ... AND vendor_product_commissions.payment_type = payment_types.type OR vendor_product_commissions.payment_type = 'DEFAULTVALUE'

What I need is to JOIN only on the row where vendor_product_commissions.payment_type = payment_types.type, unless that produces NULL, in which case I need to perform the JOIN on vendor_product_commissions.payment_type = 'DEFAULTVALUE'.

I can do this programatically with the ORM code surrounding this query, but that is very inefficient for a very large reporting system (essentially, query first for the specific type, then if none returned, query again for the "default" type).

I dont believe this feature exists in PostgreSQL, but thats why I am describing it as a "lookahead JOIN" problem - I need to have a sort of CASE statement that if the first JOIN condition produces a NULL relation, then perform the subsequent JOIN (OR) condition to match against this newly introduced value ('DEFAULTVALUE'). Can this be done in raw SQL? Or do I need to break this whole query apart and perform the selection of services and related data, and then programatically / iteratively relate it (via ORM/application language code) to the sales commission data? I have a strong hunch that the query can be modified to do this, but without being knowledgeable of a particular label or term for this problem, I am having a hard time searching for a possible SQL-based solution.

This is for a Ruby on Rails 4 application, using ActiveRecord, though the SQL JOIN statements are all in plaintext / strings since AR doesnt provide methods for LEFT JOIN (again, there are more and more types of JOIN statements than those listed above). I am not sure if Rails is relevant to my question, but I figured I would mention it.

Aucun commentaire:

Enregistrer un commentaire