【问题标题】:What is wrong with this sql innerjoin?这个 sql 内连接有什么问题?
【发布时间】:2010-02-26 11:37:06
【问题描述】:

这是我的两个表的内部连接的选择语句,

if not exists(select EmpId from SalaryDetails 
              where EmpId in (select Emp_Id 
                              from Employee where Desig_Id=@CategoryId))
begin
  // some statements here
end
else
begin
  SELECT e.Emp_Id, e.Identity_No, e.Emp_Name,
      case WHEN e.SalaryBasis=1 THEN 'Weekly' 
           ELSE 'Monthly' end as SalaryBasis,e.FixedSalary,
      (SELECT TOP 1 RemainingAdvance 
       FROM SalaryDetails 
       ORDER BY CreatedDate DESC) as Advance
    FROM Employee as e inner join Designation as d on e.Desig_Id=d.Desig_Id 
    INNER JOIN SalaryDetails as S on e.Emp_Id=S.EmpId 
End

我的结果窗格,

alt text http://img220.imageshack.us/img220/7774/resultpane.jpg

还有我的 SalaryDetails 表,

alt text http://img28.imageshack.us/img28/770/salarydettable.jpg

编辑: 我的输出必须是,

16 CR14 Natarajan Weekly 150.00 354.00
17 cr12333 盘点周刊 122.00 0.00

【问题讨论】:

  • 如果您向我们提供有关您的输出有什么问题的线索,这可能会有所帮助。我想不通,也不愿意。
  • @Lieven,正要发表同样的评论——我猜测他的 Advance 总是得到 354,而他在 SalaryDetails 中的值不同——我可能会离题,不过。
  • 你是对的,但这与内部连接无关。
  • 所以 if not exists 语句与它无关。显然,这两个用户都存在,因为您要让他们重新出现在结果中 - 问题应该是为什么您会得到重复的记录,当然?在这种情况下,我会对您说,您的 SalaryDetails 中有员工 16 和 17 的多条记录,但您没有过滤或分组它们以获取每个员工的一条记录,因此 SQL 会为您提供所有记录。您需要过滤或分组。
  • 嗯,看你的数据,前面加上DISTINCT,实现Andy给出的解决方案。

标签: sql-server-2005 inner-join


【解决方案1】:

您没有过滤任何员工 ID 上的子查询 (SELECT TOP 1 RemainingAdvance FROM SalaryDetails ORDER BY CreatedDate DESC),因此当按 CreatedDate DESC 排序时,它会为您提供整个表中的第一条记录(我猜是 354。)

您可能希望将该表表达式移动到您的 FROM 子句中,而不是您的 SELECT 中,包括您的员工 ID,然后对该表达式进行连接。

SELECT 
    e.Emp_Id,e.Identity_No,e.Emp_Name,case WHEN e.SalaryBasis=1 THEN 'Weekly' ELSE 'Monthly' end as SalaryBasis,e.FixedSalary,
    from Employee as e inner join Designation as d on e.Desig_Id=d.Desig_Id 
    inner join SalaryDetails as S on e.Emp_Id=S.EmpId 
    inner join
    (SELECT EmpID, RemainingAdvance, RANK() OVER (PARTITION BY EmpID ORDER BY CreatedDate DESC) AS SalaryRank FROM SalaryDetails ORDER BY CreatedDate DESC) as Advance ON Advance.EmpID = e.Emp_ID AND Advance.SalaryRank = 1

这只是我的想法,因此可能需要进行一些调整才能正确运行。还要注意 RANK() 函数的使用——如果你使用 TOP 1,你只会得到整个表的第一条记录。您需要的是每个员工 ID 的第一条记录。

如果是我,我可能会将该表表达式设为视图,甚至是一个标量值函数,获取您的员工 ID 并返回第一个 RemainingAdvance 值,然后您可以使用 TOP 1 并过滤员工 ID。

【讨论】:

  • @Andy 看看我的 if not exist 声明它会返回两个员工 ID 的 16,17
  • +1 鉴于不知道实际出了什么问题,我猜这是最有根据的猜测。
  • 我没有在回答中提供观点。您可以通过将表表达式 (SELECT...FROM Advance) 移动到视图中来创建一个视图,然后将其连接起来,就像它是一个表一样。抱歉,我们是来提出建议的,而不是为您工作。
  • 这是我的建议 - 将创建视图的代码放在我的答案中会给你答案;-)
【解决方案2】:

看起来您对 Designation 的联接甚至没有使用,而且您还缺少在顶部的 IF 语句中使用的 WHERE 子句。就像安迪指出的那样,我还将子查询向下移动到连接中。如果没有数据库对此进行测试可能并不准确,但我会将其重写为类似的东西;

SELECT e.Emp_Id, e.Identity_No, e.Emp_Name,
  case WHEN e.SalaryBasis=1 
       THEN 'Weekly' 
       ELSE 'Monthly' end as SalaryBasis,
  e.FixedSalary,S.RemainingAdvance as Advance
FROM Employee as e 
  INNER JOIN (
   SELECT TOP 1 EmpId, RemainingAdvance 
   FROM SalaryDetails 
   ORDER BY CreatedDate DESC) as S on e.Emp_Id=S.EmpId 
WHERE e.Desig_Id=@CategoryId

Andy 将子查询移动到视图中的建议是一个不错的建议,如果数据库很大,则更易于阅读并且可能更有效。

编辑:(回答)

(SELECT sd.empid,
               sd.remainingadvance,
               ROW_NUMBER() OVER (PARTITION BY sd.empid ORDER BY sd.createddate DESC) AS rank
          FROM SALARYDETAILS sd
          JOIN EMPLOYEE e ON e.emp_id = sd.empid
                         AND e.desig_id = @CategoryId) s
            WHERE s.rank = 1

我编辑了 jay 的答案,因为他接近我的输出...

【讨论】:

  • 提示 Jay,您在子查询中的 TOP 1 将在连接中的 EmpID 之前应用,所以如果您在 SalaryDetails 中有 2 条记录,其中 EmpID 为 16 和 17,排序时第 16 条通过 CreatedDesc,并且您的联接的 e.Emp_Id 为 17,它与 SELECT TOP 1 ... 查询不匹配。我以前也犯过这个错误——实际上直到大约 6 个月后才注意到,哎呀!您必须使用 RANK() 来获得每个员工 ID 的 TOP 1。
  • @Jay13 它对我有用...但我只得到一行...。我的 categoryId 给出了两个 empId 16,17...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
  • 2019-07-21
  • 2016-06-28
  • 2010-12-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多