简体   繁体   中英

SQL to find only data in a table with no matching records

I would like to find data in a table with no matching records based on two fields (ContractState, TransID).

For instance, assume this data set (in reality, all data sets contain hundreds of records, I'm just including a few):

AccountNbr  ContractState   TransID     Product
3335477     AL              80079       DPPO, DHMO
3335477     AL              80080       PPO
3335477     AR              80079       DPPO, DHMO
3335477     AR              80080       PPO

This should return 0 records, because there are 2 records for AL (one for each TransID) and 2 records for AR.

However, given the following data set:

AccountNbr  ContractState   TransID     Product
3335477     AL              80079       DPPO, DHMO
3335477     AL              80080       PPO
3335477     DE              80079       DHMO
3335477     WA              80080       DHMO

I would like to return only the following data set:

AccountNbr  ContractState   TransID     Product
3335477     DE              80079       DHMO
3335477     WA              80080       DHMO

because for each state, there is only 1 TransID.

I have this code, but it also includes records with matching data:

SELECT 
   'tblSQLContractState' as TableName, 
   TransID, 
   ContractState, 
   Product, 
   COUNT(*) AS [NumOfMessage]
FROM tblSQLContractState  
WHERE   TransID IN (80079, 80080)
GROUP BY 
   TransID, 
   ContractState, 
   Product
HAVING COUNT(*) = 1

Use window functions. For the data you provided, this should work:

select cs.*
from (select cs.*,
             count(*) over (partition by AccountNbr, ContractState) as cnt
      from tblSQLContractState cs
      where TransID IN (80079, 80080)
     ) cs
where cnt = 1;

You can use a NOT EXISTS to select records where there is another record with this TransId but not with this ContractState :

SELECT 
   'tblSQLContractState' as TableName, 
    cs.AccountNbr,  cs.ContractState, cs.TransID,  cs.Product
FROM tblSQLContractState cs
WHERE   cs.TransID IN (80079, 80080)
AND NOT EXISTS                                  -- no other record
(
    SELECT 1 FROM tblSQLContractState cs2
    WHERE cs2.TransID <> cs.TransID             -- with other TransId
      AND cs2.ContractState = cs.ContractState  -- and this  ContractState
)

NOT EXISTS is quite efficient in SQL-Server and can be modified/extended easily. Another advantage is that you can select all columns as opposed to a GROUP BY .

Your query is close. But you want one result row per ContractState - and it's also this attribute you want to know the number of records (aggregate COUNT(*) ) for. So remove Product and TransID from the GROUP BY clause.

As it's only combinations with a row count of 1 you want to show, you can use MIN(Product) or MAX(Product) to get the one product in question. Same for TransID .

SELECT 
   'tblSQLContractState' as TableName, 
   MAX(TransID) AS TransID, 
   ContractState, 
   MAX(Product) AS Product, 
   COUNT(*) AS [NumOfMessage]
FROM tblSQLContractState  
WHERE   TransID IN (80079, 80080)
GROUP BY ContractState
HAVING COUNT(*) = 1;

Gordon's answer is more more straight-forward and more of general use. Just the natural way to solve this in my opinion. I simply wanted to show that your query was very close :-)

UPDATE: Tim's answer is also very good. (It wasn't there when I typed mine.) He is right; all you want to know is whether there exists another record for the ContractState, so an exists clause is appropriate.

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