SQL Server 2008 - IF NOT EXISTS INSERT ELSE UPDATE - sql

I apologize, but this is kind of a two part question.
I'm extremely new to SQL and am trying to develop a time clock application for the small office that I work in. I'm playing around with the SQL backend right now and have a question about compound statements.
Where I'm stuck is if a user tries to clock out for break but never clocked in at the start of the shift, SQL needs to create a new row rather than update an existing.
Here is what I tried:
IF NOT EXISTS(SELECT * FROM Clock WHERE clockDate = '08/10/2012') AND userName = 'test')
BEGIN
INSERT INTO Clock(clockDate, userName, breakOut)
VALUES({ fn NOW() }, 'test', { fn NOW() })
END
ELSE
BEGIN
UPDATE Clock
SET breakOut = { fn NOW() }
WHERE (clockDate = '08/10/2012') AND (userName = 'test')
END
I'm using Visual Studio 2010 to do this connected to SQL Server Express 2008 on my local machine. I get an error that says "The Compound statement SQL construct or statement is not supported." However, that is followed by a message that 1 row has been affected, and when I view my Clock table it looks just like what I expect it to look like. What is the best way to acclompish this?
And my second part of this question is in my WHERE statements. Is there a function to get today's date in the clockDate column rather than have to populate today's date? Just trying to think ahead for building the front end application.
IF NOT EXISTS(SELECT * FROM Clock WHERE clockDate = { fn CURRENT_DATE() }) AND userName = 'test')
Again, this gives me the results I want, but not until after getting an error "Error in WHERE clause near 'CURRENT_DATE'. Unable to parse query text."
I hope I have explained this properly, and thank you for your help!!
EDIT:
#RThomas
#w00te
OK, so with the clockDate as a date field and breakOut as a time(0) field, should this work? Cause I'm still getting a "The Compound statement SQL construct or statement is not supported." Syntax error even though it seems to be working.
IF NOT EXISTS (SELECT * FROM Clock WHERE (clockDate = GETDATE()) AND (userName = 'test'))
BEGIN
INSERT INTO Clock(clockDate, userName, breakOut)
Values(GETDATE(), 'test', GETDATE())
END
ELSE
BEGIN
UPDATE Clock
SET breakOut = GETDATE()
WHERE (clockDate = GETDATE()) AND (userName = 'test')
END
My table results are:
clockDate userName clockIn breakOut breakIn clockOut
08/10/2012 test NULL 11:24:38 NULL NULL
This is the result I want but this error confuses me. Is this a Visual Studio error or a SQL error? And I'll read up on Merge Statements, thank you both for the links.

At first glance your original attempt seems pretty close. I'm assuming that clockDate is a DateTime fields so try this:
IF (NOT EXISTS(SELECT * FROM Clock WHERE cast(clockDate as date) = '08/10/2012')
AND userName = 'test')
BEGIN
INSERT INTO Clock(clockDate, userName, breakOut)
VALUES(GetDate(), 'test', GetDate())
END
ELSE
BEGIN
UPDATE Clock
SET breakOut = GetDate()
WHERE Cast(clockDate AS Date) = '08/10/2012' AND userName = 'test'
END
Note that getdate gives you the current date. If you are trying to compare to a date (without the time) you need to cast or the time element will cause the compare to fail.
If clockDate is NOT datetime field (just date), then the SQL engine will do it for you - no need to cast on a set/insert statement.
IF (NOT EXISTS(SELECT * FROM Clock WHERE clockDate = '08/10/2012')
AND userName = 'test')
BEGIN
INSERT INTO Clock(clockDate, userName, breakOut)
VALUES(GetDate(), 'test', GetDate())
END
ELSE
BEGIN
UPDATE Clock
SET breakOut = GetDate()
WHERE clockDate = '08/10/2012' AND userName = 'test'
END
As others have pointed out, the merge statement is another way to tackle this same logic. However, in some cases, especially with large data sets, the merge statement can be prohibitively slow, causing a lot of tran log activity. So knowing how to logic it out as shown above is still a valid technique.

As others have suggested that you should look into MERGE statement but nobody provided a solution using it I'm adding my own answer with this particular TSQL construct. I bet you'll like it.
Important note
Your code has a typo in your if statement in not exists(select...) part. Inner select statement has only one where condition while UserName condition is excluded from the not exists due to invalid brace completion. In any case you cave too many closing braces.
I assume this based on the fact that you're using two where conditions in update statement later on in your code.
Let's continue to my answer...
SQL Server 2008+ support MERGE statement
MERGE statement is a beautiful TSQL gem very well suited for "insert or update" situations. In your case it would look similar to the following code. Take into consideration that I'm declaring variables what are likely stored procedure parameters (I suspect).
declare #clockDate date = '08/10/2012';
declare #userName = 'test';
merge Clock as target
using (select #clockDate, #userName) as source (ClockDate, UserName)
on (target.ClockDate = source.ClockDate and target.UserName = source.UserName)
when matched then
update
set BreakOut = getdate()
when not matched then
insert (ClockDate, UserName, BreakOut)
values (getdate(), source.UserName, getdate());

IF NOT EXISTS(SELECT * FROM Clock
WHERE clockDate = '08/10/2012') AND userName = 'test')
Has an extra parenthesis. I think it's fine if you remove it:
IF NOT EXISTS(SELECT * FROM Clock WHERE
clockDate = '08/10/2012' AND userName = 'test')
Also, GETDATE() will put the current date in the column, though if you don't want the time you'll have to play a little. I think CONVERT(varchar(8), GETDATE(), 112) would give you just the date (not time) portion.
IF NOT EXISTS(SELECT * FROM Clock WHERE
clockDate = CONVERT(varchar(8), GETDATE(), 112)
AND userName = 'test')
should probably do it.
PS: use a merge statement :)

You need to replace it as WHERE clockDate = { fn CURRENT_DATE() } AND userName = 'test'.
Please remove extra ")" from { fn CURRENT_DATE() })

Related

Date wise optional parameter searching in SQL Stored Procedure

I want to take a leave report from my leave application table when I search. In the table I have Leavefrom(datetime), LeaveTo(datetime) columns. Now I want to take the rows on the basis of these two columns. My searching parameters are nullable they are
#employeeid, #datefrom, #dateto.
I need to get the result must between the date of Leavefrom, LeaveTo.
I am trying to make a stored procedure for this.
ALTER PROCEDURE [dbo].[SP_GetSpecificLeaveReport]
#empid int=null,
#leavefrom date=null,
#leaveto date=null
AS
BEGIN
SET NOCOUNT ON;
SELECT ela.appliedDate,ela.appliedBy,ela.leaveFrom,ela.leaveTo,ela.noOfDays,
ejd.firstName,ejd.lastName,
ltm.leaveType
from dbo.tblEmployeeLeaveApplication as ela inner join dbo.tblEmployeeJobDetails as
ejd on ela.empId=ejd.recordId inner join dbo.tblLeaveTypeMaster as ltm
on ela.leaveTypeId=ltm.record_Id where
END
This kind of queries are called catch-all queries.
There are multiple ways to do this, using iif like in Mukesh's answer is one of them, but will only work on sql server 2012 or higher.
I would recommend working with a slighly longer where clause for better performance as well as compatibility (this should work with any version, even sql server 7):
where (#empid is null or ela.empId = #empid)
and (#leavefrom is null or (
ltm.leavedatefrom >= #leavefrom
and ltm.leavedatefrom < dateadd(day, 1, #leavefrom)
)
and (#leaveto is null or (
ltm.leavedateto >= #leaveto
and ltm.leavedateto < dateadd(day, 1, #leaveto))
Note: Since your database columns are of type datetime but your parameters are of type date, I've the >=...or condition to catch all datetime values that match a specific date.
Also, you might need to cast from date to datetime.
Also, you should be aware of the fact that using catch-all queries might suffer from poor performance due to query-plan cashing.
If you do encounter a performance problem you might want to add a recompile hint to your query so that each time you execute the stored procedure you will have the optimal query plan. read this article for more details.
try this query to add
where ela.empId=IFNULL(ela.empId,ela.empId)
and ltm.leavedatefrom>=IFNULL(#leavefrom,ltm.leavedatefrom)
and ltm.leavedateto<=IFNULL(#leaveto,ltm.leavedateto)
Try following,
ALTER PROCEDURE [dbo].[SP_GetSpecificLeaveReport]
#empid int=null,
#leavefrom date=null,
#leaveto date=null
AS
BEGIN
SET NOCOUNT ON;
SELECT
ela.appliedDate,ela.appliedBy,ela.leaveFrom,ela.leaveTo,ela.noOfDays,
ejd.firstName,ejd.lastName,
ltm.leaveType
from dbo.tblEmployeeLeaveApplication as ela
inner join dbo.tblEmployeeJobDetails as ejd on ela.empId=ejd.recordId
inner join dbo.tblLeaveTypeMaster as ltm on ela.leaveTypeId=ltm.record_Id
where
1 = case when Isnull(#empid,0) == 0 then
case when ela.empId == #empid then 1 else 0 end
else 0 end
And case when isnull(#leavefrom,'1900-01-01') == '1900-01-01' then
case when ela.leaveFrom >= #leavefrom then 1 else 0 end
else 0 end
And case when isnull(#leavefrom,'1900-01-01') == '1900-01-01' then
case when ela.leaveto <= #leaveto then 1 else 0 end
else 0 end
END

Subquery returns more than 1 value: DateTime

I am trying to adjusted multiple rows for daylight savings time in a single table. I need to add an hour to any records that were written before I caught the error. I am getting the error
Subquery returns more than 1 value. This is not permitted when the subquery follows =, != etc
I understand what it is telling me, I just can't think of a way around it. This is what I am trying to do:
UPDATE Table
SET LocalDateTime = LocalDateTime + '1:00:00'
WHERE (DateWritten > '3/10/13') AND (DateWritten < '3/11/13 7:00:00') AND (varCharColumn <> 'aString')
I have also tried the variation and received the same error.
UPDATE Table
SET LocalDateTime = LocalDateTime + '1:00:00'
WHERE DateWritten IN (SELECT DateWritten FROM Table WHERE (same clause as above))
You most likely have a trigger on that table that was written in a way that it cannot handle multirow updates. If that is the case, either fix the trigger, or use a cursor for the update.
Also, as others have mentioned, you cannot use the + operator for time calculations. Use DATEADD instead.
Just to clarify: Your UPDATE cannot cause the error you are getting. The most likely cause is a trigger that was put in place by someone else. You can see Triggers in SSMS under the table itself. See if you have a trigger there and post the code if you do. Maybe we can help fix it.
If you don't want to mess with the trigger for now, use a cursor like this:
DECLARE complex_cursor CURSOR FOR
SELECT LocalDateTime
FROM dbo.Table
WHERE (DateWritten > '3/10/13') AND (DateWritten < '3/11/13 7:00:00') AND (varCharColumn <> 'aString') ;
OPEN complex_cursor;
FETCH FROM complex_cursor;
WHILE(##FETCH_STATUS = 0)
BEGIN
UPDATE dbo.Table
SET LocalDateTime = DATEADD(hour,1,LocalDateTime)
WHERE CURRENT OF complex_cursor;
FETCH FROM complex_cursor;
END
CLOSE complex_cursor;
DEALLOCATE complex_cursor;
GO
Source MSDN (with adjustments).
This should solve your immediate problem. Keep in mind however:
You really should fix the trigger as it might cause other problems.
Be careful with cursors. I general they are a a performance nightmare. I the case of a one-off update like yours they are however acceptable.
You can use DateAdd()
UPDATE Table
SET LocalDateTime = DateAdd(hour,1,LocalDateTime)
WHERE DateWritten > '3/10/13'
AND DateWritten < '3/11/13 7:00:00'
AND varCharColumn <> 'aString'
Alternatively you can also use BETWEEN for a comparison on your DateWritten column
UPDATE Table
SET LocalDateTime = DateAdd(hour,1,LocalDateTime)
WHERE DateWritten BETWEEN '3/10/13'AND '3/11/13 7:00:00'
AND varCharColumn <> 'aString'
Based off of this article, it looks like your Table may be used in a view which could be preventing updating of multiple records.
DateAdd documentation

SQL Trigger not working as expected

I have an SQL trigger as below
GO
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END
If i am right as per the trigger for every record inserted/updated when trigger runs it will check for the YEAR In the ExpirationDate column of that record and if the value is less than 1971 then it will update it with the date in the update query.
The weird thing is it is not working as expected.
The if condition does not seem to work.
Is anything wrong with this particular trigger.
YES - there's definitely something fundamentally wrong with the way you wrote this trigger.
SQL Server (assuming that's what you're using) will fire the trigger not once per row (as many folks including yourself) seem to think - the trigger is fired once per batch which might update or insert 10, 20, 50 rows at once.
Therefore, the Inserted pseudo table inside the trigger can (and will!) contain multiple rows - and in that case - what exactly does your statement here select?
Select #expDate = inserted.ExpirationDate from inserted
Either you'll just get one randon row (out of 50) and handle that (and ignore all 49 other rows), or you'll get an error....
You need to write your triggers with that in mind - you MUST always assume that Inserted (and Deleted) will contain multiple rows!
So you need to change your trigger to be something like:
CREATE TRIGGER ExpDateCheckCard
ON dbo.Card
FOR Insert, Update
AS
UPDATE dbo.Card
SET ExpirationDate = '1900-01-01 00:00:00'
FROM dbo.Card c
INNER JOIN inserted i ON i.RecordID = c.RecordID
WHERE YEAR(i.ExpirationDate) < 1971
(I've also changed your old-style JOIN syntax (comma-separated list of tables) to the ANSI standard that's been in place since 1992 - please do not use the comma-separated list of tables! Use proper ANSI JOINs. See this blog post for more background info: Bad habits to kick : using old-style JOINs)
try with this :
create trigger ExpDateCheckCard
On Card
FOR Insert, Update
As
BEGIN
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 ) Begin
UPdate Card set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
end
END
You have to try this code
CREATE TRIGGER ExpDateCheckCard AFTER INSERT,Update ON Card
FOR EACH ROW
Declare #expDate as DateTime
Select #expDate = inserted.ExpirationDate from inserted
if (YEAR(#expDate) < 1971 )
BEGIN
update Card
set ExpirationDate = '1900-01-01 00:00:00' FROM Card, inserted
where inserted.RecordID = Card.RecordID
END

updating date by stored procedure

I have a problem!
My task is to count the age of books in my library database. After that call some books as too rare, some rare , and usual using value column.
My library table ( ... , age- date , value- date)
notice: "age" - is incorrect definition of a column, it would be better to say "year of publication". Actually my task is to find age!
So, I do this, and my value column does not change :(
create procedure foo
as
declare #bookdate date,
#currentdate date,
#diff int
set #currentdate = GETDATE()
select #bookdate = age from books
select #diff = DATEDIFF (yyyy , #bookdate , #currentdate )
Version #1:
UPDATE books SET value = DATEADD(year,#diff, age)
Version #2:
UPDATE books SET value = #diff
P.S. sorry for any mistakes I made, it is my first step in sql, programming at all, and asking for help in English!
To me it sounds like you want something like this (I'm assuming you're using SQL Server as you've used the GETDATE() function):
CREATE PROCEDURE foo
AS
BEGIN
SELECT *
,DATEDIFF(yyyy,age,GETDATE()) AS YearsSincePublication
,CASE WHEN DATEDIFF(yyyy,age,GETDATE()) > 200 THEN 'Too rare'
WHEN DATEDIFF(yyyy,age,GETDATE()) > 100 THEN 'Rare'
ELSE 'Usual'
END AS Value
FROM books
END
Working form the top:
* means all columns from all tables
The datediff is working out the number of years since the publication and the AS bit names the resulting column (gives it an alias).
The CASE Statement is a way to test statements (if a equals b, do c). The first statement checks to see iff the book is more than 200 years old and if so, writes 'Too rare', the second line checks for more than 100 years, otherwise it writes 'usual'. Again, the AS is used to label the column to Value.
Finally the table we want our data from is specified, Books.
To run the stored procedure once you have created it is simply:
EXEC foo

SQL stored procedure IF EXISTS UPDATE ELSE INSERT

OK. I got a lot of help here earlier working with a SQL backend to a simple ... just not for me :( ... time clock solution for the small office I work in, so I'm back for more!
My table I'm currently working with consists of 6 columns:
clockDate date not null PK
userName varchar(50) not null PK
clockIn time(0)
breakOut time(0)
breakIn time(0)
clockOut time(0)
I though I had figured out my IF NOT EXISTS INSERT ELSE UPDATE statement from my last question, but now I'm trying to use it in a Stored Procedure, rather than a plain query window, with no success.
Basically a user clocking in is a no-brainer. However, if the user doesn't clock in, but they clock out for lunch, the statement needs to create the row instead of updating an existing row. Ok so here's my stored procedure:
ALTER PROCEDURE dbo.BreakOut
(
#userName varchar(50)
)
AS
IF EXISTS (SELECT * FROM Clock WHERE clockDate = GETDATE() AND userName = #userName)
BEGIN
UPDATE Clock SET breakOut = GETDATE()
WHERE clockDate = GETDATE() AND userName = #userName
END
ELSE
BEGIN
INSERT INTO Clock (clockDate, userName, breakOut)
VALUES (GETDATE(), #userName, GETDATE())
END
Here's my problem... If the user DID clock in for the day I get a primary key violation because the stored procedure is still trying to run the INSERT part of the statement and never runs the UPDATE line. I've tried it flipped with an IF NOT EXISTS as well with the same result. What's the trick to get IF-ELSE to work in a stored procedure? Can this be done they way I'm thinking or do I have to study Merge statement? My plan is to run the stored procedures from a simple Visual Basic program on each workstation. Maybe I'm getting in over my head :( To bad my boss is too cheap to just buy a time clock solution!
EDIT:
Thank you ALL for your help!! I'm falling in love with this site, questions get answers SO FAST!!! Here is my working stored procedure:
ALTER PROCEDURE dbo.BreakOut
(
#userName varchar(50)
)
AS
IF EXISTS (SELECT * FROM Clock WHERE DateDiff(dd, GetDate(),clockDate) = 0 AND userName = #userName)
BEGIN
UPDATE Clock SET breakOut = GETDATE()
WHERE DateDiff(dd, GetDate(),clockDate) = 0 AND userName = #userName
END
ELSE
BEGIN
INSERT INTO Clock (clockDate, userName, breakOut)
VALUES (GETDATE(), #userName, GETDATE())
END
Is this proper, or could it be improved more? Again Thank You ALL SO MUCH!!!
This is probably the problem right here: WHERE clockDate = GETDATE()
GetDate returns the current date AND the current time, which wouldn't match up with clockDate. You can compare the dates with DateDiff instead:
WHERE DateDiff(dd, GetDate(),clockDate) = 0
Your problem would appear to be the following:
Let's imagine the user clocked in at 09:00
A record like the following might exist:
ClockDate userName clockIn breakOut breakIn clockOut
12/08/2012 joe 09:00 NULL NULL NULL
Now your IF statement is doing this:
SELECT * FROM Clock WHERE clockDate = "20120812 17:24:13" AND userName = #userName
i.e. this record wont exist.
Instead, try this:
IF EXISTS (SELECT * FROM Clock WHERE clockDate = DATEADD(D, 0, DATEDIFF(D, 0, GETDATE())) AND userName = #userName)
You also need to make sure you are storing clockDate as just the date portion of GETDATE(), otherwise, you would need to adjust your query like so:
IF EXISTS (SELECT * FROM Clock WHERE DATEADD(D, 0, DATEDIFF(D, 0, clockDate)) = DATEADD(D, 0, DATEDIFF(D, 0, GETDATE())) AND userName = #userName)
Your update will never run because GETDATE returns a date and time.
http://msdn.microsoft.com/en-us/library/ms188383.aspx
CREATE PROCEDURE `SP_GENRE_SELECT`(
IN _Id INTEGER,
IN _Name VARCHAR(50),
IN _account VARCHAR (50),
IN _Password VARCHAR (50),
IN _LastConnexionDate DATETIME,
IN _CreatedDate DATETIME,
IN _UpdatedDate DATETIME,
IN _CreatedUserId INTEGER,
IN _UpdatedUserId INTEGER,
IN _Status TINYINT
)
BEGIN
SELECT *
FROM user
WHERE Id LIKE IF(_Id IS NULL,'%',CAST(_Id AS VARCHAR(50)))
AND
Name LIKE IF(_Name IS NULL,'%',CONCAT('%',_Name,'%'))
AND
Account LIKE IF(_Account IS NULL,'%',CONCAT('%',_Account,'%'))
AND
LastConnexionDate LIKE IF(_LastConnexionDate IS NULL,'%',CONCAT('%',CAST(LastConnexionDate AS VARCHAR(50),'%')))
AND
CreatedDate LIKE IF(_CreatedDate IS NULL,'%',CONCAT('%',CAST(_CreatedDate AS VARCHAR(50),'%')))
AND
UpdatedDate LIKE IF(_UpdatedDate IS NULL,'%',CONCAT('%',CAST(_UpdatedDate AS VARCHAR(50),'%')))
AND
CreatedUserID LIKE IF(_CreatedUserID IS NULL,'%',CONCAT('%',CAST(_CreatedUserID AS VARCHAR(50),'%')))
AND
UpdatedUserID LIKE IF(_UpdatedUserID IS NULL,'%',CONCAT('%',CAST(_UpdatedUserID AS VARCHAR(50),'%')))
AND
Status LIKE IF(_Status IS NULL,'%',CAST(_Status AS VARCHAR(50),'%'))
END