Here's my suggestions:
Your table should look something like
CREATE TABLE COUPONS
(COUPON_CODE NUMBER
CONSTRAINT PK_COUPONS
PRIMARY KEY
USING INDEX); -- plus whatever other fields you need
Your code to generate unique codes should be something like
DECLARE
nCoupon_code NUMBER;
bCode_unique BOOLEAN;
BEGIN
bCode_unique := FALSE;
WHILE bCode_unique = FALSE LOOP
BEGIN
nCoupon_code := GENERATE_COUPON_CODE; -- function to generate a coupon code
INSERT INTO COUPONS (COUPON_CODE)
VALUES (nCoupon_code);
bCode_unique := TRUE;
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
NULL; -- Fall through, loop back to the top, generate new code, continue
END;
END LOOP; -- bCode_unique
END;
What goes on here is that the program first set the 'code unique' flag to FALSE, indicating that a unique code has not yet been generated. A loop is then entered, and will continue as long as the 'code unique' flag is still FALSE. A coupon code is then generated and stored into the database. If the code is unique all will be well, the 'code unique' flag will be set, and the loop will be exited. HOWEVER, if the code is NOT unique the INSERT statement will fail with a DUP_VAL_ON_INDEX exception. This will happen because COUPON_CODE is the primary key on the COUPONS table, and we know that a primary key has three attributes: it must be non-NULL, if must never change, and it MUST BE UNIQUE. (For our purposes here we could have used a UNIQUE constraint on COUPONS.COUPON_CODE and gotten the same effect). If the DUP_VAL_ON_INDEX exception is thrown because the coupon code generated is not unique the exception handler will be entered where the code will do absolutely nothing; that is, the 'code unique' flag will remain FALSE, the code will drop out of the exception handler, and because the 'code unique' flag is still FALSE the loop will start over again where another code will be generated, and etc, etc, etc, until a unique code is finally generated and the INSERT succeeds.
This may seem like a lot of work, but if the coupon code generation algorithm is chosen well it won't generate a lot of collisions and thus won't have to loop too often. IMO this is a reasonable solution considering the environment - in PL/SQL you count on using the database to do some of the heavy lifting, like guaranteeing uniqueness. You can spend a ton of time trying to come up with the One True Code Generation Algorithm Which Will Never, Ever Possibly Generate A Duplicate, or you can just come up with something that's good-if-not-perfect and work with the database to make sure that the codes which are finally chosen end up unique. YMMV. And implementing the One True Algorithm can be a lot of fun, if not a particularly good use of time. :-)
Share and enjoy.