Filtering Query with NOT EXISTS - sql-server

I am currently attempting to query a database using SQL Server in Visual Studio. The database in question contains payment information, primarily identifying transactions and their resulting software licenses via OrderID and License ID. Ocassionally, these licenses get revoked due to misuse.
Right now, I'm attempting to run a query that returns all customers based upon this:
Select
[Order].LastName,
[Order].FirstName,
[Order].CompanyOrganization,
[Order].EmailAddress,
[Order].Country,
[License].LicenseID,
[License].InstanceCount
From [Order], [License]
Where
[License].OrderID = [Order].OrderID
AND [Order].Status = 1
AND not exists (Select LicenseID From [LicenseRevocation])
Order by [License].InstanceCount DESC;
The query returns no results, and I know it's because of the "NOT EXISTS" portion. However, I'm not sure why. Can somebody clear up how "EXISTS" works and how to implement it into my query?

The exists() function is true if the query inside it would produce at least one record, thus not exists() is only true if the query inside it would produce zero records.
In your case you are querying the entire LicenseRevocation inside not exists(), so your query would only return anything if that table was completely empty.
You would use a condition in the query to look for a specific record in it, something like this:
not exists (Select * From [LicenseRevocation] Where LicenseID = [License].LicenceID)
That will make the query return the records where there is no corresponding record in the LicenseRevocation table.

You need to define a condition on which you check for values if there exist or not,
Also use ON clause syntax for your joins.
Select [Order].LastName
, [Order].FirstName
, [Order].CompanyOrganization
, [Order].EmailAddress
, [Order].Country
, [License].LicenseID
, [License].InstanceCount
From [Order]
INNER JOIN [License] ON [License].OrderID = [Order].OrderID
Where [Order].[Status] = 1
AND NOT EXISTS (Select 1
From [LicenseRevocation]
WHERE LicenseID = [License].LicenseID) --<-- you are missing this condition
Order by [License].InstanceCount DESC;

this solution uses NOT IN and proper JOIN syntax but should give you the result you want
SELECT [Order].LastName, [Order].FirstName, [Order].CompanyOrganization,
[Order].EmailAddress, [Order].Country,
[License].LicenseID, [License].InstanceCount
FROM [Order]
INNER JOIN [License] ON [License].OrderID = [Order].OrderID
WHERE [Order].Status = 1
AND [License].LicenseID NOT IN (
SELECT LicenseID
FROM [LicenseRevocation] )
ORDER BY [License].InstanceCount DESC;
ps. use table ALIAS

First of all, write your query using proper JOINS, and use a standard LEFT OUTER JOIN:
Select
[Order].LastName,
[Order].FirstName,
[Order].CompanyOrganization,
[Order].EmailAddress,
[Order].Country,
[License].LicenseID,
[License].InstanceCount
From [Order]
INNER JOIN [License] ON [License].OrderID = [Order].OrderID
LEFT OUTER JOIN [LicenseRevocation] ON [License].LicenseID = [LicenseRevocation].LicenseID
Where [Order].Status = 1
AND [LicenseRevocation].LicenseID IS NULL
Order by [License].InstanceCount DESC;

Related

Does NOT EXIST (in the absense of where clause in the subquery) implicitly assume that where clause is on column selected by subquery?

Link: https://blog.udemy.com/sql-not-exists/
select * from customers
where NOT EXISTS (
select customerID from orders)
I had always thought that the subquery should be written such that the subquery table (orders) should have a where clause to lookup the value from the outer table (customers).
However above example seems to be without where clause in the subquery.
For example I would have written it like this:
select * from customers c
where NOT EXISTS (
select 1 from orders o
where c.customerID=o.customerID)
So does the subquery implicitly consider where clause to be on customerID in the 1st example?
The queries do different things. The first one, returns record if specific condition is true. It's like the followings:
select * from customers
where 1 = 0
select * from customers
where 1 = 1
In such cases, the condition is not referring the data in the specified table. You can use this, for example, to return or not return rows based on specific input parameter.
In your case, I expect that the NOT EXISTS ( select customerID from orders) will always be false, as orders exists, so no data is returned.
The second query is doing what you actually want - return customers that do not have any orders. I prefer using LEFT JOINs for such queries. In AdventureWorks2012 it will look like:
select count(*)
from Production.Product
where NOT EXISTS (
select ProductID from Sales.SalesOrderDetail)
select count(*)
from Production.Product c
where NOT EXISTS (
select 1 from Sales.SalesOrderDetail o
where c.ProductID=o.ProductID)
SELECT count(*)
from Production.Product c
LEFT JOIN Sales.SalesOrderDetail o
ON c.ProductID=o.ProductID
WHERE o.ProductID IS NULL

How do I properly add this query into my existing query within Query Designer?

I currently have the below query written within Query Designer. I asked a question yesterday and it worked on its own but I would like to incorporate it into my existing report.
SELECT Distinct
i.ProductNumber
,i.ProductType
,i.ProductPurchaseDate
,ih.SalesPersonComputerID
,ih.SalesPerson
,ic2.FlaggedComments
FROM [Products] i
LEFT OUTER JOIN
(SELECT Distinct
MIN(c2.Comments) AS FlaggedComments
,c2.SalesKey
FROM [SalesComment] AS c2
WHERE(c2.Comments like 'Flagged*%')
GROUP BY c2.SalesKey) ic2
ON ic2.SalesKey = i.SalesKey
LEFT JOIN [SalesHistory] AS ih
ON ih.SalesKey = i.SalesKey
WHERE
i.SaleDate between #StartDate and #StopDate
AND ih.Status = 'SOLD'
My question yesterday was that I wanted a way to select only the first comment made for each sale. I have a query for selecting the flagged comments but I want both the first row and the flagged comment. They would both be pulling from the same table. This was the query provided and it worked on its own but I cant figure out how to make it work with my existing query.
SELECT a.DateTimeCommented, a.ProductNumber, a.Comments, a.SalesKey
FROM (
SELECT
DateTimeCommented, ProductNumber, Comments, SalesKey,
ROW_NUMBER() OVER(PARTITION BY ProductNumber ORDER BY DateTimeCommented) as RowN
FROM [SalesComment]
) a
WHERE a.RowN = 1
Thank you so much for your assistance.
You can use a combination of row-numbering and aggregation to get both the Flagged% comments, and the first comment.
You may want to change the PARTITION BY clause to suit.
DISTINCT on the outer query is probably spurious, on the inner query it definitely is, as you have GROUP BY anyway. If you are getting multiple rows, don't just throw DISTINCT at it, instead think about your joins and whether you need aggregation.
The second LEFT JOIN logically becomes an INNER JOIN due to the WHERE predicate. Perhaps that predicate should have been in the ON instead?
SELECT
i.ProductNumber
,i.ProductType
,i.ProductPurchaseDate
,ih.SalesPersonComputerID
,ih.SalesPerson
,ic2.FlaggedComments
,ic2.FirstComments
FROM [Products] i
LEFT OUTER JOIN
(SELECT
MIN(CASE WHEN c2.RowN = 1 THEN c2.Comments) AS FirstComments
,c2.SalesKey
,MIN(CASE WHEN c2.Comments like 'Flagged*%' THEN c2.Comments) AS FlaggedComments
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY ProductNumber ORDER BY DateTimeCommented) as RowN
FROM [SalesComment]
) AS c2
GROUP BY c2.SalesKey
) ic2 ON ic2.SalesKey = i.SalesKey
JOIN [SalesHistory] AS ih
ON ih.SalesKey = i.SalesKey
WHERE
i.SaleDate between #StartDate and #StopDate
AND ih.Status = 'SOLD'

SQL - join two tables based on up-to-date entries

I have two tables
1- Table of TestModules
TestModules
2- Table of TestModule_Results
TestModule_Results
in order to get the required information for each TestModule, I am using FULL OUTER JOIN and it works fine.
FULL OUTER JOIN result
But what is required is slightly different. The above picture shows that TestModuleID = 5 is listed twice, and the requirement is to list the 'up-to-date' results based on time 'ChangedAt'
Of course, I can do the following:
SELECT TOP 1 * FROM TestModule_Results
WHERE DeviceID = 'xxx' and TestModuleID = 'yyy'
ORDER BY ChangedAt DESC
But this solution is for a single row and I want to do it in a Stored Procedure.
Expected output should be like:
ExpectedOutput
Any advise how can I implement it in a SP?
Use a Common Table Expression and Row_Number to add a field identifying the newest results, if any, and select for just those
--NOTE: a Common Table Expression requires the previous command
--to be explicitly terminiated, prepending a ; covers that
;WITH cteTR as (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY DeviceID, TestModuleID
ORDER BY ChangedAt DESC) AS ResultOrder
FROM TestModule_Results
--cteTR is now just like TestModule_Results but has an
--additional field ResultOrder that is 1 for the newest,
--2 for the second newest, etc. for every unique (DeviceID,TestModuleID) pair
)
SELECT *
FROM TestModules as M --Use INNER JOIN to get only modules with results,
--or LEFT OUTER JOIN to include modules without any results yet
INNER JOIN cteTR as R
ON M.DeviceID = R.DeviceID AND M.TestModuleID = R.TestModuleID
WHERE R.ResultOrder = 1
-- OR R.ResultOrder IS NULL --add if Left Outer Join
You say "this solution is for a single row"? Excellent. Use CROSS APPLY and change the WHERE clause from hand-input literal to the fields of the original table. APPLY operates at row level.
SELECT *
FROM TestModules t
CROSS APPLY
(
SELECT TOP 1 * FROM TestModule_Results
WHERE TestModule_Results.DeviceID = TestModules.DeviceID -- put the connecting fields here
ORDER BY ChangedAt DESC
)tr

Crystal Report: SQL Subquery Issue with 'null' Results

When I run the following SQL query in SMS, I correctly get "null" values in the second document_desc column (from patient_documents_1). This shows me that the patient documents that exist for a certain patient do not include the required bh_pharm document. Perfect!
SELECT
patients.last_name, patients.first_name,
patient_documents.document_description,
patient_documents_1.document_description
FROM patients
INNER JOIN patient_documents ON patients.pat_id = patient_documents.pat_id
LEFT OUTER JOIN (
select patient_documents.pat_id, patient_documents.document_description
from patient_documents where patient_documents.document_description = 'bh_pharm' )
as patient_documents_1 ON patients.pat_id = patient_documents_1.pat_id
WHERE
( patient_documents.timestamp >= {ts '2015-02-01 00:00:00'} AND patient_documents.timestamp < {ts '2015-03-01 00:00:00'} ) AND
patient_documents_1.document_description is null
How can I do this in a Crystal Report?
Everything I try results in an empty report. Either that or I have to include every record and every document description. I only want the report to show me what patients are missing the "bh_pharm" document. Can Crystal Reports handle 'null' results from a subquery or subreport?
Thanks in advance ...
Open the crystal report then there instead of selecting the tables select command and copy the same query there.
Now go design and generate the report.
Does it work without the LEFT JOIN to the subquery? Ie do you get results after taking out that join and all references to patient_documents_1?
If not, then make sure you are configuring crystal reports to query the SQL Server correctly.
Edit:
Does this work? It might be the curly braces but I also did some gratuitous rewriting...
WITH patient_documents_1 as
(
select patient_documents.pat_id
from patient_documents
where patient_documents.document_description = 'bh_pharm'
group by patient_documents.pat_id
),
noBH as (
SELECT
patients.last_name,
patients.first_name,
patients.pat_id
FROM patients
LEFT OUTER JOIN patient_documents_1 on patients.pat_id = patient_documents_1.pat_id
WHERE
patient_documents_1.document_description is null
)
Select
noBH.*,
patient_documents.document_description
from noBH
INNER JOIN patient_documents ON noBH.pat_id = patient_documents.pat_id
WHERE
patient_documents.timestamp between '2015-02-01' and '2015-02-28'
Oh yeah, are there patients who don't have the bh_pharm document but do have timestamps in feb 2015?
Start with a new report.
The only table you need to add to your report is the patients table. (If you alias it then make sure to change it in the query below.)
Add a SQL Expression to return the count. It will evaluate to zero when no documents with the right description are found. Copy everything below including the parentheses. Call it something like DocumentCount.
(
select count(*)
from patient_documents as pd
where
pd.document_description = 'bh_pharm'
and pd.pat_id = patients.pat_id /* alias on "patients" must match */
)
Add a filter to the record select formula to limit it to the ones you want to display.
{%DocumentCount} = 0

Is it possible to perform a join in Access on a second column if the first is blank?

I have this ugly source data with two columns, let's call them EmpID and SomeCode. Generally EmpID maps to the EmployeeListing table. But sometimes, people are entering the Employee IDs in the SomeCode field.
The person previously running this report in Excel 'solved' this problem by performing multiple vlookups with if statements, as well as running some manual checks to ensure results were accurate. As I'm moving these files to Access I am not sure how best to handle this scenario.
Ideally, I'm hoping to tell my queries to do a Left Join on SomeCode if EmpID is null, otherwise Left Join on EmpID
Unfortunately, there's no way for me to force validation or anything of the sort in the source data.
Here's the full SQL query I'm working on:
SELECT DDATransMaster.Fulfillment,
DDATransMaster.ConfirmationNumber,
DDATransMaster.PromotionCode,
DDATransMaster.DirectSellerNumber,
NZ([DDATransMaster]![DirectSellerNumber],[DDATransMaster]![PromotionCode]) AS EmpJoin,
EmployeeLookup.ID AS EmpLookup,
FROM FROM DDATransMaster
LEFT JOIN EmployeeLookup ON NZ([DDATransMaster]![DirectSellerNumber],[DDATransMaster]![PromotionCode]) = EmployeeLookup.[Employee #])
You can create a query like this:
SELECT
IIf(EmpID Is Null, SomeCode, EmpID) AS join_field,
field2,
etc
FROM YourTable
Or if the query will always be used within an Access session, Nz is more concise.
SELECT
Nz(EmpID, SomeCode) AS join_field,
field2,
etc
FROM YourTable
When you join that query to your other table, the Access query designer can represent the join between join_field and some matching field in the other table. If you were to attempt the IIf or Nz as part of the join's ON clause, the query designer can't display the join correctly in Design View --- it could still work, but may not be as convenient if you're new to Access SQL.
See whether this SQL gives you what you want.
SELECT
dda.Fulfillment,
dda.ConfirmationNumber,
dda.PromotionCode,
dda.DirectSellerNumber,
NZ(dda.DirectSellerNumber,dda.PromotionCode) AS EmpJoin,
el.ID AS EmpLookup
FROM
DDATransMaster AS dda
LEFT JOIN EmployeeLookup AS el
ON NZ(dda.DirectSellerNumber,dda.PromotionCode) = el.[Employee #])
But I would use the Nz part in a subquery.
SELECT
sub.Fulfillment,
sub.ConfirmationNumber,
sub.PromotionCode,
sub.DirectSellerNumber,
sub.EmpJoin,
el.ID AS EmpLookup
FROM
(
SELECT
Fulfillment,
ConfirmationNumber,
PromotionCode,
DirectSellerNumber,
NZ(DirectSellerNumber,PromotionCode) AS EmpJoin
FROM DDATransMaster
) AS sub
LEFT JOIN EmployeeLookup AS el
ON sub.EmpJoin = el.[Employee #])
What about:
LEFT JOIN EmployeeListing ON NZ(EmpID, SomeCode)
as your join, nz() uses the second parameter if the first is null, I'm not 100% sure this sort of join works in access. Worth 20 seconds to try though.
Hope it works.
You Could use a Union:
SELECT DDATransMaster.Fulfillment,
DDATransMaster.ConfirmationNumber,
DDATransMaster.PromotionCode,
DDATransMaster.DirectSellerNumber,
EmployeeLookup.ID AS EmpLookup
FROM DDATransMaster
LEFT JOIN EmployeeLookup ON
DDATransMaster.DirectSellerNumber = EmployeeLookup.[Employee #]
where DDATransMaster.DirectSellerNumber IS NOT NULL
Union
SELECT DDATransMaster.Fulfillment,
DDATransMaster.ConfirmationNumber,
DDATransMaster.PromotionCode,
DDATransMaster.DirectSellerNumber,
EmployeeLookup.ID AS EmpLookup
FROM DDATransMaster
LEFT JOIN EmployeeLookup ON
DDATransMaster.PromotionCode = EmployeeLookup.[Employee #]
where DDATransMaster.DirectSellerNumber IS NULL;

Resources