मेरे पास एक सारणी है जो प्रत्येक बार वेबपृष्ठ लोड होने पर उपयोगकर्ता आईडी, कोर्स, सत्रिड और अनुरोध दिनांक लॉग करता है। मैं एक दिए गए पाठ्यक्रम के लिए प्रति उपयोगकर्ता आईडी की अवधि को कैलकुलेट करना चाहता हूं। अतिव्यापी टाइमपैंस के कारण ऐसा करना समस्याग्रस्त है।लॉग तालिका से यात्रा अवधि के भाग की गणना करने के लिए एसक्यूएल क्वेरी
यहां प्रदान किया गया डेटा पाठ्यक्रम के लिए प्रति उपयोगकर्ता 10 मिनट की अवधि के परिणामस्वरूप होना चाहिए। मुझे यह अधिकार नहीं मिल रहा है।
CREATE TABLE PageLogSample (
id INT NOT NULL PRIMARY KEY IDENTITY
, userid INT
, courseid INT
, sessionid INT
, requestdate DATETIME
);
TRUNCATE TABLE PageLogSample;
INSERT INTO PageLogSample (userid, courseid, sessionid, requestdate)
-- [0, 10] = 10 minutes
SELECT 1, 1, 1, '00:00:00'
UNION ALL SELECT 1, 1, 1, '00:10:00'
-- [0, 12] - [3, 5] = 10 minutes
-- or ... [0, 3] + [5, 12] = 10 minutes
UNION ALL SELECT 2, 1, 2, '00:00:00'
UNION ALL SELECT 2, 2, 2, '00:03:00'
UNION ALL SELECT 2, 2, 2, '00:05:00'
UNION ALL SELECT 2, 1, 2, '00:12:00'
-- [0, 12] - [3, 5] = 10 minutes
-- or ... [0, 3] + [5, 12] = 10 minutes
UNION ALL SELECT 3, 1, 3, '00:00:00'
UNION ALL SELECT 3, 2, 3, '00:03:00'
UNION ALL SELECT 3, 2, 3, '00:05:00'
UNION ALL SELECT 3, 1, 3, '00:12:00'
UNION ALL SELECT 3, 2, 3, '00:15:00'
-- [1, 13] - [3, 5] = 10 minutes
-- or ... [1, 3] + [5, 13] = 10 minutes
UNION ALL SELECT 4, 2, 4, '00:00:00'
UNION ALL SELECT 4, 1, 4, '00:01:00'
UNION ALL SELECT 4, 2, 4, '00:03:00'
UNION ALL SELECT 4, 2, 4, '00:05:00'
UNION ALL SELECT 4, 1, 4, '00:13:00'
UNION ALL SELECT 4, 2, 4, '00:15:00'
-- [0, 5] + [10, 15] = 10 minutes
UNION ALL SELECT 5, 1, 5, '00:00:00'
UNION ALL SELECT 5, 1, 5, '00:05:00'
UNION ALL SELECT 5, 1, 6, '00:10:00'
UNION ALL SELECT 5, 1, 6, '00:15:00'
-- [0, 10] = 10 minutes (ignoring everything inbetween)
UNION ALL SELECT 6, 1, 7, '00:00:00'
UNION ALL SELECT 6, 1, 7, '00:03:00'
UNION ALL SELECT 6, 1, 7, '00:05:00'
UNION ALL SELECT 6, 1, 7, '00:07:00'
UNION ALL SELECT 6, 1, 7, '00:10:00'
-- [0, 11] - [5, 6] = 10 minutes
-- or ... [0, 3] + [7, 11] = 6 minutes (good)
-- or ... [0, 5] + [7, 11] = 9 minutes (better)
UNION ALL SELECT 7, 1, 8, '00:00:00'
UNION ALL SELECT 7, 1, 8, '00:03:00'
UNION ALL SELECT 7, 2, 8, '00:05:00'
UNION ALL SELECT 7, 2, 8, '00:06:00'
UNION ALL SELECT 7, 1, 8, '00:07:00'
UNION ALL SELECT 7, 1, 8, '00:11:00'
-- [0, 1] + [2, 4] + [5, 7] + [8, 13] = 10
UNION ALL SELECT 8, 1, 9, '00:00:00'
UNION ALL SELECT 8, 2, 9, '00:01:00'
UNION ALL SELECT 8, 1, 9, '00:02:00'
UNION ALL SELECT 8, 1, 9, '00:03:00'
UNION ALL SELECT 8, 2, 9, '00:04:00'
UNION ALL SELECT 8, 1, 9, '00:05:00'
UNION ALL SELECT 8, 1, 9, '00:06:00'
UNION ALL SELECT 8, 2, 9, '00:07:00'
UNION ALL SELECT 8, 1, 9, '00:08:00'
UNION ALL SELECT 8, 1, 9, '00:13:00'
;
पहले बेवकूफ दृष्टिकोण की कोशिश कर रहा है। यह सत्र के ओवरलैपिंग भागों के साथ गलती देता है।
DECLARE @courseid INT;
SET @courseid = 1;
SELECT subquery.userid
, COUNT(DISTINCT subquery.sessionid) AS sessioncount
, SUM(subquery.duration) AS duration
, CASE SUM(subquery.duration)
WHEN 10 THEN 'ok'
ELSE 'ERROR'
END
FROM (
SELECT userid
, sessionid
, DATEDIFF(MINUTE, MIN(requestdate), MAX(requestdate)) AS duration
FROM PageLogSample
WHERE courseid = @courseid
GROUP BY userid
, sessionid
) subquery
GROUP BY subquery.userid
ORDER BY subquery.userid;
-- userid sessioncount duration
-- 1 1 10 ok
-- 2 1 12 ERROR
-- 3 1 12 ERROR
-- 4 1 12 ERROR
-- 5 2 10 ok
दूसरा प्रयास। ओवरलैपिंग से बचें। यह केवल आंशिक रूप से काम करता है।
DECLARE @courseid INT;
SET @courseid = 1;
WITH cte (userid, courseid, sessionid, start, finish, duration)
AS (
SELECT userid
, courseid
, sessionid
, MIN(requestdate)
, MAX(requestdate)
, DATEDIFF(MINUTE, MIN(requestdate), MAX(requestdate))
FROM PageLogSample
GROUP BY userid
, courseid
, sessionid
)
SELECT naive.userid
, naive.sessioncount
, naive.duration AS naiveduration
, correction.duration AS correctionduration
, naive.duration - ISNULL(correction.duration, 0) AS duration
, CASE naive.duration - ISNULL(correction.duration, 0)
WHEN 10 THEN 'ok'
ELSE 'ERROR'
END
FROM (
SELECT cte.userid
, COUNT(DISTINCT cte.sessionid) AS sessioncount
, SUM(cte.duration) AS duration
FROM cte
WHERE cte.courseid = @courseid
GROUP BY cte.userid
) naive
LEFT JOIN (
SELECT errors.userid
, SUM(errors.duration) AS duration
FROM cte errors
WHERE errors.courseid <> @courseid
AND EXISTS (
SELECT *
FROM cte
WHERE cte.start <= errors.start
AND cte.finish >= errors.finish
AND cte.courseid = @courseid
)
GROUP BY errors.userid
) correction
ON naive.userid = correction.userid
;
-- userid sessioncount naiveduration correctionduration duration
-- 1 1 10 NULL 10 ok
-- 2 1 12 2 10 ok
-- 3 1 12 NULL 12 ERROR
-- 4 1 12 NULL 12 ERROR
-- 5 2 10 NULL 10 ok
अद्यतन: Ed Harpers comment सच मुझे मेरे दृष्टिकोण पर पुनर्विचार कर दिया।
तो यहां तीसरा परीक्षण आता है। यहां मैं पहली बार खोज करता हूं कि कौन सी पंक्तियां पाठ्यक्रम में प्रवेश द्वार का प्रतिनिधित्व करती हैं और जो किसी को छोड़कर प्रतिनिधित्व करती हैं। फिर मैं सभी अंतराल का योग लेता हूं और सभी शुरुआती दिनों के योग को घटा देता हूं। मुझे लगता है कि यह सही नहीं है, जबकि यह सही है।
DECLARE @courseid INT;
SET @courseid = 1;
WITH numberedcte (rn, id, userid, courseid, sessionid, requestdate)
AS (
SELECT ROW_NUMBER() OVER (PARTITION BY sessionid, userid ORDER BY id)
, id
, userid
, courseid
, sessionid
, requestdate
FROM PageLogSample
)
, typedcte (rowtype, id, userid, courseid, sessionid, requestdate, nextrequestdate)
AS (
SELECT CASE
WHEN previousrequest.courseid = nextrequest.courseid
THEN 'between'
WHEN previousrequest.courseid IS NULL
OR nextrequest.courseid = numberedcte.courseid
THEN 'begin'
WHEN nextrequest.courseid IS NULL
OR previousrequest.courseid = numberedcte.courseid
THEN 'end'
ELSE 'error?'
END AS rowtype
, numberedcte.id
, numberedcte.userid
, numberedcte.courseid
, numberedcte.sessionid
, numberedcte.requestdate
, nextrequest.requestdate
FROM numberedcte
LEFT JOIN numberedcte previousrequest
ON previousrequest.userid = numberedcte.userid
AND previousrequest.sessionid = numberedcte.sessionid
AND previousrequest.rn = numberedcte.rn - 1
LEFT JOIN numberedcte nextrequest
ON nextrequest.userid = numberedcte.userid
AND nextrequest.sessionid = numberedcte.sessionid
AND nextrequest.rn = numberedcte.rn + 1
WHERE numberedcte.courseid = @courseid
AND (
nextrequest.courseid = @courseid
OR previousrequest.courseid = @courseid
)
)
, beginsum (userid, value)
AS (
SELECT userid, SUM(DATEPART(MINUTE, requestdate))
FROM typedcte
WHERE rowtype = 'begin'
GROUP BY userid
)
, endsum (userid, value)
AS (
SELECT userid, SUM(DATEPART(MINUTE, ISNULL(nextrequestdate, requestdate)))
FROM typedcte
WHERE rowtype = 'end'
GROUP BY userid
)
SELECT beginsum.userid
, endsum.value - beginsum.value AS duration
FROM beginsum
INNER JOIN endsum
ON beginsum.userid = endsum.userid
;
यहां केवल समस्या यह है कि मैं सिर्फ अपने मूल नमूना डेटा से उपयोगकर्ता 1 और 5 के लिए आउटपुट प्राप्त है। जोड़ा गया उपयोगकर्ता 6 भी सही आउटपुट देता है। जोड़ा गया उपयोगकर्ता 7 अब मुझे एक संतोषजनक आउटपुट देता है। उपयोगकर्ता 8 लगभग सही है, मुझे पहली पंक्ति से दूसरी पंक्ति में एक मिनट याद आती है।
-- userid duration
-- 1 10
-- 5 10
-- 6 10
-- 7 9
-- 8 9
मुझे लगता है कि मैं इसे पूरी तरह से सही करने से इंच दूर हूं। गायब होने वाली एकमात्र अवधि पृष्ठ की जांच से होती है जो समूहों में नहीं होती है। क्या कोई अकेला पृष्ठदृश्य प्राप्त करने का तरीका ढूंढने में मेरी सहायता कर सकता है?
अद्यतन: यहां चौथा परीक्षण आता है। यहां मैं प्रत्येक अनुरोध के लिए एक मान आवंटित करता हूं और उन्हें जोड़ता हूं। यह मुझे आउटपुट को सटीक नहीं देता है, लेकिन ऐसा लगता है कि यह काफी अच्छा हो सकता है।
DECLARE @courseid INT;
SET @courseid = 1;
WITH numberedcte (rn, userid, courseid, sessionid, requestdate)
AS (
SELECT ROW_NUMBER() OVER (PARTITION BY sessionid, userid ORDER BY id)
, userid
, courseid
, sessionid
, requestdate
FROM PageLogSample
)
, valuecte (value, userid, courseid, sessionid)
AS (
SELECT CASE
--alone
WHEN (previousrequest.courseid IS NULL
OR previousrequest.courseid <> numberedcte.courseid
)
AND nextrequest.courseid <> numberedcte.courseid
THEN DATEDIFF(MINUTE, numberedcte.requestdate, nextrequest.requestdate)
--between
WHEN previousrequest.courseid = nextrequest.courseid
THEN 0
--begin
WHEN previousrequest.courseid IS NULL
OR nextrequest.courseid = numberedcte.courseid
THEN -1 * DATEPART(MINUTE, numberedcte.requestdate)
--ignored (end with no next request)
WHEN nextrequest.courseid IS NULL
AND previousrequest.courseid <> numberedcte.courseid
THEN 0
--end
WHEN nextrequest.courseid IS NULL
OR previousrequest.courseid = numberedcte.courseid
THEN DATEPART(MINUTE, ISNULL(nextrequest.requestdate, numberedcte.requestdate))
--impossible?
ELSE 0
END
, numberedcte.userid
, numberedcte.courseid
, numberedcte.sessionid
FROM numberedcte
LEFT JOIN numberedcte previousrequest
ON previousrequest.userid = numberedcte.userid
AND previousrequest.sessionid = numberedcte.sessionid
AND previousrequest.rn = numberedcte.rn - 1
LEFT JOIN numberedcte nextrequest
ON nextrequest.userid = numberedcte.userid
AND nextrequest.sessionid = numberedcte.sessionid
AND nextrequest.rn = numberedcte.rn + 1
WHERE numberedcte.courseid = @courseid
)
SELECT userid
, courseid
, COUNT(DISTINCT sessionid) AS sessioncount
, SUM(value) AS duration
FROM valuecte
GROUP BY userid
, courseid
ORDER BY userid
;
जैसा कि आप देख सकते हैं कि परिणाम पूरी तरह से अपेक्षित नहीं हैं।
-- userid courseid sessioncount duration
-- 1 1 1 10
-- 2 1 1 3
-- 3 1 1 6
-- 4 1 1 4
-- 5 1 2 10
-- 6 1 1 10
-- 7 1 1 9
-- 8 1 1 10
प्रदर्शन वास्तविक डेटाबेस की मेरी स्थानीय प्रति पर भयानक है। तो अगर किसी के पास विचारों को एक और अधिक प्रदर्शन करने के तरीके के रूप में लिखने के लिए है ... शूट करें।
अद्यतन: प्रदर्शन ऊपर है। मैंने एक इंडेक्स जोड़ा और यह अब एक आकर्षण काम करता है।
अच्छा प्रश्न - स्क्रिप्ट और नमूना के संदर्भ में इसे हल करना आसान बनाता है। – Andrew
डेटा के साथ कठिनाई यह है कि अनुरोधकर्ता का कोई निरंतर अर्थ नहीं है। कभी-कभी प्रारंभ समय और कभी-कभी पाठ्यक्रम का अंत समय होता है। –
अच्छे प्रश्न के लिए अच्छी टिप्पणी। –