Объединить FULL OUTER JOIN и NOT IN?

У меня есть две таблицы:

+-----------------------+
| Tables_in_my_database |
+-----------------------+
| orders                | 
| orderTaken            | 
+-----------------------+

В заказах есть атрибуты

orderId, orderName, isClosed and orderCreationTime. 

В orderTaken есть атрибуты

userId, orderId and orderStatus. 

Скажем, когда

orderStatus = 1 --> the customer has taken the order
orderStatus = 2 --> the order has been shipped
orderStatus = 3 --> the order is completed
orderStatus = 4 --> the order is canceled
orderStatus = 5 --> the order has an exception

В основном механизм моего проекта работает следующим образом: пользователь с уникальным идентификатором пользователя сможет принять заказ с веб-страницы, где каждый заказ также имеет свой уникальный идентификатор заказа. После взятия таблица orderTaken запишет userId, orderId и первоначально установит orderStatus = 1. Затем магазин обновит orderStatus в зависимости от различных ситуаций. Как только магазин обновил isClosed = 1, этот заказ вообще не будет отображаться, независимо от того, взял его пользователь или нет (не имеет смысла, но это просто isClosed == 0 в запросе).

Теперь я хочу создать веб-страницу, которая будет отображать как новые заказы, которые пользователь еще не принял (это должны быть заказы, идентификаторы которых не записаны в таблице orderTaken под идентификатором пользователя этого пользователя), так и заказы, которые пользователь уже принял с показанным orderStatus, НО orderStatus НЕ 4 или 5, группировка по orderCreationTime DESC (да, может быть, не имеет смысла, если у меня нет orderTakenTime, но давайте оставим это так), например:

OrderId 4
Order Name: PetPikachu
orderStatus = 1
CreationTime: 5am

OrderId 3
Order Name: A truck of hamsters
orderStatus = 3
CreationTime: 4am

OrderId 2
New order
Order Name: Macbuk bull
CreationTime: 3am

OrderId 1
Order Name: Jay Chou's Album
orderStatus = 2
CreationTime: 2am

У меня есть этот запрос, написанный на основе знаний, которые я узнал:

SELECT * FROM orders A WHERE A.isClosed == '0' FULL OUTER JOIN orderTaken B  WHERE B.userId = '4' AND (B.orderStatus<>'4' OR B.orderStatus<>'5') ORDER BY A.orderCreationTime DESC;

Видимо этот запрос не работает, но я боюсь иметь

ON A.orderId = B.orderId

с тех пор возвращенная таблица устранит новые заказы, для которых orderId не был записан в orderTaken B. Я также пробовал предложение NOT IN, например

SELECT * FROM orders A WHERE A.isClosed = '0' AND A.orderId NOT IN (SELECT orderId FROM orderTaken B WHERE B.userId = '$userId' AND (B.orderStatus='4' OR B.orderStatus='5')) ORDER BY creationTime DESC;

Этот запрос работает, но в возвращаемой таблице нет поля orderStatus из orderTaken B. Я думал добавить еще одно предложение JOIN orderTaken B после этого запроса, чтобы получить поля из B, но я думаю, что это не лучший способ написать запрос.

Я просто хочу объединить "НЕ В" и "ПОЛНОЕ СОЕДИНЕНИЕ". Кто-нибудь может мне помочь? Спасибо!


person Hang    schedule 17.04.2017    source источник


Ответы (2)


Кажется, вы хотите найти записи в orders, которые не назначены пользователю (т.е. не имеют связанной записи в orderTaken), а также те, которые назначены пользователю, но где orderStatus не 4 или 5.

Тогда полное внешнее соединение не требуется, так как не будет записей в orderTaken без связанной записи в orders. Left inner join можно использовать для поиска всех записей из orders, onclause будет включать данные из связанных записей из orderTaken, а where можно затем отфильтровать заказы, принятые другими пользователями, или где orderStatus равен 4 или 5:

SELECT o.*, ot.userID, ot.orderStatus
FROM orders o
LEFT JOIN orderTaken ot
ON ot.orderID = o.orderID
WHERE o.isClosed = 0
AND (ot.userID IS NULL OR ot.userID = $userID AND ot.orderStatus NOT IN (4,5))
ORDER BY o.creationTime DESC
person Terje D.    schedule 17.04.2017
comment
Спасибо, Терье! Это именно то, что я хочу! Я думал использовать LEFT JOIN, но просто не знал о предложении IS NULL. Это ключевая вещь, которую я узнал от вас. Еще раз спасибо за решение моей проблемы! :D - person Hang; 17.04.2017
comment
Теперь я исправил имя таблицы (orderTaken было написано с ошибкой как orderStatus). - person Terje D.; 17.04.2017
comment
Это делает его более точным. Еще раз спасибо, Терье! - person Hang; 18.04.2017
comment
Привет, Терье Д., не могли бы вы также взглянуть на мои комментарии под ответом j-bin? Ваш предоставленный запрос также вернет пустой набор для других пользователей после того, как один из пользователей принял заказ... :( Скажем, после того, как пользователь с userID = 3 принял заказ, если я запущу ваш запрос с userId = 4, тогда он вернет пустой набор. - person Hang; 20.04.2017
comment
Что вы хотите показать для заказов, сделанных несколькими пользователями (например, 2 и 3), но не текущим (4)? Вам нужна только одна строка, показывающая, что заказ не принят пользователем 4, или одна строка для каждого пользователя, принявшего заказ, с указанием соответствующего состояния заказа для каждого из них? - person Terje D.; 20.04.2017

Как и сказал @terje-d, вам нужно LEFT JOIN. Обновлены исходные имена таблиц и исправлен фильтр $userId.

  • Для всех открытых заказов и незавершенных заказов.
   SELECT o.`orderId`, 
          o.`orderName`,
          ot.`orderStatus`,
          o.`orderCreationTime`
     FROM orders o 
LEFT JOIN orderTaken ot
       ON o.orderId = ot.orderId
    WHERE o.isClosed = 0
      AND (
            ot.orderId IS NULL
         OR ot.orderStatus NOT IN (4,5)
      )
 ORDER BY o.`orderCreationTime` DESC
  • Для всех открытых заказов и незавершенных заказов для конкретного пользователя
   SELECT o.`orderId`, 
          o.`orderName`,
          ot.`orderStatus`,
          o.`orderCreationTime`
     FROM orders o 
LEFT JOIN orderTaken ot
       ON o.orderId = ot.orderId
    WHERE o.isClosed = 0
      AND ( ot.orderStatus IS NULL
         OR (
                ot.user_id = ?
            AND ot.orderStatus NOT IN (4,5)
         )
      )
 ORDER BY o.`orderCreationTime` DESC
person j-bin    schedule 17.04.2017
comment
Да, Jben, спасибо за улучшенный ответ! Да, Терье дал неправильное имя таблицы, но логика верна, и ваш ответ просто делает его идеальным на 110%! И спасибо за подсказку по выбору конкретного пользователя! :D Ты просто потрясающий человек! - person Hang; 17.04.2017
comment
Привет, @j-bin, на самом деле я думаю, что в этом коде могут быть ошибки? Сегодня я попытался протестировать этот запрос с несколькими пользователями, принимающими заказы одновременно. Я использую учетную запись владельца магазина, чтобы создать новый новый заказ, а затем запустить этот запрос. Я вижу, что этот новый заказ находится на странице каждого пользователя, но после того, как один из пользователей принял заказ, скажем, с userId = 3, затем другие пользователи Например, userId=2or4 больше не увидит этот заказ, однако этот заказ продолжает отображаться на странице userId=3, и статус отображается правильно. Я попытался изменить код, взяв ot.user_id = ? к предложению where и сделайте так: - person Hang; 20.04.2017
comment
ГДЕ o.isClosed = 0 И ot.user_id = 2/4 И ( (ot.orderStatus IS NULL) ИЛИ (ot.orderStatus НЕ В (4,5))), однако это по-прежнему возвращает пустой массив. Я также безуспешно пытался поместить это ограничение user_id в любое другое место. Не могли бы вы еще взглянуть на него, я был бы очень признателен! :D - person Hang; 20.04.2017
comment
Я предполагаю, что причина в том, что как только кто-то принял заказ от этого orderId, этот orderId будет записан в таблице ot, поэтому, если мы все еще сделаем его ON o.orderId = ot.orderId, даже если мы поместим ot.orderId IS NULL или ot.orderStatus IS NULL, так как он больше никогда не будет NULL, поэтому результата не будет? - person Hang; 20.04.2017
comment
Тест этого запроса проходит отлично, если только один пользователь принимает этот заказ, но после того, как этот пользователь принял заказ, если мы позволим userId равняться userId других пользователей, тогда это вернет пустой набор - person Hang; 20.04.2017