在符合条件的行之后选择三行

时间:2014-07-18 15:52:07

标签: sql sql-server database

我们有7个人在波兰旅行(;))。问题是他们访问华沙时最多可以找到他们访问过的三个城市。如果一个人两次访问华沙,它也被视为下一次旅行的起点。 例如,家伙1不仅有一次旅行 - 华沙​​,克拉科夫,华沙,格但斯克,还有华沙,格但斯克。

表A

+------+-----------+-----+
| date |   city    | guy |
+------+-----------+-----+
|    2 | Warsaw    |   1 |
|    4 | Cracow    |   1 |
|    5 | Cracow    |   2 |
|    6 | Bialystok |   3 |
|    7 | Warsaw    |   1 |
|    8 | Gdansk    |   1 |
|   10 | Warsaw    |   5 |
|   12 | Cracow    |   5 |
|   14 | Bialystok |   6 |
|   15 | Warsaw    |   7 |
|   20 | Warsaw    |   7 |
+------+-----------+-----+

所以最终的表格看起来像这样:

+-----------+-----------+-----------+-----------+
| Starting | 2nd dest. | 3th dest. | 4th dest. |
+-----------+-----------+-----------+-----------+
| Warsaw    | Cracow    | Warsaw    | Gdansk    |
| Warsaw    | Gdansk    |           |           |
| Warsaw    | Cracow    |           |           |
| Warsaw    | Warsaw    |           |           |
| Warsaw    |           |           |           |
+-----------+-----------+-----------+-----------+

问题是创建一个将自动从表A创建最终表的查询。

找到每个起点没有问题,但我不知道如何找到每个第二个目的地。我似乎也必须有某种循环 - 这个人必须与起点相同,而第二个目的地的日期必须大于此确切起点的日期。

任何帮助解决这个问题将不胜感激。 ;)

SQLFiddle以及更多示例条目数据 - http://sqlfiddle.com/#!2/de0f1 上面的数据只是一个样本,解决方案需要处理更大的集合。

3 个答案:

答案 0 :(得分:1)

使用Row_Number()Pivot的解决方案:

select guy,[1] as First, IsNULL([2], '') as Second, IsNUll([3], '') as Third, IsNull([4], '') as Forth
from (
    select row_number() over (partition by guy order by guy, date) as number, city, guy
     from voyage 
     ) x
pivot 
(
  max(city)
  for number in ([1], [2], [3], [4])
) as p

Demo here

我的例子假设日期是int类型,但是这可以很容易地重新设计以使用正确的日期....

编辑:

这将提供所需的输出(不确定这将是一般方法......):

select * 
from 
(
select guy,[1] as First,  IsNULL([2], '')  as Second, IsNUll([3], '') as Third, IsNull([4], '') as Forth
from (
select row_number() over (partition by guy order by guy, date) as number, city, guy
from voyage ) x
pivot 
(
max(city)
  for number in ([1], [2], [3], [4])
) as p
where  [1] = 'Warsaw'

union All

select guy,[3] as First, case when [4] = 'Warsaw' then '' else IsNULL([4], '') end as Second, '' as Third, '' as Forth
from (
select row_number() over (partition by guy order by guy, date) as number, city, guy
from voyage ) x
pivot 
(
max(city)
  for number in ([1], [2], [3], [4])
) as p
where  [3] = 'Warsaw'

Union all


select guy,[2] as First, '' as Second, '' as Third, '' as Forth
from (
select row_number() over (partition by guy order by guy, date) as number, city, guy
from voyage ) x
pivot 
(
max(city)
  for number in ([1], [2], [3], [4])
) as p
where  [2] = 'Warsaw'  
  ) bigdata
order by guy

Second Demo Here

答案 1 :(得分:1)

如果您使用的是SQL Server 2012或更高版本,则可以在solve the problem的帮助下轻松LEAD() analytic function

WITH ThreeDestinations AS (
  SELECT
    *,
    Destination2 = LEAD(city, 1) OVER (PARTITION BY guy ORDER BY date),
    Destination3 = LEAD(city, 2) OVER (PARTITION BY guy ORDER BY date),
    Destination4 = LEAD(city, 3) OVER (PARTITION BY guy ORDER BY date)
  FROM
    dbo.voyage
)
SELECT
  StartingPoint = city,
  Destination2,
  Destination3,
  Destination4
FROM
  ThreeDestinations
WHERE
  city = 'Warsaw'
ORDER BY
  date
;

对LEAD的三次调用会为您提供原始集合中每个城市之后的前三个(或更少)目的地。下一步,也是最后一步,只是过滤掉起点不是华沙的行。

答案 2 :(得分:0)

这应该有效。我用Cte's来查询一个来自华沙的人的第一次旅行以及来自华沙的第二次旅行。最后使用Pivot以请求的格式联合并显示结果集。希望这很有用。

;WITH cte_Itinerary([Date], City, Guy)
    AS (SELECT '2', 'Warsaw', '1'
        UNION ALL
        SELECT '4', 'Cracow', '1'
        UNION ALL
        SELECT '5', 'Cracow', '2'
        UNION ALL
        SELECT '6', 'Bialystok', '3'
        UNION ALL
        SELECT '7', 'Warsaw', '1'
        UNION ALL
        SELECT '8', 'Gdansk', '1'
        UNION ALL
        SELECT '10', 'Warsaw', '5'
        UNION ALL
        SELECT '12', 'Cracow', '5'
        UNION ALL
        SELECT '14', 'Bialystok ', '6'
        UNION ALL
        SELECT '15', 'Warsaw', '7'
        UNION ALL
        SELECT '20', 'Warsaw', '7'
    )
, cte_AddRowNumber
    AS (
    SELECT
           ROW_NUMBER() OVER (PARTITION BY Guy ORDER BY Guy, [Date]) AS number,
           City,
           Guy
    FROM cte_Itinerary
    )
, cte_FilterByWarsaw
    AS (
    SELECT
           ROW_NUMBER() OVER (PARTITION BY Guy ORDER BY Guy, [Date]) AS number,
           City,
           Guy
    FROM cte_Itinerary
    WHERE City = 'Warsaw'
    )
, cte_Result_AllDestinations
    AS (
    SELECT DISTINCT
           'One' AS ResultType,
           o.Number,
           o.City,
           o.Guy
    FROM cte_AddRowNumber AS o
         JOIN cte_FilterByWarsaw AS t
             ON o.Guy = t.Guy
            AND t.number <= o.number
    )
, cte_Result_SecondDestinations
    AS (
    SELECT 'Two' AS ResultType,
           ROW_NUMBER() OVER (PARTITION BY r1.Guy ORDER BY r1.Guy) AS number,
           r1.City,
           r1.Guy
    FROM cte_Result_AllDestinations AS r1
         JOIN cte_Result_AllDestinations AS r2
             ON r1.Guy = r2.Guy
            AND r1.number >= r2.number
    WHERE r2.City = 'Warsaw'
      AND r2.number <> 1
    )
    SELECT Guy,
           [1] AS Starting,
           ISNULL([2], '') AS [2nd Dest.],
           ISNULL([3], '') AS [3th Dest.],
           ISNULL([4], '') AS [4th Dest.]
    FROM(SELECT ResultType,Number,City,Guy
         FROM cte_Result_AllDestinations
         UNION ALL
         SELECT ResultType,Number,City,Guy
         FROM cte_Result_SecondDestinations)AS X 
         PIVOT(MAX(City)FOR NUMBER IN([1], [2], [3], [4]))AS P;