2011-01-04 13 views
9

Hej ludzie, przepraszam, to jest trochę dłuższy pytanie ...Jak wycenić różnicę między określonymi wartościami w TSQL?

Mam tabelę z następującymi kolumnami:

[ChatID] [User] [LogID] [createdon] [Tekst]

Co muszę znaleźć, to średni czas odpowiedzi dla danego identyfikatora użytkownika, do innego określonego identyfikatora użytkownika. Tak więc, jeśli moje dane wygląda następująco:

[1] [john] [20] [1/1/11 3:00:00] [Hello] 
[1] [john] [21] [1/1/11 3:00:23] [Anyone there?] 
[1] [susan] [22] [1/1/11 3:00:43] [Hello!] 
[1] [susan] [23] [1/1/11 3:00:53] [What's up?] 
[1] [john] [24] [1/1/11 3:01:02] [Not much] 
[1] [susan] [25] [1/1/11 3:01:08] [Cool] 

... to muszę zobaczyć, że Susan ma średni czas reakcji (20 + 6)/2 => 13 sekund do Jana i John ma średnio (9/1) => 9 sekund do Susan.

Nie jestem nawet pewien, czy ten może być zrobiony w logice opartej na zestawie, ale jeśli ktokolwiek ma jakieś pomysły, byłby bardzo ceniony!

+0

wiele łatwiej zrobić ustalonym w oparciu o '' lead' lag' i myślę. [Głosuj na to tutaj] (https://connect.microsoft.com/SQLServer/feedback/details/254388/over-clause-enhancement-request-lag-and-lead-functions) –

+0

Czy istnieje ograniczenie liczby użytkownicy w kontekście ChatID? np. w przypadku pojedynczego identyfikatora ChatID będzie tylko dwóch użytkowników (np. Twój przykład) lub może być nieograniczona liczba użytkowników? – chezy525

+0

Może być więcej niż 2, przykład został uproszczony. – jvenema

Odpowiedz

7

nie mam komputera w celu sprawdzenia składni ani nic, ale myślę, że to powinno dać ci miejsce wywoławcza:

WITH ChatWithRownum AS (
    SELECT ChatID, User, LogID, CreatedOn, ROW_NUMBER() OVER(ORDER BY ChatID, CreatedOn) AS rownum 
    FROM ChatLog 
) 
SELECT First.ChatID, Second.User, 
    AVG(DATEDIFF(seconds, First.CreatedOn, Second.CreatedOn)) AS AvgElapsedTime 
FROM ChatWithRownum First 
    JOIN ChatWithRownum Second ON First.ChatID = Second.ChatID 
     AND First.rownum = Second.rownum - 1 
WHERE First.User != Second.User 
GROUP BY First.ChatID, Second.User 

Zasadniczo chodzi o to, aby dodać numery wierszy danych, dzięki czemu można dołącz jeden wiersz do następnego wiersza (tak, że masz instrukcję, po której następuje natychmiastowa odpowiedź). Po połączeniu wierszy można uzyskać czas, jaki upłynął między tymi dwoma wpisami, a następnie pogrupować dane według identyfikatora ChatID (zakładam, że czasy między oddzielnymi rozmowami nie są istotne) i dwóch użytkowników. Tak jak mówiłem, choć jest to tylko miejsce już jestem pewien, że mogą występować pewne dodatkowe kryteria i/lub błędów w moim zapytaniu :)

+1

Ups, właśnie edytowałem mój kod SQL, a oryginał może być lepszy; Opuszczę tak, jak jest i możesz wyświetlić historię edycji, aby zobaczyć inne zapytanie. Aktualne zapytanie powie Ci, że John średnio xx sekund, aby odpowiedzieć każdemu, gdzie poprzednia wersja powiedziałaby, że John średnio xx sekund, aby odpowiedzieć na susan i Susan średnie yy sekundę, aby odpowiedzieć na John, itp. (Co brzmi bardziej jak to, co pierwotnie wnioskował) . –

+0

Okazało się, że moje pytanie było niedokładne (chciałem, aby czas odpowiedzi dla Johna był względny do * pierwszego * komunikatu od Susan, więc 43/2 sekundy, ale ponieważ oryginalna odpowiedź tutaj rzeczywiście odpowiedziała na pytanie (całkiem zgrabnie może dodać), dostajesz punkty :). – jvenema

+0

+1 \ * slap-on-the-brew \ * Chris Shaffer ma najlepszą odpowiedź, dlaczego jestem tak pochłonięty logiką mojej drugiej odpowiedzi http://stackoverflow.com/questions/1610599/how -can-i-do-a-sąsiedni-group-by-in-mysql/1611246 # 1611246 na tę odpowiedź –

1

można to zrobić z RowNumber() i DateDiff()

WITH TableWithOrderings AS (
    SELECT DateTime, ROW_NUMBER() OVER (ORDER BY DateTime) AS Ordering 
    FROM myTable 
) 

WITH Intervals As (
    SELECT DATEDIFF(second, A.DateTime, B.DateTime) AS IntervalSeconds 
    FROM TableWithOrderings A 
     INNER JOIN TableWithOrderings B ON B.Ordering = A.Ordering + 1 
) 

SELECT AVG(IntervalSeconds) FROM Intervals 
2

Wygląda na to, że potrzebujesz kursora, aby przejść przez każdą linię i sprawdzić zmianę użytkownika w rekordzie, uzyskać różnicę tego czasu i zapisać go gdzieś (może to być tabela tymczasowa), a następnie zagregować ją później.

wierzę, można to zrobić w TSQL, logika byłoby coś jak:

 

DECLARE delta CURSOR FOR 
SELECT user, createdon from table 
order by createdon --or logid 

OPEN delta 
fetch next from delta into @username, @time 
while @@fetch_status = 0 

begin 

FETCH PRIOR FROM delta into @username_prior, @time_prior 
IF @username_prior @username 
BEGIN 
    @timedelta = @time - @time_prior 
    @total = @total + @timedelta 
    insert into #average (@username, @total) 
END 

fetch next from delta into @username, @time 
END 

CLOSE delta 
DEALLOCATE delta 

SELECT user, AVG(time) from #average 
group by user 

Jestem pewien, że można dowiedzieć się w jaki sposób zadeklarować wszystkie parametry.

+0

+1 Ma to tę zaletę, że skanuje dane. Myślę, że składnia jest nieco wyłączona. Zdecydowanie potrzebuje "Zamów przez" na wybierz. –

+0

Kursory w SQL nie są zalecane. –

+0

@Chris - Generalnie nie, ale czasami są najlepszym rozwiązaniem, chyba że Microsoft w pełni wdroży klauzulę 'OVER'. Liczby bieżące są klasycznym przykładem. [Niektóre porównania wydajności tutaj] (http://tsql.solidq.com/OVER_Clause_and_Ordered_Calculations.doc) Bez pomysłu, który byłby najbardziej skuteczny w tym konkretnym przypadku (kursory kontra łączenie na rownumber = rownumber + 1) –

3

Spróbuj czegoś prostego, jak poniżej, zanim przejdziesz do kursorów.

select ChatId, User, datediff('second', min(CreatedOn, max(CreatedOn))/count(*) 
from ChatLog 
group by ChatId, User 

Ten działa i nie wymaga użycia kursorów. Gdybym miał więcej czasu, mógłbym prawdopodobnie wyeliminować tymczasowy stół, ale hej ... to działa.

declare @operator varchar(50) 
set @operator = 'john' 
declare @customer varchar(50) 
set @customer = 'susan' 
declare @chatid int 
set @chatid = 1 

declare @t table (chatid int, username varchar(50), responsetime int) 

insert @t (chatid, username, responsetime) 
select ChatId, 
    Username, 
    datediff(second, 
    CreatedOn, 
    (
     select min(createdon) 
     from chatlog 
     where createdon > cl.createdon 
     and username = @customer 
       and chatid = @chatid 
    )) 
from ChatLog cl 
where chatid = @chatid and username = @operator 

insert @t (chatid, username, responsetime) 
select ChatId, 
    Username, 
    datediff(second, 
    CreatedOn, 
    (
     select min(createdon) 
     from chatlog 
     where createdon > cl.createdon 
     and username = @operator 
       and chatid = @chatid 
    )) 
from ChatLog cl 
where chatid = @chatid and username = @customer 

select chatid, username, avg(responsetime) as avgresponsetime 
from @t 
group by chatid, username 
order by username 
+0

Nie myśl, że to przyniesie zestaw danych, którego szuka OP. – suhprano

+0

W pewnym sensie wiedziałem, że pierwszy nie zadziała, po prostu sugeruję, że proste instrukcje wyboru powinny być najpierw rozważone przed użyciem kursorów. Prawdopodobnie powinienem dodać to jako komentarz, zamiast rozwiązania, więc poszłam do przodu i zaimplementowałam szorstkie rozwiązanie. –

1

Spróbuj tego: dane

create table chats 
(
chat_id int not null, 
user_name text not null, 
log_id int not null primary key, 
created_on timestamp not null, 
message text not null 
); 


insert into chats(chat_id, user_name, log_id, created_on, message) 
values(1, 'john', 20, '1/1/11 3:00:00', 'Hello'), 
(1, 'john',21, '1/1/11 3:00:23', 'Anyone there?'), 
(1, 'susan',22, '1/1/11 3:00:43', 'Hello!'), 
(1, 'susan', 23, '1/1/11 3:00:53', 'What''s up?'), 
(1, 'john', 24, '1/1/11 3:01:02', 'Not much'), 
(1, 'susan', 25, '1/1/11 3:01:08', 'Cool') 

Próbka:

select c.*, 'x', next.* 
from chats c 
left join chats next on next.log_id = c.log_id + 1 
order by c.log_id 

wyjściowa:

chat_id | user_name | log_id |  created_on  | message | ?column? | chat_id | user_name | log_id |  created_on  | message  
---------+-----------+--------+---------------------+---------------+----------+---------+-----------+--------+---------------------+--------------- 
     1 | john  |  20 | 2011-01-01 03:00:00 | Hello   | x  |  1 | john  |  21 | 2011-01-01 03:00:23 | Anyone there? 
     1 | john  |  21 | 2011-01-01 03:00:23 | Anyone there? | x  |  1 | susan  |  22 | 2011-01-01 03:00:43 | Hello! 
     1 | susan  |  22 | 2011-01-01 03:00:43 | Hello!  | x  |  1 | susan  |  23 | 2011-01-01 03:00:53 | What's up? 
     1 | susan  |  23 | 2011-01-01 03:00:53 | What's up? | x  |  1 | john  |  24 | 2011-01-01 03:01:02 | Not much 
     1 | john  |  24 | 2011-01-01 03:01:02 | Not much  | x  |  1 | susan  |  25 | 2011-01-01 03:01:08 | Cool 
     1 | susan  |  25 | 2011-01-01 03:01:08 | Cool   | x  |   |   |  |      | 

Grupowanie:

select c.*, 'x', next.*, count(case when next.user_name is null or next.user_name <> c.user_name then 1 end) over(order by c.log_id) 
from chats c 
left join chats next on next.log_id + 1 = c.log_id 
order by c.log_id 

wyjściowa:

chat_id | user_name | log_id |  created_on  | message | ?column? | chat_id | user_name | log_id |  created_on  | message | count 
---------+-----------+--------+---------------------+---------------+----------+---------+-----------+--------+---------------------+---------------+------- 
     1 | john  |  20 | 2011-01-01 03:00:00 | Hello   | x  |   |   |  |      |    |  1 
     1 | john  |  21 | 2011-01-01 03:00:23 | Anyone there? | x  |  1 | john  |  20 | 2011-01-01 03:00:00 | Hello   |  1 
     1 | susan  |  22 | 2011-01-01 03:00:43 | Hello!  | x  |  1 | john  |  21 | 2011-01-01 03:00:23 | Anyone there? |  2 
     1 | susan  |  23 | 2011-01-01 03:00:53 | What's up? | x  |  1 | susan  |  22 | 2011-01-01 03:00:43 | Hello!  |  2 
     1 | john  |  24 | 2011-01-01 03:01:02 | Not much  | x  |  1 | susan  |  23 | 2011-01-01 03:00:53 | What's up? |  3 
     1 | susan  |  25 | 2011-01-01 03:01:08 | Cool   | x  |  1 | john  |  24 | 2011-01-01 03:01:02 | Not much  |  4 
(6 rows) 

zgrupowanych wynik:

with grouped_result as 
(
select c.log_id, c.user_name, count(case when next.user_name is null or next.user_name <> c.user_name then 1 end) over(order by c.log_id) as the_grouping 
from chats c 
left join chats next on next.log_id + 1 = c.log_id 
order by c.log_id 
) 
select user_name, max(log_id) as last_chat_of_each_user 
from grouped_result 
group by the_grouping 
    ,user_name 
order by last_chat_of_each_user 

wyjścia:

user_name | last_chat_of_each_user 
-----------+------------------------ 
john  |      21 
susan  |      23 
john  |      24 
susan  |      25 
(4 rows) 

Chat i odpowiedzi:

with grouped_result as 
(
select c.log_id, c.user_name, count(case when next.user_name is null or next.user_name <> c.user_name then 1 end) over(order by c.log_id) as the_grouping 
from chats c 
left join chats next on next.log_id + 1 = c.log_id 
order by c.log_id 
), 
last_chats as 
(
select user_name as responded_to, max(log_id) as last_chat_of_each_user 
from grouped_result 
group by the_grouping 
    ,responded_to 
) 
select lc.responded_to, lc.last_chat_of_each_user as responded_to_log_id, lc_the_chat.created_on as responded_to_timestamp, 'x', answered_by.user_name as responded_by, answered_by.created_on as response_created_on 
from last_chats lc 
join chats lc_the_chat on lc_the_chat.log_id = lc.last_chat_of_each_user 
join chats answered_by on answered_by.log_id = lc.last_chat_of_each_user + 1 
order by lc.last_chat_of_each_user 

wyjścia: odpowiedź średni czas

responded_to | responded_to_log_id | responded_to_timestamp | ?column? | responded_by | response_created_on 
--------------+---------------------+------------------------+----------+--------------+--------------------- 
john   |     21 | 2011-01-01 03:00:23 | x  | susan  | 2011-01-01 03:00:43 
susan  |     23 | 2011-01-01 03:00:53 | x  | john   | 2011-01-01 03:01:02 
john   |     24 | 2011-01-01 03:01:02 | x  | susan  | 2011-01-01 03:01:08 
(3 rows) 

rozmowy:

with grouped_result as 
(
select c.log_id, c.user_name, count(case when next.user_name is null or next.user_name <> c.user_name then 1 end) over(order by c.log_id) as the_grouping 
from chats c 
left join chats next on next.log_id + 1 = c.log_id 
order by c.log_id 
), 
last_chats as 
(
select user_name as responded_to, max(log_id) as last_chat_of_each_user 
from grouped_result 
group by the_grouping 
    ,responded_to 
), 
responses as 
(
select lc.responded_to, lc.last_chat_of_each_user as responded_to_log_id, lc_the_chat.created_on as responded_to_timestamp, answered_by.user_name as responded_by, answered_by.created_on as response_created_on 
from last_chats lc 
join chats lc_the_chat on lc_the_chat.log_id = lc.last_chat_of_each_user 
join chats answered_by on answered_by.log_id = lc.last_chat_of_each_user + 1 
order by lc.last_chat_of_each_user 
) 
select responded_by, responded_to, sum(response_created_on - responded_to_timestamp), count(*), avg(response_created_on - responded_to_timestamp) as average_response_to_person 
from responses 
group by responded_by, responded_to 

wyjściowa:

responded_by | responded_to | sum | count | average_response_to_person 
--------------+--------------+----------+-------+---------------------------- 
susan  | john   | 00:00:26 |  2 | 00:00:13 
john   | susan  | 00:00:09 |  1 | 00:00:09 
(2 rows) 

ułoży-of-the box na PostgreSQL. Aby to działało na Sql Server, po prostu zmień response_created_on - responded_to_timestamp na odpowiedni konstrukt DATEDIFF serwera Sql (nie mogę przypomnieć sobie z góry, co to jest DATEDIFF na sekundy)

1

To dostanie zadanie, ale nie jestem pewien jak to skala:

select spoke, responded, count(*) responses, avg(time_diff) avg_seconds from (
select a.user_name spoke, b.user_name responded, a.created_on spoke_at, min(b.created_on) responded_at, datediff(ss, a.created_on, min(b.created_on)) time_diff 
from chats a, chats b 
where a.chat_id = b.chat_id 
and a.log_id < b.log_id 
and not exists (select 1 from chats c where c.chat_id = a.chat_id and c.log_id < b.log_id and c.log_id > a.log_id) 
group by a.user_name, b.user_name, a.created_on 
) users group by spoke, responded 

spoke  responded  responses  avg_seconds  
-------- ------------ ------------ -------------- 
john  john   1    23    
susan  john   1    9    
john  susan   2    13    
susan  susan   1    10 

4 rekordu (ów) wybranych [Pobiera Metadane: 0 ms] [pobrać dane: 0ms]

powinno być dobrze z indeksu (chat_id, log_id).

Jeśli chcesz, aby wyeliminować te same reakcje, wszystko czego potrzebujesz to = w zewnętrznym gdzie klauzuli:

select spoke, responded, count(*) responses, avg(time_diff) avg_seconds from (
select a.user_name spoke, b.user_name responded, a.created_on spoke_at, min(b.created_on) responded_at, datediff(ss, a.created_on, min(b.created_on)) time_diff 
from chats a, chats b 
where a.chat_id = b.chat_id 
and a.log_id < b.log_id 
and not exists (select 1 from chats c where c.chat_id = a.chat_id and c.log_id < b.log_id and c.log_id > a.log_id) 
group by a.user_name, b.user_name, a.created_on 
) users 
where spoke != responded 
group by spoke, responded 
Powiązane problemy