sql"命令链"

时间:2017-11-03 19:01:42

标签: sql sql-server

我认为这是一种需要的正常查询,但有时您必须知道问题的通用名称才能找到有关解决问题的更多信息。我称之为“指挥链”。问题

我有以下表结构:

using System;
using AppoMobi;
using AppoMobi.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using ListView = Xamarin.Forms.ListView;

[assembly: ExportRenderer(typeof(NiftyListView), typeof(NiftyListViewRenderer))]
namespace AppoMobi.Droid.Renderers
{
    //-------------------------------------------------------------------
    class NiftyListViewRenderer : ListViewRenderer
    //-------------------------------------------------------------------
    {
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);

            var view = (NiftyListView)Element;
            if (view == null) return;

            view.EventScrollToTop += OnScrollToTop;

        }

        //-------------------------------------------------------------------------------------------
        public async void OnScrollToTop(object sender, EventArgs eventArgs)
        //-------------------------------------------------------------------------------------------
        {
            Control.SmoothScrollToPositionFromTop(0, 0);
        }

    }
}

以下示例数据:

create table employee_boss_history (
  name nvarchar(50),
  boss nvarchar(50),
  start_date date,
  end_date date  
);

此(假)数据表示某人的经理发生变化。

我想要做的是有一个查询,它将占用整个表并构建所有的命令链。每个员工和命令链应该有一行,它应该具有命令链生效的最早日期和最新日期。如果他们的命令链中的任何人改变了,那么他们应该添加另一个命令记录链。

例如,Rich有四个不同的经理,但随着时间的推移,他的命令结构至少会有六个不同的变化。也许更多,我试图手工解决。

insert into employee_boss_history (name, boss, start_date, end_date)
VALUES 
('Bill',   null,   '2002-01-01', '2016-03-01'),
('Will',  'Bill',  '2015-03-02', '2016-03-01'),
('Will',   null,   '2016-03-02', null),
('Mark',  'Bill',  '2003-01-01', '2006-12-31'),
('Mark',  'Bill',  '2012-01-01', '2016-03-01'),
('Mark',  'Will',  '2016-03-02', '2016-04-30'),
('Ross',  'Will',  '2016-05-01', null),
('Roger', 'Bill',  '2006-01-01', '2012-03-01'),
('Roger', 'Mark',  '2012-03-02', '2012-09-30'),
('Kris',  'Mark',  '2012-10-01', '2016-04-30'),
('Kris',  'Ross',  '2016-05-01', '2017-01-31'),
('Moe',   'Ross',  '2017-03-01', null),
('Rich',  'Roger', '2006-03-01', '2012-09-30'),
('Rich',  'Kris',  '2012-10-01', '2017-01-31'),
('Rich',  'Ross',  '2017-02-01', '2017-02-28'),
('Rich',  'Moe',   '2017-03-01',  null)

我已经开发了一个CTE,我可以传入一个人和一个日期,然后它会走到邻接列表并显示该日期的层次结构。它有效,但我试图建立一个像上面这样的摘要。

对于解决方案的指针表示感谢,或者如果我有更多的阅读材料(这被认为是差距和岛屿吗?),请告诉我。

谢谢。

2 个答案:

答案 0 :(得分:2)

这是使用递归CTE攻击它的第一步。

WITH recCTE AS
(
    SELECT 
        name as starting_name,
        name, 
        boss, 
        start_Date, 
        end_date, 
        CAST(name as VARCHAR(200)) as coc, 
        1 as depth
    FROM employee_boss_history
    WHERE name = 'Rich'

    UNION ALL

    SELECT
        recCTE.starting_name,
        ebh.name,
        ebh.boss,
        CASE WHEN recCTE.start_date < ebh.start_date THEN ebh.start_date ELSE recCTE.start_Date END,
        CASE WHEN recCTE.end_date > ebh.end_date THEN ebh.end_date ELSE recCTE.end_Date END,
        cast(recCTE.coc + ' | ' + ebh.name as varchar(200)),
        recCTE.depth  +  1  
    FROM recCTE
        INNER JOIN employee_boss_history ebh ON
             recCTE.boss = ebh.name AND
             recCTE.start_date <= ebh.end_date AND
             recCTE.end_Date >= ebh.start_date
  )
  SELECT starting_name, coc as chain_of_command, start_date, end_date, depth FROM recCTE;

递归CTE分为两部分。

  1. “锚”。这是UNION之上的东西。它定义了递归的起点。在这种情况下,比尔的记录。
  2. “递归”部分。在这里,我们回顾我们所处的CTE并将其加入到表中。父母/老板​​的孩子/姓名。我们必须在这里采取补充步骤来纠正我们的交叉日期,并通过一些丑陋的CASE逻辑来确保我们正确地将那段时间截断到整个指挥系统中员工/老板之间的关系为真。
  3. 最后但只是从CTE中选择。你会看到你在递归的每一步都得到了记录,但是......我认为这会让你接近你需要的位置。

    SQL Fiddle here

    +---------------+----------------------------+------------+-----------+-------+
    | starting_name |      chain_of_command      | start_date | end_date  | depth |
    +---------------+----------------------------+------------+-----------+-------+
    | Rich          | Rich                       | 3/1/2006   | 9/30/2012 |     1 |
    | Rich          | Rich                       | 10/1/2012  | 1/31/2017 |     1 |
    | Rich          | Rich                       | 2/1/2017   | 2/28/2017 |     1 |
    | Rich          | Rich                       | 3/1/2017   | (null)    |     1 |
    | Rich          | Rich | Kris                | 10/1/2012  | 4/30/2016 |     2 |
    | Rich          | Rich | Kris                | 5/1/2016   | 1/31/2017 |     2 |
    | Rich          | Rich | Kris | Mark         | 10/1/2012  | 3/1/2016  |     3 |
    | Rich          | Rich | Kris | Mark         | 3/2/2016   | 4/30/2016 |     3 |
    | Rich          | Rich | Kris | Mark | Bill  | 10/1/2012  | 3/1/2016  |     4 |
    | Rich          | Rich | Roger               | 3/1/2006   | 3/1/2012  |     2 |
    | Rich          | Rich | Roger               | 3/2/2012   | 9/30/2012 |     2 |
    | Rich          | Rich | Roger | Mark        | 3/2/2012   | 9/30/2012 |     3 |
    | Rich          | Rich | Roger | Mark | Bill | 3/2/2012   | 9/30/2012 |     4 |
    | Rich          | Rich | Roger | Bill        | 3/1/2006   | 3/1/2012  |     3 |
    +---------------+----------------------------+------------+-----------+-------+
    

答案 1 :(得分:0)

我认为以下代码可以解决问题。我首先将NULL更改为将来日期以简化事物,然后开始递归到层次结构中。当我们有一个&#34; NULL boss&#34;。

时,递归就结束了

外部查询然后只选择那些&#34; NULL bosses&#34;,否则它将返回中间步骤。

WITH d AS(
    SELECT name, boss, start_date, ISNULL(end_date, '99991231') end_date
    FROM employee_boss_history
),
r AS (
    SELECT start_date, end_date, name, boss,
        CAST(name AS VARCHAR(1000)) AS chain_of_command
    FROM d
    WHERE name = 'Rich'

    UNION ALL

    SELECT
        CASE WHEN d.start_date > r.start_date THEN d.start_date ELSE r.start_date END,
        CASE WHEN d.end_date < r.end_date THEN d.end_date ELSE r.end_date END,
        d.name,
        d.boss,
        CAST(CONCAT_WS(' | ', chain_of_command, d.name) AS VARCHAR(1000))
    FROM d
    INNER JOIN r
        ON d.name = r.boss
            AND r.start_date <= d.end_date
            AND r.end_date >= d.start_date
)

SELECT start_date, end_date, chain_of_command
FROM r
WHERE boss IS NULL
ORDER BY start_date;