7

次の点を考慮してください。

CREATE TABLE Members (MemberID INT)
INSERT Members VALUES (1001)

CREATE TABLE PCPs (PCPID INT)
INSERT PCPs VALUES (231)
INSERT PCPs VALUES (327)
INSERT PCPs VALUES (390)

CREATE TABLE Plans (PlanID INT)
INSERT Plans VALUES (555)
INSERT Plans VALUES (762)

CREATE TABLE MemberPCP (
    MemberID INT
    , PCP INT
    , StartDate DATETIME
    , EndDate DATETIME)
INSERT MemberPCP VALUES (1001, 231, '2002-01-01', '2002-06-30')
INSERT MemberPCP VALUES (1001, 327, '2002-07-01', '2003-05-31')
INSERT MemberPCP VALUES (1001, 390, '2003-06-01', '2003-12-31')

CREATE TABLE MemberPlans (
    MemberID INT
    , PlanID INT
    , StartDate DATETIME
    , EndDate DATETIME)
INSERT MemberPlans VALUES (1001, 555, '2002-01-01', '2003-03-31')
INSERT MemberPlans VALUES (1001, 762, '2003-04-01', '2003-12-31')

メンバー/PCP/プランの関係のタイムラインを構築するためのクリーンな方法を探しています。メンバーの PCP またはプランのいずれかが変更されると、結果の開始行と終了行が別々になります。たとえば、メンバーが数年にわたって PCP を 2 回変更し、プランを 1 回変更したが、それぞれの日付が異なる場合、次のように表示されます。

MemberID  PCP  PlanID  StartDate    EndDate
1001      231  555     2002-01-01   2002-06-30
1001      327  555     2002-07-01   2003-03-31
1001      327  762     2003-04-01   2003-05-31
1001      390  762     2003-06-01   2003-12-31

ご覧のとおり、メンバー/PCP/プランの関連付けに違いがある日付期間ごとに、個別の結果行が必要です。私は解決策を用意していますが、WHERE 句に多数の CASE ステートメントと条件付きロジックがあり、非常に複雑です。これを行うにはもっと簡単な方法があると思います。

ありがとう。

4

4 に答える 4

2

T-SQL と互換性があります。一般的なアプローチについては、Glenn に同意します。

別の提案: ビジネスで期間間のホップを許可する場合、このコードをさらに調整する必要があります。それ以外の場合は、次のレコードの StartDate から EndDate の値を延期する方が、コードの動作をより制御するのに適していると思います。その場合、データがこのクエリに到達する前にルールを確認する必要があります。

編集: Andriy M の投稿から With ステートメントと SQL Fiddle について学びました。SQL Fiddleでも私の答えを見ることができます。

編集: Andriy によって指摘されたバグを修正しました。

WITH StartDates AS (
SELECT MemberId, StartDate FROM MemberPCP UNION
SELECT MemberId, StartDate FROM MemberPlans UNION
SELECT MemberId, EndDate + 1 FROM MemberPCP UNION
SELECT MemberId, EndDate + 1 FROM MemberPlans
),
EndDates AS (
SELECT MemberId, EndDate = StartDate - 1 FROM MemberPCP UNION
SELECT MemberId, StartDate - 1 FROM MemberPlans UNION
SELECT MemberId, EndDate FROM MemberPCP UNION
SELECT MemberId, EndDate FROM MemberPlans
),
Periods AS (
SELECT s.MemberId, s.StartDate, EndDate = min(e.EndDate)
  FROM StartDates s
       INNER JOIN EndDates e
           ON s.StartDate <= e.EndDate
          AND s.MemberId = e.MemberId
 GROUP BY s.MemberId, s.StartDate
)
SELECT MemberId = p.MemberId,
       pcp.PCP, pl.PlanId,
       p.StartDate, p.EndDate
  FROM Periods p
       LEFT JOIN MemberPCP pcp
           -- because of the way we divided period,
           -- there will be one and only one record that fits this join clause
           ON p.StartDate >= pcp.StartDate
          AND p.EndDate <= pcp.EndDate
          AND p.MemberId = pcp.MemberId
       LEFT JOIN MemberPlans pl
           ON p.StartDate >= pl.StartDate
          AND p.EndDate <= pl.EndDate
          AND p.MemberId = pl.MemberId
 ORDER BY p.MemberId, p.StartDate
于 2012-07-02T22:22:15.743 に答える
1

私のアプローチは、開始点として各メンバーの開始日の一意の組み合わせを取得し、そこからクエリの他の部分を構築することです。

--
-- Traverse down a list of 
-- unique Member ID and StartDates
-- 
-- For each row find the most 
-- recent PCP for that member 
-- which started on or before
-- the start date of the current
-- row in the traversal
--
-- For each row find the most 
-- recent PlanID for that member
-- which started on or before
-- the start date of the current
-- row in the traversal
-- 
-- For each row find the earliest
-- end date for that member
-- (from a collection of unique
-- member end dates) that happened
-- after the start date of the
-- current row in the traversal
-- 
SELECT MemberID,
  (SELECT TOP 1 PCP 
   FROM MemberPCP 
   WHERE MemberID = s.MemberID 
   AND StartDate <= s.StartDate 
   ORDER BY StartDate DESC
  ) AS PCP,
  (SELECT TOP 1 PlanID 
   FROM MemberPlans 
   WHERE MemberID = s.MemberID 
   AND StartDate <= s.StartDate 
   ORDER BY StartDate DESC
  ) AS PlanID,
  StartDate,  
  (SELECT TOP 1 EndDate 
   FROM (
    SELECT MemberID, EndDate 
    FROM MemberPlans 
    UNION 
    SELECT MemberID, EndDate 
    FROM MemberPCP) e
   WHERE EndDate >= s.StartDate 
   ORDER BY EndDate
  ) AS EndDate
FROM ( 
  SELECT
    MemberID,
    StartDate
  FROM MemberPlans
  UNION 
  SELECT
    MemberID,
    Startdate
  FROM MemberPCP
) s
ORDER BY StartDate
于 2012-06-14T21:58:40.460 に答える
1

おそらく最も効率的ではありませんが、少なくとも単純で簡単な解決策として、次のことを行います。

  • 1) 範囲を拡大します。

  • 2) 拡張された範囲に参加します。

  • 3) 結果をグループ化します。

もちろん、これは日付のみが使用されていることを前提としています (つまり、時間の部分は00:00すべてのテーブルStartDateEndDate両方のテーブルにあります)。

日付範囲を拡張するには、次のように数値テーブルを使用することを好みます。

SELECT
  m.MemberID,
  m.PCP,
  Date = DATEADD(DAY, n.Number, m.StartDate)
FROM MemberPCP m
  INNER JOIN Numbers n
    ON n.Number BETWEEN 0 AND DATEDIFF(DAY, m.StartDate, m.EndDate)

についても同様ですMemberPlans

結合された行セットを作成するには、 を使用FULL JOINしますが、両方のテーブルがまったく同じ期間をカバーしていることが事前にわかっている場合は、次のようにすることもできINNER JOINます。

SELECT *
FROM MemberPCPExpanded pcp
  FULL JOIN MemberPlansExpanded plans
    ON pcp.MemberID = plans.MemberID AND pcp.Date = plans.Date

結果の行をグループ化し、次のすべての組み合わせの最小日付と最大日付を見つけるだけで済みます(MemberID, PCP, PlanID)

SELECT
  MemberID  = ISNULL(pcp.MemberID, plans.MemberID),,
  pcp.PCP,
  plans.PlanID,
  StartDate = MIN(ISNULL(pcp.Date, plans.Date)),
  EndDate   = MAX(ISNULL(pcp.Date, plans.Date))
FROM MemberPCPExpanded pcp
  FULL JOIN MemberPlansExpanded plans
    ON pcp.MemberID = plans.MemberID AND pcp.Date = plans.Date
GROUP BY
  ISNULL(pcp.MemberID, plans.MemberID),
  pcp.PCP,
  plans.PlanID

INNER JOINの代わりにを使用する場合FULL JOIN、これらすべての式は必要ないことに注意してください。たとえば、 の代わりにとの代わりにISNULL()、いずれかのテーブルの列を選択するだけで十分です。pcp.MemberIDISNULL(pcp.MemberID, plans.MemberID)pcp.DateISNULL(pcp.Date, plans.Date)

完全なクエリは次のようになります。

WITH MemberPCPExpanded AS (
  SELECT
    m.MemberID,
    m.PCP,
    Date = DATEADD(DAY, n.Number, m.StartDate)
  FROM MemberPCP m
    INNER JOIN Numbers n
      ON n.Number BETWEEN 0 AND DATEDIFF(DAY, m.StartDate, m.EndDate)
),
MemberPlansExpanded AS (
  SELECT
    m.MemberID,
    m.PlanID,
    Date = DATEADD(DAY, n.Number, m.StartDate)
  FROM MemberPlans m
    INNER JOIN Numbers n
      ON n.Number BETWEEN 0 AND DATEDIFF(DAY, m.StartDate, m.EndDate)
)
SELECT
  MemberID  = ISNULL(pcp.MemberID, plans.MemberID),
  pcp.PCP,
  plans.PlanID,
  StartDate = MIN(ISNULL(pcp.Date, plans.Date)),
  EndDate   = MAX(ISNULL(pcp.Date, plans.Date))
FROM MemberPCPExpanded pcp
  FULL JOIN MemberPlansExpanded plans
    ON pcp.MemberID = plans.MemberID AND pcp.Date = plans.Date
GROUP BY
  ISNULL(pcp.MemberID, plans.MemberID),
  pcp.PCP,
  plans.PlanID
ORDER BY
  MemberID,
  StartDate

このクエリは SQL Fiddle で試すことができます。

于 2012-07-03T10:13:33.763 に答える
0

たぶん、これは開始のためのいくつかのアイデアを与えるでしょう:

SELECT y.memberid, y.pcp, z.planid, x.startdate, x.enddate
  FROM (
        WITH startdates AS (

            SELECT startdate FROM memberpcp
            UNION
            SELECT startdate FROM memberplans
            UNION
            SELECT enddate + 1 FROM memberpcp
            UNION
            SELECT enddate + 1 FROM memberplans

            ), enddates AS (
            SELECT enddate FROM memberpcp
            UNION
            SELECT enddate FROM memberplans

          )

        SELECT s.startdate, e.enddate
          FROM startdates s 
              ,enddates e
          WHERE e.enddate = (SELECT MIN(enddate)
                               FROM enddates
                               WHERE enddate > s.startdate)
       ) x
       ,memberpcp y
       ,memberplans z

  WHERE (y.startdate, y.enddate) = (SELECT startdate, enddate FROM memberpcp WHERE startdate <= x.startdate AND enddate >= x.enddate)
    AND (z.startdate, z.enddate) = (SELECT startdate, enddate FROM memberplans WHERE startdate <= x.startdate AND enddate >= x.enddate)

Oracle で実行した結果は次のとおりです。

1001    231 555 01-JAN-02   30-JUN-02
1001    327 555 01-JUL-02   31-MAR-03
1001    327 762 01-APR-03   31-MAY-03
1001    390 762 01-JUN-03   31-DEC-03

アイデアは、最初にさまざまな日付範囲を定義することでした。それは「WITH」節にあります。次に、他のテーブルの各範囲でルックアップを行います。ここでは、範囲の重複などに関する多くの仮定があります。しかし、おそらく始まりです。tsql を使用した分析関数の適切なサポートがない可能性があるため、分析関数なしでこれを調べてみました。知らない。実際の日付範囲を構築するときは、メンバー ID によっても範囲を構築する必要があります。

于 2012-06-15T18:06:45.417 に答える