Smallint stayed null after executing update SQL statement in Sybase - sql

The DB was Sybase. I executed update SQL statement like
UPDATE table_name SET smallint_col = 0,
datetime_col = CONVERT(datetime, '1999-09-09') WHERE some_col = 'sth';
to set a null smallint attribute into a nonnull value.
I checked the update result with select statement right after the update statement via JDBC.
The attribute stayed null. Only the datetime attribute have been updated to intended value.
Also, here are what I have tried:
Set the target value to a nonnull value larger than 0. Still same result as above.
Added and smallint_col IS NULL in the WHERE clause. This time the update of both attributes failed.
Is there anything wrong with my update SQL statement?
What could be a possible reason for this?
Is there any possibility that the update of a certain attribute of a table is restricted?
Btw, I can only access to the DB with JDBC and I am trying to avoid talking to the DBA.

I just checked below whole SQL statements under Sybase ASE database, there is no problem on the update operation.
If you are using JDBC call, you can get the return count of the update SQL execution. to check the affected count.
create table table_name (id int not null, smallint_col smallint null,
datetime_col datetime, some_col varchar(32) null)
go
insert into table_name values(1, null, '2020-05-05', 'sth')
go
select * from table_name
go
UPDATE table_name SET smallint_col = 0,
datetime_col = CONVERT(datetime, '1999-09-09') WHERE some_col = 'sth'
go
select * from table_name
go
Please double check your table definition. Is it the same as the above?

Related

Getting an error converting from varchar to numeric

I made a historical table along with a trigger function built in the reference table. Is based on possible name changes for an user and as well recording down the date.
My trigger built:
Target table:
The trigger function pulls the names off the table.
But I'm getting error converting data type. All my data that I'm updating are of VARCHAR type. Where am I missing?
Change the INSERT..VALUES statement to INSERT..SELECT and set aliases for all the columns. Make sure the aliases match the every column in the target table (defaulting all NOT NULL columns), and they are in the same order as they are declared.
Note that the SELECT statement uses the patientLastNameChange table, because the MAX() without a GROUP BY ensures only one row is returned.
I recommend to use COALESCE to set the MAX() result to 0 if it returns NULL. Then simply add 1 to increment the lastNameChangeID. I think this is more readable.
CREATE TRIGGER patientLastNameChangeTrigger
ON patient
AFTER UPDATE
AS
BEGIN
DECLARE #oldLastName VARCHAR(255) = (SELECT pt_lastName FROM DELETED);
DECLARE #newLastName VARCHAR(255) = (SELECT pt_lastName FROM INSERTED);
IF #oldLastName <> #newLastName
INSERT INTO dbo.patientLastNameChange (lastnameChangeID, patientID, oldLastName, newLastName, datechanged)
SELECT
COALESCE(MAX(plnc.lastnameChangeID),0)+1 AS lastnameChangeID,
(SELECT patientID FROM INSERTED) AS patientID,
#oldLastName AS oldLastName,
#newLastName AS newLastName,
GETDATE() AS datechanged
FROM patientLastNameChange plnc;
END;

SQL Anywhere Error -728: Update operation attempted on non-updatable remote query

What I'm trying to do:
update table_name set field2 = substring(REGEXP_SUBSTR(field1, 'item=[0-9]+', charindex('item=', field1)), 6)
But I'm getting
SQL Anywhere Error -728: Update operation attempted on non-updatable remote query
Can I solve it somehow? I don't use local/remote tables. I use one table.
So I guess I found soltion... even 2.
Unfortunately still no way to use REGEXP_SUBSTR...
I do:
first
alter table my_table add item_position int null
alter table my_table add is_char int null
alter table my_table add item_part varchar(200) null
alter table my_table add item bigint null
update my_table set item_position = charindex('item=', field1)+5;
update my_table set item_part = substring(field1, item_pos, 10);
update my_table set is_char = charindex('&', clid_part)-1;
update my_table set item = case when is_char = -1 then item_part else substring(item_part, 1, charindex('&', item_part)-1) end;
or
cast(str_replace(substring(field1, charindex('item=', field1)+5, 10), substring(substring(field1, charindex('item=', field1)+5, 10),
(charindex('&', substring(field1, charindex('clid=', field1)+5, 10)))), '') as integer) as item
Something like this
I suggest to double check that table_name is actually a table, but not a view. If it is a view, you may see its definition with sp_helptext command, such as
sp_helptext 'view_name'
or
sp_helptext 'schema_name.view_name'

timestamp, rowversion and datetime - I need a mark when a row is inserted or updated

I have the following requirement:
I have a table with 1 unique auto-incremental Int ID and data columns.
I need the following:
Every time a row is inserted into that table, a column at the right end of the table must hold the full datetime of that insert.
Also, if a row is updated I need that column that holds the full datetime of the insert of that row into the table, to be updated to hold the update time for that row.
Now the obvious and very straightforward way to do this is:
you create your table:
create table My_Test_Table
(my_id int identity not null,
my_data nvarchar(max));
you alter your table adding the datetime column and a Default constraint on it:
ALTER TABLE My_Test_Table
ADD [timestamp] datetime;
ALTER TABLE My_Test_Table
ADD CONSTRAINT DF_My_Test_Table_timestamp DEFAULT GETDATE() FOR [timestamp];
then you make a nice trigger for update, like so:
CREATE TRIGGER DML_Trigger_update_My_Test_Table
ON My_Test_Table
FOR UPDATE
AS
IF ##ROWCOUNT <> 0
BEGIN
IF EXISTS (SELECT * FROM INSERTED) AND EXISTS (SELECT * FROM DELETED)
BEGIN
UPDATE My_Test_Table
SET [timestamp] = GETDATE()
FROM INSERTED
WHERE My_Test_Table.my_id = INSERTED.my_id;
END
END
Now the tricky part is:
I want, for reasons that are beyond scope here, to implement this exact thing as above but without a Trigger!
Is it possible?
I do not want to use the SQL type timestamp or rowversion, this won't work for me, I need the date, time down to the milliseconds to be clearly stored in that column.
Any ideas would be much appreciated.
You don't need a trigger
You can use the DEFAULT keyword as the source value in the UPDATE statement to use, well, the DEFAULT constraint defined on that column
UPDATE
MyTable
SET
foo = ...,
bar = ...,
ChangedDateTime = DEFAULT
WHERE
...;
I wouldn't use a column called timestamp because this has meaning on SQL Server, as a synonym for rowversion. For SQL Server 2008+ use datetime2(3) to accurately record milliseconds. The "old" datetime is accurate to a rounded 3.33 milliseconds only

atomic compare and swap in a database

I am working on a work queueing solution. I want to query a given row in the database, where a status column has a specific value, modify that value and return the row, and I want to do it atomically, so that no other query will see it:
begin transaction
select * from table where pk = x and status = y
update table set status = z where pk = x
commit transaction
--(the row would be returned)
it must be impossible for 2 or more concurrent queries to return the row (one query execution would see the row while its status = y) -- sort of like an interlocked CompareAndExchange operation.
I know the code above runs (for SQL server), but will the swap always be atomic?
I need a solution that will work for SQL Server and Oracle
Is PK the primary key? Then this is a non issue, if you already know the primary key there is no sport. If pk is the primary key, then this begs the obvious question how do you know the pk of the item to dequeue...
The problem is if you don't know the primary key and want to dequeue the next 'available' (ie. status = y) and mark it as dequeued (delete it or set status = z).
The proper way to do this is to use a single statement. Unfortunately the syntax differs between Oracle and SQL Server. The SQL Server syntax is:
update top (1) [<table>]
set status = z
output DELETED.*
where status = y;
I'm not familiar enough with Oracle's RETURNING clause to give an example similar to SQL's OUTPUT one.
Other SQL Server solutions require lock hints on the SELECT (with UPDLOCK) to be correct.
In Oracle the preffered avenue is use the FOR UPDATE, but that does not work in SQL Server since FOR UPDATE is to be used in conjunction with cursors in SQL.
In any case, the behavior you have in the original post is incorrect. Multiple sessions can all select the same row(s) and even all update it, returning the same dequeued item(s) to multiple readers.
As a general rule, to make an operation like this atomic you'll need to ensure that you set an exclusive (or update) lock when you perform the select so that no other transaction can read the row before your update.
The typical syntax for this is something like:
select * from table where pk = x and status = y for update
but you'd need to look it up to be sure.
I have some applications that follow a similar pattern. There is a table like yours that represents a queue of work. The table has two extra columns: thread_id and thread_date. When the app asks for work froom the queue, it submits a thread id. Then a single update statement updates all applicable rows with the thread id column with the submitted id and the thread date column with the current time. After that update, it selects all rows with that thread id. This way you dont need to declare an explicit transaction. The "locking" occurs in the initial update.
The thread_date column is used to ensure that you do not end up with orphaned work items. What happens if items are pulled from the queue and then your app crashes? You have to have the ability to try those work items again. So you might grab all items off the queue that have not been marked completed but have been assigned to a thread with a thread date in the distant past. Its up to you to define "distant."
Try this. The validation is in the UPDATE statement.
Code
IF EXISTS (SELECT * FROM sys.tables WHERE name = 't1')
DROP TABLE dbo.t1
GO
CREATE TABLE dbo.t1 (
ColID int IDENTITY,
[Status] varchar(20)
)
GO
DECLARE #id int
DECLARE #initialValue varchar(20)
DECLARE #newValue varchar(20)
SET #initialValue = 'Initial Value'
INSERT INTO dbo.t1 (Status) VALUES (#initialValue)
SELECT #id = SCOPE_IDENTITY()
SET #newValue = 'Updated Value'
BEGIN TRAN
UPDATE dbo.t1
SET
#initialValue = [Status],
[Status] = #newValue
WHERE ColID = #id
AND [Status] = #initialValue
SELECT ColID, [Status] FROM dbo.t1
COMMIT TRAN
SELECT #initialValue AS '#initialValue', #newValue AS '#newValue'
Results
ColID Status
----- -------------
1 Updated Value
#initialValue #newValue
------------- -------------
Initial Value Updated Value

DateCreated column in Sql Server?

Is there a special way to declare a DateCreated column in a MS Sql Server table so that it will automatically fill it with the appropriate time-stamp when created?
Or.. do I have to provide the datetime to it when I do the query, manually?
Default values suffer from two major drawbacks.
if the insert statement specifies a value for the column, the default isn't used.
the column can be updated any time.
These mean that you can't be certain that the values haven't been modified outside of your control.
If you want true data integrity (so that you're sure the date in the row is the creation date), you need to use triggers.
An insert trigger to set the column to the current date and an update trigger to prevent changes to that column (or, more precisely, set it to its current value) are the way to implement a DateCreated column.
An insert and update trigger to set the column to the current date is the way to implement a DateModified column.
(edit from user Gabriel - here's my attempt to implement this as described - i'm not 100% sure it's correct but I'm hoping the OP reviews it...):
CREATE TRIGGER [dbo].[tr_Affiliate_IU]
ON [dbo].[Affiliate]
AFTER INSERT, UPDATE
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Get the current date.
DECLARE #getDate DATETIME = GETDATE()
-- Set the initial values of date_created and date_modified.
UPDATE
dbo.Affiliate
SET
date_created = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
LEFT OUTER JOIN DELETED D ON I.id = D.id
WHERE
D.id IS NULL
-- Ensure the value of date_created does never changes.
-- Update the value of date_modified to the current date.
UPDATE
dbo.Affiliate
SET
date_created = D.date_created
,date_modified = #getDate
FROM
dbo.Affiliate A
INNER JOIN INSERTED I ON A.id = I.id
INNER JOIN DELETED D ON I.id = D.id
END
You can set the default value of the column to "getdate()"
We have DEFAULT on CreatedDate and don't enforce with Triggers
There are times when we want to set the date explicitly - e.g. if we import data from some other source.
There is a risk that Application Bug could mess with the CreateDate, or a disgruntled DBA for that matter (we don't have non-DBAs connecting direct to our DBs)
I suppose you might set Column-level permissions on CreateDate.
A half-way-house might be to have an INSERT TRIGGER create a row in a 1:1 table, so that column was outside the main table. The second table could have SELECT permissions, where the main table has UPDATE permissions, and thus not need an UPDATE trigger to prevent changes to CreateDate - which would remove some "weight" when updating rows normally.
I suppose you coul have an UPDATE/DELETE trigger on the second table to prevent change (which would never be executed in normal circumstances, so "lightweight")
Bit of a pain to have the extra table though ... could have one table for all CreateDates - TableName, PK, CreateDate. Most database architects will hate that though ...
Certainly is.
Here is an example in action for you.
Create table #TableName
(
ID INT IDENTITY(1,1) PRIMARY KEY,
CreatedDate DATETIME NOT NULL DEFAULT GETDATE(),
SomeDate VARCHAR(100)
)
INSERT INTO #TableName (SomeDate)
SELECT 'Some data one' UNION ALL SELECT 'some data two'
SELECT * FROM #TableName
DROP TABLE #TableName
Setting the default value isn't enough, you should add a trigger to prevent updating:
CREATE TRIGGER UpdateRecord ON my_table
AFTER UPDATE AS UPDATE my_table
SET [CreatedDate] = ((SELECT TOP 1 [CreatedDate] FROM Deleted d where d.[id]=[id]))