SQL Server trigger active card - sql

I have the following tables:
Cards (IDCard, IDPerson, Balance, Active)
Persons (IDPerson)
Every time a new card is inserted, I need to check if that card already belongs to a person. If so, I need to change the active to false and the new card must receive the balance of the previous card and the state must be true.
select count(c.IDPerson)
from cards c, inserted i
where c.IDPerson = i.IDPerson;
if ##ROWCOUNT > 0
begin
print'this card already exists'
select c.IDPerson, c.Balance, c.Active
INTO #tmp
FROM Cards c, inserted i
where c.IDCard = i.IDCard
update Cards
set Active = 0
from #tmp
where #tmp.IDPerson = Cards.IDPerson;
end
When I insert a new record into cards table, the old card does turn false (0), however the new card also stays with active equal to false.
This in an after insert on cards table.
Can someone tell me how to fix the problem?

Although your trigger is running within the transaction, I think it is still capable of updating itself.
When you update where the INSERTED.IDPERSON = CARD.IDPERSON - this includes the record you have inserted; thus all records are updated to Active = false.
Rather, you should do
update Cards
set Active = 0
from #tmp
where #tmp.IDPerson = Cards.IDPerson
AND Cards.IDCARD != #tmp.IDCARD --This is the new condition
I've created a copy of what I believe you are asking in this DBFiddle Link
User marc_s is also correct in saying you should use JOIN syntax here. Your entire operation could be completed in one much cleaner statement
UPDATE c
SET c.Active = 0
FROM Cards c
INNER JOIN Inserted i
ON i.IDPERSON = c.IDPERSON
AND i.IDCARD != c.IDCARD

The if is not appropriate. Remember, that the trigger could be handling multiple rows at the same time.
An explicit join does the filtering you want. This is a little complicated, but you can do it with two joins:
update c2
set Active = (case when c2.IDCard = i.IDCard then 1 else 0 end)
from Cards c
inserted i
on c.IDCard = i.IDCard join
Cards c2
on c.IDPerson = c2.IDPerson;

Related

Update with Join PL/SQL with on/and condition - it tries to update all rows

I've looked through other answers and can't see this particular issue; I want to update a field where an internal code number is used to make sure the two customers are the same, and is the customer on a particular order.
When I run the below code, I get the warning "Are you sure you want to update all records" - and no, I don't!
UPDATE CustomerTable a
SET a.Text = 'text'
from a
join OrderTable b
on a.Customer = b.Customer
and b.Order = '10';
How do I get it to update that customer record and only that customer record?
Thank you!
You are repeating the table name in the update. You want to phrase this as:
update CustomerTable ct
set Text = 'text'
from OrderTable ot
where ot.Customer = ct.Customer and ot.Order = '10';
Or, in a structure that will work in almost any database:
update CustomerTable ct
set Text = 'text'
where exists (select 1
from OrderTable ot
where ot.Customer = ct.Customer and
ot.Order = '10'
);

SQL update table refers to itself

I have a Table ProfileText which contains four columns: Id, Text, State, PreviousText.
State is an enum with the following states: released, draft.
PreviousText is a reference to another ProfileText.
Whenever a new ProfileText is created, the previous ProfileText is set as PreviousText. The State does not matter. This creates a kind of "timeline" of released and drafted texts. Now I want to modify PreviousText. The "timeline" should only contain entries with State = released.
Example old: A(released) -> B(draft) -> C(released) -> null
Example new: A(released) -> C(released) -> null
How can I make this update table in SQL?
If I understand your goal correctly, you should be able to accomplish this with an update that makes use of a recursive CTE (common table expression):
with recursive timeline as (
select * from ProfileText where State = 'released'
union
select a.Id, a.Text, a.State, b.PreviousText
from timeline as a
join ProfileText as b on b.Id = a.PreviousText
where b.State <> 'released'
)
update ProfileText as a set PreviousText = b.PreviousText
from timeline as b
left join ProfileText as c on c.Id = b.PreviousText
where a.Id = b.Id and
(b.PreviousText is null or c.State = 'released'
and a.PreviousText <> b.PreviousText);
What this does is it selects all "released" records in the non-recursive part of the CTE regardless of the state of previous records they may be referencing, then for any that reference previous records which are not released, it yields additional records containing the original values of the released entry with the reference to the previous entry's previous entry, continuing until it reaches a released record or a null reference.
Then all it has to do is update all entries in the table that are in the newly constructed timeline and either have a null entry in the timeline (necessary for when the oldest released entry is not the oldest entry) or have an entry referencing a previous "released" record that is not reflected in the old timeline.

How to use the original value in a SQL Server update trigger to change a second record

I have a SQL Server table with student, cohort, program, grad date, and status. Some students have records that are specifically tied together – if the cohort for one row changes, the cohort for another row with status 2 needs to change as well. In most cases I catch this programmatically but there are a LOT of ways to change a student’s cohort so we’re missing some.
Essentially if someone runs the first line update statement, the second also needs to run
UPDATE StudentPrograms
SET Cohort = 201610
WHERE Student = 'A1234' AND Program = 'MBA' AND Cohort = 201510
UPDATE StudentPrograms
SET Cohort = 201610
WHERE Student = 'A1234' AND Cohort = 201510 AND Status = 2
I would like to put an update trigger on the table to change both cohorts, but the problem is I can’t figure out how to capture both the starting cohort that identifies the record that needs to change and the cohort that it would need to change to. Once the update has run and the trigger fires, all I have is the updated cohort, so I don’t know which record to change.
CREATE TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
DECLARE #student VARCHAR(10);
DECLARE #cohort INT;
SELECT #student = i.student FROM inserted i
SELECT #cohort = i.cohort FROM inserted i
SET NOCOUNT ON;
IF UPDATE(Cohort)
UPDATE dbo.StudentPrograms
SET Cohort = #cohort
WHERE Student = #student AND Status = 2 AND Cohort = ???
Unfortunately the records do have to be in this table and not a sub table for a lot of reasons. The records are occasionally also deleted / reinserted, so I run into the same problems if I tie them together by adding another field with a key - if that record gets deleted / updated / reinserted, I still need to capture the information, record it, then record the change.
Thanks for the immediate help! Here's what I ended up with, changed a bit so it works with batch updates.
ALTER TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
SET NOCOUNT ON;
IF UPDATE(Cohort)
UPDATE a
SET Cohort = i.Cohort
FROM dbo.StudentPrograms AS a
INNER JOIN inserted AS i ON a.Student = i.Student
INNER JOIN deleted AS d ON a.Cohort = d.Cohort
AND a.Student = d.Student
WHERE a.Status = 2
When creating a trigger, you can access the old values using the deleted table
WHERE Student = #student AND Status = 2 AND Cohort = (SELECT TOP 1 Cohort from deleted)
Ideally, you should handle this in a stored procedure but as a temporary fix you can use the following code.
They way you are handling updates in your code, it will never work if more than one row was updated.
Triggers are fired once in response to a triggering action (update,delete,insert) for all the rows affected by that action, NOT for each row affected by the triggering action.
Keeping all this in mind your temporary solution should look something like...
CREATE TRIGGER [dbo].[tr_SP_Cohort]
ON [dbo].StudentPrograms
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE s
SET s.Cohort = i.Cohort
FROM deleted d
INNER JOIN dbo.StudentPrograms s ON d.Student = s.Student
AND d.Cohort = s.Cohort
INNER JOIN inserted i ON i.Student = s.Student
WHERE s.[Status] = 2;
END

Copy data from a row in one table to a row in another table based on information from a third table

I have moved an expiration date from one table to another. Since I created the new column, some new dates have been added, but most are still set to NULL.
I need to copy the dates from the original table and column (a) to the new table and column (b) only if b is NULL and this is marked as a system.
I have created the below statement, but it throws an error. I think I might have to use FROM, but I'm relatively new to T-SQL, and have had no experience with it before. Does anyone see where I have made my mistake?
UPDATE purchs
SET purchs.dexpiredate = client.dexpire
FROM
INNER JOIN client ON client.iclientid = purchs.iclientid
INNER JOIN feature ON purchs.ifeatureid = feature.ifeatureid
WHERE feature.lsystem = 1 and purchs.dexpiredate IS NULL
GO
UPDATE p
SET p.dexpiredate = c.dexpire
FROM purchs p
INNER JOIN client c ON c.iclientid = p.iclientid
INNER JOIN feature f ON f.ifeatureid = p.ifeatureid
WHERE
f.lsystem = 1 and
p.dexpiredate IS NULL

SQL Delete When column a and column b does not exist

Ok, so you have something like this working. You Insert into a table from a tmp table, where the Equipment Number and the Account Number are missing...
Insert INTO ClientEquipments(
SUB_ACCT_NO_SBB,
EquipmentDate,
EquipmentText,
EquipmentNumber)
Select
replace(a.SUB_ACCT_NO_SBB,'"','') as SUB_ACCT_NO_SBB,
getdate() as edate,'' as etext,
replace(a.equipmentNumber,'"','') equipmentNumber
from clientspaymenttemp a
where not exists
(select b.equipmentNumber
from clientEquipments b
where b.sub_acct_no_sbb=replace(a.SUB_ACCT_NO_SBB,'"','') and b.equipmentNumber=replace(a.equipmentNumber,'"',''))
group by SUB_ACCT_NO_SBB,equipmentNumber
But found a problem if the Equipment Number belonged to a different account number before, then my previous query will insert a new row, with the same Equipment Number but a new Account Number.
What I need it to do is simple:
If Account Number and Equipment Number exists, leave it alone no need to insert.
If Equipment Number exists, but it's assigned to a different Account Number, delete the old row. (Import file handles assignments so I am 100% sure that it needs to be assigned to new account)
Something Like this added somewhere in the previous code:
DELETE FROM ClientEquipments
WHERE (clientEquipmentId =
(SELECT clientEquipmentId
FROM ClientEquipments AS ClientEquipments_1
WHERE (equipmentNumber = '0012345CAEC6')))
If nothing exists then Insert a new row.
:::EDIT SOME MORE INFORMATION TO HELP ME OUT:::
I am reading a CSV file:
Sample Data:
Account | Name | Address | Some Extra Stuff | Equipment Number
"1234","First1,Last1","Address 1",etc etc... "ENum1234"
"1234","First1,Last1","Address 1",etc etc... "ENum5678"
"5678","First2,Last2","Address 2",etc etc... "ENum9123"
"9123","First3,Last3","Address 3",etc etc... "ENum4567"
This gets bulked imported into a temp table. (dbo.clients_temp)
Notice how account 1234 has 2 equipment numbers.
From here I insert new accounts into dbo.clients by doing a query from dbo.clients_temp to dbo.clients
Then I update dbo.clients with new information from dbo.clients_temp (ie Account 1234 might exists but now they have a new address.)
Now that my dbo.clients table is update with new clients, and new information for existing clients, I need to update my dbo.equipments table. I was originally doing what you see above, Insert Where Not Exists Account Number and Equipment Number.
Now the problem is that since equipments do change accounts, for example, Account Number 5678 might have become inactive which I don't track or care for at the database level, but the equipment Number might now belong to Account Number 1234. In this case, my original query will insert a new row into the database, since Account 1234 and Equipment Number are not returned in the SELECT.
Ok, I have lost this now :P I will try and revisit the question later on the weekend because I just confused myself O.o
I had to modify Gordon's answer above a bit, but that did the trick...
Below is the relevant line of code that deletes the inactive accounts.
DELETE FROM ClientEquipments WHERE EquipmentNumber =
(SELECT E.equipmentNumber FROM ClientEquipments As E INNER JOIN ClientsPaymentTemp AS T
on E.equipmentNumber = T.equipmentNumber and e.SUB_ACCT_NO_SBB <> T.SUB_ACCT_NO_SBB)
-- Fix Account Numbers and Equipment Numbers
update ClientPaymentTemp
set SUB_ACCT_NO_SBB = replace(SUB_ACCT_NO_SBB,'"',''),
equipmentNumber = replace(equipmentNumber,'"','')
-- Delete Existing Accounts Mapped to New Equipment
delete e
from ClientEquipments e
inner join clientspaymenttemp t
on e.EquipmentNumber = t.EquipmentNumber
and e.SUB_ACCT_NO_SBB <> t.SUB_ACCT_NO_SBB
-- Insert New Accounts
insert into ClientEquipments
(SUB_ACCT_NO_SBB,
EquipmentDate,
EquipmentText,
EquipmentNumber)
Select
SUB_ACCT_NO_SBB,
getdate() as edate,
'' as etext,
equipmentNumber
from ClientsPaymentTemp a
where not exists (select 1 from ClientEquipments where SUB_ACCT_NO_SBB = a.SUB_ACCT_NO_SBB and EquipmentNumber = a.EquipmentNumber)
I may be misunderstanding, but if all you're looking to do is delete a record where the account number isn't equal to something and the equipment number is equal to something, can't you just perform a delete with multiple where conditions?
Example:
DELETE FROM table
WHERE
equipmentNumber = someNumber AND
accountNumber <> someAccount
You could then get the number of rows affected using ##ROWCOUNT to check the number of rows affected and then insert if nothing was deleted. The example from the TechNet link above uses the following example:
USE AdventureWorks;
GO
UPDATE HumanResources.Employee
SET Title = N'Executive'
WHERE NationalIDNumber = 123456789
IF ##ROWCOUNT = 0
PRINT 'Warning: No rows were updated';
GO
I would think you could easily adapt that to do what you're looking to do.