PostgreSQL - Check for equal values in two attributes - database

I have a PostgreSQL 11.6 server. On this server, I have a database with table users with attributes id_user, id_manager, displayName, givenName, surname, active. I would like somehow to check if the row is inserted or updated that id_user and id_manager are not equal.
I created a trigger:
create trigger "Test"
before insert or update
of id_user, id_manager
on users
for each row
execute procedure "Test"();
and trigger function:
create function "Test"() returns trigger
language plpgsql
as
$func$
BEGIN
IF (SELECT id_user FROM users)
= (SELECT id_manager FROM users)
THEN
RAISE EXCEPTION 'User ID and Manager ID cannot be the same values!';
END IF;
RETURN NEW;
END
$func$;
alter function "Test"() owner to xxxxxx;
When I executed insert where id_user and id_manager have the same values, the insert was completed with no error.
Could anyone please help with that?
Thanks

Use a check constraint:
ALTER TABLE users ADD CHECK (user_id <> manager_id);

Related

How can I create a stored procedure or trigger to update 2 columns in a table?

I am trying to create a stored procedure to update 2 columns from a table in SQL Server. I have 2 tables products and order details.
Every time a product is sold, I want to update the product_quantity column, which means product_quantity - quantity (product on hand minus the quantity that is ordered). The product_quantity attribute should not go below 0.
I tried to build a view
drop view if exists v_products
go
create view v_products
as
select
product_name, product_quantity, product_status,
quantity, p.product_id
from
products p
inner join
order_details od on p.product_id = od.product_id
and then tried to create a procedure based on the view.
DROP PROCEDURE IF EXISTS p_products_product_quantity
GO
CREATE PROCEDURE p_products_product_quantity
#product_quantity INT,
#sold_quantity INT,
#product_status VARCHAR(50),
#product_id INT
AS
BEGIN
SET NOCOUNT ON;
UPDATE v_products
SET product_status = #product_status,
quantity = #sold_quantity,
product_quantity = #product_quantity,
product_id = #product_id
-- ,product_quantity = #product_quantity - #sold_quantity
WHERE #product_quantity - #sold_quantity = 0
END
I am expecting for example if I have
product_quantity 23
and quantity of the next order (sold quantity) is 23.
I want the product_quantity to change to 0 and product_status to 'not available'.
I see someone already mentioned that you can use a trigger for this so im going to give another alternative if you dont want to go down that route. You can just update both tables within the scope of your insert into the order table eg :
--Inserts order here
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO order_detail
VALUES (#unit_price, #size, #quantity, #discount, GETDATE(),
#productid,
#orderid, #paymentid) --Im assuming you have already inserted into your order table in a different caller or same stored procedure
UPDATE product
SET product_quantity = (product_quantity - #quantity)
COMMIT;
END TRY
BEGIN CATCH
ROLLBACK;
END CATCH
In your stored procedure you will wrap it around a transaction so if all inserts and updates were successful it will commit that transaction to the database and if a single operation fails on the stored procedure then we rollback (all previous operations will be rolledback to ensure we dont create orphaned rows)
Hope this helps! :)

New permission added to list. Update all users to have new permission via Trigger

So I have a table with all of the reports that our in-house site has. I am trying to make a trigger so that when a new report is added to this list that all users get updated with the permission. (Default access False)
I don't full understand Triggers and have been performing this task with a couple of sql statements in conjunction with C#. Though I'm certain there is a way to do this with just an SQL Trigger.
This is the closest I have gotten thus far (Not Functioning). Names altered for Security.
CREATE TRIGGER [New Trigger]
ON [dbo].[List_Table]
FOR INSERT
AS
BEGIN
INSERT INTO [dbo].[Permission_Table]
(UserId, ReportId, Granted)
(SELECT UserId FROM [dbo].[User_Table]),(SELECT ReportId FROM inserted),false
END
The goal is for it to add a row in the permission table for each user with the list table id and the value of false.
I think you just want an insert . . . select:
CREATE TRIGGER [New Trigger]
ON [dbo].[List_Table]
FOR INSERT
AS
BEGIN
INSERT INTO [dbo].[Permission_Table](UserId, ReportId, Granted)
SELECT u.UserId, i.ReportId, 'false'
FROM Inserted i CROSS JOIN
[dbo].[User_Table] u;
END

How to write a sql trigger correctly

I want to add a trigger and I never wrote one.
When I update or add a new values to my table, I want to check that some value is allowed.
I started the trigger but I don't really know how to end it:
CREATE TRIGGER isAguide
ON tblDiving
AFTER INSERT, UPDATE AS
BEGIN
DECLARE #Found bit
select #Found = count(*)
from tblAuthorized
WHERE diver_number = #id
and level_name = 'guide'
I want to allow adding ONLY if #found=1.
#id is the value that the user is trying to add to the table.
So first, how do I get the value the user entered in this 'id' Column?
And second, How do I continue the trigger I wrote?
Is it ok I chose AFTER INSERT?
I want to add something like:
if(#found=1) THEN allow adding/updating
else don't allow.
Ty
If you're using an AFTER trigger you can throw an error and roll back the transaction if the id is not found.
CREATE TRIGGER isAguide
ON tblDiving AFTER INSERT, UPDATE AS
BEGIN
DECLARE #Found bit, #id int;
SET #id = SELECT id FROM inserted;
SET #Found =
(
SELECT count(*)
FROM tblAuthorized
WHERE diver_number = #id
AND level_name = 'guide'
);
IF #Found = 0
THEN
RAISERROR ('The ID entered is not valid', 16, 1);
ROLLBACK TRANSACTION;
RETURN
END;
END;
Instead of writing a trigger I'd recommend to create a separate table "Guides" with the diver number in there as the only column (being a primary key).
Add a foreign key reference to the related Id column of your tblDiving table.
Not only will this be compliant with a normalized data structure, it'll also be faster.
Finally you'll need to consider the scenario of changing the level_name of a diver_number from 'guide' to something different after there are some rows inserted into the tblDiving table. How do you want the app to react?

Instead of trigger in SQL Server loses SCOPE_IDENTITY?

I have a table where I created an INSTEAD OF trigger to enforce some business rules.
The issue is that when I insert data into this table, SCOPE_IDENTITY() returns a NULL value, rather than the actual inserted identity.
Insert + Scope code
INSERT INTO [dbo].[Payment]([DateFrom], [DateTo], [CustomerId], [AdminId])
VALUES ('2009-01-20', '2009-01-31', 6, 1)
SELECT SCOPE_IDENTITY()
Trigger:
CREATE TRIGGER [dbo].[TR_Payments_Insert]
ON [dbo].[Payment]
INSTEAD OF INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF NOT EXISTS(SELECT 1 FROM dbo.Payment p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo)
) AND NOT EXISTS (SELECT 1 FROM Inserted p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom <> p.DateFrom AND i.DateTo <> p.DateTo) AND
((i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo))
)
BEGIN
INSERT INTO dbo.Payment (DateFrom, DateTo, CustomerId, AdminId)
SELECT DateFrom, DateTo, CustomerId, AdminId
FROM Inserted
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
END
The code worked before the creation of this trigger. I am using LINQ to SQL in C#. I don't see a way of changing SCOPE_IDENTITY to ##IDENTITY. How do I make this work?
Use ##identity instead of scope_identity().
While scope_identity() returns the last created id in the current scope, ##identity returns the last created id in the current session.
The scope_identity() function is normally recommended over the ##identity field, as you usually don't want triggers to interfer with the id, but in this case you do.
Since you're on SQL 2008, I would highly recommend using the OUTPUT clause instead of one of the custom identity functions. SCOPE_IDENTITY currently has some issues with parallel queries that cause me to recommend against it entirely. ##Identity does not, but it's still not as explicit, and as flexible, as OUTPUT. Plus OUTPUT handles multi-row inserts. Have a look at the BOL article which has some great examples.
I was having serious reservations about using ##identity, because it can return the wrong answer.
But there is a workaround to force ##identity to have the scope_identity() value.
Just for completeness, first I'll list a couple of other workarounds for this problem I've seen on the web:
Make the trigger return a rowset. Then, in a wrapper SP that performs the insert, do INSERT Table1 EXEC sp_ExecuteSQL ... to yet another table. Then scope_identity() will work. This is messy because it requires dynamic SQL which is a pain. Also, be aware that dynamic SQL runs under the permissions of the user calling the SP rather than the permissions of the owner of the SP. If the original client could insert to the table, he should still have that permission, just know that you could run into problems if you deny permission to insert directly to the table.
If there is another candidate key, get the identity of the inserted row(s) using those keys. For example, if Name has a unique index on it, then you can insert, then select the (max for multiple rows) ID from the table you just inserted to using Name. While this may have concurrency problems if another session deletes the row you just inserted, it's no worse than in the original situation if someone deleted your row before the application could use it.
Now, here's how to definitively make your trigger safe for ##Identity to return the correct value, even if your SP or another trigger inserts to an identity-bearing table after the main insert.
Also, please put comments in your code about what you are doing and why so that future visitors to the trigger don't break things or waste time trying to figure it out.
CREATE TRIGGER TR_MyTable_I ON MyTable INSTEAD OF INSERT
AS
SET NOCOUNT ON
DECLARE #MyTableID int
INSERT MyTable (Name, SystemUser)
SELECT I.Name, System_User
FROM Inserted
SET #MyTableID = Scope_Identity()
INSERT AuditTable (SystemUser, Notes)
SELECT SystemUser, 'Added Name ' + I.Name
FROM Inserted
-- The following statement MUST be last in this trigger. It resets ##Identity
-- to be the same as the earlier Scope_Identity() value.
SELECT MyTableID INTO #Trash FROM MyTable WHERE MyTableID = #MyTableID
Normally, the extra insert to the audit table would break everything, because since it has an identity column, then ##Identity will return that value instead of the one from the insertion to MyTable. However, the final select creates a new ##Identity value that is the correct one, based on the Scope_Identity() that we saved from earlier. This also proofs it against any possible additional AFTER trigger on the MyTable table.
Update:
I just noticed that an INSTEAD OF trigger isn't necessary here. This does everything you were looking for:
CREATE TRIGGER dbo.TR_Payments_Insert ON dbo.Payment FOR INSERT
AS
SET NOCOUNT ON;
IF EXISTS (
SELECT *
FROM
Inserted I
INNER JOIN dbo.Payment P ON I.CustomerID = P.CustomerID
WHERE
I.DateFrom < P.DateTo
AND P.DateFrom < I.DateTo
) ROLLBACK TRAN;
This of course allows scope_identity() to keep working. The only drawback is that a rolled-back insert on an identity table does consume the identity values used (the identity value is still incremented by the number of rows in the insert attempt).
I've been staring at this for a few minutes and don't have absolute certainty right now, but I think this preserves the meaning of an inclusive start time and an exclusive end time. If the end time was inclusive (which would be odd to me) then the comparisons would need to use <= instead of <.
Main Problem : Trigger and Entity framework both work in diffrent scope.
The problem is, that if you generate new PK value in trigger, it is different scope. Thus this command returns zero rows and EF will throw exception.
The solution is to add the following SELECT statement at the end of your Trigger:
SELECT * FROM deleted UNION ALL
SELECT * FROM inserted;
in place of * you can mention all the column name including
SELECT IDENT_CURRENT(‘tablename’) AS <IdentityColumnname>
Like araqnid commented, the trigger seems to rollback the transaction when a condition is met. You can do that easier with an AFTER INSTERT trigger:
CREATE TRIGGER [dbo].[TR_Payments_Insert]
ON [dbo].[Payment]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF <Condition>
BEGIN
ROLLBACK TRANSACTION
END
END
Then you can use SCOPE_IDENTITY() again, because the INSERT is no longer done in the trigger.
The condition itself seems to let two identical rows past, if they're in the same insert. With the AFTER INSERT trigger, you can rewrite the condition like:
IF EXISTS(
SELECT *
FROM dbo.Payment a
LEFT JOIN dbo.Payment b
ON a.Id <> b.Id
AND a.CustomerId = b.CustomerId
AND (a.DateFrom BETWEEN b.DateFrom AND b.DateTo
OR a.DateTo BETWEEN b.DateFrom AND b.DateTo)
WHERE b.Id is NOT NULL)
And it will catch duplicate rows, because now it can differentiate them based on Id. It also works if you delete a row and replace it with another row in the same statement.
Anyway, if you want my advice, move away from triggers altogether. As you can see even for this example they are very complex. Do the insert through a stored procedure. They are simpler and faster than triggers:
create procedure dbo.InsertPayment
#DateFrom datetime, #DateTo datetime, #CustomerId int, #AdminId int
as
BEGIN TRANSACTION
IF NOT EXISTS (
SELECT *
FROM dbo.Payment
WHERE CustomerId = #CustomerId
AND (#DateFrom BETWEEN DateFrom AND DateTo
OR #DateTo BETWEEN DateFrom AND DateTo))
BEGIN
INSERT into dbo.Payment
(DateFrom, DateTo, CustomerId, AdminId)
VALUES (#DateFrom, #DateTo, #CustomerId, #AdminId)
END
COMMIT TRANSACTION
A little late to the party, but I was looking into this issue myself. A workaround is to create a temp table in the calling procedure where the insert is being performed, insert the scope identity into that temp table from inside the instead of trigger, and then read the identity value out of the temp table once the insertion is complete.
In procedure:
CREATE table #temp ( id int )
... insert statement ...
select id from #temp
-- (you can add sorting and top 1 selection for extra safety)
drop table #temp
In instead of trigger:
-- this check covers you for any inserts that don't want an identity value returned (and therefore don't provide a temp table)
IF OBJECT_ID('tempdb..#temp') is not null
begin
insert into #temp(id)
values
(SCOPE_IDENTITY())
end
You probably want to call it something other than #temp for safety sake (something long and random enough that no one else would be using it: #temp1234235234563785635).

Sql Server: how to write trigger on a table with denied SELECT

Hello I have a table on which I have denied SELECT privs to a user.
This table has a trigger which references the INSERTED table, basically doing an
AFTER UPDATE SET <table>.[UPDATED] = getdate()
WHERE ROWID IN SELECT ROWID FROM INSERTED
It is giving me an error though, saying "SELECT PERMISSIONS DENIED", I am guessing because of the SELECT FROM INSERTED.
How can I keep the SELECT deny, but allow the trigger to SELECT from the INSERTED pseudotable?
Thanks in advance!
Consider adding an EXECUTE AS clause so the trigger runs with the schema owner's permissions.
CREATE TRIGGER [dbo].[TR_Product_Update] ON [Product]
WITH EXECUTE AS OWNER
AFTER UPDATE
AS
SELECT ProductId
FROM INSERTED
Why did you deny select? What about just not granting select to them? There is a subtle difference between denying select and just not granting it to them. Also, if you denied select to any of the system level roles, then that would also probably be part of the problem.
--EDIT--
In the comments you asked whether or not SQL Server has context info. 2005 does and you can see how to use it here.
Session Variable – Context_Info: Session is a powerful tool in any of the programming language. SQL-Server is not a full fledge programming language but it do supports session variable for current session or connection. It stores value of session in 128 byte of binary information.
join to the inserted table instead something like:
update t1
set updated = getdate()
from table1 t1
join inserted i
on i.rowid = t1.rowid
This will probaly also perfom better than a subselect anyway.
I suspect your problem is that your UPDATE statement itself requires the SELECT permission.
I created a test database as follows:
DROP DATABASE triggerPermissionTest
CREATE DATABASE triggerPermissionTest
GO
USE triggerPermissionTest
GO
CREATE USER foo FROM LOGIN tester
GO
CREATE TABLE triggerTable (id int)
GO
DENY SELECT ON triggerTable to foo
GRANT UPDATE ON triggerTable to foo
GO
CREATE TRIGGER execAsTrigger ON triggerTable
AFTER UPDATE AS SELECT * FROM triggerTable
GO
INSERT INTO triggerTable VALUES (1)
GO
and tried the following Update statements with the 'tester' login:
UPDATE triggerTable SET id = 2
GO
UPDATE triggerTable SET id = id *2
GO
The first one executes fine and the trigger executes (providing the results of select * from triggerTable to the user without select permissions, demonstrating that it managed to do a select).
The second one gives me an error stating that I don't have select permission on triggerTable (because it needs to select from triggerTable in order to get the value of id to do the update).
This leads me to conclude that the trigger is incidental to the problem, and the UPDATE statement is the cause of the permission issue.

Resources