Problems with an SQL Query - sql

I need help with an sql table.
The table contains the following columns:
EventID, PersonID, EventType, EventTime(datetime), and a few other less significant columns.
Lets say that the two main event types are opening a door and closing a door.
I need the time duration(in seconds) between the same person opening a door and closing it or him opening a door and opening another one.(this of course allows sequences of simply opening doors)
Just to be clear if person 1 opened a door and person 2 closed a door no rows should be returned from the query.
I would like for it to be efficient but that isn't a must.
I'm using the 2008 SQL microsoft server(SQLEXPRESS)
Here is an example of a table:
EventID | PersonID | EventType | EventDate | PreviousDoor | CurrentDoor
1 | 1 | 1 | 12/10/2010 12:00:01.024 | 0 | 23
2 | 1 | 2 | 12/10/2010 12:05:40.758 | 23 | 0
3 | 2 | 1 | 12/10/2010 12:12:05.347 | 0 | 12
4 | 1 | 1 | 12/10/2010 12:50:12.142 | 0 | 23
5 | 2 | 2 | 12/10/2010 13:00:06.468 | 12 | 23
6 | 3 | 1 | 13/10/2010 13:00:06.468 | 0 | 23
EventType: 1(Opened door), 2(Closed door)
Result should be:
EventID | PersonID | EventType | EventDate | SecondsDifference
1 | 1 | 1 | 12/10/2010 12:00:01.024 | 339
3 | 2 | 1 | 12/10/2010 12:12:05.347 | 2881
I could really use your guys help.
Thanks in advance.

I think this should do it:
SELECT p1.EventID,
p1.PersonID,
p1.EventType,
p1.EventDate,
DATEDIFF(SECOND, p1.EventDate, p2.EventDate) AS SecondsDifference
FROM [Event] p1
LEFT JOIN [Event] p2 --Left join to self returning only closed door events
ON p2.PersonID = p1.PersonID
AND p2.EventType = 2 -- Closed Door
AND p1.EventID < p2.EventID --We don't want to bring back events that happened before the next event
WHERE p2.EventID IS NOT NULL --We don't want to show any people that have not closed a door

Try something like this:
select t1.EventID, t1.PersonID, t1.EventType, t1.EventDate, datediff(second, t1.EventDate, t2.EventDate) as 'SecondsDifference'
from [Event] t1
inner join [Event] t2 on t2.PersonID = t1.PersonID and t2.EventType = 2 and t2.PreviousDoor = t2.CurrentDoor and t2.EventID < t1.EventID
where t1.EventType = 1

Will using ROW_NUMBER and PARTITION help?
I'm not sure if the following is a legal SQL statement so please consider it as semi pseudo code.
SELECT *,
ROW_NUMBER() OVER(PARTITION BY PersonID ORDER BY EventTime) AS RowNumber,
datediff(seconds, t2.EventTime, t1.EventTime) AS SecondsDiff
FROM Events t1
INNER JOIN
SELECT *,
ROW_NUMBER() OVER(PARTITION BY PersonID ORDER BY EventTime) AS RowNumber
From Events t2
ON t1.RowNumber + 1 = t2.RowNumber
AND t1.PersonID = t2.PersonID AND t1.EventType = 1
AND (t2.EventType = 1 OR t2.EventType = 2)

Related

How to get count from one table which is mutually dependent to another table

I have two table
Let's name as first table: QC_Meeting_Master
Second table: QC_Project_Master I want to calculate count of problems_ID Which is mutually depend on second table
ID | QC_ID | Problems_ID |
___|_______|_____________|
1 | 1 | 2 |
2 | 1 | 7 |
ID | QC_ID | Problem_ID |
___|_______|_____________|
1 | 1 | 7 |
2 | 1 | 7 |
3 | 1 | 7 |
4 | 1 | 7 |
5 | 1 | 2 |
6 | 1 | 2 |
7 | 1 | 2 |
select COUNT(Problem_ID) from [QC_Project_Master] where Problem_ID in
(select Problems_ID from QC_Meeting_Master QMM join QC_Project_Master QPM on QMM.Problems_ID = QPM.Problem_ID)
I have to calculate Count of QC_Project_Master (problem_ID) on basis of QC_Meeting_Master (Problems_ID)
it means for first table: QC_Meeting_Master(Problems_ID) = 2,
then count should be 3
And for Second table: QC_Project_Master (Problems_ID) = 7,
then count should be 4
use conditional aggregation
select sum(case when t2.Problem_ID=2 then 1 else 0 end),
sum(case when t2.Problem_ID=7 then 1 else 0 end) from
table1 t1 join table2 t2 on t1.QC_ID=t2.QC_ID and t1.Problems_ID=t2.Problems_ID
if you need all the group count then use below
select t2.QC_ID,t2.Problems_ID, count(*) from
table1 t1 join table2 t2
on t1.QC_ID=t2.QC_ID and t1.Problems_ID=t2.Problems_ID
group by t2.QC_ID,t2.Problems_ID
As far as I understood your problem this is simple aggregation and JOIN as below:
SELECT mm.QC_ID, mm.Problem_ID, pm.cnt
FROM QC_Meeting_Master mm
INNER JOIN
(
SELECT QC_ID, Problem_ID, COUNT(*) cnt
FROM QC_Project_Master
GROUP BY QC_ID, Problem_ID
) pm
ON pm.QC_ID = mm.QC_ID AND pm.Problem_ID = mm.Problem_ID;

Efficient ROW_NUMBER increment when column matches value

I'm trying to find an efficient way to derive the column Expected below from only Id and State. What I want is for the number Expected to increase each time State is 0 (ordered by Id).
+----+-------+----------+
| Id | State | Expected |
+----+-------+----------+
| 1 | 0 | 1 |
| 2 | 1 | 1 |
| 3 | 0 | 2 |
| 4 | 1 | 2 |
| 5 | 4 | 2 |
| 6 | 2 | 2 |
| 7 | 3 | 2 |
| 8 | 0 | 3 |
| 9 | 5 | 3 |
| 10 | 3 | 3 |
| 11 | 1 | 3 |
+----+-------+----------+
I have managed to accomplish this with the following SQL, but the execution time is very poor when the data set is large:
WITH Groups AS
(
SELECT Id, ROW_NUMBER() OVER (ORDER BY Id) AS GroupId FROM tblState WHERE State=0
)
SELECT S.Id, S.[State], S.Expected, G.GroupId FROM tblState S
OUTER APPLY (SELECT TOP 1 GroupId FROM Groups WHERE Groups.Id <= S.Id ORDER BY Id DESC) G
Is there a simpler and more efficient way to produce this result? (In SQL Server 2012 or later)
Just use a cumulative sum:
select s.*,
sum(case when state = 0 then 1 else 0 end) over (order by id) as expected
from tblState s;
Other method uses subquery :
select *,
(select count(*)
from table t1
where t1.id < t.id and state = 0
) as expected
from table t;

Check the first value according to first date

I have two tables
guid | id | Status
-----| -----| ----------
1 | 123 | 0
2 | 456 | 3
3 | 789 | 0
The other table is
id | modified date | Status
------| --------------| ----------
1 | 26-08-2017 | 3
1 | 27-08-2017 | 0
1 | 01-09-2017 | 0
1 | 02-09-2017 | 0
2 | 26-08-2017 | 3
2 | 01-09-2017 | 0
2 | 02-09-2017 | 3
3 | 01-09-2017 | 0
3 | 02-09-2017 | 3
3 | 03-09-2017 | 0
Every time the status in the first table changes for each id it also modifies date and status in second table.Like for id 1 status was changed 4 times.
I want to select those ids by joining both tables whose value of status was 0 in its first modified date.
In this example it should return only id 3 because only id 3 has a status value as 0 on it first modified date 01-09-2017.Ids 1& 2 have value 3 in their first modified date.
Any help
Try using below(Assuming first table as A and second table as B):
;with cte as (
Select a.id, b.Status, row_number() over(partition by a.id order by [modified date] asc) row_num
from A
inner join B
on a.id = b.id
)
Select * from cte where
status = 0 and row_num = 1
Think this will do what your looking for.
WITH cte
AS (SELECT id
, ROW_NUMBER() OVER (PARTITION BY (id) ORDER BY [modified date]) RN
, Status
FROM SecondTable
)
SELECT *
FROM FirstTable
JOIN cte ON FirstTable.id = cte.id
AND RN = 1
WHERE cte.Status = 0
Just expand out the * and return what fields you need.

SQL Outer Join to show rows with no data

Need some help to get out some data and im stuck in the join swamp. I run on a MS SQL Server.
I have a list of clients in one table (clientgroup)
clientgroup
Groupid | clientid
1 | 11
1 | 12
1 | 13
1 | 14
1 | 15
And I have another table where I have if the clientid has some clientactivity
clientcontactlog
Logid | clientid
1 | 11
2 | 14
3 | 15
4 | 11
5 | 11
6 | 11
Then I have another table with info about the clientactivity
contactlog
Logid | logtype | logdate | logtext
1 | 1 | ’2016-05-16’ | ’Toys’
2 | 1 | ’2016-05-16’ | ’Toys’
3 | 1 | ’2016-05-16’ | ’Toys’
4 | 2 | ’2016-05-17’ | ’Lunch’
5 | 2 | ’2016-05-18’ | ’Dinner on Mars’
6 | 1 ! ’2016-05-19’ | ’Dinner on Mars’
I now want to make a mothly statistic (sum on logtypes in contactlog) about this and include all clients I have in my client list with id 1. So the output also shows the clients that have no record clientcontactlog. This is what I need help with.. I get all the data where we have input but I also need to show 0 on the clients that has no record.
Output should be
Clientname | sum(logtype1)
Client11 | 2
Client12 | 0
Client13 | 0
Client14 | 1
Client15 | 1
Thanks for input and help
Pretty sure that you mean COUNT(LogType), not SUM(LogType), since Summing a date makes no sense whatsoever. In any case, this can do what you are asking for.
SELECT ClientName, IsNull(ct, 0) as LogCount
FROM clientgroup cg
INNER JOIN Clients c
ON cg.clientid = c.clientid
LEFT JOIN (
SELECT LogID,
Count(*) as ct
FROM ClientContactLog
GROUP BY ClientID
) as cl
Notes:
I assumed a Clients table containing the client name.
I am not looking at the contactlog table because it is not needed to provide what you are requesting.
Assuming there is a client table where the clientname column is, you can do that this way:
select t2.clientname, coalesce(count(distinct t4.logtype), 0)
from clientgroup t1
join client t2
on t1.clientid = t2.id
left join
clientcontactlog t3
on t1.clientid = t3.clientid
left join
contactlog t4
on t3.Logid = t4.Logid
where t1.Groupid = 1

How can I return rows that meet a specific sequence of events?

I am trying to pull records for UserIDs that meet a certain sequence of events. If a user has a JOIN, then a subsequent CANCEL, and then a subsequent JOIN, I want to return them in the result set. I need to run this query for one day at a time, or several days at a time, as needed.
The table below shows examples of UserIDs that meet and do not meet the sequence.
+--------+--------+---------------------+------------+------------------+
| rownum | UserID | Timestamp | ActionType | Return in query? |
+--------+--------+---------------------+------------+------------------+
| 1 | 12345 | 2016-11-01 08:25:39 | JOIN | yes |
| 2 | 12345 | 2016-11-01 08:27:00 | NULL | yes |
| 3 | 12345 | 2016-11-01 08:28:20 | DOWNGRADE | yes |
| 4 | 12345 | 2016-11-01 08:31:34 | NULL | yes |
| 5 | 12345 | 2016-11-01 08:32:44 | CANCEL | yes |
| 6 | 12345 | 2016-11-01 08:45:51 | NULL | yes |
| 7 | 12345 | 2016-11-01 08:50:57 | JOIN | yes |
| 1 | 9876 | 2016-11-01 16:05:42 | JOIN | yes |
| 2 | 9876 | 2016-11-01 16:07:33 | CANCEL | yes |
| 3 | 9876 | 2016-11-01 16:09:09 | JOIN | yes |
| 1 | 56565 | 2016-11-01 18:15:16 | JOIN | no |
| 2 | 56565 | 2016-11-01 19:22:25 | CANCEL | no |
| 3 | 56565 | 2016-11-01 20:05:05 | CANCEL | no |
| 1 | 34343 | 2016-11-01 05:32:56 | JOIN | no |
+--------+--------+---------------------+------------+------------------+
I have read up on gaps and islands, and looked at all sorts of complicated forum posts that dance around what I'm trying to achieve.
Currently, all I'm able to do is look at one day's worth of records, with no constraint on the sequence logic that I need:
SELECT
ROW_NUMBER() OVER (PARTITION BY UserID ORDER BY tmsmp) rownum
,UserID
,tmstmp
,ActionType
FROM
t
INNER JOIN (
SELECT UserID
FROM t
WHERE tmstmp BETWEEN '2016-11-20 00:00:01' AND '2016-11-20 11:59:59'
GROUP BY UserID
HAVING COUNT(*) >= 2
) AS sub ON t1.UserID = sub.UserID
Thank you for your input!
You can use LEAD() :
SELECT * FROM (
SELECT t.* ,
LAG(t.ActionType,1) OVER(PARTITION BY t.userid ORDER BY t.timestamp) AS LAST_ACTION,
LAG(t.ActionType,2) OVER(PARTITION BY t.userid ORDER BY t.timestamp) AS LAST_ACTION2,
LEAD(t.ActionType,1) OVER(PARTITION BY t.userid ORDER BY t.timestamp) AS NEXT_Action,
LEAD(t.ActionType,2) OVER(PARTITION BY t.userid ORDER BY t.timestamp) AS NEXT_Action2
FROM YourTable t
WHERE tmstmp BETWEEN <Start> AND <End>)
WHERE (t.actionType = 'JOIN' AND
t.NEXT_Action = 'Cancel' AND
t.NEXT_Action2 = 'JOIN')
OR (t.LAST_ACTION= 'JOIN' AND
t.actionType= 'Cancel' AND
t.NEXT_Action = 'JOIN')
OR (t.LAST_ACTION2= 'JOIN' AND
t.LAST_ACTION = 'Cancel' AND
t.actionType= 'JOIN')
In my sample queries I'll do the best I can with the information you've given, but you're a little unclear about what the source table(s) look like. You show one table above (with no name), but then reference two different tables in your sample query... a little hard to see what's going on.
So I'll assume a single table, named t, and you can adjust as needed...
Then how I would handle this, is first identify the users
select distinct userid
from t first_join
inner join t cancel
on first_join.tmstmp < cancel.tmstp
and first_join.userid = cancel.userid
inner join t.second_join
on second_join.tmstmp > cancel.tmstp
and second_join.userid = cancel.userid
where first_join.actiontype = 'JOIN'
and cancel.actiontype = 'CANCEL'
and second_join.actiontype = 'JOIN'
So now you can get all records for those users
SELECT *
FROM T
WHERE USERID IN (
select distinct userid
from t first_join
inner join t cancel
on first_join.tmstmp < cancel.tmstp
and first_join.userid = cancel.userid
inner join t.second_join
on second_join.tmstmp > cancel.tmstp
and second_join.userid = cancel.userid
where first_join.actiontype = 'JOIN'
and cancel.actiontype = 'CANCEL'
and second_join.actiontype = 'JOIN'
)
Assuming you mean that the records are in order with no gaps, just use lag(), lead() or a combination:
select distinct userId
from (select t.*,
lag(ActionType) over (partition by userId order by tmstamp) as prev_at,
lead(ActionType) over (partition by userId order by tmstamp) as next_at,
from t
) t
where ActionType = 'Cancel' and prev_at = 'Join' and next_at = 'Join';
If gaps are allowed, then you can do this differently:
select distint userid
from t
where ActionType = 'Cancel' and
exists (select 1
from t t2
where t2.userId = t.userId and
t2.at = 'Join' and
t2.tmstamp < t.tmstamp
) and
exists (select 1
from t t2
where t2.userId = t.userId and
t2.at = 'Join' and
t2.tmstamp > t.tmstamp
);