【问题标题】:How to construct query to change the output of an sql table如何构造查询以更改 sql 表的输出
【发布时间】:2016-12-19 10:46:02
【问题描述】:

我需要一些帮助...所以,让我向您介绍我的问题。

我有以下 SQL Server 表:

| RankCode | SeaPortInd | WatchKeepingInd |      EffectiveDate      | VesselCode |        FromDate         |         ToDate          |        LastDate         | LastUser |
+----------+------------+-----------------+-------------------------+------------+-------------------------+-------------------------+-------------------------+----------+
| C/E      |          0 |               0 | 1900-01-01 00:00:00.000 |        031 | 1900-01-01 05:00:00.000 | 1900-01-01 07:00:00.000 | 2016-08-11 12:40:00.000 | d.baltas |
| C/E      |          0 |               0 | 2016-06-02 00:00:00.000 |        031 | 1900-01-01 00:00:00.000 | 1900-01-01 00:00:00.000 | 1900-01-01 00:00:00.000 | d.baltas |
| C/E      |          0 |               1 | 2016-06-01 00:00:00.000 |        031 | 1900-01-01 01:00:00.000 | 1900-01-01 02:00:00.000 | 2016-08-11 17:58:00.000 | d.baltas |
| C/E      |          0 |               1 | 2016-06-02 00:00:00.000 |        031 | 1900-01-01 01:00:00.000 | 1900-01-01 02:00:00.000 | 2016-08-10 17:58:00.000 | d.baltas |
| C/E      |          1 |               1 | 2016-06-01 00:00:00.000 |        031 | 1900-01-01 03:00:00.000 | 1900-01-01 04:00:00.000 | 2016-08-10 17:58:00.000 | d.baltas |
| MSTR     |          0 |               0 | 2016-06-02 00:00:00.000 |        031 | 1900-01-01 16:00:00.000 | 1900-01-01 22:00:00.000 | 2016-08-10 17:58:00.000 | d.baltas |
| MSTR     |          0 |               1 | 2016-06-01 00:00:00.000 |        031 | 1900-01-01 08:00:00.000 | 1900-01-01 12:00:00.000 | 2016-08-10 17:58:00.000 | d.baltas |
| MSTR     |          1 |               0 | 2016-06-03 00:00:00.000 |        031 | 1900-01-01 08:00:00.000 | 1900-01-01 14:00:00.000 | 2016-08-11 15:00:00.000 | d.baltas |
+----------+------------+-----------------+-------------------------+------------+-------------------------+-------------------------+-------------------------+----------+

我想得到如下表的输出:

表格的更多解释:

计划的每日海上工作时间意味着 SeaPortInd = 1

在港口安排的每日工作时间意味着 SeaPortInd = 0

Watchkeeping 意味着 WatchkeepingInd = 1

NonWatchkeeping 表示 WatchkeepingInd = 0

我设法拿到了下表:

+----------+--------------------+
| RankCode | SeaNonWatchkeeping |
| C/E      |  00:00 - 00:00     |
|          |  05:00 - 07:00     |
| MSTR     |  16:00 - 22:00     |
+----------+--------------------+

用查询:

SELECT CASE 
        WHEN row_number() OVER (
                PARTITION BY RankCode ORDER BY FromDate asc
    ) = 1
            THEN RankCode
        ELSE ''
        END AS RankCode

    ,substring(convert(VARCHAR(255), FromDate, 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), ToDate, 120), 11, 6) AS SeaNonWatchkeeping

FROM WorkingHoursSchedule WHERE SeaPortInd = 0 AND watchkeepingind = 0

您能帮我获取 SeaportInd = 0 和 Watchkeeping= 1 等案例吗?

我使用的是 SQL Server 2008,但查询也将在一些以前的版本中运行,最低版本为 SQL Server 2005

提前致谢!

【问题讨论】:

    标签: sql sql-server sql-server-2008 tsql sql-server-2005


    【解决方案1】:

    这个应该可以的:

    WITH WorkingHoursSchedule AS
    (
        SELECT * FROM (VALUES
        ('C/E ', 0, 0, '1900-01-01 00:00:00.000', '031', '1900-01-01 05:00:00.000', '1900-01-01 07:00:00.000', '2016-08-11 12:40:00.000', 'd.baltas'),
        ('C/E ', 0, 0, '2016-06-02 00:00:00.000', '031', '1900-01-01 00:00:00.000', '1900-01-01 00:00:00.000', '1900-01-01 00:00:00.000', 'd.baltas'),
        ('C/E ', 0, 1, '2016-06-01 00:00:00.000', '031', '1900-01-01 01:00:00.000', '1900-01-01 02:00:00.000', '2016-08-11 17:58:00.000', 'd.baltas'),
        ('C/E ', 0, 1, '2016-06-02 00:00:00.000', '031', '1900-01-01 01:00:00.000', '1900-01-01 02:00:00.000', '2016-08-10 17:58:00.000', 'd.baltas'),
        ('C/E ', 1, 1, '2016-06-01 00:00:00.000', '031', '1900-01-01 03:00:00.000', '1900-01-01 04:00:00.000', '2016-08-10 17:58:00.000', 'd.baltas'),
        ('MSTR', 0, 0, '2016-06-02 00:00:00.000', '031', '1900-01-01 16:00:00.000', '1900-01-01 22:00:00.000', '2016-08-10 17:58:00.000', 'd.baltas'),
        ('MSTR', 0, 1, '2016-06-01 00:00:00.000', '031', '1900-01-01 08:00:00.000', '1900-01-01 12:00:00.000', '2016-08-10 17:58:00.000', 'd.baltas'),
        ('MSTR', 1, 0, '2016-06-03 00:00:00.000', '031', '1900-01-01 08:00:00.000', '1900-01-01 14:00:00.000', '2016-08-11 15:00:00.000', 'd.baltas')
        )T(RankCode, SeaPortInd, WatchKeepingInd, EffectiveDate, VesselCode, FromDate, ToDate, LastDate, LastUser)
    ), Src AS
    (
        SELECT ROW_NUMBER() OVER (PARTITION BY RankCode ORDER BY RankCode, SeaPortInd, WatchKeepingInd) RN, RankCode
        FROM WorkingHoursSchedule
    ), SeaWatchKeeping AS
    (
        SELECT ROW_NUMBER() OVER (PARTITION BY RankCode ORDER BY FromDate) RN, RankCode,
            SUBSTRING(CONVERT(VARCHAR(255), FromDate, 120), 12, 5) + ' - ' + SUBSTRING(CONVERT(VARCHAR(255), ToDate, 120), 12, 5) SeaWatchKeeping
        FROM WorkingHoursSchedule
        WHERE SeaPortInd = 0 AND WatchKeepingInd = 1
    ), SeaNonWatchKeeping AS
    (
        SELECT ROW_NUMBER() OVER (PARTITION BY RankCode ORDER BY FromDate) RN, RankCode,
            SUBSTRING(CONVERT(VARCHAR(255), FromDate, 120), 12, 5) + ' - ' + SUBSTRING(CONVERT(VARCHAR(255), ToDate, 120), 12, 5) SeaNonWatchKeeping
        FROM WorkingHoursSchedule
        WHERE SeaPortInd = 0 AND WatchKeepingInd = 0
    ), LandWatchKeeping AS
    (
        SELECT ROW_NUMBER() OVER (PARTITION BY RankCode ORDER BY FromDate) RN, RankCode,
            SUBSTRING(CONVERT(VARCHAR(255), FromDate, 120), 12, 5) + ' - ' + SUBSTRING(CONVERT(VARCHAR(255), ToDate, 120), 12, 5) LandWatchKeeping
        FROM WorkingHoursSchedule
        WHERE SeaPortInd = 1 AND WatchKeepingInd = 1
    ), LandNonWatchKeeping AS
    (
        SELECT ROW_NUMBER() OVER (PARTITION BY RankCode ORDER BY FromDate) RN, RankCode,
            SUBSTRING(CONVERT(VARCHAR(255), FromDate, 120), 12, 5) + ' - ' + SUBSTRING(CONVERT(VARCHAR(255), ToDate, 120), 12, 5) LandNonWatchKeeping
        FROM WorkingHoursSchedule
        WHERE SeaPortInd = 1 AND WatchKeepingInd = 0
    )
    SELECT CASE WHEN S.RN=1 THEN S.RankCode ELSE NULL END RankCode, SeaWatchKeeping, SeaNonWatchKeeping, LandWatchKeeping, LandNonWatchKeeping
    FROM Src S
    LEFT JOIN SeaNonWatchKeeping SN ON S.RN=SN.RN AND S.RankCode=SN.RankCode
    LEFT JOIN SeaWatchKeeping SW ON S.RN=SW.RN AND S.RankCode=SW.RankCode
    LEFT JOIN LandNonWatchKeeping LN ON S.RN=LN.RN AND S.RankCode=LN.RankCode
    LEFT JOIN LandWatchKeeping LW ON S.RN=LW.RN AND S.RankCode=LW.RankCode
    WHERE SeaWatchKeeping IS NOT NULL OR SeaNonWatchKeeping IS NOT NULL OR LandWatchKeeping IS NOT NULL OR LandNonWatchKeeping IS NOT NULL
    

    根据RankCode占用的空间折叠行并按FromDate排序:

    RankCode SeaWatchKeeping SeaNonWatchKeeping LandWatchKeeping LandNonWatchKeeping
    -------- --------------- ------------------ ---------------- -------------------
    C/E      01:00 - 02:00   00:00 - 00:00      03:00 - 04:00    NULL
    NULL     01:00 - 02:00   05:00 - 07:00      NULL             NULL
    MSTR     08:00 - 12:00   16:00 - 22:00      NULL             08:00 - 14:00
    

    【讨论】:

    • 我认为你的陆地/海上标志是倒退的 :-)
    【解决方案2】:

    以下 SQL 使用 CASE 表达式根据您的要求进行评估。

    SELECT  [RankCode]
        ,   CASE
                WHEN [SeaPortInd] = 1 AND [WatchKeepingInd] = 1 THEN substring(convert(VARCHAR(255), [FromDate], 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), [ToDate], 120), 11, 6)
            END as 'At Sea: Watchkeeping (from...to)'
        ,   CASE
                WHEN [SeaPortInd] = 1 AND [WatchKeepingInd] = 0 THEN substring(convert(VARCHAR(255), [FromDate], 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), [ToDate], 120), 11, 6)
            END as 'At Sea: Non-Watchkeeping duties (from...to)'    
        ,   CASE
                WHEN [SeaPortInd] = 0 AND [WatchKeepingInd] = 1 THEN substring(convert(VARCHAR(255), [FromDate], 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), [ToDate], 120), 11, 6)
            END as 'In Port: Watchkeeping (from...to)'
        ,   CASE
                WHEN [SeaPortInd] = 0 AND [WatchKeepingInd] = 0 THEN substring(convert(VARCHAR(255), [FromDate], 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), [ToDate], 120), 11, 6)
            END as 'In Port: Non-Watchkeeping duties (from...to)'   
      FROM [dbo].[WorkingHoursSchedule]
    

    结果如下:

    【讨论】:

      【解决方案3】:

      我假设每艘船都有自己的时间表,并允许一次检索多个船舶时间表。此解决方案使用计数表来创建插槽。

      --original data
      declare @WorkingHoursSchedule table (
              Ident           int identity(1,1),
              RankCode        varchar(5),
              SeaPortInd      bit,
              WatchKeepingInd bit,
              EffectiveDate   datetime,
              VesselCode      varchar(5),
              FromDate        datetime,
              ToDate          datetime,
              LastDate        datetime,
              LastUser        varchar(128));
      
      insert @WorkingHoursSchedule values
      ('C/E',0,0,'1900-01-01 00:00:00.000','031','1900-01-01 05:00:00.000','1900-01-01 07:00:00.000','2016-08-11 12:40:00.000','d.baltas'),
      ('C/E',0,0,'2016-06-02 00:00:00.000','031','1900-01-01 00:00:00.000','1900-01-01 00:00:00.000','1900-01-01 00:00:00.000','d.baltas'),
      ('C/E',0,1,'2016-06-01 00:00:00.000','031','1900-01-01 01:00:00.000','1900-01-01 02:00:00.000','2016-08-11 17:58:00.000','d.baltas'),
      ('C/E',0,1,'2016-06-02 00:00:00.000','031','1900-01-01 01:00:00.000','1900-01-01 02:00:00.000','2016-08-10 17:58:00.000','d.baltas'),
      ('C/E',1,1,'2016-06-01 00:00:00.000','031','1900-01-01 03:00:00.000','1900-01-01 04:00:00.000','2016-08-10 17:58:00.000','d.baltas'),
      ('MSTR',0,0,'2016-06-02 00:00:00.000','031','1900-01-01 16:00:00.000','1900-01-01 22:00:00.000','2016-08-10 17:58:00.000','d.baltas'),
      ('MSTR',0,1,'2016-06-01 00:00:00.000','031','1900-01-01 08:00:00.000','1900-01-01 12:00:00.000','2016-08-10 17:58:00.000','d.baltas'),
      ('MSTR',1,0,'2016-06-03 00:00:00.000','031','1900-01-01 08:00:00.000','1900-01-01 14:00:00.000','2016-08-11 15:00:00.000','d.baltas');
      
      --create and populate tally table if you don't already a permanent one - arbitrary 1000 rows for demo...you should figure out if that is enough
      declare @Tally table (N int PRIMARY KEY);
      insert  @Tally
      select  top (1000) row_number() over (order by o1.object_id) from sys.columns o1, sys.columns o2 order by 1;
      --select    * from @Tally order by N;
      
      with ranked_slots_cte as (  --ranked slots by vessel/rank for each combination of indicator
              select  *, row_number() over (partition by VesselCode, RankCode, SeaPortInd, WatchKeepingInd order by FromDate) time_slot
              from    @WorkingHoursSchedule
              --where EffectiveDate <= getdate() and (LastDate is null or LastDate > getdate())   --it looks like this might be a good place to check these values
              --where VesselCode = '031'  --I assumed getting schedules for all vessels with grouping, but you could filter here instead.
      )
      select      max_slots.VesselCode, max_slots.RankCode, slots.N TimeSlot
                  , substring(convert(VARCHAR(255), r1.FromDate, 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), r1.ToDate, 120), 11, 6)
                  , substring(convert(VARCHAR(255), r2.FromDate, 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), r2.ToDate, 120), 11, 6)
                  , substring(convert(VARCHAR(255), r3.FromDate, 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), r3.ToDate, 120), 11, 6)
                  , substring(convert(VARCHAR(255), r4.FromDate, 120), 11, 6) + ' -' + substring(convert(VARCHAR(255), r4.ToDate, 120), 11, 6)
      from        (--total slots per vessel/rank
                  select  VesselCode, RankCode, max(time_slot) max_slot
                  from    ranked_slots_cte
                  group   by VesselCode, RankCode
                  --order by VesselCode, RankCode
                  ) max_slots
      join        @Tally slots    --create the appropriate number of slots per vessel/rank
                  on slots.N <= max_slots.max_slot
      --join each of the appropriate indicator combinations by ranked time slot
      left join   ranked_slots_cte r1     --At Sea/Watch
                  on  r1.VesselCode = max_slots.VesselCode 
                  and r1.RankCode = max_slots.RankCode
                  and r1.time_slot = slots.N
                  and r1.SeaPortInd = 1
                  and r1.WatchKeepingInd = 1
      left join   ranked_slots_cte r2     --At Sea/Not Watch
                  on  r2.VesselCode = max_slots.VesselCode 
                  and r2.RankCode = max_slots.RankCode
                  and r2.time_slot = slots.N
                  and r2.SeaPortInd = 1
                  and r2.WatchKeepingInd = 0
      left join   ranked_slots_cte r3     --In Port/Watch
                  on  r3.VesselCode = max_slots.VesselCode 
                  and r3.RankCode = max_slots.RankCode
                  and r3.time_slot = slots.N
                  and r3.SeaPortInd = 0
                  and r3.WatchKeepingInd = 1
      left join   ranked_slots_cte r4     --In Port/Not Watch
                  on  r4.VesselCode = max_slots.VesselCode 
                  and r4.RankCode = max_slots.RankCode
                  and r4.time_slot = slots.N
                  and r4.SeaPortInd = 0
                  and r4.WatchKeepingInd = 0
      order by    max_slots.VesselCode, max_slots.RankCode, slots.N;
      

      输出如下:

      031  C/E   1  03:00 - 04:00  NULL           01:00 - 02:00  00:00 - 00:00
      031  C/E   2  NULL           NULL           01:00 - 02:00  05:00 - 07:00
      031  MSTR  1  NULL           08:00 - 14:00  08:00 - 12:00  16:00 - 22:00
      

      【讨论】:

      • 感谢您的回答!我从@Paweł Dyl 那里看到了答案,这对我来说更简单。但我也喜欢你的回答,我想知道你是否可以在这里向我解释更多插槽的使用?感谢您的宝贵时间!
      • 从您的示例输出看来,船舶与陆地的时间表应该都是连续的时间段。如果在父表中没有某种方式来匹配哪个非观看时间段与哪个观看时间段(例如 step_num 或其他东西)相匹配,数据看起来就像它支持从位置 1 开始的时间段,用于每列中的每个等级。该解决方案计算出所有列中每个等级所需的最大时隙(max_slots 子查询)。然后它加入一个计数表以实际创建正确数量的插槽。其他联接拉入数据列值。
      • 海上 C/E 的示例输出图像中的当前数据看起来像(Watch、Non-Watch、Watch、Non-Watch、Watch、Watch、Watch)。如果您需要支持(Watch,Non-Watch,Watch,Watch,Non-Watch,Watch,Watch),那么我的解决方案将无法处理,Paweł Dyl's 也不会。您需要提供一种方法来指定非观看时间段的排列位置或进行一些更高级的时间匹配。
      • 即使您没有将其作为接受的答案,也请随意投票赞成这个答案:-) 另外,如果您确实像我之前的评论所怀疑的那样在非观看之间存在差距,请告诉我并我可以稍微修改一下。
      • 非值班时间始终是值班时间的子集。也没有标准的方法来匹配哪个非观看时段与观看相匹配。不过,我喜欢你的方法,我学到了一些新东西!!非常感谢!
      猜你喜欢
      • 2016-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多