Exclude specific rows from table in Stored Procedure - sql

I have a table named Blacklist and table named Order.
Both have CustomerId column.
Stored Procedure ExecOrder manipulates Order table.
My goal is to exclude Orders that have Blacklisted CustomerId (meaning : Order's CustomerId is in Blacklist table).
I edited ExecOrder SP like this:
DECLARE #Temp TABLE
(
CustomerId UNIQUEIDENTIFIER
);
INSERT INTO #Temp
SELECT RightSide
FROM Blacklist
WHERE LeftSide = #CustomerId;
BEGIN
DECLARE db_cursor CURSOR
FOR SELECT OrderId
FROM dbo.[Order] ord
LEFT OUTER JOIN #Temp t ON t.CustomerId <> ord.CustomerId AND ord.CustomerId <> #CustomerId
WHERE AssetId = #BaseAsset
AND CurrencyId = #QuoteAsset
AND OrderTypeId <> #OrderType
AND [QuotePrice] <= #QuotePrice
AND OrderStatusId = 10
AND Amount > ISNULL(AmountFilled, 0)
AND OrderId < #OrderId
AND OrderBookId = #OrderBookId
AND DeliveryStart = #DeliveryPeriodStart
AND DeliveryEnd = #DeliveryPeriodEnd
AND #MinAmount <= Amount - ISNULL(AmountFilled, 0)
ORDER BY OrderDate;
END;
#Temp table returns correct list of CustomerIds.
Problem is that blacklisted orders are not excluded.

Your #Temp table is holding blacklisted customers.
-- contains blacklisted customer
INSERT INTO #Temp
SELECT RightSide
FROM Blacklist
WHERE LeftSide = #CustomerId;
Now, you need to select orders of customers not existing in #Temp table.
-- You need to select orders, where customerId not exists in #Temp table
SELECT OrderId
FROM dbo.[Order] ord
WHERE NOT EXISTS(SELECT 1 from #Temp WHERE customerId = ord.CustomerId) ...

You should connect with blacklist and take only where connected blacklist not existst:
LEFT OUTER JOIN #Temp t ON t.CustomerId <> ord.CustomerId AND ord.CustomerId <> #CustomerId
Change to:
LEFT OUTER JOIN #Temp t ON t.CustomerId = ord.CustomerId OR ord.CustomerId = #CustomerId
Next you should check if it is empty:
WHERE ... AND t.CustomerId IS NULL

Related

SQL - More efficient way instead of using a cursor

-- Declare the table we are interested in reverting.
DECLARE #table_name VARCHAR(1000)
SET #table_name = 'tblCustomers' -- change this
-- Declare cursor and use the select statement (the one we want to loop through).
DECLARE customer_cursor CURSOR FOR
SELECT C.CustomerId
FROM tblCustomers C
WHERE ModifiedBy like '%crm%'
ORDER BY C.CustomerId DESC
-- Open the cursor and copy the columns into the original_consumer variable.
DECLARE #customer_id INT
OPEN customer_cursor
FETCH NEXT FROM customer_cursor
INTO #customer_id
-- Now loop through the old consumer id's and update their corresponding purchase and refunds records.
WHILE ##FETCH_STATUS = 0
BEGIN
IF EXISTS ( SELECT TOP 1 UserName
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC)
BEGIN
UPDATE tblCustomers
SET ModifiedBy = (SELECT TOP 1 UserName
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC),
ModifiedDate = (SELECT TOP 1 TransactionDate
FROM tblAudit
WHERE TableName = #table_name
AND TableId IN (
SELECT CONVERT(VARCHAR, CustomerId)
FROM tblCustomers -- change this
WHERE CustomerId = #customer_id
)
AND UserName != 'crmuser'
ORDER BY TransactionDate DESC)
WHERE CustomerId = #customer_id
END
FETCH NEXT FROM customer_cursor INTO #customer_id
END
-- Finally close and deallocate the cursor to stop memory leakage.
CLOSE customer_cursor
DEALLOCATE customer_cursor
This looks like it might work:
UPDATE tblCustomers
SET ModifiedBy = a.UserName,
ModofiedDate = amax.LatestChangeDate
from tblCustomers t
inner join -- get the latest transaction_date for each customer
(
select customerId, max(transaction_date) LatestChangeDate
from tblAudit
where TableName = #table_name
) amax on amax.customerId = t.customer_id
inner join -- get the details of changes for customer and latest date
(
select CustomerId, UserName, transaction_date
from tblAudit
where table_name = #table_name
) a on a.customerId = t.customerId and a.transaction_date = amax.LatestChangeDate
WHERE t.CustomerId = #customer_id
);
(I might have some of the column names wrong.)
BEGIN TRAN
SELECT COUNT(*)
FROM tblInvoices
WHERE ModifiedBy = 'DataImporterUser'
;WITH cte AS
(
SELECT TableId,
TransactionDate,
UserName,
TransactionDetail,
ROW_NUMBER() OVER (PARTITION BY TableId ORDER BY TransactionDate DESC) AS rn
FROM tblAudit
WHERE TableName = 'tblInvoices'
AND UserName <> 'DataImporterUser'
)
UPDATE C
SET C.ModifiedBy = cte.UserName,
C.ModifiedDate = cte.TransactionDate
FROM tblInvoices AS C
INNER JOIN cte
ON cte.TableId = C.InvoiceId
WHERE rn = 1
SELECT COUNT(*)
FROM tblEvents
WHERE ModifiedBy = 'DataImporterUser'
ROLLBACK

Trigger, SQL SERVER 2008

i have two tables
PaymentData
Ser Customerid Totalpaid
1. AGP001 2400
2. AGP002 1000
3. AGP003 1500
Receipt
Receipt# Customerid Paid
1. AGP001 1200
2. AGP001 1200
I want to create a trigger on Receipt table, and trigger will fire on insert, update, and delete operations which updates the totalpaid field of PaymentData table. Everytime a new Receipt record is inserted or updated against some customerid, totalpaid field for that customer will updated as well.
Trigger should do following.
Update PaymentData.totalpaid = sum(Recipt.paid)
where Receipt.customerID = PaymentData.customerID
I guess you need some trigger like:
CREATE TRIGGER dbo.OnReceiptUpdate
ON dbo.Receipt
AFTER INSERT,DELETE,UPDATE --operations you want trigger to fire on
AS
BEGIN
SET NOCOUNT ON;
DECLARE #customer_id VARCHAR(10)
SET #customer_id= COALESCE
(
(SELECT customer_id FROM inserted), --table inserted contains inserted rows (or new updated rows)
(SELECT customer_id FROM deleted) --table deleted contains deleted rows
)
DECLARE #total_paid DECIMAL
SET #total_paid =
(
SELECT SUM(paid)
FROM Receipt
WHERE customer_id = #customer_id
)
UPDATE PaymentData
SET total_paid = #total_paid
WHERE customer_id = #customer_id
IF ##ROWCOUNT = 0 --if nothing was updated - you don't have record in PaymentData, so make it
INSERT INTO PaymentData (customer_id, total_paid)
VALUES (#customer_id, #total_paid)
END
GO
Keep in mind - it aint gonna work with multiply updates/deletes/inserts - This is just example of how you need to do it
Try this trigger with multiply updates, inserts or deletes.
CREATE TRIGGER [dbo].upd_PaymentData ON dbo.Receipt
FOR INSERT, UPDATE, DELETE
AS
IF ##ROWCOUNT = 0 return
SET NOCOUNT ON;
DECLARE #actionTable nvarchar( 10),
#insCount int = (SELECT COUNT(*) FROM inserted),
#delCount int = (SELECT COUNT(*) FROM deleted)
SELECT #actionTable = CASE WHEN #insCount > #delCount THEN 'inserted'
WHEN #insCount < #delCount THEN 'deleted' ELSE 'updated' END
IF #actionTable IN ('inserted', 'updated')
BEGIN
;WITH cte AS
(
SELECT r.Customerid, SUM(r.Paid) AS NewTotalPaid
FROM dbo.Receipt r
WHERE r.Customerid IN (SELECT i.Customerid FROM inserted i)
GROUP BY r.Customerid
)
UPDATE p
SET p.Totalpaid = c.NewTotalPaid
FROM dbo.PaymentData p JOIN cte c ON p.Customerid = c.Customerid
END
ELSE
BEGIN
;WITH cte AS
(
SELECT d.Customerid, SUM(ISNULL(r.Paid, 0)) AS NewTotalPaid
FROM deleted d LEFT JOIN dbo.Receipt r ON d.Customerid = r.Customerid
GROUP BY d.Customerid
)
UPDATE p
SET p.Totalpaid = c.NewTotalPaid
FROM dbo.PaymentData p JOIN cte c ON p.Customerid = c.Customerid
END
CREATE TRIGGER [dbo].upd_PaymentData ON dbo.Receipt
FOR INSERT, UPDATE, DELETE
AS
IF ##ROWCOUNT = 0 return
SET NOCOUNT ON;
DECLARE #actionTable nvarchar( 10),
#insCount int = (SELECT COUNT(*) FROM inserted),
#delCount int = (SELECT COUNT(*) FROM deleted)
SELECT #actionTable = CASE WHEN #insCount > #delCount THEN 'inserted'
WHEN #insCount < #delCount THEN 'deleted' ELSE 'updated' END
IF #actionTable IN ('inserted', 'updated')
BEGIN
;WITH cte AS
(
SELECT r.Customerid, SUM(r.Paid) AS NewTotalPaid,<strike> r.paymentDate</strike>
FROM dbo.Receipt r
WHERE r.Customerid IN (SELECT i.Customerid FROM inserted i)
GROUP BY r.Customerid
)
UPDATE p
SET p.Totalpaid = c.NewTotalPaid
<strike>SET p.lastpaymentDate = c.paymentDate</strike>
FROM dbo.PaymentData p JOIN cte c ON p.Customerid = c.Customerid
END
ELSE
BEGIN
;WITH cte AS
(
SELECT d.Customerid, SUM(ISNULL(r.Paid, 0)) AS NewTotalPaid
FROM deleted d LEFT JOIN dbo.Receipt r ON d.Customerid = r.Customerid
GROUP BY d.Customerid
)
UPDATE p
SET p.Totalpaid = c.NewTotalPaid
FROM dbo.PaymentData p JOIN cte c ON p.Customerid = c.Customerid
END
I tried to add more functionality as indicated inside tage but it gives error and is not working.
Simply added one field in both tables.
In paymentdata added lastpaymentdate,
And in receipt added paymentdate.
On inserting or updating receipt table, paymentdata.lastpaymentdate should also update to receipt.paymentdate.

Can this cursor in SQL Server 2008 be refactored into something "set based"

DECLARE #PaymentTime VARCHAR (50)
DECLARE #DueDate VARCHAR (50)
DECLARE #CustomerID int
DECLARE MYCURSOR5 CURSOR
FOR SELECT distinct Payment.PaymentTime,Invoice.DueDate, Customer.CustomerID
FROM Cus_Cred_Terms INNER JOIN
Customer ON Cus_Cred_Terms.CustomerID = Customer.CustomerID INNER JOIN
Payment ON Customer.CustomerID = Payment.CustomerID INNER JOIN
Invoice ON Payment.InvoiceID = Invoice.InvoiceID
open MYCURSOR5
FETCH MYCURSOR5 INTO #PaymentTime, #DueDate, #CustomerID
WHILE ##FETCH_STATUS = 0
BEGIN
if(#PaymentTime > #DueDate)
begin
print'payment time:'
PRINT #PaymentTime
print'due date:'
PRINT #DueDate
print''
print #CustomerID
print''
update dbo.Cus_Cred_Terms
set dbo.Cus_Cred_Terms.CreditAllowed = 0
FROM Cus_Cred_Terms INNER JOIN
Customer ON Cus_Cred_Terms.CustomerID = Customer.CustomerID INNER JOIN
Payment ON Customer.CustomerID = Payment.CustomerID
where Customer.CustomerID = #CustomerID and dbo.Cus_Cred_Terms.CreditAllowed = 1
end
FETCH MYCURSOR5 INTO #PaymentTime, #DueDate, #CustomerID
END
CLOSE MYCURSOR5
It loops through each field checking if the DueDate is smaller than the actual PaymentDate and if it is, then it sets a field in another table to 0 (1 for credit allowed and 0 for credit not allowed)
So the question is, can I avoid using a cursor to do this? If you need more information please ask.
Try something like this:
;WITH CTE AS
(
SELECT DISTINCT
Customer.CustomerID
FROM
Cus_Cred_Terms
INNER JOIN
Customer ON Cus_Cred_Terms.CustomerID = Customer.CustomerID
INNER JOIN
Payment ON Customer.CustomerID = Payment.CustomerID
INNER JOIN
Invoice ON Payment.InvoiceID = Invoice.InvoiceID
WHERE
Payment.PaymentTime > Invoice.DueDate
)
UPDATE
dbo.Cus_Cred_Terms
SET
dbo.Cus_Cred_Terms.CreditAllowed = 0
FROM
CTE
WHERE
dbo.Cus_Cred_Terms.CustomerID = CTE.CustomerID
AND dbo.Cus_Cred_Terms.CreditAllowed = 1
The CTE (Common Table Expression) basically has the SELECT that your cursor had - except it also check the constraint Payment.PaymentTime > Invoice.DueDate. Only those customer ID's that match this criteria are selected.
From those CustomerID values, the table dbo.Cus_Cred_Terms is updated if the CreditAllowed column for this CustomerID in that table is equal to 1.

SQL Server Update trigger for bulk updates

Below is a SQL Server 2005 update trigger. For every update on the email_subscriberList table where the isActive flag changes we insert an audit record into the email_Events table. This works fine for single updates but for bulk updates only the last updated row is recorded. How do I convert the below code to perform an insert for every row updated?
CREATE TRIGGER [dbo].[Email_SubscriberList_UpdateEmailEventsForUpdate_TRG]
ON [dbo].[Email_subscriberList]
FOR UPDATE
AS
DECLARE #CustomerId int
DECLARE #internalId int
DECLARE #oldIsActive bit
DECLARE #newIsActive bit
DECLARE #email_address varchar(255)
DECLARE #mailinglist_name varchar(255)
DECLARE #email_event_type varchar(1)
SELECT #oldIsActive = isActive from Deleted
SELECT #newIsActive = isActive from Inserted
IF #oldIsActive <> #newIsActive
BEGIN
IF #newIsActive = 1
BEGIN
SELECT #email_event_type = 'S'
END
ELSE
BEGIN
SELECT #email_event_type = 'U'
END
SELECT #CustomerId = customerid from Inserted
SELECT #internalId = internalId from Inserted
SELECT #email_address = (select email from customer where customerid = #CustomerId)
SELECT #mailinglist_name = (select listDescription from Email_lists where internalId = #internalId)
INSERT INTO Email_Events
(mailshot_id, date, email_address, email_event_type, mailinglist_name)
VALUES
(#internalId, getDate(), #email_address, #email_event_type,#mailinglist_name)
END
example
untested
CREATE TRIGGER [dbo].[Email_SubscriberList_UpdateEmailEventsForUpdate_TRG]
ON [dbo].[Email_subscriberList]
FOR UPDATE
AS
INSERT INTO Email_Events
(mailshot_id, date, email_address, email_event_type, mailinglist_name)
SELECT i.internalId,getDate(),c.email,
case i.isActive when 1 then 'S' else 'U' end,e.listDescription
from Inserted i
join deleted d on i.customerid = d.customerid
and i.isActive <> d.isActive
join customer c on i.customerid = c.customerid
join Email_lists e on e.internalId = i.internalId
Left outer joins, for in case there are no related entries in customer or email_Lists (as is possible in the current code) -- make them inner joins if you know there will be data present (i.e. foreign keys are in place).
CREATE TRIGGER [dbo].[Email_SubscriberList_UpdateEmailEventsForUpdate_TRG]
ON [dbo].[Email_subscriberList]
FOR UPDATE
AS
INSERT INTO Email_Events
(mailshot_id, date, email_address, email_event_type, mailinglist_name)
select
i.InternalId
,getdate()
,cu.Email
,case i.IsaActive
when 1 then 'S'
else 'U'
end
,el.ListDescription
from inserted i
inner join deleted d
on i.CustomerId = d.CustomerId
and i.IsActive <> d.IsActive
left outer join Customer cu
on cu.CustomerId = i.CustomerId
left outer join Email_Lists el
on el.InternalId = i.InternalId
Test it well, especially for concurrency issues. Those joins within the trigger make me nervous.

SQL statement to select group containing all of a set of values

In SQL Server 2005, I have an order details table with an order id and a product id. I want to write a sql statement that finds all orders that have all the items within a particular order. So, if order 5 has items 1, 2, and 3, I would want all other orders that also have 1, 2, and 3. Also, if order 5 had 2 twice and 3 once, I'd want all other orders with two 2s and a 3.
My preference is that it return orders that match exactly, but orders that are a superset are acceptable if that's much easier / performs much better.
I tried a self-join like the following, but that found orders with any of the items rather than all of the items.
SELECT * FROM Order O1
JOIN Order O2 ON (O1.ProductId = O2.ProductId)
WHERE O2.OrderId = 5
This also gave me duplicates if order 5 contained the same item twice.
If the OrderDetails table contains a unique constraint on OrderId and ProductId, then you can do something like this:
Select ...
From Orders As O
Where Exists (
Select 1
From OrderDetails As OD1
Where OD1.ProductId In(1,2,3)
And OD1.OrderId = O.Id
Group By OD1.OrderId
Having Count(*) = 3
)
If it is possible to have the same ProductId on the same Order multiple times, then you could change the Having clause to Count(Distinct ProductId) = 3
Now, given the above, if you want the situation where each order has the same signature with duplicate product entries, that is trickier. To do that you would need the signature of order in question over the products in question and then query for that signature:
With OrderSignatures As
(
Select O1.Id
, (
Select '|' + Cast(OD1.ProductId As varchar(10))
From OrderDetails As OD1
Where OD1.OrderId = O1.Id
Order By OD1.ProductId
For Xml Path('')
) As Signature
From Orders As O1
)
Select ...
From OrderSignatures As O
Join OrderSignatures As O2
On O2.Signature = O.Signature
And O2.Id <> O.Id
Where O.Id = 5
This sort of thing is very difficult to do in SQL, as SQL is designed to generate its result set by, at the most basic level, comparing a set of column values on a single row each to another value. What you're trying to do is compare a single column value (or set of column values) on multiple rows to another set of multiple rows.
In order to do this, you'll have to create some kind of order signature. Strictly speaking, this isn't possible to do using query syntax alone; you'll have to use some T-SQL.
declare #Orders table
(
idx int identity(1, 1),
OrderID int,
Signature varchar(MAX)
)
declare #Items table
(
idx int identity(1, 1),
ItemID int,
Quantity int
)
insert into #Orders (OrderID) select OrderID from [Order]
declare #i int
declare #cnt int
declare #j int
declare #cnt2 int
select #i = 0, #cnt = max(idx) from #Orders
while #i < #cnt
begin
select #i = #i + 1
declare #temp varchar(MAX)
delete #Items
insert into #Items (ItemID, Quantity)
select
ItemID,
Count(ItemID)
from OrderItem oi
join #Orders o on o.idx = #i and o.OrderID = oi.OrderID
group by oi.ItemID
order by oi.ItemID
select #j = min(idx) - 1, #cnt2 = max(idx) from #Items
while #j < #cnt2
begin
select #j = #j + 1
select #temp = isnull(#temp + ', ','') +
'(' +
convert(varchar,i.ItemID) +
',' +
convert(varchar, i.Quantity) +
')'
from #Items i where idx = #j
end
update #Orders set Signature = #temp where idx = #i
select #temp = null
end
select
o_other.OrderID
from #Orders o
join #Orders o_other on
o_other.Signature = o.Signature
and o_other.OrderID <> o.OrderID
where o.OrderID = #OrderID
This assumes (based on the wording of your question) that ordering multiple of the same item in an order will result in multiple rows, rather than using a Quantity column. If the latter is the case, just remove the group by from the #Items population query and replace Count(ItemID) with Quantity.
I think this should work. I'm using 108 as an example OrderID, so you'll have to replace that twice below or use a variable.
WITH TempProducts(ProductID) AS
(
SELECT DISTINCT ProductID FROM CompMarket
WHERE OrderID = 108
)
SELECT OrderID FROM CompMarket
WHERE ProductID IN (SELECT ProductID FROM TempProducts)
AND OrderID != 108
GROUP BY OrderID
HAVING COUNT(DISTINCT ProductID) >= (SELECT COUNT(ProductID) FROM TempProducts)
This uses a CTE to get a list of an Order's Products, then selects all order IDs that have products that are all in this list. To make sure that the Orders returned have all the products, this compares the Count of the CTE to the Counts of the returned Order's Products.