アクセスの連続する最小日数を決定するSQL?


125

次のユーザー履歴テーブルには、特定のユーザーが(24時間UTC期間に)Webサイトにアクセスした日ごと1つのレコードが含まれています。何千ものレコードがありますが、ユーザーあたり1日1レコードのみです。ユーザーがその日のWebサイトにアクセスしていない場合、レコードは生成されません。

ID UserId CreationDate
------ ------ ------------
750997 12 2009-07-07 18:42:20.723
750998 15 2009-07-07 18:42:20.927
751000 19 2009-07-07 18:42:22.283

私が探しているのは、パフォーマンスの良いこのテーブルのSQLクエリです。これにより、1日を逃すことなく(n)日間、どのユーザーIDがWebサイトにアクセスしたかがわかります。

言い換えると、このテーブルに連続する(前日または後)日付の(n)レコードを持っているユーザーの数は?シーケンスから欠落している日がある場合、シーケンスは壊れており、1から再開する必要があります。ここでは、ギャップのない連続した日数を達成したユーザーを探しています。

このクエリと特定のスタックオーバーフローバッジの類似点は、もちろん偶然です。


28(<30)日のメンバーシップの後にマニアバッジを獲得しました。神秘主義。
キリルV.リヤドビンスキー2009

3
日付はUTCとして保存されますか?もしそうなら、CA居住者が1日午前8時にサイトを訪問し、翌日の午後8時に訪問するとどうなりますか?彼/彼女は太平洋時間帯で連続した日に訪問しますが、DBはUTCとして時刻を格納しているため、DBにはそのように記録されません。
ガイ

Jeff / Jarrod-meta.stackexchange.com/questions/865/…を確認してください。
Rob Farley、

回答:


69

答えは明らかに:

SELECT DISTINCT UserId
FROM UserHistory uh1
WHERE (
       SELECT COUNT(*) 
       FROM UserHistory uh2 
       WHERE uh2.CreationDate 
       BETWEEN uh1.CreationDate AND DATEADD(d, @days, uh1.CreationDate)
      ) = @days OR UserId = 52551

編集:

さて、ここに私の深刻な答えがあります:

DECLARE @days int
DECLARE @seconds bigint
SET @days = 30
SET @seconds = (@days * 24 * 60 * 60) - 1
SELECT DISTINCT UserId
FROM (
    SELECT uh1.UserId, Count(uh1.Id) as Conseq
    FROM UserHistory uh1
    INNER JOIN UserHistory uh2 ON uh2.CreationDate 
        BETWEEN uh1.CreationDate AND 
            DATEADD(s, @seconds, DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate), 0))
        AND uh1.UserId = uh2.UserId
    GROUP BY uh1.Id, uh1.UserId
    ) as Tbl
WHERE Conseq >= @days

編集:

[Jeff Atwood]これは非常に高速なソリューションであり、受け入れられるに値しますが、Rob Farleyのソリューションも優れており、間違いなくさらに高速です(!)。ぜひチェックしてみてください!


@Artem:それは私が最初に考えたものでしたが、私がそれについて考えたとき、(UserId、CreationDate)にインデックスがある場合、レコードはインデックスに連続して表示され、うまく機能するはずです。
Mehrdad Afshari、

これに賛成票を投じると、500k行で約15秒で結果が返されます。
ジムT

4
DATEADD(dd、DATEDIFF(dd、0、CreationDate)、0)を使用して、これらすべてのテスト(右側のみまたはSARGを強制終了)でCreateionDateを日数に切り捨てます。これは、ゼロから指定された日付を減算することで機能します。Microsoft SQL Serverは1900-01-01 00:00:00と解釈し、日数を示します。その後、この値はゼロ日付に再度追加され、時刻が切り捨てられた同じ日付になります。
IDisposable

1
IDisposableの変更なしでは、計算が正しくありません。私は個人的にデータを自分で検証しました。1日のギャップを持つ一部のユーザーはWOULD間違ってバッジを取得します。
Jeff Atwood、

3
このクエリは、23:59:59.5に発生する訪問を見逃す可能性がありますON uh2.CreationDate >= uh1.CreationDate AND uh2.CreationDate < DATEADD(dd, DATEDIFF(dd, 0, uh1.CreationDate) + @days, 0)。「31日後ではない」を意味するように変更するとどうでしょう。@secondsの計算をスキップできることも意味します。
Rob Farley

147

どうですか(前のステートメントがセミコロンで終わっていることを確認してください):

WITH numberedrows
     AS (SELECT ROW_NUMBER() OVER (PARTITION BY UserID 
                                       ORDER BY CreationDate)
                - DATEDIFF(day,'19000101',CreationDate) AS TheOffset,
                CreationDate,
                UserID
         FROM   tablename)
SELECT MIN(CreationDate),
       MAX(CreationDate),
       COUNT(*) AS NumConsecutiveDays,
       UserID
FROM   numberedrows
GROUP  BY UserID,
          TheOffset  

日のリスト(数値として)とrow_numberがある場合、欠落した日によって、これら2つのリスト間のオフセットが少し大きくなるという考えです。したがって、一貫したオフセットを持つ範囲を探しています。

この最後に「ORDER BY NumConsecutiveDays DESC」を使用するか、しきい値に「HAVING count(*)> 14」と言います...

私はこれをテストしていません-頭の上から書いてください。うまくいけばSQL2005以降で動作します。

...そしてtablename(UserID、CreationDate)のインデックスが非常に役立ちます

編集済み:オフセットは予約語であることが判明したため、代わりにTheOffsetを使用しました。

編集:COUNT(*)を使用するという提案は非常に有効です。そもそもそうすべきだったのですが、実際には考えていませんでした。以前は、代わりにdatediff(day、min(CreationDate)、max(CreationDate))を使用していました。

ロブ


1
ああ、あなたも追加する必要があります。before
with-

2
Mladen-いいえ、前のステートメントをセミコロンで終了する必要があります。;)ジェフ-わかりました。代わりに[オフセット]を指定してください。オフセットは予約語だと思います。私が言ったように、私はそれをテストしていませんでした。
Rob Farley、

1
これはよくある問題なので、繰り返します。DATEADD(dd、DATEDIFF(dd、0、CreationDate)、0)を使用して、これらすべてのテスト(右側のみまたはSARGを強制終了)でCreateionDateを日数に切り捨てます。これは、ゼロから指定された日付を減算することで機能します。Microsoft SQL Serverは1900-01-01 00:00:00と解釈し、日数を示します。その後、この値はゼロ日付に再度追加され、時刻が切り捨てられた同じ日付になります。
IDisposable

1
IDisposable-うん、私はよく自分でやる。ここでそれをすることについて心配していませんでした。intにキャストするよりも速くはありませんが、何時間、何ヶ月など、数を数える柔軟性があります。
Rob Farley、

1
DENSE_RANK()でこれを解決することについてブログ記事を書いたところです。tinyurl.com/denserank
Rob Farley、

18

テーブルスキーマを変更できる場合は、でLongestStreak終わる連続する日数を設定した列をテーブルに追加することをお勧めしますCreationDate。ログイン時にテーブルを更新するのは簡単です(すでに行っていることと同様に、当日の行が存在しない場合は、前日の行が存在するかどうかを確認します。trueの場合LongestStreakは、新しい行、それ以外の場合は、1に設定します。

この列を追加すると、クエリは明白になります。

if exists(select * from table
          where LongestStreak >= 30 and UserId = @UserId)
   -- award the Woot badge.

1
+1私は同様の考えを有するが、前日のレコードがある場合1であろうビットフィールド(IsConsecutive)と、それ以外の場合は0た
フレドリックMORK

7
我々はこのためにスキーマを変更するつもりはない
ジェフ・アトウッド

また、IsConsecutiveは、UserHistoryテーブルで定義された計算列にすることができます。また、行をIFFに挿入したときに作成された(格納された)計算列をマテリアライズドにすることもできます。
IDisposable

(NOBODYはSELECT *を実行するため、この計算された列を追加しても、列が参照されない限りクエリプランに影響を与えないことがわかります...右の人?!?)
IDisposable

3
それは間違いなく有効な解決策ですが、私が求めたものではありません。だから私はそれに「横向きの親指」を与えます..
ジェフ・アトウッド

6

以下の行に沿ったいくつかの見事に表現力のあるSQL:

select
        userId,
    dbo.MaxConsecutiveDates(CreationDate) as blah
from
    dbo.Logins
group by
    userId

ユーザー定義の集計関数があると仮定します(これはバグがあることに注意してください)。

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Runtime.InteropServices;

namespace SqlServerProject1
{
    [StructLayout(LayoutKind.Sequential)]
    [Serializable]
    internal struct MaxConsecutiveState
    {
        public int CurrentSequentialDays;
        public int MaxSequentialDays;
        public SqlDateTime LastDate;
    }

    [Serializable]
    [SqlUserDefinedAggregate(
        Format.Native,
        IsInvariantToNulls = true, //optimizer property
        IsInvariantToDuplicates = false, //optimizer property
        IsInvariantToOrder = false) //optimizer property
    ]
    [StructLayout(LayoutKind.Sequential)]
    public class MaxConsecutiveDates
    {
        /// <summary>
        /// The variable that holds the intermediate result of the concatenation
        /// </summary>
        private MaxConsecutiveState _intermediateResult;

        /// <summary>
        /// Initialize the internal data structures
        /// </summary>
        public void Init()
        {
            _intermediateResult = new MaxConsecutiveState { LastDate = SqlDateTime.MinValue, CurrentSequentialDays = 0, MaxSequentialDays = 0 };
        }

        /// <summary>
        /// Accumulate the next value, not if the value is null
        /// </summary>
        /// <param name="value"></param>
        public void Accumulate(SqlDateTime value)
        {
            if (value.IsNull)
            {
                return;
            }
            int sequentialDays = _intermediateResult.CurrentSequentialDays;
            int maxSequentialDays = _intermediateResult.MaxSequentialDays;
            DateTime currentDate = value.Value.Date;
            if (currentDate.AddDays(-1).Equals(new DateTime(_intermediateResult.LastDate.TimeTicks)))
                sequentialDays++;
            else
            {
                maxSequentialDays = Math.Max(sequentialDays, maxSequentialDays);
                sequentialDays = 1;
            }
            _intermediateResult = new MaxConsecutiveState
                                      {
                                          CurrentSequentialDays = sequentialDays,
                                          LastDate = currentDate,
                                          MaxSequentialDays = maxSequentialDays
                                      };
        }

        /// <summary>
        /// Merge the partially computed aggregate with this aggregate.
        /// </summary>
        /// <param name="other"></param>
        public void Merge(MaxConsecutiveDates other)
        {
            // add stuff for two separate calculations
        }

        /// <summary>
        /// Called at the end of aggregation, to return the results of the aggregation.
        /// </summary>
        /// <returns></returns>
        public SqlInt32 Terminate()
        {
            int max = Math.Max((int) ((sbyte) _intermediateResult.CurrentSequentialDays), (sbyte) _intermediateResult.MaxSequentialDays);
            return new SqlInt32(max);
        }
    }
}

4

n日間にわたって継続するには、n行が必要であるという事実を利用できるようです。

したがって、次のようなもの:

SELECT users.UserId, count(1) as cnt
FROM users
WHERE users.CreationDate > now() - INTERVAL 30 DAY
GROUP BY UserId
HAVING cnt = 30

はい、確かにレコード数でゲートすることができます。しかし、毎日のギャップが多く、数年かけて120日間訪問する可能性があるため、これはいくつかの可能性を排除するだけです
Jeff Atwood

1
わかりましたが、このページの受賞に追いついたら、1日に1回実行するだけで済みます。その場合、上記のような方法でうまくいくと思います。追いつくには、BETWEENを使用してWHERE句をスライディングウィンドウに変えるだけです。
ビル

1
タスクの各実行はステートレスでスタンドアロンです。質問の表以外の以前の実行については何も知りません
ジェフ・アトウッド

3

単一のSQLクエリでこれを行うのは、私には非常に複雑に見えます。この答えを2つの部分に分けましょう。

  1. 今までにすべきことと今から始めるべきこと:
    今日ログインしたかどうかすべてのユーザーをチェックするcronジョブを毎日実行し、ユーザーがいる場合はカウンターをインクリメントするか、そうでない場合は0に設定します。
  2. 今すべきこと:
    -このテーブルを、Webサイトを実行しておらず、しばらくは必要ないサーバーにエクスポートします。;)
    -ユーザー、日付の順に並べ替えます。
    -順番に進み、カウンターを維持します...

クエリとループにコードを書くことができます。それは私が言うdaryです。現在、SQLのみの方法に興味があります。
Jeff Atwood、

2

これがあなたにとって非常に重要である場合、このイベントを調達し、この情報を提供するためにテーブルを運転してください。クレイジーなクエリでマシンを殺す必要はありません。


2

再帰CTE(SQL Server 2005以降)を使用できます。

WITH recur_date AS (
        SELECT t.userid,
               t.creationDate,
               DATEADD(day, 1, t.created) 'nextDay',
               1 'level' 
          FROM TABLE t
         UNION ALL
        SELECT t.userid,
               t.creationDate,
               DATEADD(day, 1, t.created) 'nextDay',
               rd.level + 1 'level'
          FROM TABLE t
          JOIN recur_date rd on t.creationDate = rd.nextDay AND t.userid = rd.userid)
   SELECT t.*
    FROM recur_date t
   WHERE t.level = @numDays
ORDER BY t.userid

2

Joe Celkoは、SQL for Smartiesでこれに関する完全な章を持っています(RunsおよびSequencesと呼びます)。家に本を持っていないので、仕事に着いたら…答えます。(履歴テーブルがdbo.UserHistoryと呼ばれ、日数が@Daysであると仮定)

もう1つのリードは、実行に関するSQLチームのブログです

私が持っている他のアイデアですが、ここで作業するのに便利なSQLサーバーを持っていないのは、次のようにパーティション化されたROW_NUMBERでCTEを使用することです。

WITH Runs
AS
  (SELECT UserID
         , CreationDate
         , ROW_NUMBER() OVER(PARTITION BY UserId
                             ORDER BY CreationDate)
           - ROW_NUMBER() OVER(PARTITION BY UserId, NoBreak
                               ORDER BY CreationDate) AS RunNumber
  FROM
     (SELECT UH.UserID
           , UH.CreationDate
           , ISNULL((SELECT TOP 1 1 
              FROM dbo.UserHistory AS Prior 
              WHERE Prior.UserId = UH.UserId 
              AND Prior.CreationDate
                  BETWEEN DATEADD(dd, DATEDIFF(dd, 0, UH.CreationDate), -1)
                  AND DATEADD(dd, DATEDIFF(dd, 0, UH.CreationDate), 0)), 0) AS NoBreak
      FROM dbo.UserHistory AS UH) AS Consecutive
)
SELECT UserID, MIN(CreationDate) AS RunStart, MAX(CreationDate) AS RunEnd
FROM Runs
GROUP BY UserID, RunNumber
HAVING DATEDIFF(dd, MIN(CreationDate), MAX(CreationDate)) >= @Days

上記はおそらく必要以上に難しいかもしれませんが、日付だけでなく「実行」の定義が他にある場合は、脳のくすぐりとして残されます。


2

いくつかのSQL Server 2012オプション(以下ではN = 100と仮定)。

;WITH T(UserID, NRowsPrevious)
     AS (SELECT UserID,
                DATEDIFF(DAY, 
                        LAG(CreationDate, 100) 
                            OVER 
                                (PARTITION BY UserID 
                                     ORDER BY CreationDate), 
                         CreationDate)
         FROM   UserHistory)
SELECT DISTINCT UserID
FROM   T
WHERE  NRowsPrevious = 100 

私のサンプルデータでは、以下がより効率的に機能しましたが

;WITH U
         AS (SELECT DISTINCT UserId
             FROM   UserHistory) /*Ideally replace with Users table*/
    SELECT UserId
    FROM   U
           CROSS APPLY (SELECT TOP 1 *
                        FROM   (SELECT 
                                       DATEDIFF(DAY, 
                                                LAG(CreationDate, 100) 
                                                  OVER 
                                                   (ORDER BY CreationDate), 
                                                 CreationDate)
                                FROM   UserHistory UH
                                WHERE  U.UserId = UH.UserID) T(NRowsPrevious)
                        WHERE  NRowsPrevious = 100) O

どちらも、ユーザーごとに1日あたり最大1つのレコードがあるという質問で述べられている制約に依存しています。


1

このようなもの?

select distinct userid
from table t1, table t2
where t1.UserId = t2.UserId 
  AND trunc(t1.CreationDate) = trunc(t2.CreationDate) + n
  AND (
    select count(*)
    from table t3
    where t1.UserId  = t3.UserId
      and CreationDate between trunc(t1.CreationDate) and trunc(t1.CreationDate)+n
   ) = n

1

単純な数学プロパティを使用して、誰が連続してサイトにアクセスしたかを特定しました。このプロパティは、最初のアクセスと最後の時間の日差がアクセステーブルログのレコード数と等しいことを意味します。

Oracle DBでテストしたSQLスクリプトは次のとおりです(他のDBでも動作するはずです)。

-- show basic understand of the math properties 
  select    ceil(max (creation_date) - min (creation_date))
              max_min_days_diff,
           count ( * ) real_day_count
    from   user_access_log
group by   user_id;


-- select all users that have consecutively accessed the site 
  select   user_id
    from   user_access_log
group by   user_id
  having       ceil(max (creation_date) - min (creation_date))
           / count ( * ) = 1;



-- get the count of all users that have consecutively accessed the site 
  select   count(user_id) user_count
    from   user_access_log
group by   user_id
  having   ceil(max (creation_date) - min (creation_date))
           / count ( * ) = 1;

テーブル準備スクリプト:

-- create table 
create table user_access_log (id           number, user_id      number, creation_date date);


-- insert seed data 
insert into user_access_log (id, user_id, creation_date)
  values   (1, 12, sysdate);

insert into user_access_log (id, user_id, creation_date)
  values   (2, 12, sysdate + 1);

insert into user_access_log (id, user_id, creation_date)
  values   (3, 12, sysdate + 2);

insert into user_access_log (id, user_id, creation_date)
  values   (4, 16, sysdate);

insert into user_access_log (id, user_id, creation_date)
  values   (5, 16, sysdate + 1);

insert into user_access_log (id, user_id, creation_date)
  values   (6, 16, sysdate + 5);

1
declare @startdate as datetime, @days as int
set @startdate = cast('11 Jan 2009' as datetime) -- The startdate
set @days = 5 -- The number of consecutive days

SELECT userid
      ,count(1) as [Number of Consecutive Days]
FROM UserHistory
WHERE creationdate >= @startdate
AND creationdate < dateadd(dd, @days, cast(convert(char(11), @startdate, 113)  as datetime))
GROUP BY userid
HAVING count(1) >= @days

このステートメントでcast(convert(char(11), @startdate, 113) as datetime)は、日付の時刻部分が削除されているため、真夜中に開始します。

私はまたcreationdateuserid列にはインデックスが付けます。

これですべてのユーザーとその合計日数がわかるわけではないことに気づきました。ただし、選択した日付から設定された日数を訪問しているユーザーがわかります。

修正されたソリューション:

declare @days as int
set @days = 30
select t1.userid
from UserHistory t1
where (select count(1) 
       from UserHistory t3 
       where t3.userid = t1.userid
       and t3.creationdate >= DATEADD(dd, DATEDIFF(dd, 0, t1.creationdate), 0) 
       and t3.creationdate < DATEADD(dd, DATEDIFF(dd, 0, t1.creationdate) + @days, 0) 
       group by t3.userid
) >= @days
group by t1.userid

これを確認したところ、すべてのユーザーとすべての日付がクエリされます。それはスペンサーの最初の(ジョーク?)ソリューションに基づいていますが、私の作品です。

更新:2番目のソリューションの日付処理を改善しました。


近いですが、固定された開始日ではなく、任意の(n)日間機能するものが必要です
Jeff Atwood

0

これはあなたが望むことをするはずですが、効率をテストするのに十分なデータがありません。複雑なCONVERT / FLOORは、日時フィールドから時間部分を取り除くことです。SQL Server 2008を使用している場合は、CAST(x.CreationDate AS DATE)を使用できます。

@RangeをINTとして宣言
セット@範囲= 10

SELECT DISTINCT UserId、CONVERT(DATETIME、FLOOR(CONVERT(FLOAT、a.CreationDate)))
  FROM tblUserLogin a
存在する場所
   (選択1 
      FROM tblUserLogin b 
     WHERE a.userId = b.userId 
       AND(SELECT COUNT(DISTINCT(CONVERT(DATETIME、FLOOR(CONVERT(FLOAT、CreationDate)))))) 
              FROM tblUserLogin c 
             WHERE c.userid = b.userid 
               AND CONVERT(DATETIME、FLOOR(CONVERT(FLOAT、c.CreationDate)))CONVERT(DATETIME、FLOOR(CONVERT(FLOAT、a.CreationDate)))とCONVERT(DATETIME、FLOOR(CONVERT(FLOAT、a.CreationDate))の間)+ @ Range-1)= @Range)

作成スクリプト

CREATE TABLE [dbo]。[tblUserLogin](
    [Id] [int] IDENTITY(1,1)NOT NULL、
    [UserId] [int] NULL、
    [CreationDate] [datetime] NULL
)[PRIMARY]

かなり残忍。406,624行で26秒。
Jeff Atwood、

バッジを授与するためにチェックする頻度はどれくらいですか?1日1回だけであれば、遅い期間の26秒のヒットはそれほど悪くないようです。ただし、テーブルが大きくなるとパフォーマンスは低下します。質問を再度読んだ後は、1日あたり1つのレコードしかないため、時間を取り除く必要はありません。
デイブバーカー

0

Spencerはほとんどそれを行いましたが、これは動作するコードでなければなりません:

SELECT DISTINCT UserId
FROM History h1
WHERE (
    SELECT COUNT(*) 
    FROM History
    WHERE UserId = h1.UserId AND CreationDate BETWEEN h1.CreationDate AND DATEADD(d, @n-1, h1.CreationDate)
) >= @n

0

私の頭の上のMySQLish:

SELECT start.UserId
FROM UserHistory AS start
  LEFT OUTER JOIN UserHistory AS pre_start ON pre_start.UserId=start.UserId
    AND DATE(pre_start.CreationDate)=DATE_SUB(DATE(start.CreationDate), INTERVAL 1 DAY)
  LEFT OUTER JOIN UserHistory AS subsequent ON subsequent.UserId=start.UserId
    AND DATE(subsequent.CreationDate)<=DATE_ADD(DATE(start.CreationDate), INTERVAL 30 DAY)
WHERE pre_start.Id IS NULL
GROUP BY start.Id
HAVING COUNT(subsequent.Id)=30

テストされておらず、ほぼ間違いなくMSSQLの変換が必要ですが、いくつかのアイデアが得られたと思います。


0

Tallyテーブルを使用するのはどうですか?よりアルゴリズム的なアプローチに従い、実行計画は簡単です。テーブルをスキャンする1から 'MaxDaysBehind'までの数値をtallyTableに入力します(つまり、90は3か月遅れて検索します)。

declare @ContinousDays int
set @ContinousDays = 30  -- select those that have 30 consecutive days

create table #tallyTable (Tally int)
insert into #tallyTable values (1)
...
insert into #tallyTable values (90) -- insert numbers for as many days behind as you want to scan

select [UserId],count(*),t.Tally from HistoryTable 
join #tallyTable as t on t.Tally>0
where [CreationDate]> getdate()-@ContinousDays-t.Tally and 
      [CreationDate]<getdate()-t.Tally 
group by [UserId],t.Tally 
having count(*)>=@ContinousDays

delete #tallyTable

0

ビルのクエリを少し調整します。グループ化する前に日付を切り捨てて、1日あたり1回のログインのみをカウントする必要があるかもしれません...

SELECT UserId from History 
WHERE CreationDate > ( now() - n )
GROUP BY UserId, 
DATEADD(dd, DATEDIFF(dd, 0, CreationDate), 0) AS TruncatedCreationDate  
HAVING COUNT(TruncatedCreationDate) >= n

EDITEDは、convert(char(10)、CreationDate、101)の代わりにDATEADD(dd、DATEDIFF(dd、0、CreationDate)、0)を使用します。

@IDisposable以前にdatepartを使用することを検討していましたが、構文を調べるのが面倒なので、代わりにidを使用して変換することを考えました。私はそれが大きな影響を与えたことを知っています。今私は知っている。


SQL DATETIMEを日付のみに切り捨てるには、DATEADD(dd、DATEDIFF(dd、0、UH.CreationDate)、0)
IDisposable

(上記は、0の間の丸1日の差(たとえば、1900-01-01 00:00:00.000)を取り、その後、丸1日の差を0に追加して(たとえば、1900-01-01 00:00:00)機能します。 。これにより、DATETIMEの時刻部分が破棄されます)
IDisposable

0

次のようなスキーマを想定しています。

create table dba.visits
(
    id  integer not null,
    user_id integer not null,
    creation_date date not null
);

これにより、ギャップのある日付シーケンスから連続した範囲が抽出されます。

select l.creation_date  as start_d, -- Get first date in contiguous range
    (
        select min(a.creation_date ) as creation_date 
        from "DBA"."visits" a 
            left outer join "DBA"."visits" b on 
                   a.creation_date = dateadd(day, -1, b.creation_date ) and 
                   a.user_id  = b.user_id 
            where b.creation_date  is null and
                  a.creation_date  >= l.creation_date  and
                  a.user_id  = l.user_id 
    ) as end_d -- Get last date in contiguous range
from  "DBA"."visits" l
    left outer join "DBA"."visits" r on 
        r.creation_date  = dateadd(day, -1, l.creation_date ) and 
        r.user_id  = l.user_id 
    where r.creation_date  is null
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.