2011-01-04 11 views
9

Hola gente, siento que es un poco de una cuestión ya ...¿Cómo promedié la diferencia entre valores específicos en TSQL?

Tengo una tabla con las siguientes columnas:

[ChatID] [usuario] [identificación de registro] [CreatedOn] [Texto]

Lo que necesito encontrar es el tiempo de respuesta promedio para una identificación de usuario determinada, a otra identificación de usuario específica. Por lo tanto, si mis datos se parece a:

[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] 

... entonces tengo que ver que Susan tiene un tiempo medio de respuesta (20 + 6)/2 => 13 segundos a John, y John tiene un promedio de (9/1) => 9 segundos para Susan.

Ni siquiera estoy seguro de que pueda se haga en lógica basada en conjuntos, pero si alguien tiene alguna idea, ¡sería muy apreciada!

+0

mucho más fácil hacerlo establece en función con '' lag' y lead' creo. [Vote por eso aquí] (https://connect.microsoft.com/SQLServer/feedback/details/254388/over-clause-enhancement-request-lag-and-lead-functions) –

+0

¿Existe un límite en el número de usuarios dentro de un contexto ChatID? es decir, para un solo ChatID, ¿solo habrá dos usuarios (como su ejemplo) o puede haber un número ilimitado de usuarios? – chezy525

+0

Puede haber más de 2, el ejemplo fue simplificado. – jvenema

Respuesta

7

no tengo un PC para verificar la sintaxis ni nada, pero creo que esto debe darle un punto de partida:

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 

En esencia, la idea es añadir números de fila de los datos para que pueda une una fila a la siguiente fila (para que tengas una declaración seguida de su respuesta inmediata). Una vez que haya unido las filas, puede obtener el tiempo transcurrido entre las dos entradas, y luego agrupar los datos mediante el ChatID (supongo que los tiempos entre los chats separados no son relevantes) y los dos usuarios. Como he dicho, sin embargo, esto es sólo un punto de partida como estoy seguro de que puede haber algunos criterios adicionales y/o errores en mi consulta :)

+1

Vaya, acabo de editar mi SQL y el original puede ser mejor; Me iré como está y podrás ver el historial de edición para ver la otra consulta. La consulta actual le dirá que John promedia xx segundos para responder a cualquier persona, donde la versión anterior indicaba promedios de juan xx segundos para responder a los promedios de susan y susa y segundos para responder a juan, etc. (que suena más como lo que solicitó originalmente) . –

+0

Resulta que mi pregunta era incorrecta (quería que el tiempo de respuesta de John fuera relativo al * primer * mensaje de Susan, así que 43/2 segundos, pero dado que la respuesta original aquí realmente respondió la pregunta (bastante bien) podría agregar), obtienes los puntos :). – jvenema

+0

+1 \ * slap-on-the-brow \ * Chris Shaffer tiene la mejor respuesta, por qué estoy tan empeñado en aprovechar la lógica de mi otra respuesta http://stackoverflow.com/questions/1610599/how -can-i-do-a-contiguous-group-by-in-mysql/1611246 # 1611246 a esta respuesta –

1

Esto se puede hacer con RowNumber() y 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

Parece que necesita un cursor para recorrer cada línea y comprobar el cambio de usuario en el registro, obtener la diferencia de ese momento y almacenarla en algún lugar (tabla temporal tal vez) y agregarla más tarde.

creo que se puede hacer en TSQL, la lógica sería algo así como:

 

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 

Estoy seguro de que puede encontrar la manera de declarar todos los parámetros.

+0

+1 Esto tiene la ventaja de un escaneo a través de los datos. Creo que la sintaxis es un poco escasa. Definitivamente necesita un 'Order By' en la selección. –

+0

No se recomiendan los cursores en SQL. –

+0

@Chris: generalmente no, pero a veces son la mejor solución a menos que o hasta que Microsoft implemente por completo la cláusula 'OVER'. Ejecutar totales es un ejemplo clásico. [Algunas comparaciones de rendimiento aquí] (http://tsql.solidq.com/OVER_Clause_and_Ordered_Calculations.doc) No hay idea de cuál sería la más eficiente en este caso en particular (cursores vs unirse a rownumber = número de piso + 1) –

3

Pruebe algo simple como el siguiente antes de pasar a los cursores.

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

Éste funciona y no implica el uso de cursores. Si tuviera más tiempo, probablemente podría incluso eliminar la tabla temporal, pero bueno ... funciona.

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

No piense que esto producirá el conjunto de datos que OP está buscando. – suhprano

+0

De alguna manera sabía que el primero no funcionaría, simplemente sugería que las declaraciones simples de selección deberían considerarse primero antes de usar los cursores. Probablemente debería haber agregado eso como un comentario en lugar de una solución, así que seguí adelante e implementé una solución aproximada. –

1

Prueba esto: datos

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') 

muestra:

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

de salida:

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  |   |   |  |      | 

La agrupación:

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 

Salida:

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) 

El resultado agrupado:

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 

de salida:

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

de Chat y respuestas:

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 

Salida: tiempo de respuesta promedio

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) 

de chat:

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 

Salida:

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) 

Will trabajar fuera de la caja en PostgreSQL. Para que funcione en el Servidor Sql, simplemente cambie el response_created_on - responded_to_timestamp a la construcción correspondiente Sql Server DATEDIFF (no puedo recordar la parte superior de mi cabeza ¿cuál es el DATEDIFF por segundos)

1

Esto hará el trabajo, pero no estoy seguro cómo se va a escalar:

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 registro (s) seleccionado [Fetch metadatos: 0 ms] [Recuperar datos: 0 ms]

debería estar bien con un índice en (chat_id, log_id).

Si desea eliminar las mismas respuestas, todo lo que necesita es a = en el exterior, donde la cláusula:

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 
Cuestiones relacionadas