简体   繁体   中英

LEFT OUTER JOIN with IS NULL

I have a at LEFT OUTER JOIN chain which only works for 90% of the cases.

The Tables

days:
id, date_value
1, 2017-01-01
2, 2017-01-02
3, 2017-01-03

periods:
id, starts_on, ends_on, name, federal_state_id, country_id
1, 2017-01-01, 2017-01-01, Test1, 1, NULL
2, 2017-01-03, 2017-01-03, Test2, 2, NULL

slots:
id, days_id, periods_id
1, 1, 1
2, 3, 2

The SQL-Query

SELECT days.date_value, periods.name, periods.federal_state_id 
FROM days 
LEFT OUTER JOIN slots ON (days.id = slots.day_id) 
LEFT OUTER JOIN periods ON (slots.period_id = periods.id) 
WHERE days.date_value >= '2017-01-01' AND 
days.date_value <='2017-01-03' AND 
(periods.id IS NULL OR periods.country_id = 1 OR 
periods.federal_state_id = 1) 
ORDER BY days.date_value;

The Result

2017-01-01, Test1, 1
2017-01-02, NULL, NULL

But I'd like to get this result:

2017-01-01, Test1, 1
2017-01-02, NULL, NULL
2017-01-03, NULL, NULL

As fare as I understand it the periods.id IS NULL doesn't match for the last entry because there is period #2 with a federal_state_id of 2 .

How do I have to change the SQL-Query to get the result I want?

If you move the condition on the periods entries to the join condition, they will not be part of the set that is joined with the slots, producing the desired NULL for name and federal_state_id . So the set that will be joined will only contain:

periods:
id, starts_on, ends_on, name, federal_state_id, country_id
1, 2017-01-01, 2017-01-01, Test1, 1, NULL

As such, every entry of date can be returned (except for the condition on the range). The query would then look like this:

SELECT 
  days.date_value, 
  periods.name, 
  periods.federal_state_id 
FROM days 
LEFT OUTER JOIN slots 
  ON (days.id = slots.day_id) 
LEFT OUTER JOIN periods 
  ON (slots.period_id = periods.id) AND 
     ((periods.country_id = 1 OR 
     periods.federal_state_id = 1))
WHERE 
  days.date_value >= '2017-01-01' AND 
  days.date_value <='2017-01-03' 
ORDER BY 
  days.date_value;

Please notice that the condition periods.id IS NULL is not needed any more as the set the conditions are applied to consist of only slots and periods where every period entry will have a value for id as it is the primary key. And as slots and periods are joined by a left join slots, having no match in the periods table are not removed.

In your original query you first joined all the entries of period (if the id matched) and then removed all the entries that didn't have the desired country_id or federal_state_id . This removed the dates that did have a match in period but did not meet the condition.

I always recomend formating SQL statements. Errors are easier to find.

Move the conditions to the joins like:

         SELECT days.date_value
               ,periods.name
               ,periods.federal_state_id 
           FROM dbo.days 
LEFT OUTER JOIN dbo.slots 
             ON days.id = slots.days_id
LEFT OUTER JOIN dbo.periods 
             ON slots.periods_id = periods.id
            AND (
                 periods.country_id          = 1 
                 OR periods.federal_state_id = 1
                )  
          WHERE days.date_value  >= '2017-01-01' 
            AND days.date_value  <='2017-01-03'  
       ORDER BY days.date_value
       ;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM