[SQL] SQL 최소 순차적 일 액세스를 결정하기 위해?
SQLSQL 최소 순차적 일 액세스를 결정하기 위해?
다음 사용자 기록 테이블은 주어진 사용자가 (24 시간 UTC 기간) 웹 사이트에 액세스 한 모든 일에 대해 하나 개의 레코드가 포함되어 있습니다. 그것은 많은 수천 개의 레코드 만, 사용자 당 하루에 하나 개의 기록을 가지고 있습니다. 사용자가 해당 일의 웹 사이트에 액세스하지 않은 경우, 레코드가 생성되지 않습니다.
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
내가 찾고 사용자가 하루 놓치지 않고 (n)의 연속 일 동안 웹 사이트에 액세스 한있는 나에게 말한다 좋은 성능이 테이블에 SQL 쿼리입니다.
즉, 얼마나 많은 사용자 (하루 전, 또는 하루 후) 연속 날짜가이 표에서 (n)의 기록이? 모든 일이 순서에서 누락 된 경우, 순서는 고장 1 다시 다시 시작해야합니다; 우리는 빈틈이없는 여기에 일의 연속 수를 달성 한 사용자를 찾고 있습니다.
이 쿼리 특정 스택 오버플로 배지 간의 유사성은 물론, 순전히 우연입니다 .. :)
해결법
-
==============================
1.대답은 분명하다 :
대답은 분명하다 :
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
편집하다:
[제프 앳 우드]이 좋은 빠른 솔루션이며 수용 할 가치가 있지만, 롭 팔리의 솔루션은 또한 우수하고 틀림없이 더 빠른 (!). 너무 그것을 확인하시기 바랍니다!
-
==============================
2.방법에 대한 (그리고 확인하십시오 확인 세미콜론으로 종료 이전 문) :
방법에 대한 (그리고 확인하십시오 확인 세미콜론으로 종료 이전 문) :
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는 다음 놓친 일이이 두 목록 약간 더 큰 사이의 오프셋합니다. 그래서 우리는 오프셋 일관성이있는 범위를 찾고 있습니다.
당신은 임계 값에 대해 "COUNT (*)> (14)를 갖는"이 말에 "ORDER BY NumConsecutiveDays DESC"를 사용하거나 말할 수 ...
나는이 생각을 시험하지 않았다 - 단지 내 머리 위로 떨어져을 작성. 희망 SQL2005과에서 작동합니다.
... 그리고 매우 테이블 이름에 인덱스가 도움이 될 것입니다 (사용자 ID, 만든 날짜)
편집 : 오프셋을 예약어 밝혀, 그래서 내가 대신 TheOffset을 사용했다.
편집 : 사용 COUNT (*)에 대한 제안은 매우 유효합니다 - 나는 처음에 있음을 했어야하지만 정말 생각하지 않았습니다. 이전에는 DATEDIFF (하루 분 (에서 CreationDate), 최대 (에서 CreationDate))을 대신 사용 하였다.
롭
-
==============================
3.당신이 테이블 스키마를 변경할 수 있다면, 난 당신이에서 CreationDate로 끝나는 연속 일수로 설정하려는 테이블에 열 LongestStreak을 추가하는 게 좋을 것. 행이 현재의 날이 존재하지 않는 경우 모든 행이 전날이있는 경우, 그것은 당신이 이미하고있는 일에 (유사한 로그인 시간에 테이블을 업데이트하기 쉽습니다, 당신은 확인할 수 있습니다. true의 경우, 당신의 LongestStreak을 증가합니다 새 행, 그렇지 않으면, 당신은 1로 설정합니다)
당신이 테이블 스키마를 변경할 수 있다면, 난 당신이에서 CreationDate로 끝나는 연속 일수로 설정하려는 테이블에 열 LongestStreak을 추가하는 게 좋을 것. 행이 현재의 날이 존재하지 않는 경우 모든 행이 전날이있는 경우, 그것은 당신이 이미하고있는 일에 (유사한 로그인 시간에 테이블을 업데이트하기 쉽습니다, 당신은 확인할 수 있습니다. true의 경우, 당신의 LongestStreak을 증가합니다 새 행, 그렇지 않으면, 당신은 1로 설정합니다)
쿼리는이 열을 추가 한 후 명백 할 것이다 :
if exists(select * from table where LongestStreak >= 30 and UserId = @UserId) -- award the Woot badge.
-
==============================
4.의 라인을 따라 일부 잘 표현 SQL :
의 라인을 따라 일부 잘 표현 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); } } }
-
==============================
5.당신이되고 n 개의 행이 필요 연속을 통해 N 일 수 있다는 사실을 이용할 수있는 것 같다.
당신이되고 n 개의 행이 필요 연속을 통해 N 일 수 있다는 사실을 이용할 수있는 것 같다.
뭔가 같은 :
SELECT users.UserId, count(1) as cnt FROM users WHERE users.CreationDate > now() - INTERVAL 30 DAY GROUP BY UserId HAVING cnt = 30
-
==============================
6.하나의 SQL 쿼리 이렇게하면 너무 나에게 복잡하게 보인다. 저 두 부분으로이 답변을 분해 할 수 있습니다.
하나의 SQL 쿼리 이렇게하면 너무 나에게 복잡하게 보인다. 저 두 부분으로이 답변을 분해 할 수 있습니다.
-
==============================
7.이 당신에게 그렇게 중요하다면,이 이벤트를 소스 당신에게이 정보를 제공하기 위해 테이블을 구동한다. 필요가 모든 미친 쿼리 기계를 죽일 수 없습니다.
이 당신에게 그렇게 중요하다면,이 이벤트를 소스 당신에게이 정보를 제공하기 위해 테이블을 구동한다. 필요가 모든 미친 쿼리 기계를 죽일 수 없습니다.
-
==============================
8.당신은 재귀 CTE (SQL 서버 2005 +) 사용할 수 있습니다 :
당신은 재귀 CTE (SQL 서버 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
-
==============================
9.조 셀코는에 smarties에 대한 SQL이에 완전한 장 (그것을 실행 호출하고 시퀀스)가 있습니다. 나는 직장에 도착하면 그래서 ... 나는 실제로이 답변 해 드리겠습니다 집에서 그 책이 없습니다. (가정 역사 테이블은 dbo.UserHistory 불러 일의 수는 @Days를하다)
조 셀코는에 smarties에 대한 SQL이에 완전한 장 (그것을 실행 호출하고 시퀀스)가 있습니다. 나는 직장에 도착하면 그래서 ... 나는 실제로이 답변 해 드리겠습니다 집에서 그 책이 없습니다. (가정 역사 테이블은 dbo.UserHistory 불러 일의 수는 @Days를하다)
또 다른 리드 실행에 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
위는 그것으로,하지만 당신은 그냥 날짜보다 "실행"의 다른 정의가있을 때를위한 뇌 자극으로 왼쪽보다 더 열심히 가능성 WAY입니다.
-
==============================
10.SQL 서버 (N = 100 이하 가정) 2,012 옵션의 커플.
SQL 서버 (N = 100 이하 가정) 2,012 옵션의 커플.
;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
모두 사용자 당 하루에 최대 하나의 기록에서이 있다는 질문에 명시된 제약에 의존하고 있습니다.
-
==============================
11.이 같은?
이 같은?
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
-
==============================
12.나는 연속적으로 사이트에 액세스하는 사람들 식별하는 간단한 수학 속성을 사용했다. 이 속성은 처음 액세스와 지난 시간의 일의 차이가 액세스 테이블 로그 레코드의 수와 동일해야한다는 것입니다.
나는 연속적으로 사이트에 액세스하는 사람들 식별하는 간단한 수학 속성을 사용했다. 이 속성은 처음 액세스와 지난 시간의 일의 차이가 액세스 테이블 로그 레코드의 수와 동일해야한다는 것입니다.
여기에 내가 (이 아니라 다른 데시벨에서 작동한다) 오라클 DB에서 테스트하는 것이 SQL 스크립트는 다음과 같습니다
-- 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);
-
==============================
13.
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
우리는 자정에 시작되도록 (날짜로 변환 (CHAR (11), @StartDate, 113)) 문 캐스트는 날짜의 시간 부분을 제거합니다.
나는에서 CreationDate 및 사용자 ID 열을 인덱싱하는 것도 생각할 겁니다.
난 그냥이 당신에게 모든 사용자와 총 연속 일을 말하지 않을 것을 깨달았다. 사용자가 선택한 날짜에서 일의 세트 번호를 방문 한 것이다 그러나 당신을 말할 것이다.
개정 솔루션 :
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
나는이 문제를 확인했습니다 그것은 모든 사용자와 모든 날짜를 쿼리합니다. 그것은 스펜서의 1 (농담?) 솔루션을 기반으로하지만, 내 작동된다.
업데이트 : 두 번째 솔루션에서 처리 날짜를 향상시켰다.
-
==============================
14.이것은 당신이 원하는 일을해야하지만 테스트의 효율성에있는 데이터가 충분하지 않습니다. 컨볼 CONVERT / FLOOR 물건은 날짜 필드 오프 시간 부분을 제거하는 것이다. 당신은 SQL 서버 2008을 사용하는 경우 당신은 CAST (x.CreationDate AS 날짜)를 사용할 수 있습니다.
이것은 당신이 원하는 일을해야하지만 테스트의 효율성에있는 데이터가 충분하지 않습니다. 컨볼 CONVERT / FLOOR 물건은 날짜 필드 오프 시간 부분을 제거하는 것이다. 당신은 SQL 서버 2008을 사용하는 경우 당신은 CAST (x.CreationDate AS 날짜)를 사용할 수 있습니다.
DECLARE @Range as INT SET @Range = 10 SELECT DISTINCT UserId, CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, a.CreationDate))) FROM tblUserLogin a WHERE EXISTS (SELECT 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))) BETWEEN CONVERT(DATETIME, FLOOR(CONVERT(FLOAT, a.CreationDate))) and 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 ) ON [PRIMARY]
-
==============================
15.스펜서는 거의 그것을했다,하지만이 작업 코드를해야한다 :
스펜서는 거의 그것을했다,하지만이 작업 코드를해야한다 :
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
-
==============================
16.내 머리, MySQLish의 상단 꺼짐 :
내 머리, 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에 대한 몇 가지 변환을 필요로하지만 나는 몇 가지 아이디어를주고 있다고 생각합니다.
-
==============================
17.어떻게 탈리 테이블을 사용하여 하나 어떻습니까? 그것은 더 알고리즘 접근 방식을 따르며, 실행 계획은 바람이다. 테이블을 스캔 할 것을 1 'MaxDaysBehind'의 숫자로 tallyTable을 채우십시오 (예. 90, 뒤에 3개월 위해 등을 볼 것이다).
어떻게 탈리 테이블을 사용하여 하나 어떻습니까? 그것은 더 알고리즘 접근 방식을 따르며, 실행 계획은 바람이다. 테이블을 스캔 할 것을 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
-
==============================
18.미세 조정 빌의 쿼리 A를 비트. 당신은 하루에 단 하나의 로그인을 계산 그룹화하기 전에 날짜를 절단해야 할 수도 있습니다 ...
미세 조정 빌의 쿼리 A를 비트. 당신은 하루에 단 하나의 로그인을 계산 그룹화하기 전에 날짜를 절단해야 할 수도 있습니다 ...
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 대신 변환 (숯 (10)에서 CreationDate 101)의 DATEADD (DD, DATEDIFF (DD, 0에서 CreationDate), 0)를 사용한다.
@IDisposable 나는 날짜 부분 이전 버전을 사용하고자했지만 내가 대신 변환을 사용하는 거라고 생각 그래서 구문을 찾아 너무 게으른했다. 나는 그것이 큰 영향 감사했다 알고 힘! 지금은 알고있다.
-
==============================
19.같이가는 스키마를 가정 :
같이가는 스키마를 가정 :
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
from https://stackoverflow.com/questions/1176011/sql-to-determine-minimum-sequential-days-of-access by cc-by-sa and MIT license
'SQL' 카테고리의 다른 글
[SQL] 데이터베이스에 "이벤트 반복"표현하는 가장 좋은 방법은 무엇입니까? (0) | 2020.06.20 |
---|---|
[SQL] 어떻게 SQL을 사용하여 최근 24 시간 레코드를 선택하려면? (0) | 2020.06.20 |
[SQL] 문자열의 UPDATE 및 REPLACE는 부분 (0) | 2020.06.19 |
[SQL] 외래 키 제약 조건 : 때 ON UPDATE 및 DELETE ON을 사용하는 (0) | 2020.06.19 |
[SQL] 날짜를 구문 분석 후 오라클을 선택 문에서 수를 돌려줍니다 (0) | 2020.06.19 |