From: Lennart Jonsson on
On 2010-06-25 19:47, --CELKO-- wrote:
> Here is a problem that has me stuck. This is a skeleton of an
> auction. We start with a simple table of bids for the items. The key
> is the item's identifier and sequential bid number within each time.
> To give the table a key when an item is put on auction, we put in a
> dummy bid and amount.
>
> CREATE TABLE Auctions
> (item_id CHAR(8) NOT NULL,
> bid_seq INTEGER DEFAULT 0 NOT NULL
> CHECK (bid_seq >= 0),
> PRIMARY KEY (item_id, bid_seq),
> bid_amt DECIMAL (8,2) DEFAULT 0.00 NOT NULL
> CHECK (bid_amt >= 0.00));
>
> INSERT INTO Auctions (item_id)
> VALUES ('Item A'), ('Item B'), ('Item C');
>
> Assume a single bidder so that the Actions tables does not need a
> bidder_id column as part of the key. He puts in a list of his bids on
> the items on auction.
>
> CREATE TABLE Bid_List
> (item_id CHAR(8) NOT NULL PRIMARY KEY,
> bid_amt DECIMAL (8,2) NOT NULL
> CHECK (bid_amt >= 0.00));
>
> INSERT INTO Bid_List (item_id, bid_amt)
> VALUES ('Item A', 14.55), ('Item C', 13.50);
>
> Notice that he cannot make two bids on the same item thanks to the key
> and cannot bid a negative amount (he can do charity work for free,
> however). There is no need to use IDENTITY or @@IDENTITY (row at a
> time numbering) to get multiple items sequentially numbered.
>
> INSERT INTO Auctions (item_id, bid_seq, bid_amt)
> SELECT DISTINCT Bid_List.item_id, MAX(Auctions.bid_seq) OVER
> (PARTITION BY Auctions.item_id)+1, Bid_List.bid_amt
> FROM Auctions, Bid_List
> WHERE Auctions.item_id = Bid_List.item_id;
>
> item_id bid_seq bid_amt
> =========================
> Item A 0 0.00
> Item A 1 14.55
> Item B 0 0.00
> Item B 1 13.50
> Item C 0 0.00
>
> Obviously it is easy to have VIEWs to show the items with and without
> a bid, the highest current bid, etc. When some of the items have real
> bids, the dummy bids can be dropped to save space and make other
> computations in the real system much easier. To clean out the dummy
> bids once we get a real bid can be done with way:
>
> DELETE FROM Auctions
> WHERE bid_seq = 0
> AND EXISTS
> (SELECT *
> FROM Auctions AS A1
> WHERE Auctions.item_id = A1.item_id
> AND bid_seq > 0);
>
> item_id bid_seq bid_amt
> =========================
> Item A 1 14.55
> Item B 1 13.50
> Item C 0 0.00
>
> There might be a way to put all of this code into a single MERGE
> statement. I cannot figure it out. Anyone see a solution?

Not very pretty,and I'm not sure it will hold for all cases, but:

MERGE INTO Auctions a
USING (
select item_id, 0 as bid_seq, bid_amt,
coalesce((select max(bid_seq)
from Auctions x
where x.item_id = y.item_id),0) + 1 as next_bid_seq
from Bid_List y
) b on (a.item_id, a.bid_seq) = (b.item_id, b.bid_seq)
when matched then
update set (bid_seq, bid_amt) = (1, b.bid_amt)
when not matched then
insert (item_id, bid_seq, bid_amt)
values (b.item_id, b.next_bid_seq, b.bid_amt);

/Lennart
From: Lennart Jonsson on
On 2010-06-29 01:17, Lennart Jonsson wrote:
[...]
> MERGE INTO Auctions a
> USING (
> select item_id, 0 as bid_seq, bid_amt,
> coalesce((select max(bid_seq)
> from Auctions x
> where x.item_id = y.item_id),0) + 1 as next_bid_seq
> from Bid_List y
> ) b on (a.item_id, a.bid_seq) = (b.item_id, b.bid_seq)
> when matched then
> update set (bid_seq, bid_amt) = (1, b.bid_amt)
> when not matched then
> insert (item_id, bid_seq, bid_amt)
> values (b.item_id, b.next_bid_seq, b.bid_amt);
>

coalesce can be removed since it is asserted that there is at least 1
bid per item, and the constant 1 can be replaced with next_bid_seq. It
may also make it clearer to name the 0 as init_bid_seq or even dummy_bid_seq

MERGE INTO Auctions a
USING (
select item_id, 0 as dummy_bid_seq, bid_amt,
(select max(bid_seq)
from Auctions x
where x.item_id = y.item_id) + 1 as next_bid_seq
from Bid_List y
) b
on (a.item_id, a.bid_seq) = (b.item_id, b.dummy_bid_seq)
when matched then
update set (bid_seq, bid_amt) = (b.next_bid_seq, b.bid_amt)
when not matched then
insert (item_id, bid_seq, bid_amt)
values (b.item_id, b.next_bid_seq, b.bid_amt);

/Lennart
From: Tonkuma on
On Jun 29, 8:33 am, Lennart Jonsson <erik.lennart.jons...(a)gmail.com>
wrote:
> ....
> coalesce can be removed since it is asserted that there is at least 1
> bid per item, and the constant 1 can be replaced with next_bid_seq. It
> may also make it clearer to name the 0 as init_bid_seq or even dummy_bid_seq
> ....
The problem might be how to guarantee the existence of all item_id in
Bid_List were in Auctions.

If there was a row in Bid_list which item_id was not in Auctions,
COALESCE would be necessary.
For example:
(Item E in Bid_list is not in Auctions.)
------------------------------ Commands Entered
------------------------------
SELECT * FROM Auctions
ORDER BY item_id , bid_seq;
------------------------------------------------------------------------------

ITEM_ID BID_SEQ BID_AMT
-------- ----------- ----------
Item A 1 14.55
Item A 2 16.95
Item B 1 20.05
Item C 1 13.50
Item D 0 0.00

5 record(s) selected.


------------------------------ Commands Entered
------------------------------
SELECT * FROM Bid_List;
------------------------------------------------------------------------------

ITEM_ID BID_AMT
-------- ----------
Item A 17.05
Item B 20.15
Item D 7.05
Item E 9.99

4 record(s) selected.

------------------------------ Commands Entered
------------------------------
MERGE INTO Auctions a
USING (
select item_id, 0 as dummy_bid_seq, bid_amt,
(select max(bid_seq)
from Auctions x
where x.item_id = y.item_id) + 1 as next_bid_seq
from Bid_List y
) b
on (a.item_id, a.bid_seq) = (b.item_id, b.dummy_bid_seq)
when matched then
update set (bid_seq, bid_amt) = (b.next_bid_seq, b.bid_amt)
when not matched then
insert (item_id, bid_seq, bid_amt)
values (b.item_id, b.next_bid_seq, b.bid_amt);
------------------------------------------------------------------------------
DB21034E The command was processed as an SQL statement because it was
not a
valid Command Line Processor command. During SQL processing it
returned:
SQL0407N Assignment of a NULL value to a NOT NULL column
"TBSPACEID=3,
TABLEID=89, COLNO=1" is not allowed. SQLSTATE=23502


If COALESCE was used, Item E was added in Auctions.

------------------------------ Commands Entered
------------------------------
MERGE INTO Auctions a
USING (
select item_id, 0 as bid_seq, bid_amt,
coalesce((select max(bid_seq)
from Auctions x
where x.item_id = y.item_id),0) + 1 as
next_bid_seq
from Bid_List y
) b on (a.item_id, a.bid_seq) = (b.item_id, b.bid_seq)
when matched then
update set (bid_seq, bid_amt) = (1, b.bid_amt)
when not matched then
insert (item_id, bid_seq, bid_amt)
values (b.item_id, b.next_bid_seq, b.bid_amt);
------------------------------------------------------------------------------
DB20000I The SQL command completed successfully.

------------------------------ Commands Entered
------------------------------
SELECT * FROM Auctions
ORDER BY item_id , bid_seq;
------------------------------------------------------------------------------

ITEM_ID BID_SEQ BID_AMT
-------- ----------- ----------
Item A 1 14.55
Item A 2 16.95
Item A 3 17.05
Item B 1 20.05
Item B 2 20.15
Item C 1 13.50
Item D 1 7.05
Item E 1 9.99

8 record(s) selected.

From: Tonkuma on
On Jun 30, 3:49 pm, Tonkuma <tonk...(a)fiberbit.net> wrote:
> On Jun 29, 8:33 am, Lennart Jonsson <erik.lennart.jons...(a)gmail.com>
> wrote:> ....
> > coalesce can be removed since it is asserted that there is at least 1
> > bid per item, and the constant 1 can be replaced with next_bid_seq. It
> > may also make it clearer to name the 0 as init_bid_seq or even dummy_bid_seq
> > ....
>
> The problem might be how to guarantee the existence of all item_id in
> Bid_List were in Auctions.
>
> If there was a row in Bid_list which item_id was not in Auctions,
> COALESCE would be necessary.
> For example:
> (Item E in Bid_list is not in Auctions.)
> ------------------------------ Commands Entered
> ------------------------------
> SELECT * FROM Auctions
>  ORDER BY item_id , bid_seq;
> ---------------------------------------------------------------------------­---
>
> ITEM_ID  BID_SEQ     BID_AMT
> -------- ----------- ----------
> Item A             1      14.55
> Item A             2      16.95
> Item B             1      20.05
> Item C             1      13.50
> Item D             0       0.00
>
>   5 record(s) selected.
>
> ------------------------------ Commands Entered
> ------------------------------
> SELECT * FROM Bid_List;
> ---------------------------------------------------------------------------­---
>
> ITEM_ID  BID_AMT
> -------- ----------
> Item A        17.05
> Item B        20.15
> Item D         7.05
> Item E         9.99
>
>   4 record(s) selected.
>
Here is an example which ignore "Item E".

------------------------------ Commands Entered
------------------------------
MERGE INTO Auctions a
USING Bid_List b
ON b.item_id = a.item_id
AND a.bid_seq = 0
WHEN MATCHED THEN
UPDATE
SET (bid_seq , bid_amt) = (1 , b.bid_amt)
WHEN NOT MATCHED
AND EXISTS
(SELECT 0
FROM Auctions e
WHERE e.item_id = b.item_id
) THEN
INSERT ( item_id , bid_seq , bid_amt )
VALUES ( b.item_id
, (SELECT MAX(bid_seq) + 1
FROM Auctions m
WHERE m.item_id = b.item_id)
, b.bid_amt
)
;
------------------------------------------------------------------------------
DB20000I The SQL command completed successfully.

------------------------------ Commands Entered
------------------------------
SELECT * FROM Auctions
ORDER BY item_id , bid_seq
;
------------------------------------------------------------------------------

ITEM_ID BID_SEQ BID_AMT
-------- ----------- ----------
Item A 1 14.55
Item A 2 16.95
Item A 3 17.05
Item B 1 20.05
Item B 2 20.15
Item C 1 13.50
Item D 1 7.05

7 record(s) selected.

From: Will Honea on
Tonkuma wrote:

> WHEN NOT MATCHED
> AND EXISTS
> (SELECT 0
> FROM Auctions e
> WHERE e.item_id = b.item_id
> ) THEN
> INSERT

Not the same issue, but I had never considered the syntax until I saw this.
The same structure works to avoid an annoying "more than on row identified
for update" bomb that has been plaguing me for a while now. Serendipity,
indeed.

Thank you.

--
Will Honea