Wintermute
Wintermute

Reputation: 3055

Join on an optional null value

I'm having trouble with the following query:

SELECT 
    job.name,
    job_element.label,
    job_element_role_hours.role,
    job_element_role_hours.hours_budgeted,
    latest_rate.hourly_rate,
    ( job_element_role_hours.hours_budgeted * latest_rate.hourly_rate ) AS line_cost
FROM
    job_element
    INNER JOIN job ON job_element.job = job.id
    INNER JOIN job_element_role_hours ON job_element_role_hours.element = job_element.id
    LEFT JOIN(
        SELECT 
            rate.*
        FROM
            rate
        LEFT JOIN rate AS newest ON (
            rate.role = newest.role
            AND COALESCE(rate.client_company, 1) = COALESCE(newest.client_company, 1)
            AND COALESCE(rate.client_group, 1) = COALESCE(newest.client_group, 1)
            AND COALESCE(rate.client_contact, 1) = COALESCE(newest.client_contact, 1)
            AND newest.date_from > rate.date_from
        )
        WHERE newest.id IS NULL
    ) AS latest_rate ON (
        latest_rate.role = job_element_role_hours.role
        AND (
            COALESCE(latest_rate.client_company, 1) = COALESCE(job.client_company, 1)
            OR latest_rate.client_company IS NULL
        )
        AND (
            COALESCE(latest_rate.client_group, 1) = COALESCE(job.client_group, 1)
            OR latest_rate.client_group IS NULL
        )
        AND (
            COALESCE(latest_rate.client_contact, 1) = COALESCE(job.client_contact, 1)
            OR latest_rate.client_contact IS NULL
        )

    )
WHERE job.id = 4

So... this query fetches a list of Elements for a Job. Each Element is broken down by "Role Hours" - an Element called "Build an HTML email", for example, might have two hours of Designer, and two hours of Developer - and then the calculates a cost based on the Hourly Rate for each Role.

The issue is where I join the latest_rate subquery back to the main query. Here's my rates table:

'CREATE TABLE `rate` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `client_company` int(11) DEFAULT NULL,
  `client_group` int(11) DEFAULT NULL,
  `client_contact` int(11) DEFAULT NULL,
  `role` int(11) DEFAULT NULL,
  `date_from` datetime DEFAULT NULL,
  `hourly_rate` decimal(18,2) DEFAULT NULL,
  `last_edited_by` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
)

There might be several rates for a role: a "global" rate where the role is set but the client_company, client_group and client_contact fields are NULL; a company-specific rate where the role and client_company are set but everything else is NULL, and a client-specific rate where the role, client_company and client_contact is set but the client_group is NULL.

I want to get the one rate for the role that "most closely" matches the client_company, client_group and client_contact set for the Job. So if there's a Rate record with the role and client_company set (and the client_company matches the one for the Job), I want that one. Otherwise, I'll fall back to the one with NULL client_company.

My attempt at joining is clearly wrong:

AND (
     COALESCE(latest_rate.client_company, 1) = COALESCE(job.client_company, 1)
     OR latest_rate.client_company IS NULL
)

It'll return all the records that either match the client_company, or have a NULL client_company.

How can I get it to just get the one with the most matching fields?

Thanks :)

Upvotes: 2

Views: 127

Answers (1)

anuragw
anuragw

Reputation: 133

What you could try is have one query that basically gets all matching rates against one parameter that you absolutely want to match - seems to be role in your case.

Then you can do soemthing like:

MAX(
    CASE WHEN <conditions 1> THEN <calculation 1> END,
    CASE WHEN <conditions 2> THEN <calculation 2> END,
    ...
    )

This would give you the maximum rate possible, i.e, the rate applicable if you had the most matching fields.

Upvotes: 2

Related Questions