简体   繁体   中英

Select only those records where all linked records fulfill a condition

I have a classical 1:n relation between two tables as given below: 在此处输入图片说明

I need to select only those customers where 'NOT OrderDate IS NULL' for all their orders. I started with

SELECT Customers.Id, Customers.LastName
  FROM Customers, Orders
 WHERE Customers.Id = Orders.CustomerId AND NOT Orders.OrderDate IS NULL AND ...

and wanted to build further with 'FOR ALL' but failed.

I tried the suggestions given in the answers but none gave the correct results.

Here is my workaround with two temp tables as shown below:

DECLARE @TableA TABLE (
    Id int,
    CountA   int
)

DECLARE @TableB TABLE (
    Id int,
    CountB   int
)

INSERT INTO @TableA (Id, CountA)
SELECT        Customers.Id, COUNT(Orders.Id)
FROM            Customers INNER JOIN
                         Orders ON Customers.Id = Orders.CustomerId
GROUP BY Customers.ID

INSERT INTO @TableB (Id, CountB)
SELECT        Customers.Id, COUNT(Orders.Id)
FROM            Customers INNER JOIN
                         Orders ON Customers.Id = Orders.CustomerId
WHERE (NOT Orders.OrderDate IS NULL)                     
GROUP BY Customers.ID

Select tA.Id 
FROM  @TableA tA INNER JOIN  @TableB tB on tA.Id = tB.Id
WHERE tA.CountA = tB.CountB

Both temp tables differ only in that respect that the first selects the group count without a condition in Orders and the second temp selects them with a condition. Then joining the two temp tables where CountA = CountB gives only those customers where all related Orders fulfill the condition.

If someone finds a more elegant way, please let me know.

Any suggestions how to tackle this?

In cases like this, you need to think not of finding the records where all the related records meet a condition.

Instead, think of finding all the records WHERE there does NOT EXIST a related record that breaks the condition.

The most straightforward way to write this query, is with ALL :

SELECT Customers.Id, Customers.LastName
FROM Customers
WHERE '2000-01-01' < ALL(SELECT OrderDate FROM Orders WHERE Orders.CustomerId = Customers.Id)

You could also write it as a group query on the orders table, something like

WITH CustomerOrderDateRange(CustomerId, MinOrderDate, MaxOrderDate) AS (
  SELECT CustomerId, MIN(OrderDate), MAX(OrderDate)
  FROM Orders
  GROUP BY CustomerId
)
SELECT Customers.Id, Customers.LastName
FROM Customers
JOIN CustomerOrderDateRange
  ON Customers.Id = CustomerOrderDateRange.CustomerId
WHERE
  CustomerOrderDateRange.MinOrderDate > '2000-01-01'

I think this is cleaner if you need multiple criteria, for example a max date range as well.

Just locate the records you want to exclude from it, and put in a not in clause

 select *
 from Customers
 where Customers.Id not in (
   SELECT Customers.Id
   FROM Customers 
   join Orders on Customers.Id = Orders.CustomerId
   WHERE Orders.OrderDate < '2000-01-01'
   group by Customers.Id 
 )

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