如何通过组合SQL表中的字段来生成以前的值表?

时间:2014-04-02 20:16:15

标签: sql sql-server

假设我在SQL Server数据库中有一些数据。

Location   PayID    Year
------------------------
Loc1        100     2010
Loc1        100     2011
Loc1        101     2012
Loc2        200     2010
Loc2        201     2011
Loc2        202     2012

我正在尝试在SQL Server中编写一个查询,该查询将为我提供一个包含两列的表,我可以搜索这些列以查找特定位置的先前PayID。所以输出就是。

PayID   PrevID
-----------------
101     100
202     201
201     200

当前一年的ID与当前年份不同时,它只需要一个条目,如果用户返回超过一年时我没有正确的匹配,我将递归查询它所以它将拉取每个以前的ID基于刚刚拉出的那个,直到找到与第一个表匹配的PayID和年份。

对此的任何帮助将不胜感激。如果我能找到它,我会专心搜索并发布解决方案。

4 个答案:

答案 0 :(得分:3)

这可以通过递归CTE轻松完成:

with cte as (
  select Location, PayID, PayID as PrevID, Year from payhistory
  union all
  select p.Location, p.PayID, cte.PayID as PrevID, p.Year
  from payhistory p
  join cte on cte.Location = p.Location and cte.Year + 1 = p.Year
)
select distinct Location, PayID, PrevID
from cte
where PayID <> PrevID;

以下是我得到的结果:

| LOCATION | PAYID | PREVID |
|----------|-------|--------|
|     Loc1 |   101 |    100 |
|     Loc2 |   201 |    200 |
|     Loc2 |   202 |    201 |

演示:http://www.sqlfiddle.com/#!3/e0ac0/4

答案 1 :(得分:1)

我没有看到指定的版本,所以我在2012年使用LAG。如果您想要更少的信息,可以过滤结果。您可以用LAG(PayID,1,PayID)替换LAG(PayID,1,NULL)来改变第一个payid的行为。

DECLARE @tbl TABLE (Location VARCHAR(4),   PayID INT,    Year INT)
INSERT INTO @tbl VALUES
 ('Loc1',100,2010)
,('Loc1',100,2011)
,('Loc1',101,2012)
,('Loc2',200,2010)
,('Loc2',201,2011)
,('Loc2',202,2012)

SELECT Location 
      ,PayID
      ,LAG(PayID,1,NULL) OVER (PARTITION BY Location ORDER BY Year ASC) PrevID
  FROM @tbl

http://www.sqlfiddle.com/#!6/e0ac0/2

答案 2 :(得分:0)

关键假设 - 此查询仅在每个位置的PayIds是连续数字时才有效。

select distinct l1.payId as PayId, 
l2.payId as PrevId
from locs as l1
inner join locs as l2
on l1.location = l2.location
and l1.payid = (l2.payid + 1)

答案 3 :(得分:0)

以下解决方案是非递归的,它可能会提供更好的性能:

DECLARE @Payment TABLE (
    ID          INT IDENTITY(1,1) PRIMARY KEY,
    Location    VARCHAR(50) NOT NULL,
    PayID       INT NOT NULL,
    [Year]      SMALLINT NOT NULL
);
INSERT @Payment
SELECT 'Loc1', 100, 2010
UNION ALL SELECT 'Loc1', 100, 2011
UNION ALL SELECT 'Loc1', 101, 2012
UNION ALL SELECT 'Loc2', 200, 2010
UNION ALL SELECT 'Loc2', 201, 2011
UNION ALL SELECT 'Loc2', 202, 2012


SELECT  z.Location, z.GroupID,
        MAX(CASE WHEN z.RowType = 1 THEN z.[Year] END)  AS CurrentYear,
        MAX(CASE WHEN z.RowType = 0 THEN z.[Year] END)  AS PreviousYear,
        MAX(CASE WHEN z.RowType = 1 THEN z.[PayID] END) AS CurrentPayID,
        MAX(CASE WHEN z.RowType = 0 THEN z.[PayID] END) AS PreviousPayID
FROM
(
    SELECT  y.PayID, y.[Location], y.[Year],
            -- It "groups" rows two by two: current row and previous row will have the same GroupID
            (ROW_NUMBER() OVER(PARTITION BY y.Location ORDER BY y.RowNum + n.Num ASC) + 1) / 2 AS GroupID,
            -- RowType: 1=Current row, 0=Previous row
            ROW_NUMBER() OVER(PARTITION BY y.Location ORDER BY y.RowNum + n.Num ASC) % 2 AS RowType 
    FROM 
    (
        SELECT  x.Location, x.[Year], x.PayID, ROW_NUMBER() OVER(PARTITION BY x.Location ORDER BY x.[Year] DESC) RowNum
        FROM    @Payment x
    ) y
    -- For every location, it duplicates every row except the last one
    INNER JOIN (VALUES (1), (2)) n(Num) ON y.RowNum = 1 AND n.Num = 1 OR y.RowNum > 1 
) z
GROUP BY z.Location, z.GroupID
HAVING  MAX(CASE WHEN z.RowType = 1 THEN z.[Year] END) = MAX(CASE WHEN z.RowType = 0 THEN z.[Year] END) + 1
AND     MAX(CASE WHEN z.RowType = 1 THEN z.[PayID] END) <> MAX(CASE WHEN z.RowType = 0 THEN z.[PayID] END)
ORDER BY z.Location;

输出:

Location  GroupID CurrentYear PreviousYear CurrentPayID PreviousPayID
--------- ------- ----------- ------------ ------------ -------------
Loc1      1       2012        2011         101          100
Loc2      1       2012        2011         202          201
Loc2      2       2011        2010         201          200