【问题标题】:How do a Skip a Row With SQL Cursor without a Temp Table如何在没有临时表的情况下使用 SQL 游标跳过一行
【发布时间】:2021-12-17 20:45:02
【问题描述】:

我在 SQL 中有一个包含以下列的大表:

CompanyId(int) Email(Varchar 255) First_Name(Varchar 50) Last_Name(Varchar 50)
1 jim_halpert@dundermifflin.com Jim Halpert
2 bvance@vancerefridgerations.com Bob Vance
1 michael_scott@dundermifflin.com Michael Scott

CompanyId 可以重复多次,因为公司附加到各种电子邮件中。

我的雇主希望我找到属于特定类型的公司电子邮件。在几个 IF 语句之后,最终结果将打印如下消息:

“CompanyId”有 firstName_lastName@companyName.domain 类型的电子邮件

“CompanyId”具有 firstInitial_lastName@companyName.domain 类型的电子邮件

我的雇主告诉我使用光标找到我的解决方案,但是一旦我启动光标,我需要检查 CompanyId 以查看该 id 是否已被循环并找到类型。如果 CompanyId 已经经历了循环,我想跳过它。

这是我目前的代码

      DECLARE @CoId INT
            ,@Email VARCHAR(255)
            ,@FName varchar(50)
            ,@LName varchar(50)
        Declare @condition bit = 1 
    
    Declare CoCursor CURSOR
    For SELECT E.CompanyID, E.Email, P.First_Name, P.Last_Name 
    From Table_Email as E
    Left Join Table_People as P
    ON E.EmployeeID = P.EmployeeID
    Order by CompanyID, Email
    
    -- Loop through the rows with Cursors for CompanyId and Email
    
    OPEN CoCursor
        Fetch NEXT FROM CoCursor
            INTO @CoId, @Email, @FName, @LName
    
        While @@FETCH_STATUS = 0
            BEGIN
            -- Check to see if CompanyId has been logged before, if not, proceed
                -- Check to see if email matches criteria
                IF @Email LIKE @FName + '[_]' + @LName + '@%'
                    BEGIN
                -- If yes, check next email where CompanyIds match
                    ;WITH EmailTable(CompanyID, Email, First_Name, Last_Name) AS (
                         SELECT E.CompanyID, E.Email, P.First_Name, P.Last_Name 
                         From Table_Emails as E
                         Left Table_People as P
                         ON E.EmployeeID = P.EmployeeID
                         Where E.CompanyID = @CoId 
                         And E.Email LIKE P.First_Name + '[_]' + P.Last_Name + '@%')
                    Select TOP(2) @condition = 0
                    FROM EmailTable         
                    Having COUNT(*) > 1
                    -- If email matches previous email, log Company Id with the email type
                    IF @condition = 0
                    PRINT CONVERT(VARCHAR(50), @CoId) + ' has email like firstName_lastName@CompanyAddress.domain' 
                -- If not, go to next Email check                           
                    END
                IF @Email LIKE LEFT(@FName,1) + @LName + '@%'
                -- If yes, check next email where CompanyIds match
                -- If email matches previous email, log Company Id with the email type
        -- If it has been logged move onto next record.
            Fetch NEXT FROM CoCursor
                INTO @CoId, @Email, @FName, @LName
        -- Once the result is logged go to next row 
            END         
    CLOSE CoCursor
    DEALLOCATE CoCursor

我看到的一个解决方案使用临时表来记录以前使用过的 ID,但我的雇主不希望我使用临时表。 How Can I Skip a row(an iteration) in MSSQL Cursor based on some condition?

谢谢!

【问题讨论】:

  • 为什么不首先将每家公司的 1 行放入 CURSOR 中?虽然我一开始就告诉你CURSOR 的原因是我无法理解的。
  • ;WITH 分号是一个语句终止符,而不是“开始符”。为所有语句使用终止符,您不需要这些杂物。

标签: sql sql-server database-cursor


【解决方案1】:

游标速度慢且效率低,很少需要。

这根本不需要光标。一个简单的带有group by 的过滤连接就足够了

SELECT
  CONCAT(e.CompanyID), ' has email like firstName_lastName@CompanyAddress.domain')
FROM Table_Email e
JOIN Table_People p ON p.EmployeeID = e.EmployeeID
WHERE e.Email LIKE p.First_Name + '[_]' + p.Last_Name + '@%'
GROUP BY
  e.CompanyID
ORDER BY
  e.CompanyID;

显然,出于某种非常奇怪的原因,您的雇主强制使用光标,我相信您会告诉他们所有不使用光标的原因。但不管怎样,你去吧:

您的原始代码仍然很复杂,您可以将上面的代码用作光标的SELECT 来简化它。

注意游标使用局部变量,这意味着您不必释放它

DECLARE @CompanyID int;

DECLARE @crsr CURSOR;
SET @crsr = CURSOR FAST_FORWARD FOR
SELECT
  e.CompanyID
FROM Table_Email e
JOIN Table_People p ON p.EmployeeID = e.EmployeeID
WHERE e.Email LIKE p.First_Name + '[_]' + p.Last_Name + '@%'
GROUP BY
  e.CompanyID
ORDER BY
  e.CompanyID;

OPEN @crsr;

FETCH NEXT FROM @crsr
  INTO @CompanyID;

WHILE @@FETCH_STATUS = 0
BEGIN
    PRINT CONCAT(e.CompanyID), ' has email like firstName_lastName@CompanyAddress.domain');

    FETCH NEXT FROM @crsr
      INTO @CompanyID;
END;

CLOSE @crsr;

【讨论】:

  • 尽管我不想使用光标,但我的雇主告诉我这样做。
  • 雇主并不总是最了解,@morganherg。我经常不让我的经理告诉我应该如何实现需求,因为我比他们更了解 SQL Server。优秀的员工知道何时质疑意图并拒绝它。
  • 否则我们总是可以用古老的比喻:如果你的雇主告诉你解决办法是走进交通,你会吗,@morganherg? (我希望不会。)
  • 雇主和我的高级开发人员一样。我仍在学习高级 SQL 查询的技巧,他们希望我使用 while 循环或游标来学习位模块化。尽管每个人的答案都有意义,但我还是被光标困住了。
  • 好的,我也将它转换为光标。模块化与while循环和游标无关,首先了解游标是何时或是否使用它们
【解决方案2】:

我不太喜欢游标,所以我将提出另一种可能的解决方案(我认为这是一个更好的解决方案,尽管您的雇主出于某种奇怪的原因想要一个游标)。

您的样本数据:

create table Table_Email (
    CompanyID int, 
    EmployeeID int,
    Email varchar(255)
)

create table Table_People (
    CompanyID int, 
    EmployeeID int,
    First_Name varchar(50),
    Last_Name varchar(50),
)

insert into Table_Email values (1, 1, 'jim_halpert@dundermifflin.com')
insert into Table_Email values (2, 2, 'bvance@vancerefridgerations.com')
insert into Table_Email values (1, 3, 'michael_scott@dundermifflin.com')

insert into Table_People values (1, 1, 'Jim', 'Halpert')
insert into Table_People values (2, 2, 'Bob', 'Vance')
insert into Table_People values (1, 3, 'Michael', 'Scott')

我提出的查询:

SELECT CONVERT(VARCHAR(50), CompanyID) + ' has email like firstName_lastName@CompanyAddress.domain' FROM 
(
 SELECT E.CompanyID, E.Email, P.First_Name, P.Last_Name 
 From Table_Email as E Left Join Table_People as P 
 ON E.EmployeeID = P.EmployeeID 
 WHERE Email LIKE P.First_Name + '[_]' + P.Last_Name + '@%' 
) AS a
group by CompanyID
order by CompanyID

返回:

1 has email like firstName_lastName@CompanyAddress.domain

SQL 小提琴:http://sqlfiddle.com/#!18/4c30e8/1

【讨论】:

    【解决方案3】:

    使光标中的 CompanyId 不同

    Declare CoCursor CURSOR
    For Select CompanyID, 
                Email, 
                First_Name, 
                Last_Name
    FROM (SELECT E.CompanyID, 
                E.Email, 
                P.First_Name, 
                P.Last_Name, 
                ROW_NUMBER() OVER(PARTITION BY E.CompanyID ORDER BY E.Email ASC) as RN
    From Table_Emails as E
    Left Table_People as P
    ON E.EmployeeID = P.EmployeeID) as T
    Where RN = 1
    Order by CompanyID
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-31
      • 2021-08-24
      • 2011-12-06
      • 1970-01-01
      • 2010-09-08
      相关资源
      最近更新 更多