3

注文のある注文テーブルがあります。特定の日の各ユーザー (セットベースの方法で優先) のサブスクリプション日数を計算したいと考えています。

create table #orders (orderid int, userid int, subscriptiondays int, orderdate date)
insert into #orders
    select 1, 2, 10, '2011-01-01'
    union 
    select 2, 1, 10, '2011-01-10'
    union 
    select 3, 1, 10, '2011-01-15'
    union 
    select 4, 2, 10, '2011-01-15'

declare @currentdate date = '2011-01-20'

--userid 1 is expected to have 10 subscriptiondays left
(since there is 5 left when the seconrd order is placed)
--userid 2 is expected to have 5 subscriptionsdays left 

これは以前に行われたと確信していますが、何を検索すればよいかわかりません。現在の合計にかなり似ていますか?

したがって、@currentdate を '2011-01-20' に設定すると、次の結果が得られます。

userid      subscriptiondays
1           10
2           5

@currentdate を「2011-01-25」に設定すると

userid      subscriptiondays
1           5
2           0

@currentdate を「2011-01-11」に設定すると

userid      subscriptiondays
1           9
2           0

ありがとう!

4

4 に答える 4

4

再帰的な共通テーブル式を使用する必要があると思います。

編集:再帰的な共通テーブル式を使用する代わりに、手続き型の実装をさらに下に追加しました。私が含めた再帰的な CTE クエリではおそらく処理できないデータ シナリオが多数あると思われるため、その手続き型アプローチを使用することをお勧めします。

以下のクエリは、提供されたシナリオに対する正しい回答を提供しますが、おそらく、いくつかの追加の複雑なシナリオを考え出し、バグがあるかどうかを確認する必要があります。

たとえば、以前の複数の注文が後の注文と重複している場合、このクエリが機能しなくなる可能性があると感じています。

with CurrentOrders (UserId, SubscriptionDays, StartDate, EndDate) as
(
    select
        userid,
        sum(subscriptiondays),
        min(orderdate),
        dateadd(day, sum(subscriptiondays), min(orderdate))
    from #orders
    where
        #orders.orderdate <= @currentdate
        -- start with the latest order(s)
        and not exists (
            select 1
            from #orders o2
        where
            o2.userid = #orders.userid
            and o2.orderdate <= @currentdate
            and o2.orderdate > #orders.orderdate
        )
    group by
        userid

    union all

    select
        #orders.userid,
        #orders.subscriptiondays,
        #orders.orderdate,
        dateadd(day, #orders.subscriptiondays, #orders.orderdate)
    from #orders
    -- join any overlapping orders
    inner join CurrentOrders on
        #orders.userid = CurrentOrders.UserId
        and #orders.orderdate < CurrentOrders.StartDate
        and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > CurrentOrders.StartDate
)
select
    UserId,
    sum(SubscriptionDays) as TotalSubscriptionDays,
    min(StartDate),
    sum(SubscriptionDays) - datediff(day, min(StartDate), @currentdate) as RemainingSubscriptionDays
from CurrentOrders
group by
    UserId
;

Philip は、共通テーブル式の再帰制限に関する懸念について言及しました。以下は、テーブル変数と while ループを使用した手続き型の代替手段であり、同じことを達成すると私は信じています。

この代替コードが機能することを確認しましたが、少なくとも提供されたサンプル データについては、このアプローチに関するコメントをお待ちしております。良いアイデア?悪いアイデア?注意すべき懸念事項はありますか?

declare @ModifiedRows int

declare @CurrentOrders table
(
    UserId int not null,
    SubscriptionDays int not null,
    StartDate date not null,
    EndDate date not null
)

insert into @CurrentOrders
select
    userid,
    sum(subscriptiondays),
    min(orderdate),
    min(dateadd(day, subscriptiondays, orderdate))
from #orders
where
    #orders.orderdate <= @currentdate
    -- start with the latest order(s)
    and not exists (
        select 1
        from #orders o2
        where
            o2.userid = #orders.userid
            and o2.orderdate <= @currentdate
            -- there does not exist any other order that surpasses it
            and dateadd(day, o2.subscriptiondays, o2.orderdate) > dateadd(day, #orders.subscriptiondays, #orders.orderdate)
    )
group by
    userid

set @ModifiedRows = @@ROWCOUNT


-- perform an extra update here in case there are any additional orders that were made after the start date but before the specified @currentdate
update co set
    co.SubscriptionDays = co.SubscriptionDays + #orders.subscriptiondays
from @CurrentOrders co
inner join #orders on
    #orders.userid = co.UserId
    and #orders.orderdate <= @currentdate
    and #orders.orderdate >= co.StartDate
    and dateadd(day, #orders.subscriptiondays, #orders.orderdate) < co.EndDate


-- Keep attempting to update rows as long as rows were updated on the previous attempt
while(@ModifiedRows > 0)
begin
    update co set
        SubscriptionDays = co.SubscriptionDays + overlap.subscriptiondays,
        StartDate = overlap.orderdate
    from @CurrentOrders co
    -- join any overlapping orders
    inner join (
        select
            #orders.userid,
            sum(#orders.subscriptiondays) as subscriptiondays,
            min(orderdate) as orderdate
        from #orders
        inner join @CurrentOrders co2 on
            #orders.userid = co2.UserId
            and #orders.orderdate < co2.StartDate
            and dateadd(day, #orders.subscriptiondays, #orders.orderdate) > co2.StartDate
        group by
            #orders.userid
    ) overlap on
        overlap.userid = co.UserId

    set @ModifiedRows = @@ROWCOUNT
end

select
    UserId,
    sum(SubscriptionDays) as TotalSubscriptionDays,
    min(StartDate),
    sum(SubscriptionDays) - datediff(day, min(StartDate), @currentdate) as RemainingSubscriptionDays
from @CurrentOrders
group by
    UserId

EDIT2: 上記のコードにいくつかの調整を加えて、さまざまな特殊なケースに対処しました。たとえば、同じ日付で終了するユーザーの 2 つの注文が発生した場合などです。

たとえば、セットアップ データを次のように変更すると、元のコードで問題が発生しましたが、これを修正しました。

insert into #orders
    select 1, 2, 10, '2011-01-01'
    union 
    select 2, 1, 10, '2011-01-10'
    union 
    select 3, 1, 10, '2011-01-15'
    union 
    select 4, 2, 6, '2011-01-15'
    union 
    select 5, 2, 4, '2011-01-17'

EDIT3: 他の特殊なケースに対処するために、いくつかの追加調整を行いました。特に、以前のコードでは、次のセットアップ データで問題が発生しました。これを修正しました。

insert into #orders
    select 1, 2, 10, '2011-01-01'
    union 
    select 2, 1, 6, '2011-01-10'
    union 
    select 3, 1, 10, '2011-01-15'
    union 
    select 4, 2, 10, '2011-01-15'
    union 
    select 5, 1, 4, '2011-01-12'
于 2011-12-13T21:20:06.407 に答える
0

実際、subscriptiondaysの合計から最初のサブスクライブ可能な日付と@currentdateまでの日数を引いたものを次のように計算する必要があります。

select userid, 
       sum(subsribtiondays)-
       DATEDIFF('dd', 
                (select min(orderdate) 
                 from #orders as a 
                 where a.userid=userid),  @currentdate)
from #orders
where orderdate <= @currentdata
group by userid
于 2011-12-13T19:56:56.963 に答える
0

問題の私の解釈:

  • X 日、顧客はサブスクリプション日数の「スパン」を購入します (つまり、N 日間有効)
  • スパンは購入日から始まり、X 日から X + (N - 1) 日まで有効です...ただし、以下を参照してください。
  • お客様が最初のスパンの有効期限が切れた後に2 番目のスパンを購入した場合(または既存のスパンがすべて期限切れになった後に新しいスパンを購入した場合)、プロセスを繰り返します。(30 日前の 10 日間の 1 回の購入は、今日の 2 回目の購入には影響しません。)
  • 既存のスパンがまだ有効である間にお客様がスパンを購入した場合、新しいスパンが適用されday immediately after end of current span(s)ます。that date + (N – 1)
  • これは反復的です。顧客が 1 月 1 日、1 月 2 日、1 月 3 日に 10 日間のスパンを購入した場合、次のようになります。

    1日現在:1月1日~1月10日

    2日現在:1月1日~1月10日、1月11日~1月20日(実質1月1日~1月20日)

    3日現在:1月1日~1月10日、1月11日~1月20日、1月21日~1月30日(実質1月1日~1月30日)

これが実際に問題である場合、T-SQL で解決するのは恐ろしい問題です。特定の購入の「有効期間」を決定するには、その全体的な累積効果のために、購入された順序で以前のすべての購入の有効期間を計算する必要があります。これは、ユーザーが 1 人で行が 3 つある場合は些細な問題ですが、何十回も購入している何千人ものユーザーにとっては些細なことではありません (おそらく、これが必要です)。

私はそれを次のように解決します:

  • テーブルEffectiveDateにデータ型の列を追加しますdate
  • すべての行をユーザーごと、および注文日ごとに順を追って確認する 1 回限りのプロセスを作成し、上記で説明したように有効日を計算します。
  • データの挿入に使用されるプロセスを変更して、新しいエントリが作成された時点でEffectiveDateを計算します。このようにすると、そのユーザーが行った最新の購入を参照するだけで済みます。
  • 注文の削除 (キャンセル?) または更新 (設定ミス?) に関するその後の問題を解決する

私は間違っているかもしれませんが、セットベースの戦術を使用してこれに対処する方法がわかりません. (再帰的な CTE などは機能しますが、非常に多くのレベルまでしか再帰できません。また、この問題の限界もわかりません。実行する必要がある頻度や、パフォーマンスがどれだけ高くなければならないかは言うまでもありません。 .) 再帰なしでこれを解決する人を見て、賛成します!

そしてもちろん、これは問題に対する私の理解が正しい場合にのみ適用されます。そうでない場合は、無視してください。

于 2011-12-13T20:08:12.373 に答える
0

私の明確なコメント/質問が正しい場合は、DATEDIFF を使用する必要があります。

DATEDIFF(dd, orderdate,  @currentdate)
于 2011-12-13T19:44:59.167 に答える