SQL / DB2 - retrieve value and update/increment simultaniously - sql

I'm connecting to a DB2 database and executing SQL statements.
One example of what is being done is:
select field from library/file
[program code line finishes executing]
[increment value by one]
update library/file set field = 'incremented value'
I have a need to immediately update the value while returning the value. Rather than having to wait for the script to complete, and then run a separate UPDATE statement.
The concept of what I would like to do is this:
select field from library/file; update library/file set field = (Current Value + 1); go;
Please note... this is not the common SQL database most would be familiar with, it is a DB2 database on an IBM i.
Thanks!

Consider using a DB2 SEQUENCE to manage the next available number, if this file is simply intended to have a single row storing your counter. That is what a SEQUENCE is designed to do.
To set it up, use a CREATE SEQUENCE statement.
To increment the value and retrieve, use a SEQUENCE reference expression of the form NEXT VALUE FOR sequence-name. To find out what the most recent value was, use the PREVIOUS VALUE FOR sequence-name. These expressions can be used like a regular any column expression, such as in a SELECT or INSERT statement.
Suppose, for example you want to do this for invoice numbers (and maybe your accounting department doesn't want their first invoice number to be 000001, so we will initialize it higher).
CREATE SEQUENCE InvoiceSeq
as decimal (7,0)
start with 27000; -- for example
You could get a number for a new invoice like this:
SELECT NEXT VALUE FOR InvoiceSeq
INTO :myvar
FROM SYSIBM/SYSDUMMY1;
But what is this SYSIBM/SYSDUMMY1 table? We're not really getting anything from table, so why are we pretending to do so? The SELECT needs a FROM-table clause. But since we don't need one, let's use a VALUES INTO statement.
VALUES NEXT VALUE FOR InvoiceSeq
INTO :myvar;
So that has incremented the counter, and put the value into our variable. You could use that value to INSERT into our InvoiceHeaders and InvoiceDetails tables.
Or, you could increment the counter as you write an InvoiceHeader, then use it again when writing the InvoiceDetails.
INSERT INTO InvoiceHeaders
(InvoiceNbr, Customer, InvoiceDate)
VALUES (NEXT VALUE FOR InvoiceSeq, :custnbr, :invdate);
for each invoice detail
INSERT INTO InvoiceDetails
(InvoiceNbr, InvoiceLine, Reason, Fee)
VALUES (PREVIOUS VALUE FOR InvoiceSeq, :line, :itemtxt, :amt);
The PREVIOUS VALUE is local to the particular job, so there should be no risk of another job getting the same number.

update library/file set field = field + 1;
select field from library/file;
[program code line finishes executing]
[increment value by one]
This handles the problem of another app updating the number between the time you fetch it and the time you update it. Update it and then use it. If two apps try to update simultaneously, one will wait.
A SEQUENCE object is designed exactly for this purpose, but if you are forced to keep this 'next ID' file updated, this is how I'd do it. Follow the link in the comment by #Clockwork-Muse for info on the SEQUENCE object, or try this example from V5R4.

His request is like this:
UPDATE sometable
SET somecounter = somecounter + 10,
:returnvar = somecounter + 10;
Updates and retrieves at the same time.
This is possible in MSSQL, In fact I use it alot there,
but DB2 doesnt seem to have this feature.

Related

Violation of PRIMARY KEY constraint PK. Cannot insert duplicate key in object (table1), The duplicate key value is (Col1, Col2, Col3, Col4)

I have a product entry page through we keep on adding product entries in our database.
Product location wise there are 2 series. e.g., ABCTMP(Series(1-max)) and XYZ(Series(1-max)).
Table is having primary key constraint which is a combination of 4 columns. Out of 4, only one is giving an issue while increment series combination wise.
That first column is location wise product code as stated above and it is of data type char(20) as it stores values like ABCTMP01 and through classic asp code. We increment that last 01 value by addition of one into existing value.
Right now, facing issue when last value reaches 99 and turns to 100. It generates code 100 through code but unable to insert in database and giving this error that was due to existing entry in database.
Duplicate key part is same one which I mentioned above in subject/header. If I delete record from table of record no. 100 to check, it gives me proper record of 99 through above query and through above classic asp code, it generates next code as 99+1 = 100.
But when I again try to add next series record for 101, even through SQL mgt studio, it gives me below error.
Violation of PRIMARY KEY constraint 'PK'. Cannot insert duplicate key in object 'prdct_mst_tab'. The duplicate key value is (PWATERTMP100 , 006, Y, 01). The statement has been terminated.
Have tried by dropping constraint and changing size of data type char(20) to char(30) as there are dependencies on table. But not worked. Then, have tried by changing data type from char(30) to varchar(30), still not worked. Then again tried by manually
executing insert command
in SQL itself, but same error occurred for 101th record.
Before generating next series, there is select statement to check latest inserted record which will get incremented later.
For generating next record of 101,there select statement must show last inserted record of 100, but it's still giving 99th record and code is generating as 100 again and the error continues the same. I do not understand why it's not taking 100th record when I execute SELECt statement in SQL mgt studio. DataType of that PWATERTMP100 column is char(20).
Below my classic asp code for series generation and SQL 'SELECT top 1 *' statement for record count for location wise product.
select top 1 *
from prdct_mst_tab
where pmt_prdct_cd like 'PWATER%'
and pmt_umt_unit_cd='006'
AND PMT_CMT_CMPNY_CD='01'
order by pmt_prdct_cd desc
Classic ASP Code: -
If recordset.eof Then
getcode="ABCTMP01"
Else
getcode = clng(Mid(recordset("Column1"),10,20))
response.write("Hello" & getcode)
getcode = getcode +1
response.write("<br />Hello" & getcode)
getcode = "ABCTMP" & getcode
response.write("<br />Hello" & getcode)
End if
Below for adding generated product code in database table.
Sql is as below
select * from Table1
recordset.open sql,con,3,2
recordset.addnew
recordset("Column1")=getcode
recordset.update
recordset.close
Note : Values given above are sample one.
I want the record gets inserted even when it turns from 99 to 100, means code will become ABCTMP99 - ABCTMP100 and continue from series starting with 100 range(3 digits)like 100, 101, 102....
The problem is that the order by in this:
select top 1 * from prdct_mst_tab
where pmt_prdct_cd like 'PWATER%'
and pmt_umt_unit_cd='006'
AND PMT_CMT_CMPNY_CD='01'
order by pmt_prdct_cd desc
Does not do what you expect.
Try running this in management studio:
select * from prdct_mst_tab
where pmt_prdct_cd like 'PWATER%'
and pmt_umt_unit_cd='006'
AND PMT_CMT_CMPNY_CD='01'
order by pmt_prdct_cd desc
You'll see that the 100 appears before 99 because it is ordering it alphanumerically not numerically.
In fact you will also see that 10 appears before 9 - how did you ever get past this?
You have a fundamental design flaw. I will add to that by posing a solution which cements the design flaw in place and introduces new bugs. But it will give you a result.
One workaround is to do something this:
select
MAX(
CASE
WHEN ISNUMERIC(RIGHT(RTRIM(pmt_prdct_cd),3)) = 1
THEN RIGHT(RTRIM(pmt_prdct_cd),3)
ELSE '0' + RIGHT(RTRIM(pmt_prdct_cd),2)
END
) As LargestNumber
from prdct_mst_tab
where pmt_prdct_cd like 'PWATER%'
and pmt_umt_unit_cd='006'
AND PMT_CMT_CMPNY_CD='01'
What does this do?
It checks if the last three characters are a number. If it is it uses it.
If it isn't a number it grabs the last two characters and puts a zero in front.
Then it picks the largest number out of all of those.
note - this returns a number, it doesn't return the full product code. So you'll need to remove the ASP Mid code that tries to pull the number out.
This might work until you find some other data or case that you haven't mentioned yet. Like for example if there are trailing characters that aren't numeric. Or for when you need a four character number
Make no mistake - you have a fundamental design flaw and this just prolongs the issue, adds complexity, and introduces more bugs down the track
Some basic observations:
char is a bad data type for this
It has concurrency issues - if two requests call this at the same time (easily done from a web app), it returns the same number and they both try and insert a duplicate value
You should not be assigning and storing incrementing numbers like this. Just use an IDENTITY in the database.
I guess since you are using classic ASP, you are not in a situation that you can redesign this.
You need to decide whether you are going to patch this with something that will introduce new bugs or fix it properly.
Does each product code really need to be incremented within it's own domain like that? Is there any issue with having ABC01 then DEF02 then XYZ03?

How do I update a value in a table in PostgreSQL on a schedule?

Let's say I have a vet application, and there're 2 databases(let's say doctors perform operations):
User database with fields : id, email, name, password and regStamp.
PetOperations database with fields: id, id(reference to user), doctorName, operationStamp and operationStatus.
What if I want to update a operationStatus field whenever someone puts a new petOperation in Pet database(initial status was PERFOMING after 20 minutes it becomes PERFORMED, but only for this unique operationId, if currentTime - operationStamp >=20). How can I do that? Maybe, there's a better way rather than subtraction times?
I think you need to rethink your data model a little bit. As I understand your question you have operations which are scheduled, and you want to treat them as performed after the scheduled time slice is complete.
PostgreSQL has no native facility to update a value in 20 minutes. You could use a cron job, but I think the more elegant solution is to change your data model instead.
Add a "table method" and you get status calculation.
suppose your table now contains:
id, id(reference to user), doctorName, operationStamp, operationTsrange
Then you create a function something like:
create or replace function status(operation) returns text language sql
as $$
select case when $1.operationTsrange is null then 'Not Scheduled'
when now() << $1.operationTsrange THEN 'Scheduled'
when now() <# $1.operationTsrange THEN 'Performing'
when now() >> $1.operationTsrange THEN 'Performed'
END;
$$;
Then you can search on this. And if you need different length intervals you can specify them on update time.
I am not sure what you want to do.
If you just want to set the initial operationstatus when you INSERT a row, use a BEFORE INSERT trigger.
If you want the value to change depending on when it is selected, use a “computed column”, that is, don't define the column in the table, but rather define a view on the table that contains the column, calculated as appropriate.

PostgreSQL select value and increment at once

I'm looking for a possible solution to the following. I have data stored in a table to keep track of a special increment number the customer wants in the DB. This is a special number they use internally.
What I would like to do is automatically increment this number in the table when I select it. So I don't have the problem of another transaction, from someone else using the system, using the same ID number.
So I want to select the current number and increment it by one at once so I don't have duplicates. How would I go about doing this if it is even possible?
UPDATE the_table
SET the_column = the_column + 1
WHERE qualifier = X
RETURNING the_column;
This ought to do the trick, with the caveat that it will return the new id rather than the old one:
UPDATE foo
SET id=nextval('foo_sequence')
WHERE ...
RETURNING *

SQL on-demand cache table (possibly using SQL MERGE)

I am working on implementing an on-demand SQL cache table for an application so I have
CacheTable with columns Type, Number, Value
Then I have a function called GetValue( Type, Number )
So I want to have a function that does the following
If (CacheTable contains Type, Number) then return value
Else call GetValue( Type, Number) and put that value into CacheTable and return the Value
Does anyone know the most elegant way to do this?
I was thinking of using a SQL merge.
Not sure how elegant one can get, but we might do it just the way you describe. Query the database
select Value from Tab1 where Type=#type and Number=#num
and if no rows are returned, compute the value, then store it in the database for next time.
However, if the "compute the value" requires the database itself, and we can compute it in the database, then we can do the whole cycle with one database round trip -- more 'elegant' perhaps but faster at least than 3 round trips (lookup, compute, store).
declare #val int
select #val=Value from Tab1 where Type=#type and Number=#num
if ##ROWCOUNT=0 BEGIN
exec compute_val #type,#num,#val OUTPUT
insert into Tab1 values (#type,#num,#val)
END
SELECT #val[Value]--return
The only use for SQL Merge is if you think there may be concurrent users and the number is inserted between above select and insert, giving an error on the insert. I'd just catch the error and skip the insert (as we can assume the value won't be different by definition).

MySQL - Set default value for field as a string concatenation function

I have a table that looks a bit like this actors(forename, surname, stage_name);
I want to update stage_name to have a default value of
forename." ".surname
So that
insert into actors(forename, surname) values ('Stack', 'Overflow');
would produce the record
'Stack' 'Overflow' 'Stack Overflow'
Is this possible?
Thanks :)
MySQL does not support computed columns or expressions in the DEFAULT option of a column definition.
You can do this in a trigger (MySQL 5.0 or greater required):
CREATE TRIGGER format_stage_name
BEFORE INSERT ON actors
FOR EACH ROW
BEGIN
SET NEW.stage_name = CONCAT(NEW.forename, ' ', NEW.surname);
END
You may also want to create a similar trigger BEFORE UPDATE.
Watch out for NULL in forename and surname, because concat of a NULL with any other string produces a NULL. Use COALESCE() on each column or on the concatenated string as appropriate.
edit: The following example sets stage_name only if it's NULL. Otherwise you can specify the stage_name in your INSERT statement, and it'll be preserved.
CREATE TRIGGER format_stage_name
BEFORE INSERT ON actors
FOR EACH ROW
BEGIN
IF (NEW.stage_name IS NULL) THEN
SET NEW.stage_name = CONCAT(NEW.forename, ' ', NEW.surname);
END IF;
END
According to 10.1.4. Data Type Default Values no, you can't do that. You can only use a constant or CURRENT_TIMESTAMP.
OTOH if you're pretty up-to-date, you could probably use a trigger to accomplish the same thing.
My first thought is if you have the two values in other fields what is the compelling need for redundantly storing them in a third field? It flies in the face of normalization and efficiency.
If you simply want to store the concatenated value then you can simply create a view (or IMSNHO even better a stored procedure) that concatenates the values into a pseudo actor field and perform your reads from the view/sproc instead of the table directly.
If you absolutely must store the concatenated value you could handle this in two ways:
1) Use a stored procedure to do your inserts instead of straight SQL. This way you can receive the values and construct a value for the field you wish to populate then build the insert statement including a concatenated value for the actors field.
2) So I don't draw too many flames, treat this suggestion with kid gloves. Use only as a last resort. You could hack this behavior by adding a trigger to build the value if it is left null. Generally, triggers are not good. They add unseen cost and interactions to fairly simple interactions. You can, though, use the CREATE TRIGGER to update the actors field after a record is inserted or updated. Here is the reference page.
As of MySQL 8.0.13, you can use DEFAULT clause for a column which can be a literal constant or an expression.
If you want to use an expression then, simply enclose the required expression within parentheses.
(concat(forename," ",surname))
There are two ways to accomplish what you are trying to do as per my knowledge:
(important: consider backing up your table first before running below queries)
1- Drop the column "stage_name" all together and create a new one with DEFAULT constraint.
ALTER TABLE actors ADD COLUMN stage_name VARCHAR(20) DEFAULT (concat(forename," ",surname))
2- This will update newer entries in the column "stage_name" but not the old ones.
ALTER TABLE actors alter stage_name set DEFAULT (concat(forename," ",surname));
After that, if you need to update the previous values in the column "stage_name" then simply run:
UPDATE actors SET stage_name=(concat(forename," ",surname));
I believe this should solve your problem.