76

我很抱歉,但这是一个由两部分组成的问题。

我对 SQL 非常陌生,正在尝试为我工作的小型办公室开发一个时钟应用程序。我现在正在使用 SQL 后端,并且有一个关于复合语句的问题。

我陷入困境的地方是,如果用户尝试打卡休息但从未在轮班开始时打卡,SQL 需要创建一个新行而不是更新现有行。

这是我尝试过的:

    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

我正在使用 Visual Studio 2010 连接到本地计算机上的 SQL Server Express 2008 来执行此操作。我收到一条错误消息,提示“不支持复合语句 SQL 构造或语句”。但是,紧随其后的是 1 行受到影响的消息,当我查看我的时钟表时,它看起来就像我期望的那样。完成这个的最好方法是什么?

这个问题的第二部分是在我的 WHERE 语句中。是否有一个函数可以在 clockDate 列中获取今天的日期,而不必填充今天的日期?只是想提前考虑构建前端应用程序。

    IF NOT EXISTS(SELECT * FROM Clock WHERE clockDate = { fn CURRENT_DATE() }) AND userName = 'test')

同样,这给了我想要的结果,但直到收到错误“'CURRENT_DATE'附近的 WHERE 子句出错。无法解析查询文本。”

我希望我已经正确解释了这一点,并感谢您的帮助!

编辑:

@RThomas @w00te

好的,所以将clockDate 作为日期字段并将breakOut 作为时间(0)字段,这应该有效吗?因为我仍然收到“不支持复合语句 SQL 构造或语句”。语法错误,即使它似乎正在工作。

    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

我的表格结果是:

clockDate  userName  clockIn  breakOut  breakIn  clockOut

08/10/2012  test      NULL    11:24:38   NULL     NULL

这是我想要的结果,但这个错误让我感到困惑。这是 Visual Studio 错误还是 SQL 错误?我会阅读合并声明,谢谢你们的链接。

4

4 回答 4

68

乍一看,您最初的尝试似乎非常接近。我假设 clockDate 是一个 DateTime 字段,所以试试这个:

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 

请注意,getdate 为您提供当前日期。如果您尝试与日期(没有时间)进行比较,则需要强制转换,否则时间元素将导致比较失败。


如果clockDate 不是日期时间字段(只是日期),那么 SQL 引擎会为您完成 - 无需强制执行 set/insert 语句。

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 

正如其他人所指出的,合并语句是处理相同逻辑的另一种方法。但是,在某些情况下,尤其是对于大型数据集,merge 语句可能会非常缓慢,从而导致大量 tran log 活动。因此,如上所示知道如何将其逻辑化仍然是一种有效的技术。

于 2012-08-10T17:36:28.663 回答
37

正如其他人建议您应该查看 MERGE 语句,但没有人提供使用它的解决方案,我正在使用这个特定的 TSQL 构造添加我自己的答案。我打赌你会喜欢的。

重要的提示

if您的代码部分在您的语句中有错字not exists(select...)。内部select语句只有一个条件,而 UserName 条件由于无效的大括号完成where而被排除在外。not exists在任何情况下,你都会有太多的右括号。

我假设这是基于您稍后在代码中的语句中使用两个where条件的事实。update

让我们继续我的回答...

SQL Server 2008+ 支持 MERGE 语句

MERGE 语句是一个漂亮的 TSQL gem,非常适合“插入或更新”情况。在您的情况下,它看起来类似于以下代码。考虑到我正在声明可能是存储过程参数的变量(我怀疑)。

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());
于 2014-02-28T10:39:31.943 回答
6
IF NOT EXISTS(SELECT * FROM Clock
WHERE clockDate = '08/10/2012') AND userName = 'test')

有一个额外的括号。我认为如果你删除它很好:

IF NOT EXISTS(SELECT * FROM Clock WHERE
clockDate = '08/10/2012' AND userName = 'test')

此外, GETDATE() 会将当前日期放在列中,但如果您不想要时间,您将不得不玩一点。我认为 CONVERT(varchar(8), GETDATE(), 112) 只会给你日期(而不是时间)部分。

IF NOT EXISTS(SELECT * FROM Clock WHERE
clockDate = CONVERT(varchar(8), GETDATE(), 112)
AND userName = 'test')

可能应该这样做。

PS:使用合并语句:)

于 2012-08-10T17:31:43.167 回答
2

您需要将其替换为WHERE clockDate = { fn CURRENT_DATE() } AND userName = 'test'. 请 ")"{ fn CURRENT_DATE() })

于 2012-08-10T17:35:54.943 回答