Использовать внутреннее соединение, если запись существует, иначе использовать левое соединение
У меня есть следующая структура таблицы:
dbo.Owner
OwnerID OwnerName
1 John
2 Marie
3 Alex
и dbo.Pet
PetID PetTag Status OwnerID
1 A341 Active 1
2 A342 Inactive 1
3 A343 Active 2
4 A345 Active 2
Мне нужно вернуть всех владельцев, у которых есть только активные домашние животные или нет домашних животных.
Итак, в этом примере выше мне нужно вернуть владельца 2 (все домашние животные активны) и владельца 3 (без домашних животных)
Я буду извлекать данные на С# с помощью Entity Framework, но простого SQL будет достаточно.
Вот что я придумал до сих пор:
select mi.* from Owner o
join Pet p
on o.OwnerID= p.OwnerID
where o.Status='Active'
union select * from Owner
where OwnerID not in (select OwnerID from Pet)
Теперь этот запрос выше работает, но он включает OwnerID = 1. И также мне было интересно, есть ли способ сделать это в 1 запросе без объединения.
Ответы
Ответ 1
Если ваши значения для Status
являются "активными" и "неактивными", вы можете упростить свой запрос. Когда вы говорите:
Мне нужно вернуть всех владельцев, у которых есть только активные домашние животные или нет домашних животных.
Тогда это фактически переводится на:
Мне нужно вернуть всех владельцев, у которых нет неактивных домашних животных.
Тогда ваш запрос станет намного проще.
В запросе Entity Framework:
owners = context.Owners
.Where(o => !o.Pets.Any(p => p.Status == "Inactive"))
.ToList();
SQL-запрос, сгенерированный этим:
SELECT
[Extent1].[OwnerID] AS [OwnerID],
[Extent1].[OwnerName] AS [OwnerName]
FROM [dbo].[Owners] AS [Extent1]
WHERE NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Pets] AS [Extent2]
WHERE ([Extent1].[OwnerID] = [Extent2].[OwnerID]) AND (N'Inactive' = [Extent2].[Status])
)
Или удалить беспорядок:
SELECT
OwnerID,
OwnerName
FROM Owners o
WHERE NOT EXISTS (SELECT
1
FROM Pets p
WHERE (o.OwnerID = p.OwnerID AND p.Status = 'Inactive')
)
Если у вас больше значений для состояния, вы можете использовать (Entity Framework):
owners = context.Owners
.Where(o => o.Pets.Any(p => p.Status == "Active") || !o.Pets.Any())
.Where(o => !o.Pets.Any(p => p.Status == "Inactive" /* || p.Status == "Lost" and any other values */))
.ToList();
который будет генерировать SQL-запрос:
SELECT
[Extent1].[OwnerID] AS [OwnerID],
[Extent1].[OwnerName] AS [OwnerName]
FROM [dbo].[Owners] AS [Extent1]
WHERE (( EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Pets] AS [Extent2]
WHERE ([Extent1].[OwnerID] = [Extent2].[OwnerID]) AND (N'Active' = [Extent2].[Status])
)) OR ( NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Pets] AS [Extent3]
WHERE [Extent1].[OwnerID] = [Extent3].[OwnerID]
))) AND ( NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Pets] AS [Extent4]
WHERE ([Extent1].[OwnerID] = [Extent4].[OwnerID]) AND (N'Inactive' = [Extent4].[Status])
))
Вы хотите проверить это на производительность, и могут быть лучшие способы, но он дает желаемый результат. Он предполагает, что у вас есть внешний ключ/навигационное свойство.
Ответ 2
Попробуйте следующий запрос:
select o.*
from dbo.owner o
where not exists(
select *
from dbo.pet p
where p.ownerid=o.ownerid and
p.status='Inactive'
);
Ответ 3
SELECT OwnerID, OwnerName
FROM Owner
WHERE OwnerID NOT IN (
SELECT OwnerID from Pet
WHERE Status='Inactive'
Этот простой запрос выполнит эту задачу.
OwnerId OwnerName
2 Marie
3 Alex
И если вы хотите выбрать владельца с по крайней мере одним ACTIVE или NO PET, используйте следующий запрос.
SELECT o.OwnerID o.OwnerName
FROM Owner o
LEFT JOIN Pet p
ON o.OwnerID= p.OwnerID
AND (p.Status='Active'
OR p.OwnerID is NULL)
OwnerId OwnerName
1 John
2 Marie
3 Alex
Этот запрос вернет имя OWNER до тех пор, пока все владельцы всех домашних животных НЕАКТИВНЫ
Теперь для другого случая.
Если у вашего стола есть шанс иметь OwnerId как NULL в таблице домашних животных.
Пожалуйста, используйте нижеприведенный запрос. (Mysql)
SELECT OwnerID, OwnerName
FROM Owner
WHERE OwnerID NOT IN (
SELECT IFNULL(OwnerID,0) from Pet
WHERE Status='Inactive');
ADDED IFNULL() в подзапросе.
SQLFIDDLE
Ответ 4
Интересно, что это можно сделать с помощью LEFT JOIN. Я понятия не имею, работает ли это по-разному с запросами NOT EXISTs, предложенными другими ответами.
CREATE TABLE [Owner] (
OwnerID int PRIMARY KEY,
OwnerName nvarchar(50)
);
INSERT INTO [Owner]
VALUES
(1, 'John'),
(2, 'Marie'),
(3, 'Alex');
CREATE TABLE Pet (
PetID int PRIMARY KEY,
PetTag nvarchar(10),
Status nvarchar(30),
OwnerID int FOREIGN KEY REFERENCES [Owner](OwnerID)
);
INSERT INTO Pet
VALUES
(1,'A341','Active', 1),
(2,'A342','Inactive', 1),
(3,'A343','Active', 2),
(4,'A345','Active', 2);
SELECT * FROM [Owner];
SELECT * FROM Pet;
SELECT
o.*
FROM
[Owner] o
LEFT JOIN Pet p
ON o.OwnerID = p.OwnerID
AND p.Status <> 'Active'
WHERE
p.OwnerID IS NULL;
DROP TABLE Pet, [Owner];
Ответ 5
select DISTINCT
o.Id
FROM Owner o
LEFT JOIN Pet p ON o.OwnerID= p.OwnerID
where p.Status='Active' OR p.OwnerID IS NULL
Ответ 6
SELECT DISTINCT RESULT FROM (
SELECT CASE WHEN POID is NULL
THEN OID
WHEN OID NOT IN (SELECT DISTINCT
OwnerID from Pet
WHERE Status='Inactive')
THEN OID
END AS RESULT
FROM (
SELECT O.OwnerID as OID, P.OwnerID as POID
FROM Owner o
LEFT JOIN Pet p
ON o.OwnerID= p.OwnerID
) T
)T2 WHERE RESULT IS NOT NULL
SQL Fiddle
Ответ 7
Интересно, что, хотя вы отметили его сущность-framework, большинство ответов не придумали упрощения, которые предлагает сущность-инфраструктура.
Между Owners
и Pets
существует соотношение "один ко многим". Каждый Owner
имеет ноль или более Pets
, каждый Pet
принадлежит ровно одному Owner
.
Если вы правильно настроили свои классы фреймворка для отношения one-to-many, они будут такими:
class Owner
{
public int Id {get; set;}
// every Owner has zero or more Pets:
public virtual ICollection<Pet> Pets {get; set;}
... // other properties
}
class Pet
{
public int Id {get; set;}
// every Pet belongs to exactly one Owner, using foreign key:
public int OwnerId {get; set;}
public Owner Owner {get; set;}
}
class MyDbConnection : DbConnection
{
public DbSet<Owner> Owners {get; set;}
public DbSet<Pet> Pets {get; set;}
}
Этого достаточно для того, чтобы структура сущности распознала, что вы создали отношения "один ко многим". Всякий раз, когда это необходимо, инфраструктура сущности сделает для вас правильное соединение.
Мне нужно вернуть всех владельцев, у которых есть только активные домашние животные или нет домашних животных.
Это те же коллекции, что и:
Мне нужно вернуть всех владельцев, у которых нет неактивных домашних животных
(обратите внимание на себя: Владельцы без домашних животных, несомненно, не имеют Неактивных домашних животных!)
Если вы правильно настроили классы, запросы будут более читабельными. Вы можете думать о коллекциях, а не о таблицах, которые связаны друг с другом с помощью идентификаторов
using (var dbConnection = new MyDbConnection())
{
var requestedOwners = dbConnection.Owners // Give me all Owners
.Where(owner => !owner.Pets.Any() // that have no Pets at all
|| owner.Pets.All(pet => pet.Status == Active)); // or have only active Pets
}
Entity Framework распознает, что для этого требуется соединение, и оно преобразует это в правильные внутренние соединения для вас.
Второй запрос еще проще и, вероятно, быстрее, потому что вы можете продолжить следующий Owner
, как только будет найден Неактивный Pet
var ownersWithoutInactivePets = dbContext.Owners // give me all Owners
.Where(owner => !owner.Pets // that don't have
.Any(pet => pet.Status == Inactive); // any inactive Pets
Опять же, структура сущности сделает для вас соединение