Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

The Archive Base

The Archive Base Logo The Archive Base Logo

The Archive Base Navigation

  • SEARCH
  • Home
  • About Us
  • Blog
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • Add group
  • Groups page
  • Feed
  • User Profile
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Buy Points
  • Users
  • Help
  • Buy Theme
  • SEARCH
Home/ Questions/Q 414127
In Process

The Archive Base Latest Questions

Editorial Team
  • 0
Editorial Team
Asked: May 12, 20262026-05-12T18:15:07+00:00 2026-05-12T18:15:07+00:00

I have a particularly difficult business constraint that I would like to enforce at

  • 0

I have a particularly difficult business constraint that I would like to enforce at the database level. The data is financial in nature, and so must be protected from inconsistencies to the nth degree – no trusting the business layer with this stuff. I use the word “temporal” somewhat loosely, meaning that I intend to control how an entity can and cannot change over time.

Glossing over the details, here’s the design:

  • An invoice can feature several fees.
  • Fees are assigned to an invoice shortly after creation of the invoice.
  • The invoice reaches a stage in the process after which it is “locked.”
  • From this point on, no fee may be added to or removed from this invoice.

Here’s a stripped down data definition:

CREATE TABLE Invoices
(
    InvoiceID INT IDENTITY(1,1) PRIMARY KEY,
)

CREATE TABLE Fees
(
    FeeID INT IDENTITY(1,1) PRIMARY KEY,
    InvoiceID INT REFERENCES Invoices(InvoiceID),
    Amount MONEY
)

You’ll notice that the “lockable” nature of the invoice isn’t represented here; how to represent it – and whether it needs to be directly represented at all – is still an open question.

I have come to believe that this is one of those arrangements that cannot be translated into domain-key normal form, though I may be wrong. (There really is no way to tell, after all.) That said, I still hold out hope for a highly-normalized solution.

I happen to be implementing this on SQL Server 2008 (the syntax may have been a hint), but I’m a curious guy, so if there are solutions that work on other DBMS’s, I’d love to hear about those as well.

  • 1 1 Answer
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

1 Answer

  • Voted
  • Oldest
  • Recent
  • Random
  1. Editorial Team
    Editorial Team
    2026-05-12T18:15:08+00:00Added an answer on May 12, 2026 at 6:15 pm

    Don’t complicate it, I’d go with triggers. There is no shame in using them, this is what they are there for.

    To avoid lots of logic in the triggers, I add an “Editable” bit column into the header table, then basically use a divide with Editable to either work or cause a divide by zero error, which I CATCH and convert to a Invoice is not editable, no changes permitted message. There are no EXISTS used to eliminate extra overhead. Try this:

    CREATE TABLE testInvoices
    (
         InvoiceID   INT      not null  IDENTITY(1,1) PRIMARY KEY
        ,Editable    bit      not null  default (1)  --1=can edit, 0=can not edit
        ,yourData    char(2)  not null  default ('xx')
    )
    go
    
    CREATE TABLE TestFees
    (
        FeeID     INT IDENTITY(1,1) PRIMARY KEY
       ,InvoiceID INT REFERENCES testInvoices(InvoiceID)
       ,Amount    MONEY
    )
    go
    
    CREATE TRIGGER trigger_testInvoices_instead_update
    ON testInvoices
    INSTEAD OF UPDATE
    AS
    BEGIN TRY
        --cause failure on updates when the invoice is not editable
        UPDATE t 
            SET Editable =i.Editable
               ,yourData =i.yourData
            FROM testInvoices            t
                INNER JOIN INSERTED      i ON t.InvoiceID=i.InvoiceID
            WHERE 1=CONVERT(int,t.Editable)/t.Editable    --div by zero when not editable
    END TRY
    BEGIN CATCH
    
        IF ERROR_NUMBER()=8134 --catch div by zero error
            RAISERROR('Invoice is not editable, no changes permitted',16,1)
        ELSE
        BEGIN
            DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
            SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
        END
    
    END CATCH
    GO
    
    
    CREATE TRIGGER trigger_testInvoices_instead_delete
    ON testInvoices
    INSTEAD OF DELETE
    AS
    BEGIN TRY
        --cause failure on deletes when the invoice is not editable
        DELETE t
        FROM testInvoices            t
            INNER JOIN DELETED       d ON t.InvoiceID=d.InvoiceID
            WHERE 1=CONVERT(int,t.Editable)/t.Editable    --div by zero when not editable
    END TRY
    BEGIN CATCH
    
        IF ERROR_NUMBER()=8134 --catch div by zero error
            RAISERROR('Invoice is not editable, no changes permitted',16,1)
        ELSE
        BEGIN
            DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
            SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
        END
    
    END CATCH
    GO
    
    CREATE TRIGGER trigger_TestFees_instead_insert
    ON TestFees
    INSTEAD OF INSERT
    AS
    BEGIN TRY
        --cause failure on inserts when the invoice is not editable
        INSERT INTO TestFees
                (InvoiceID,Amount)
            SELECT
                f.InvoiceID,f.Amount/i.Editable  --div by zero when invoice is not editable
                FROM INSERTED                f
                    INNER JOIN testInvoices  i ON f.InvoiceID=i.invoiceID
    END TRY
    BEGIN CATCH
    
        IF ERROR_NUMBER()=8134 --catch div by zero error
            RAISERROR('Invoice is not editable, no changes permitted',16,1)
        ELSE
        BEGIN
            DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
            SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
        END
    
    END CATCH
    GO
    
    CREATE TRIGGER trigger_TestFees_instead_update
    ON TestFees
    INSTEAD OF UPDATE
    AS
    BEGIN TRY
        --cause failure on updates when the invoice is not editable
        UPDATE f 
            SET InvoiceID =ff.InvoiceID
               ,Amount    =ff.Amount/i.Editable --div by zero when invoice is not editable
            FROM TestFees                f
                INNER JOIN INSERTED     ff ON f.FeeID=ff.FeeID
                INNER JOIN testInvoices  i ON f.InvoiceID=i.invoiceID
    END TRY
    BEGIN CATCH
    
        IF ERROR_NUMBER()=8134 --catch div by zero error
            RAISERROR('Invoice is not editable, no changes permitted',16,1)
        ELSE
        BEGIN
            DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
            SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
        END
    
    END CATCH
    GO
    
    CREATE TRIGGER trigger_TestFees_instead_delete
    ON TestFees
    INSTEAD OF DELETE
    AS
    BEGIN TRY
        --cause failure on deletes when the invoice is not editable
        DELETE f
        FROM TestFees                f
            INNER JOIN DELETED      ff ON f.FeeID=ff.FeeID
            INNER JOIN testInvoices  i ON f.InvoiceID=i.invoiceID AND 1=CONVERT(int,i.Editable)/i.Editable --div by zero when invoice is not editable
    END TRY
    BEGIN CATCH
    
        IF ERROR_NUMBER()=8134 --catch div by zero error
            RAISERROR('Invoice is not editable, no changes permitted',16,1)
        ELSE
        BEGIN
            DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
            SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
        END
    
    END CATCH
    GO
    

    here is a simple test script to test out the different combinations:

    INSERT INTO testInvoices VALUES(default,default) --works
    INSERT INTO testInvoices VALUES(default,default) --works
    INSERT INTO testInvoices VALUES(default,default) --works
    
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (1,111)  --works
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (1,1111) --works
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,22)   --works
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,222)  --works
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,2222) --works
    
    update testInvoices set Editable=0 where invoiceid=3 --works
    INSERT INTO TestFees (InvoiceID,Amount) VALUES (3,333) --error<<<<<<<
    
    UPDATE TestFees SET Amount=1 where feeID=1 --works
    UPDATE testInvoices set Editable=0 where invoiceid=1 --works
    UPDATE TestFees SET Amount=11111 where feeID=1 --error<<<<<<<
    UPDATE testInvoices set Editable=1 where invoiceid=1 --error<<<<<<<
    
    UPDATE testInvoices set Editable=0 where invoiceid=2 --works
    DELETE TestFees WHERE invoiceid=2 --error<<<<<
    
    DELETE FROM testInvoices where invoiceid=2 --error<<<<<
    
    UPDATE testInvoices SET Editable='A' where invoiceid=1 --error<<<<<<< Msg 245, Level 16, State 1, Line 1 Conversion failed when converting the varchar value 'A' to data type bit.
    
    • 0
    • Reply
    • Share
      Share
      • Share on Facebook
      • Share on Twitter
      • Share on LinkedIn
      • Share on WhatsApp
      • Report

Sidebar

Related Questions

I have a general question about the way that database indexing works, particularly in
I have a particularly difficult form that I am trying to click the search
When I asked this previously I should have mentioned that it's particularly a light-weight
In the end, I have decided that this isn't a problem that I particularly
I have a feeling that this should really not be all that difficult, yet
Assume I have a UIMA toolchain that does something like this: tokenize -> POS
More particularly - I have a window handle of another running application. This application
Is it particularly bad to have a very, very large SQL query with lots
I have recently run into a particularly sticky issue regarding committing the result of
I have a project where I am taking some particularly ugly live HTML and

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help
  • SEARCH

Footer

© 2021 The Archive Base. All Rights Reserved
With Love by The Archive Base

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.