Treating null field as zero for an update query - sql-server-express

I'm using the SQL Express 2010 query builder. I need to be able to increment a field.
In my behind code, I make a call such as
tableAdapter.IncrementLikeCount(id);
If I just use an increment, the like field can be null, so I want to either
a. treat the null as zero in that field OR
b. set to 1, if null, and increment otherwise.
The most current thing I tried is option b with the following code in the query builder:
UPDATE [dbo].[myTable]
SET [LikeCount] = IIF(ISNULL([LikeCount]), 1, LikeCount + 1)
WHERE ([ID] = #Original_ID)
However, this does not work. The query builder keeps rewriting the expression inside the ISNULL without the square brackets and with a comma, as the following:
UPDATE [dbo].[myTable]
SET [LikeCount] = IIF(ISNULL(LikeCount,), 1, LikeCount + 1)
WHERE ([ID] = #Original_ID)
Is there a clean, simple way of doing this?

The ISNULL statement needs a default to fall back to, like
ISNULL(LikeCount, 0)
where the 0 is the value that LikeCount becomes IF in fact it is null.
So, try
UPDATE [dbo].[myTable]
SET [LikeCount] = (ISNULL(LikeCount, 0) + 1)
WHERE ([ID] = #Original_ID)
UPDATE
As to the query you posted in your comment:
UPDATE Documents
SET docLikeCount = ISNULL(docLikeCount, 0) + 1
WHERE docID = #Original_docID

Related

Smallint stayed null after executing update SQL statement in Sybase

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?

Enumerate the multiple rows in a multi-update Trigger

I have something like the table below:
CREATE TABLE updates (
id INT PRIMARY KEY IDENTITY (1, 1),
name VARCHAR (50) NOT NULL,
updated DATETIME
);
And I'm updating it like so:
INSERT INTO updates (name, updated)
VALUES
('fred', '2020-11-11),
('fred', '2020-11-11'),
...
('bert', '2020-11-11');
I need to write an after update Trigger and enumerate all the name(s) that were added and add each one to another table but can't work out how enumerate each one.
EDIT: - thanks to those who pointed me in the right direction, I know very little SQL.
What I need to do is something like this
foreach name in inserted
look it up in another table and
retrieve a count of the updates a 'name' has done
add 1 to the count
and update it back into the other table
I can't get to my laptop at the moment, but presumably I can do something like:
BEGIN
SET #count = (SELECT UCount from OTHERTAB WHERE name = ins.name)
SET #count = #count + 1
UPDATE OTHERTAB SET UCount = #count WHERE name = ins.name
SELECT ins.name
FROM inserted ins;
END
and that would work for each name in the update?
Obviously I'll have to read up on set based SQL processing.
Thanks all for the help and pointers.
Based on your edits you would do something like the following... set based is a mindset, so you don't need to compute the count in advance (in fact you can't). It's not clear whether you are counting in the same table or another table - but I'm sure you can work it out.
Points:
Use the Inserted table to determine what rows to update
Use a sub-query to calculate the new value if its a second table, taking into account the possibility of null
If you are really using the same table, then this should work
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE(UCount,0) + 1
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
If however you are using a second table then this should work:
BEGIN
UPDATE OTHERTAB SET
UCount = COALESCE((SELECT UCount+1 from OTHERTAB T2 WHERE T2.[name] = OTHERTAB.[name]),0)
WHERE [name] in (
SELECT I.[name]
FROM Inserted I
);
END;
Using inserted and set-based approach(no need for loop):
CREATE TRIGGER trg
ON updates
AFTER INSERT
AS
BEGIN
INSERT INTO tab2(name)
SELECT name
FROM inserted;
END

Can I check if a variable is NULL within an Update's SET?

I have a stored procedure that uses a simple UPDATE with some variables passed to it. But I don't want to update those fields when their variables aren't null. This is essentially what my statement looks like.
UPDATE myTable
SET myColumn = #myColumn,
mColumn1 = #myColumn1
WHERE myColumn2 = #myColumn2
Is there anyway to apply some conditional logic within the SET? I have around 10 fields that need to be checked, so I wouldn't want to do an update per field or something like that.
Any ideas?
COALESCE is your friend. It returns its first non-NULL argument. I'm not actually sure from your narrative which way around you want things, it's either:
UPDATE myTable
SET myColumn = COALESCE(myColumn,#myColumn),
mColumn1 = COALESCE(myColumn1,#myColumn1)
WHERE myColumn2 = #myColumn2
Which keeps the current column's value if the column's not null, or
UPDATE myTable
SET myColumn = COALESCE(#myColumn,myColumn),
mColumn1 = COALESCE(#myColumn1,myColumn1)
WHERE myColumn2 = #myColumn2
Which keeps the current column's value if the variable is null.
Try to use coalesce function as below
UPDATE myTable
SET myColumn = coalesce(myColumn,#myColumn),
mColumn1 = coalesce(mColumn1,#myColumn1)
WHERE myColumn2 = #myColumn2
Above code updates your columns only when they are null. If they are not null the code sets the same value stored in the columns.
ISNULL ( variable , in case of null default value)
INFO

MSSQL: Update statement avoiding the CHECK constraint

Working in MS2000, I have a table called JobOwners that maps Jobs (JPSID) to the Employees that own them (EmpID). It also contains the date they started owning that job (DateStarted), date they stopped owning that job (DateEnded) and if the ownership is active (IsActive). Looks like this.
CREATE TABLE JobOwners
(
LogID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
JPSID int NOT NULL FOREIGN KEY REFERENCES JobsPerShift(JPSID),
EmpID int NOT NULL FOREIGN KEY REFERENCES Employees(EmpID),
DateStarted datetime,
DateEnded datetime,
IsActive tinyint NOT NULL
)
There should be no duplicates of JPSID that are active, although inactive duplicates should be fine. With some research I found I could accomplish this using a function on a CHECK constraint.
CREATE FUNCTION CheckActiveCount(#JPSID INT)
RETURNS INT AS
BEGIN
DECLARE #result INT
SELECT #result = COUNT(*) FROM JobOwners WHERE JPSID = #JPSID AND IsActive = 1
RETURN #result
END
GO
ALTER TABLE JobOwners
ADD CONSTRAINT CK_JobOwners_IsActive
CHECK ((IsActive = 1 AND dbo.CheckActiveCount(JPSID) <= 1) OR (IsActive = 0))
This works well enough. It will allow me to insert JPSID 2 with IsActive 1, as there is no other active JPSID 2. It will let me insert JPSID 2 with IsActive 0, because the check isn't applied when IsActive is 0. It rejects when I try to insert JPSID 2 with IsActive 1 again though, because it conflicts with the constraint. See below.
INSERT INTO JobOwners
VALUES(2,2,NULL,NULL,1)
(1 row(s) affected)
INSERT INTO JobOwners
VALUES(2,2,NULL,NULL,0)
(1 row(s) affected)
INSERT INTO JobOwners
VALUES(2,3,NULL,NULL,1)
INSERT statement conflicted with COLUMN FOREIGN KEY constraint...
The problem occurs if I try to update one of the inactive records to active. For some reason, it allows me.
UPDATE JobOwners SET IsActive = 1
WHERE LogID = 3
(1 row(s) affected)
If I run the same statement again, then it conflicts with the constraint, but not the first time. The front end of this app would never change an inactive record to active, it would just insert a new record, but it's still not something I'd like the table to allow.
I'm wondering if it might be best to separate the active job owners and have a seperate table for job owner history, but I'm not certain on the best practice here.
Any help would be greatly appreciated.
Thank you,
Ben
There is a known issue where certain operations will lead to a check constraint that calls a UDF to be bypassed. The bug was listed on Connect (before it was scuttled and all the links were orphaned) and it has been acknowledged, but closed as Won't Fix. This means we need to rely on workarounds.
My first workaround would probably be an instead of update trigger. Thanks to Martin for keeping me honest and for making me test this further - I found that I did not protect against two rows being updated to 1 in the same statement. I've corrected the logic and added a transaction to help prevent a race condition:
CREATE TRIGGER dbo.CheckJobOwners ON dbo.JobOwners
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRANSACTION;
UPDATE j SET IsActive = 1 -- /* , other columns */
FROM dbo.JobOwners AS j INNER JOIN inserted AS i
ON i.LogID = j.LogID
WHERE i.IsActive = 1 AND NOT EXISTS
( -- since only one can be active, we don't need an expensive count:
SELECT 1 FROM dbo.JobOwners AS j2
WHERE j2.JPSID = i.JPSID
AND j2.IsActive = 1 AND j2.LogID <> i.LogID
)
AND NOT EXISTS
( -- also need to protect against two rows updated by same statement:
SELECT 1 FROM inserted AS i2
WHERE i2.JPSID = i.JPSID
AND i2.IsActive = 1 AND i2.LogID <> i.LogID
);
-- *if* you want to report errors:
IF (##ROWCOUNT <> (SELECT COUNT(*) FROM inserted WHERE IsActive = 1))
RAISERROR('At least one row was not updated.', 11, 1);
-- assume setting active = 0 always ok & that IsActive is not nullable
UPDATE j SET IsActive = 0 -- /* , other columns */
FROM dbo.JobOwners AS j INNER JOIN inserted AS i
ON j.LogID = i.LogID
WHERE i.IsActive = 0;
COMMIT TRANSACTION;
END
GO
(My only reason for an instead of instead of after trigger is that you only update the rows you need to update, instead of having to rollback after the fact (which won't let you only rollback the invalid updates in the case of a multi-row update)).
There is a lot of good discussion about this issue here:
https://web.archive.org/web/20171013131650/http://sqlblog.com/blogs/tibor_karaszi/archive/2009/12/17/be-careful-with-constraints-calling-udfs.aspx
EDIT: HUGE caveat. See Aaron's comment on this SO question for reasons you probably want to avoid combining UDFs and CHECK CONSTRAINTS. However, since (even after reading and understanding Aaron's concerns) my answer is still viable in our system because of 1) how our system works and 2) we actually want UPDATE statements to fail in the scenarios he describes, I am leaving my answer here. As it ALWAYS is, it is up to you to make sure you understand the ramifications of using the script in this answer. YOU HAVE BEEN WARNED
I followed the link in Aaron's (accepted) answer. In the description there was a specific piece of text that caught my attention "(to check values that are not passing as parameters)".
That gave me an idea. I have a table with columns CustomerId, ContactId, ContactType all of type "int". The PK is CustomerId and ContactId. I needed to be able to limit each CustomerId to only have one "Primary" Contact (ContactType = 1) but as many "secondary" and "other" contacts as people wanted to add. I had setup my UDF to accept only CustomerId as a parameter. So, I added ContactType as well but since I only cared about ContactType = 1, I just hard-coded the ContactType parameter to 1 inside the function. It worked on SQL2012 but I have no idea about other versions.
Here is a test script. I "squished" together some of the statements to reduce the amount of scrolling needed. Note: the constraint ALLOWS zero Primary Contacts because it would be impossible to set a different Contact as the Primary if you did not first remove an existing Primary.
CREATE TABLE [dbo].[CheckConstraintTest](
[CustomerId] [int] NOT NULL,
[ContactId] [int] NOT NULL,
[ContactType] [int] NULL,
CONSTRAINT [PK_CheckConstraintTest] PRIMARY KEY CLUSTERED (
[CustomerId] ASC,
[ContactId] ASC
))
GO
CREATE FUNCTION dbo.OnlyOnePrimaryContact (
#CustId int, #ContactType int ) RETURNS bit
AS BEGIN
DECLARE #result bit, #count int
SET #ContactType = 1 --only care about "1" but needed parm to force SQL to "care" about that column
SELECT #count = COUNT(*) FROM CheckConstraintTest WHERE [CustomerId] = #CustId AND [ContactType] = #ContactType
IF #count < 2 SET #result = 1
ELSE SET #result = 0
RETURN #result
END
GO
ALTER TABLE [dbo].[CheckConstraintTest] WITH CHECK ADD CONSTRAINT [SinglePrimaryContact] CHECK (([dbo].[OnlyOnePrimaryContact]([CustomerId],[ContactType])=(1)))
GO
ALTER TABLE [dbo].[CheckConstraintTest] CHECK CONSTRAINT [SinglePrimaryContact]
GO
INSERT INTO [CheckConstraintTest] (CustomerId, ContactId, ContactType)
VALUES (1,1,1), (1,2,2), (1,3,2), (1,4,2), (2,1,1)
INSERT INTO [CheckConstraintTest] (CustomerId, ContactId, ContactType)
VALUES (1,5,1) --This should fail
UPDATE [CheckConstraintTest] --This should fail
SET ContactType = 1
WHERE CustomerId = 1 AND ContactId = 2
UPDATE [CheckConstraintTest] --This should work
SET ContactType = 2
WHERE CustomerId = 1 AND ContactId = 1
INSERT INTO [CheckConstraintTest] (CustomerId, ContactId, ContactType)
VALUES (1,5,1) --This should work now since we change Cust 1, Contact 1, to "secondary" in previous statement

Add a new column to an existing table, with a value equal to the ID

In my database in a sql-server 2005 instance, I need to add a new column to a table with existing data. The table currently looks like this:
CREATE TABLE [dbo].[Status](
[Status_ID] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](80) COLLATE Latin1_General_CI_AS NOT NULL
)
The column I want to add is also of type INT and nulls are not allowed. Furthermore, I want the initial values for this column to be equal to the ID (it cannot be a computed column).
This is the script I have written for this:
ALTER TABLE [dbo].[Status]
add Status_Code int NOT NULL DEFAULT 1
GO
//Get the number of rows
DECLARE #RowCount INT
SET #RowCount = (SELECT COUNT(Status_ID) FROM Status)
//Create an iterator
DECLARE #I INT
SET #I = 1
//Loop through the rows of a table #myTable
WHILE (#I <= #RowCount)
BEGIN
UPDATE Status
SET Status_Code = #I
WHERE Status_ID = #I
//Increment the iterator
SET #I = #I + 1
END
This script seems to work perfectly. However, it seems like a lot of code for a rather small task. Does anyone know of a more efficient way to code this?
Why loop through the table to do the update? Just do this:
ALTER TABLE [dbo].[Status]
add Status_Code int NOT NULL DEFAULT 1
UPDATE Status
SET Status_Code = Status_ID
Create the new column, and make it nullable.
Then write a simple update statement to insert the existing id's into the new columns.
Then make your column not-nullable.
I would go with Gerrie's answer as you want the column to be NOT NULL. If you specify a default value, SQL server will still allow you to leave out that column in any subsequent insert statement (the column will be assigned the default value)