DELETE WHERE NOT EXISTS Not Being Applied Correctly - tsql

I'm using NOT EXISTS during a DELETE statement in a stored procedure and the not exists is not being applied to the data.
Using the following example data:
CREATE TABLE Region
(
RegionID INT IDENTITY(1,1)
,RegionName VARCHAR(25)
)
GO
INSERT INTO Region(RegionName)
VALUES ('East Coast')
,('Mid West')
,('West Coast')
GO
CREATE TABLE Customer
(
CustomerID INT IDENTITY(1,1)
,FirstName VARCHAR(5)
,Region INT
)
GO
INSERT INTO Customer(FirstName,Region)
VALUES('Tom',1)
,('Mike',2)
,('Jean',3)
GO
CREATE TABLE Orders
(
OrderID INT IDENTITY(1,1)
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
GO
INSERT INTO Orders(CustomerID,OrderAmount,OrderDate)
VALUES(1,10,'2018-11-30')
,(2,12,'2018-11-30')
,(2,15,'2018-12-01')
,(2,8,'2018-12-02')
,(2,11,'2018-12-03')
,(3,13,'2018-12-01')
,(3,20,'2018-12-03')
GO
Using that data I'm trying to create a procedure that does the following:
CREATE PROCEDURE udsp_GetOrdersOfXAmount #OrderAmount INT, #RegionID INT = 0
AS
BEGIN
DECLARE #ProcedureTemp TABLE
(
OrderID INT
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
INSERT INTO #ProcedureTemp(OrderID,CustomerID,OrderAmount,OrderDate)
SELECT * FROM Orders WHERE OrderAmount >= #OrderAmount
--Do several other UPDATES/ DELETES to #ProcedureTemp
--This is where the issue lies
IF #RegionID > 0
BEGIN
DELETE T FROM #ProcedureTemp T
WHERE NOT EXISTS
(
SELECT *
FROM Customer C
JOIN #ProcedureTemp T ON T.CustomerID = C.CustomerID
WHERE C.Region = #RegionID
)
END
SELECT * FROM #ProcedureTemp
END
GO
If you execute the procedure with the #RegionID parameter populated, you will see the procedure is not honoring the filter by region.
E.G.
EXEC udsp_GetOrdersOfXAmount 10,3
However, if you run the sub query used in the DELETE statement as its own query, you will see the WHERE clause logic provided is working. I don't understand why the it isn't working when used with NOT EXISTS in the DELETE statement.
DECLARE #OrderAmount INT = 10, #RegionID INT = 3
DECLARE #ProcedureTemp TABLE
(
OrderID INT
,CustomerID INT
,OrderAmount INT
,OrderDate DATE
)
INSERT INTO #ProcedureTemp(OrderID,CustomerID,OrderAmount,OrderDate)
SELECT * FROM Orders WHERE OrderAmount >= #OrderAmount
SELECT *
FROM Customer C
JOIN #ProcedureTemp T ON T.CustomerID = C.CustomerID
WHERE C.Region = #RegionID
Thank you in advance for any help you can provide.

You don't need the join in the inner query.
The fact that you are using the same alias for the outer query and the inner one is confusing to me, I'm guessing SQL Server also should have a problem with it.
Try writing it like this:
DELETE T
FROM #ProcedureTemp T
WHERE NOT EXISTS
(
SELECT *
FROM Customer C
-- You already have the T from the outer statement
WHERE T.CustomerID = C.CustomerID
AND C.Region = #RegionID
)

Related

Issue with While Loop with Nested While loop using SQL Server 2016

I am using SQL Server 2016 and getting lost in the weeds using NESTED WHILE LOOPS.
In the INNER WHILE LOOP I'm populating a TEMP table (#tempClaimRecordsForInsert) that holds a PolicyNo and ClaimNo. There are 773 records. The TEMP table gets populated correctly without issue.
The problem is in the OUTER WHILE LOOP where I'm trying to populate another TEMP table where I'm trying to add 773 claims to each UserSysID from the temp table (there are 16 UserSysID records) created in the inner loop. I am NOT getting any records in the TEMP table (#tempRecordsForInsert) with all records.
Any help/direction would be greatly appreciated. Thanks.
Here is my SQL code:
DECLARE #counterUserID int = 1;
DECLARE #counterClaims int = 1;
CREATE TABLE #tempClaimRecordsForInsert (
PolicyNo varchar(10)
, ClaimNo varchar(6)
);
CREATE TABLE #tempRecordsForInsert (
ReinsuranceAuditSysID int
, PolicyNo varchar(10)
, ClaimNo varchar(6)
)
WHILE #counterUserID <= (SELECT COUNT(*) FROM #tempAuditorUserIdList)
BEGIN
DECLARE #UserSysID int = (SELECT l.UserSysID FROM #tempAuditorUserIdList l WHERE l.RN = #counterUserID);
WHILE #counterClaims <= (SELECT COUNT(*) FROM #ClaimNo)
BEGIN
DECLARE #RN1 int = (SELECT c.RN FROM #ClaimNo c WHERE c.RN = #counterClaims);
INSERT INTO #tempClaimRecordsForInsert
SELECT c.PolicyNo, c.ClaimNo --, x.RN
FROM dbo.CMS_Claims c
INNER JOIN #ClaimNo x
ON c.ClaimNo = x.ClaimNo
WHERE x.RN = #RN1;
SET #counterClaims = (#counterClaims + 1)
END
INSERT INTO #tempRecordsForInsert
SELECT #UserSysID as ReinsuranceAuditSysID, i.ClaimNo, i.PolicyNo
FROM #tempClaimRecordsForInsert i
WHERE #UserSysID = #counterUserID
SET #counterUserID = (#counterUserID + 1)
END
SELECT * FROM #tempClaimRecordsForInsert;
DROP TABLE #tempClaimRecordsForInsert;
SELECT * FROM #tempRecordsForInsert;
DROP TABLE #tempRecordsForInsert;
Without seeing the table data is difficult to know but if you had UserSysId in the #tempClaimRecordsForInsert, then the where clause of the outer insert should be:
UserSysID = (SELECT l.UserSysID FROM #tempAuditorUserIdList l WHERE l.RN = #counterUserID)
#Jayvee, I was finally able to resolve my problem but again thank you for taking time to look at my question. Below is the code that fixed the issue. I didn't have to put in #UserSysID into the #tempClaimRecordsForInsert which I was grateful for. I found the answer stepping through my code using the SSMS Debugger in SQL Server 2016. Here is the code in the event that someone else has the same issue:
CREATE TABLE #tempClaimRecordsForInsert (
PolicyNo varchar(10)
, ClaimNo varchar(6)
);
CREATE TABLE #tempRecordsForInsert (
ReinsuranceAuditSysID int
, PolicyNo varchar(10)
, ClaimNo varchar(6)
);
DECLARE #counterUserID int = 1;
DECLARE #counterClaims int = 1;
WHILE #counterUserID <= (SELECT COUNT(*) FROM #tempAuditorUserIdList)
BEGIN
DECLARE #UserSysID int = (SELECT l.UserSysID FROM #tempAuditorUserIdList l WHERE l.RN = #counterUserID);
WHILE #counterClaims <= (SELECT COUNT(*) FROM #ClaimNo)
BEGIN
DECLARE #RN1 int = (SELECT c.RN FROM #ClaimNo c WHERE c.RN = #counterClaims);
INSERT INTO #tempClaimRecordsForInsert
SELECT c.PolicyNo, c.ClaimNo
FROM dbo.CMS_Claims c
INNER JOIN #ClaimNo x
ON c.ClaimNo = x.ClaimNo
WHERE x.RN = #RN1;
SET #counterClaims = (#counterClaims + 1)
END
INSERT INTO #tempRecordsForInsert
SELECT #UserSysID as ReinsuranceAuditSysID, i.PolicyNo, i.ClaimNo
FROM #tempClaimRecordsForInsert i
WHERE #UserSysID = (SELECT l.UserSysID FROM #tempAuditorUserIdList l WHERE l.RN = #counterUserID)
SET #counterUserID = (#counterUserID + 1);
END
SELECT * FROM #tempClaimRecordsForInsert ORDER BY ClaimNo asc;
SELECT * FROM #tempRecordsForInsert ORDER BY ReinsuranceAuditSysID asc, ClaimNo asc;
DROP TABLE #tempClaimRecordsForInsert;
DROP TABLE #tempRecordsForInsert;

How to use openxml within a user defined function in SQL Server

I have an XML structure that I parse using OPENXML within a stored procedure to retrieve parameters used to perform a query. This procedure was a base procedure that a different stored procedure (procedure 2) is calling. Procedure 2 uses an insert-exec construct to get the data from the base procedure. This works great as long as we only call Procedure 2 or the base procedure.
My first problem is that I have a different procedure (procedure 3) that now needs to get the result from procedure 2 (I need the business rules that this procedure enforces), but cannot due to the message:
An INSERT EXEC statement cannot be nested.
I then tried to take the base procedure and make it a table valued function, but when I execute it, I receive the message:
Only functions and some extended stored procedures can be executed from within a function.
How do I get around one or both of these issues?
EDIT 1
I am including a code snippet to show the base procedure (Procedure 1) and the procedure implementing business requirements on the results of that procedure (Procedure 2). If there is a 3rd procedure that needs the results with the business rules applied, we run into problems.
create procedure dbo.p_Proc
#Xml xml
as
begin
set nocount on;
declare #l_idoc int
, #InfoId int
, #InfoTypeId int
, #Id int
, #Name varchar(50)
, #StatusId int
, #RoleId int
, #XmlBase xml
, #l_path varchar(100);
declare #T_TABLE table(
InfoId int
, InfoTypeId int
);
declare #T_RESULT table
(
Field1 int
, Field2 varchar(50)
, Field3 int
);
EXEC sp_xml_preparedocument #l_idoc OUTPUT, #Xml;
set #l_path = '/xml/Info';
insert into #T_TABLE(InfoId, InfoTypeId)
select InfoId, InfoTypeId
from OPENXML (#l_idoc, #l_path, 1)
with (
InfoId int './#InfoId'
, InfoTypeId int './#InfoTypeId'
);
select #InfoId = InfoId
, #InfoTypeId = InfoTypeId
from #T_TABLE;
-- create the XML to call the base widgets
select #XmlBase =
(
select *
from
(
select t.Id, t.Name, t.StatusId, t.RoleId
from #T_TABLE w
inner join dbo.T_TABLE2 t
on t.InfoId = w.InfoId
and t.InfoTypeId = w.InfoTypeId
) b
for xml raw('Widget'), root('Xml')
);
-- retrieve widgets from base security
insert into #T_RESULT(Field1, Field2, Field3)
exec dbo.p_ProcBase #Xml = #XmlBase;
-- apply business logic here
select w.Field1, w.Field2, w.Field3
from #T_RESULT w;
end;
go
create procedure dbo.p_ProcBase
#Xml xml = null
as
begin
set nocount on;
declare #l_idoc int
, #Id int
, #Name varchar(50)
, #StatusId int
, #RoleId int
, #l_path varchar(100);
declare #T_Table table(
Id int
, Name varchar(50)
, StatusId int
, RoleId int
);
EXEC sp_xml_preparedocument #l_idoc OUTPUT, #Xml;
set #l_path = '/Xml/Widget';
insert into #T_Table(Id, Name, StatusId, RoleId)
select Id, Name, StatusId, RoleId
from OPENXML (#l_idoc, #l_path, 1)
with (
ProjectId int './#Id'
, WidgetTypeName varchar(50) './#Name'
, WorkflowStatusId int './#StatusId'
, UserRoleId bigint './#RoleId'
);
select #Id = w.Id
, #Name = w.Name
, #StatusId = w.StatusId
, #RoleId = w.RoleId
from #T_Table w;
-- retrieve enabled widgets for which the user has a role in the current workflow state
select t.Field1, t.Field2, t.Field3
from dbo.T_TABLE t
where t.StatusId = #StatusId
and t.RoleId = #RoleId;
end;
In order to send a data set (table) between procs, you must use a Table type, store the output of proc2 in a variable of table type and add a readonly only table type parameter to proc3
First you must create a table type to map your output from proc2:
CREATE TYPE T_RESULT AS TABLE
(
Field1 int
, Field2 varchar(50)
, Field3 int
);
In dbo.p_Proc change #T_RESULT to:
declare #T_RESULT T_RESULT
Then create proc3:
CREATE PROCEDURE dbo.proc3
#T_RESULT T_RESULT READONLY
AS
BEGIN
SET NOCOUNT ON
INSERT INTO T3(...)
SELECT ... FROM #T_RESULT
END
Don't forget to add READONLY after a table type parameter in a proc.

Common Table Expression Select where last observation was at a location

I have the following tables
Location table
[ID] [int] IDENTITY(1,1) NOT NULL
Package table
[ID] [int] IDENTITY(1,1) NOT NULL
PackageObservation table
[PackageID] int
[LocationID] int
[Date] datetime
[Quantity] int
For a given location I want to select packages where the last observation of the package was at the location
What is the Transact SQL?
I think it involves a common table expression but I cant figure it out.
More information.
The following almost does it, but I don't really want to assume that the identity field is in date order
select max(id) ,packageid
from packageobservation o1
where not exists (
select o2.id from packageobservation o2
where o2.[date] > o1.[date] )
group by packageid
You can use following SQL statement:
DECLARE #locationID int = 1
SELECT po.PackageID, MAX(po.[Date]) AS DateAtLocation
FROM PackageObservation po
WHERE po.LocationID=#locationID
AND NOT EXISTS (SELECT * FROM PackageObservation po2
WHERE po2.PackageID = po.PackageID AND
po2.LocationID <> po.LocationID AND
po2.[Date] >= po.[Date] )
GROUP BY po.PackageID
For better speed you can also add combined index on [LocationID],[PackageID] and [Date].
Seems to me that using CTE is not necessary here.

Update Manager Table Tree by Recursion

I have a table (see image) Employees (manager is manager of another and so on) with id, parentid, salary, totalsalary. The last one needs to be updated so that every employee had Sum of its Descendants salary. I have already written script, which gets total salary by id and than updates column in cursor, but it's heavy... any other ways?
DECLARE #id INT ;
DECLARE #s INT ;
DECLARE curs CURSOR FOR
SELECT personid FROM dbo.Employees
OPEN curs ;
FETCH NEXT FROM curs INTO #id ;
WHILE ##FETCH_STATUS = 0
BEGIN
WITH Xemps ( ID )
AS ( SELECT PersonID AS ID
FROM dbo.Employees
WHERE PersonID = #id
UNION ALL
SELECT e.PersonID AS ID
FROM dbo.Employees AS e
INNER JOIN Xemps AS x ON e.ManagerID = x.ID
)
SELECT #s = SUM(Salary)
FROM dbo.Employees
WHERE PersonID IN ( SELECT id
FROM Xemps )
UPDATE dbo.Employees
SET SalarySum = #s
WHERE PersonID = #id
FETCH NEXT FROM curs INTO #id
END
CLOSE curs ;
DEALLOCATE curs ;
The cursor is not necessary, this can be done using just a recursive common table expression:
WITH Emp AS
( SELECT EmployeeID, Salary, ManagerID
FROM dbo.Employee
UNION ALL
SELECT e.EmployeeID, e.Salary, Emp.ManagerID
FROM dbo.Employee e
INNER JOIN Emp
ON e.ManagerID = Emp.EmployeeID
)
UPDATE dbo.Employee
SET SalarySum = COALESCE(s.Salary, 0) + e.Salary
FROM dbo.Employee e
LEFT JOIN
( SELECT ManagerID, SUM(Salary) [Salary]
FROM Emp
GROUP BY ManagerID
) s
ON s.ManagerID = e.EmployeeID
Create a function that performs the sum for each employee where they are a manager:
create function dbo.fn_TotalSalary
{
#EmployeeId int
}
returns float
as
begin
declare #totalSalary float
select #totalSalary = sum(salary)
from dbo.employees
where employeeid = #employeeid or managerid = #employeeid
return #totalSalary
end
Then alter the Employees table, changing the TotalSalary column to a computed column using the function:
dbo.fn_TotalSalary(EmployeeId)
As employees earn more, the computed column will update automatically. Then you can simply call
select * from Employees
to get the details. Doing it this way would mean that your data is always 100% accurate and up to date rather than potentially retrieving stale data.

sql missing rows when grouped by DAY, MONTH, YEAR

If I select from a table group by the month, day, year,
it only returns rows with records and leaves out combinations without any records, making it appear at a glance that every day or month has activity, you have to look at the date column actively for gaps. How can I get a row for every day/month/year, even when no data is present, in T-SQL?
Create a calendar table and outer join on that table
My developer got back to me with this code, underscores converted to dashes because StackOverflow was mangling underscores -- no numbers table required. Our example is complicated a bit by a join to another table, but maybe the code example will help someone someday.
declare #career-fair-id int
select #career-fair-id = 125
create table #data ([date] datetime null, [cumulative] int null)
declare #event-date datetime, #current-process-date datetime, #day-count int
select #event-date = (select careerfairdate from tbl-career-fair where careerfairid = #career-fair-id)
select #current-process-date = dateadd(day, -90, #event-date)
while #event-date <> #current-process-date
begin
select #current-process-date = dateadd(day, 1, #current-process-date)
select #day-count = (select count(*) from tbl-career-fair-junction where attendanceregister <= #current-process-date and careerfairid = #career-fair-id)
if #current-process-date <= getdate()
insert into #data ([date], [cumulative]) values(#current-process-date, #day-count)
end
select * from #data
drop table #data
Look into using a numbers table. While it can be hackish, it's the best method I've come by to quickly query missing data, or show all dates, or anything where you want to examine values within a range, regardless of whether all values in that range are used.
Building on what SQLMenace said, you can use a CROSS JOIN to quickly populate the table or efficiently create it in memory.
http://www.sitepoint.com/forums/showthread.php?t=562806
The task calls for a complete set of dates to be left-joined onto your data, such as
DECLARE #StartInt int
DECLARE #Increment int
DECLARE #Iterations int
SET #StartInt = 0
SET #Increment = 1
SET #Iterations = 365
SELECT
tCompleteDateSet.[Date]
,AggregatedMeasure = SUM(ISNULL(t.Data, 0))
FROM
(
SELECT
[Date] = dateadd(dd,GeneratedInt, #StartDate)
FROM
[dbo].[tvfUtilGenerateIntegerList] (
#StartInt,
,#Increment,
,#Iterations
)
) tCompleteDateSet
LEFT JOIN tblData t
ON (t.[Date] = tCompleteDateSet.[Date])
GROUP BY
tCompleteDateSet.[Date]
where the table-valued function tvfUtilGenerateIntegerList is defined as
-- Example Inputs
-- DECLARE #StartInt int
-- DECLARE #Increment int
-- DECLARE #Iterations int
-- SET #StartInt = 56200
-- SET #Increment = 1
-- SET #Iterations = 400
-- DECLARE #tblResults TABLE
-- (
-- IterationId int identity(1,1),
-- GeneratedInt int
-- )
-- =============================================
-- Author: 6eorge Jetson
-- Create date: 11/22/3333
-- Description: Generates and returns the desired list of integers as a table
-- =============================================
CREATE FUNCTION [dbo].[tvfUtilGenerateIntegerList]
(
#StartInt int,
#Increment int,
#Iterations int
)
RETURNS
#tblResults TABLE
(
IterationId int identity(1,1),
GeneratedInt int
)
AS
BEGIN
DECLARE #counter int
SET #counter= 0
WHILE (#counter < #Iterations)
BEGIN
INSERT #tblResults(GeneratedInt) VALUES(#StartInt + #counter*#Increment)
SET #counter = #counter + 1
END
RETURN
END
--Debug
--SELECT * FROM #tblResults