By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
443,718 Members | 1,840 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 443,718 IT Pros & Developers. It's quick & easy.

Trigger and Row Update Help

P: n/a
I have a table that matches up Securities and Exchanges. Individual
securities can belong on multiple exchanges. One of the columns, named
PrimaryExchangeFlag, indicates if a particular exchange is the primary
exchange for that symbol. Each symbol can only have one primary
exchange.

I am trying to write a insert/update/delete trigger that enforces this
rule. The rules I have thought of are as follows:

Insert If new row has flag set, turn off flag for other rows for
that symbol. Otherwise, do nothing.

Update If updated row has flag set, turn off flag for other rows
for that symbol. Otherwise, set flag on first (MAX or MIN or TOP 1
???) row for that symbol.

Delete If deleted row had flag set, set flag on first row for that
symbol. Otherwise, do nothing.

My basic problem is how to do this when the trigger gets thrown for
multiple rows. (Since SQL does not throw individual triggers for each
row.)

Thanks.
Jul 20 '05 #1
Share this Question
Share on Google+
8 Replies


P: n/a
Sorry. I should have included some test code to make it easier for you.
Cutting and pasting will give you a test table and some
insert/delete/update statements.

USE tempdb
GO

DROP TABLE TestTable
GO

CREATE TABLE TestTable (
[Symbol] VARCHAR(15),
[Exchange] VARCHAR(5),
[PrimaryFlag] BIT
)
GO

INSERT INTO TestTable VALUES ('MSFT', 'XNAS', 1) -- first row - so flag
should be set
INSERT INTO TestTable VALUES ('QQQ', 'XNYS', 1) -- first row - so flag
should be set
INSERT INTO TestTable VALUES ('QQQ', 'XASE', 1) -- second row - so flag
should be set, but previous row should be unset
INSERT INTO TestTable VALUES ('MSFT', 'XASE', 0) -- second row - should
be unset for this, previous row not changed

UPDATE TestTable SET [PrimaryFlag] =1 WHERE [Symbol] = 'MSFT' AND
[Exchange] = 'XASE' -- set flag, unset first row

DELETE FROM TestTable WHERE [Symbol] = 'QQQ' AND [Exchange] = 'XASE' --
first row should be set
DELETE FROM TestTable WHERE [Symbol] = 'MSFT' AND [Exchange] = 'XASE' --
no change to flags
GO

SELECT * FROM [TestTable]

*** Sent via Developersdex http://www.developersdex.com ***
Don't just participate in USENET...get rewarded for it!
Jul 20 '05 #2

P: n/a
>> I have a table that matches up Securities and Exchanges. <<

Then let's use a meaningful names, reasonable data types, keys and
constraints even in the sample data. Are there really CHAR(15)
symbols in use today? And they vary in length, too?

There is not such thing as an ordering in an SQL database, so all that
talk about "first row", "second row" etc. is totally meaningless.
This is not a file system; all relationships are shown as values in
columns. Never write with BIT datatypes and flags. The datatype is
proprietary, and the use of flags is bad SQL programming. Let's try
again with good DDL:

CREATE TABLE SecuritiesExchanges
(symbol CHAR(5) NOT NULL,
exchange_code CHAR(5) NOT NULL
CHECK (exchange_code IN ()),
exch_prefer INTEGER DEFAULT 1 NOT NULL -- order them!
CHECK (exch_prefer >= 0),
PRIMARY KEY (exchange, symbol, exch_prefer));

Let (exch_prefer = 1) mark the primary exchange and re-write your
rules in stored procedures that you will use to insert/update/delete
from the table.

Insertion: If I add a new (exchange, symbol) then I have to mark it as
the primary exchange for that security or not.

BEGIN
IF @make_primary = 'N'
THEN INSERT INTO SecuritiesExchanges
VALUES (@my_symbol, @my_exchange_code,
(SELECT MAX(S1.exch_prefer) + 1
FROM SecuritiesExchanges AS S1
WHERE S1.symbol = ScuritiesExchanges.symbol));
IF @make_primary = 'Y'
THEN BEGIN
UPDATE SecuritiesExchanges
SET exch_prefer = exch_prefer +1
WHERE S1.symbol = ScuritiesExchanges.symbol;
INSERT INTO SecuritiesExchanges
VALUES @my_symbol, @my_exchange_code, 1);
END;
ELSE PRINT 'Flag must be Y or N' END IF;
END;

Deletion: If I delete a (exchange, symbol) then I have to renumber all
of the exch_prefer values for that symbol.

BEGIN
DELETE FROM SecuritiesExchanges
WHERE symbol = @my_symbol
AND exchange_code = @my_exchange_code;
UPDATE SecuritiesExchanges
SET exch_prefer
= (SELECT COUNT (S1.exch_prefer)
FROM SecuritiesExchanges AS S1
WHERE S1.exch_prefer
<= SecuritiesExchanges.exch_prefer
AND S1.symbol
<= SecuritiesExchanges.symbol)
WHERE symbol = @my_symbol;
END;

Update: If I update an existing (exchange, symbol) then I have to mark
it as the primary exchange for that security or not. I also might
want to re-order the preferences, so I'll need better specs before
doing that code. But it is all simple algebra.
Jul 20 '05 #3

P: n/a
Joe,

First off. Thank you for responding.

Quess I will just rattle off my questions (or answer some of yours).

1) What is wrong with my names? They sound reasonable to me. No
abbreviations and the name tells you want the column represents.

2) Yes there are variable length (and greater than) 5 character
symbols. NYSE and Amex symbols are normally 3 or less characters while
Nasdaq are 4 or 5. But NYSE symbols can also have a prefix that
indicates if it is preferred, class a or b, warrant, or some
combination of them. So a 3 character preferred class a stock would be
ABC.PRA (making 7 characters. To take this further, some foreign
exchanges do not use symbols but use what is called an ISIN number
(which is 7 characters).

3) I realize that there is no such thing as first or second row. I was
merely using those words in comments to signify a particular row being
inserted before the others. In other words, the MSFT/XNAS row was
inserted first, then the MSFT/ASE row was inserted.

4) Did not know about the BIT datatype. Good to know.

5) When you say that flags are bad SQL programming, you are talking
about true/false or yes/no types of columns? Your comment says to
order them using integer values. In my case, each symbol only has ONE
primary exchange, the rest are not. There is no preference or order to
the none primary exchanges.

6) Why code the one-and-only-one primary exchange logic in a stored
procedure as opposed to a trigger? By having it in a stored procedure
I am limited to single security inserts since cannot pass datasets as
parameters. I can also have the problem of having somebody NOT use the
SP and then I have multiple primary exchanges for a single security.

Again. Thank you for the help. Hope my ignorance does not annoy you
too much.
Jul 20 '05 #4

P: n/a
Jason (Ja*******@hotmail.com) writes:
I have a table that matches up Securities and Exchanges. Individual
securities can belong on multiple exchanges. One of the columns, named
PrimaryExchangeFlag, indicates if a particular exchange is the primary
exchange for that symbol. Each symbol can only have one primary
exchange.

I am trying to write a insert/update/delete trigger that enforces this
rule. The rules I have thought of are as follows:

Insert If new row has flag set, turn off flag for other rows for
that symbol. Otherwise, do nothing.

Update If updated row has flag set, turn off flag for other rows
for that symbol. Otherwise, set flag on first (MAX or MIN or TOP 1
???) row for that symbol.

Delete If deleted row had flag set, set flag on first row for that
symbol. Otherwise, do nothing.

My basic problem is how to do this when the trigger gets thrown for
multiple rows. (Since SQL does not throw individual triggers for each
row.)


The repro below includes a trigger that seems to fulfil the requirements.
To be able to determine "first row", I added an exch_no column. I also
added PRIMARY KEY and UNIQUE constraints to set the frames for what I'm
working with.

There is one potential problem: if you update PrimaryFlag for the
row with the lowest exch_no to 0 and if it was 1 before, the trigger
will flip it back to 1. This situation was not covered in your requirements.

The UPDATE statement in the trigger includes an EXISTS clause within
comments. As far as I can see, this clause is not needed from a functional
point of view. For performance, it could have. Then again, accessing
the inserted/deleted tables can be expensive, why it is often a good
idea to read them into a temp table or table variable.

Finally, if you expect one-row operations to be common, it may be a good
idea to say IF @@rowcount = 1 first in the trigger and have a special
code path for this case, as you may avoid performance problems in this
case.

Here is a script:

CREATE TABLE TestTable (
[Symbol] VARCHAR(15),
[Exchange] VARCHAR(5),
exch_no int,
[PrimaryFlag] BIT,
CONSTRAINT pk_exch PRIMARY KEY (Symbol, Exchange),
CONSTRAINT u_exch UNIQUE (Symbol, exch_no)
)
go
CREATE TRIGGER testtable_tri on TestTable FOR
INSERT, UPDATE, DELETE AS

UPDATE TestTable
SET PrimaryFlag = 0
FROM TestTable t
WHERE EXISTS (SELECT *
FROM inserted i
WHERE t.Symbol = i.Symbol
AND t.Exchange <> i.Exchange
AND i.PrimaryFlag = 1)

UPDATE TestTable
SET PrimaryFlag = 1
FROM TestTable t
JOIN (SELECT t.Symbol, exch_no = MIN(t.exch_no)
FROM TestTable t
WHERE t.PrimaryFlag = 0
AND NOT EXISTS (SELECT *
FROM TestTable t2
WHERE t2.Symbol = t.Symbol
AND t2.PrimaryFlag = 1)
GROUP BY t.Symbol) AS t3
ON t.Symbol = t3.Symbol
AND t.exch_no = t3.exch_no
/*WHERE EXISTS (SELECT *
FROM deleted d
WHERE d.Symbol = t.Symbol)*/
GO

INSERT INTO TestTable VALUES ('MSFT', 'XNAS', 1, 1)
-- first row - so flagshould be set
INSERT INTO TestTable VALUES ('QQQ', 'XNYS', 1, 1)
-- first row - so flag should be set
INSERT INTO TestTable VALUES ('QQQ', 'XASE', 2, 1)
-- second row - so flag should be set, but previous row should be unset
INSERT INTO TestTable VALUES ('MSFT', 'XASE', 2, 0)
-- second row - should be unset for this, previous row not changed

select * from TestTable ORDER BY Symbol, exch_no

UPDATE TestTable SET [PrimaryFlag] =0
WHERE [Symbol] = 'QQQ' AND [Exchange] = 'XNYS'
-- set flag, unset first row

select * from TestTable ORDER BY Symbol, exch_no

DELETE FROM TestTable WHERE [Symbol] = 'QQQ' AND [Exchange] = 'XASE'
-- first row should be set
DELETE FROM TestTable WHERE [Symbol] = 'MSFT' AND [Exchange] = 'XASE'
-- no change to flags
GO

SELECT * FROM [TestTable] ORDER BY Symbol, exch_no

--
Erland Sommarskog, SQL Server MVP, so****@algonet.se

Books Online for SQL Server SP3 at
http://www.microsoft.com/sql/techinf...2000/books.asp
Jul 20 '05 #5

P: n/a
Jason (Ja*******@hotmail.com) writes:
1) What is wrong with my names? They sound reasonable to me. No
abbreviations and the name tells you want the column represents.
There is nothing wrong with your names. Of course, "exchange" is a little
abstract for someone who don't know the business domain, but that is
not relevant.
3) I realize that there is no such thing as first or second row. I was
merely using those words in comments to signify a particular row being
inserted before the others. In other words, the MSFT/XNAS row was
inserted first, then the MSFT/ASE row was inserted.
Joe has a point so far that you need to somehow cover this in the data
model. Of course, "first" could also mean the alphabetic order of the
exchange code, or whatever.
4) Did not know about the BIT datatype. Good to know.
Unless you are striving for portability, there is no reason to stay away
from datatypes that are proprietary to SQL Server. Joe Celko seems to
think that everyone changes their DB engine at least twice a year.
5) When you say that flags are bad SQL programming, you are talking
about true/false or yes/no types of columns? Your comment says to
order them using integer values. In my case, each symbol only has ONE
primary exchange, the rest are not. There is no preference or order to
the none primary exchanges.
Good knows what Joe meant. Joe is good if you want to know some theory,
or help devlish SELECT statements. But for general programming help
for MS SQL Server his advice are not always appropriate.
6) Why code the one-and-only-one primary exchange logic in a stored
procedure as opposed to a trigger? By having it in a stored procedure
I am limited to single security inserts since cannot pass datasets as
parameters. I can also have the problem of having somebody NOT use the
SP and then I have multiple primary exchanges for a single security.


Actually, you can pass data sets to stored procedures these days, thanks
to the wonders of XML.

That said, your comment is very relevant. Whether to use a trigger or to
have the logic in a stored procedure is a question that is always open
for discussion. Having a trigger, means that if someone bypasses the
trigger by running an SQL statement direct, you maintain the logic.
However, a trigger can be dropped or disabled, and if this happens,
your updating logic will fail silently. A stored procedure can also
be dropped, but attempts to call it will give an error message.
--
Erland Sommarskog, SQL Server MVP, so****@algonet.se

Books Online for SQL Server SP3 at
http://www.microsoft.com/sql/techinf...2000/books.asp
Jul 20 '05 #6

P: n/a
Thanks for the help.

Going back and rereading my own post exposed some questionable
requirements.

If the security that is being updated currently has the primary
exchange flag set and you are currently trying to turn it off, who
then becomes the primary exchange?!

One possible answer would be SELECT TOP 1 but a better response would
be not to allow it to happen. The business rule should probably be
that a security cannot have its primary flag unset manually, but can
only be changed by setting another row as the primary.
Jul 20 '05 #7

P: n/a
>> 1) What is wrong with my names? They sound reasonable to me. No
abbreviations and the name tells you want the column represents. <<

ISO-11179 is more of a "genus-specie" model. If these are known in
your trade, then they can stand along
2) Yes there are variable length (and greater than) 5 character symbols) <<

That is one I did not know! I am still tryignto figure out CUSIP
rules. Thanks!
5) When you say that flags are bad SQL programming, you are talking about true/false or yes/no types of columns? <<

They are all too often a computed column. You would not store the
total cost of a line item in an invoice detail when you have price and
quantity in the same row. You would not store the total cost of an
invoice in an invoice header when you can computer them from the
invoice details. It is faster these days to compute them as needed
and it is safer (we used to have to do this with punch cards for
physical reasons). So why would you have the logical status in a
computed column either?
In my case, each symbol only has ONE primary exchange, the rest are not. There is no preference or order to the none primary exchanges. <<

Then consider just "prefer = 1 or prefer >1 " in your queries. The
sequence gives you a simple datatype with easy validation and update
rules. Bits don't.
6) Why code the one-and-only-one primary exchange logic in a stored procedure as opposed to a trigger? <<

Triggers are less portable in SQL Server. I also assume that you
would do other edits in the SP.
I can also have the problem of having somebody NOT use the SP and

then I have multiple primary exchanges for a single security. <<

Good point. If we get to full Standard SQL-92, you can do it with a
CHECK() constraint, which would be the best way.
Jul 20 '05 #8

P: n/a
>> I can also have the problem of having somebody NOT use the SP and
then I have multiple primary exchanges for a single security. <<

CREATE TABLE SecuritiesExchanges
(symbol CHAR(5) NOT NULL,
exchange_code CHAR(5) NOT NULL
CHECK (exchange_code IN ()),
exch_prefer INTEGER DEFAULT 1 NOT NULL
CHECK (exch_prefer >= 0),
PRIMARY KEY (exchange, symbol, exch_prefer),
UNIQUE (symbol, exch_prefer) -- does that do it?
);
Jul 20 '05 #9

This discussion thread is closed

Replies have been disabled for this discussion.