Question

My application involves using submitting data (the "request") from a form into an SQL Server 2005 database, for later review and approval by a supervisor. Users should have permission to insert a new request, but not be able to modify the ones they have already submitted.

With a single table, this is straightforward: grant them the INSERT privilege only without the UPDATE privilege. But the request actually spans two tables, with a one-to-many relationship. I need to prevent the user from inserting additional child rows for an existing request. Ideally, this should be enforced at the database level: allow a parent row and one or more child rows to be inserted in the same transaction, but once that transaction is committed prevent new child rows from being inserted with that foreign key.

What's the best way to achieve this? Are there any ways to enforce this special flavour of "referential integrity" without triggers? And if triggers are they only way, then how can I test that the parent row has been inserted within the current transaction?

Was it helpful?

Solution

Stored procedure or trigger

If you grant rights on the child table, then they'll be able to write.

A trigger will disallow the write and a stored procedure allows you to prevent the write in the first place because only the stored proc writes to the tables.

There is no "native" referential integrity that can capture your business logic because it's custom to your situation.

OTHER TIPS

The following example illustrates how you could use a trigger to achieve this behaviour. Note that this will not work if the child rows are inserted one at a time inside the transaction, rather than in a single INSERT statement.

CREATE TABLE parent1
(id INT PRIMARY KEY)

CREATE TABLE child1
(id INT
,parent_id INT
)
GO

ALTER TABLE child1 ADD CONSTRAINT chilld1fk FOREIGN KEY (parent_id)
REFERENCES parent1 (id)
GO


CREATE TRIGGER trg_child1
ON child1
INSTEAD OF INSERT
AS

        SELECT parent_id
        FROM child1 AS c
        WHERE EXISTS (SELECT 1
                      FROM inserted AS i
                      WHERE i.parent_id = c.parent_id
                     )

        IF @@ROWCOUNT > 0
            BEGIN
                RAISERROR('You cannot amend this request',16,1)
            END
        ELSE        
            BEGIN
                INSERT child1
                SELECT id
                       ,parent_id
                FROM inserted
            END
GO                                                            

BEGIN TRAN
        INSERT parent1
        VALUES (1)

        INSERT child1 
        (id
        ,parent_id
        )
        SELECT 10,1
        UNION SELECT 11,1        
COMMIT

-- attempting to insert another child outside the transaction
-- will result in an error
INSERT child1
SELECT 12,1

SELECT * FROM child1

Use stored procedures to insert data:

  • first stored procedure inserts parent row; it automatically adds information about inserting user and sets status field,

  • second stored procedure inserts child rows after checking its parent row; it raises error if calling user has no right to add items to given parent row or parent row's status disallows adding new positions.

Alternatively you can use triggers to do the checks. But this can be somewhat trickier than explicitly calling stored procedures. And people sometimes tend to forget about triggers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top