How to select number of days from history table? - sql

EDITED: history table may contain rows that have no effect in the time calculation (such as comments, or sending reminders...)
I have 2 tables with the following structure
PS: for the sake of simplicity, I avoided going into too much details (in reality, I have another table for STATUS codes, and another for users, etc.)
Tickets Table
--------------------------------------------------
| ID | Ticket_Details | Issued_By | Status |
--------------------------------------------------
| 001 | 'PC not working' | 'John' | On Hold |
| 002 | 'Printer broken' | 'Mike' | Rejected |
| 003 | 'Network down' | 'Alex' | Submitted |
| .. | ... | .. | ... |
--------------------------------------------------
History Table
------------------------------------------------------------------------
| ID | Ticket_ID | Ticket_Status | History_Details | Insert_Date |
------------------------------------------------------------------------
| 01 | 001 | new | submitted | 23-Feb-2015 |
| 02 | 001 | submitted | assigned to [Frank] | 25-Feb-2015 |
| 03 | 001 | submitted | commented 'needs ti.'| 25-Feb-2015 |
| 04 | 001 | assigned | put on hold by[Frank]| 26-Feb-2015 |
| 05 | 001 | on hold | reminder sent | 01-Mar-2015 |
| 06 | 002 | new | submitted | 23-Feb-2015 |
| 07 | 002 | submitted | rejected by [Sam] | 24-Feb-2015 |
| 08 | 003 | new | submitted | 25-Feb-2015 |
------------------------------------------------------------------------
Each row of the history table contains the following information:
1- the id (auto inc) primary key
2- a foreign key to ticket id
3- the status of the ticket before it was modified
4- remarks of what action was done to the ticket
5- the date [and time] of the action
Notice, that some rows describe a change in the status, while some rows, there was no change. Only a comment or a reminder was sent, while the ticket stayed at its status.
now what I want to achieve is a table that shows the current status of the ticket with the number of days it had been assigned to this status. So for the sample data above, the output should be: (considering today's date is 1-Mar-2015)
desired output
--------------------------------------------------
| ID | Ticket_Details | Status | Since |
--------------------------------------------------
| 001 | 'PC not working' | On Hold | 3 days |
| 002 | 'Printer broken' | Rejeced | 5 days |
| 003 | 'Network down' | submitted | 4 days |
| .. | ... | .. | |
--------------------------------------------------
Basically, I am using the information stored in insert_date in history table to determine how long the ticket has been in that status.
Here is what I tried:
select
t.id,
t.ticket_details,
t.status,
datediff(day, h.insert_date, getdate() ) "since"
from
tickets t
left join history h on t.id = h.ticket_id
and h.id = ( select max(id) from history h2
where h2.ticket_id = h.ticket_id
AND h2.ticket_status = t.status )
I am telling SQL to look for the last time in history where this ticket has had this status..
but I got in-accurate data since some of the "since" values came out nulls.
What mistake am I doing?

I think this is a good use of cross apply:
select t.*, datediff(day, minid, getdate()) as days_since
from tickets t outer apply
(select min(insert_date) as minid
from history h
where h.ticket_id = t.ticket_id and h.ticket_status = t.status
) h;
Note: this returns the earliest date that a ticket was at the current status. This assumes that tickets never return to a previous status, which is consistent with the information in your question.

Maybe try
select
t.id,
t.ticket_details,
t.status,
datediff(day, h.insert_date, getdate() ) "since"
from
tickets t
left join (SELECT MAX(insert_date) as insert_date,ticket_id
FROM history group by ticket_id) as h
on t.id=h.ticket_id

Related

Finding maximum value from each group set in Oracle

I have below scenario:
Input Data:
---------------------------------------------------------------
| ID | Account | Sub_Acct | Email |
---------------------------------------------------------------
| 100 | AD | AD1 | 123#xyz.com |
| 100 | AB | AB1 | test#abc.com, 123#xyz.com |
| 100 | AB | AB2 | test#abc.com, 123#xyz.com |
| 200 | CD | CD1 | test.1#pqr.com, 123#abc.com |
| 200 | AB | AB1 | test.2#pqr.com |
| 200 | CD | CD2 | test.1#pqr.com, 123#abc.com |
| 200 | AB | AB2 | 123#abc.com |
| 200 | CD | CD3 | test.1#pqr.com, 123#abc.com |
---------------------------------------------------------------
I need to take count of individual accounts partitioned by IDs. Whichever account has maximum count, I want to display that respective account with single entry with column Sub_acct populated as NULL. Rest all other accounts should be populated with their respective Sub_acct values within that specific ID.
Email domains to be extracted from Email column. The email_domain column values of the secondary accounts (within specific ID) will have values from the primary account( i.e. maximum count).. Below is the expected output:
------- -------------------------------------------
| ID | Account | Sub_Acct | Email_Domain |
------- -------------------------------------------
| 100 | AB | | abc.com, xyz.com |
| 100 | AD | AD1 | abc.com, xyz.com |
| 200 | CD | | pqr.com, abc.com |
| 200 | AB | AB1 | pqr.com, abc.com |
| 200 | AB | AB2 | pqr.com, abc.com |
---------------------------------------------------
I have edited this question. Sorry for the trouble caused.
Can someone pls help with the sql query in Oracle. Thanks in advance.
This works in Oracle 10...
with rank1 as
(
select id,
account,
email, count(account) as account_count,
rank() over (partition by id order by count(account) desc) as order_rank
from table1
group by id, account, email
)
select t1.*,
r1.account as primary_account,
r1.email as primary_email
from table1 t1
join rank1 r1
on r1.id = t1.id
where r1.order_rank = 1
In your table an ID/Account pair can occur multiple times. And in your example such a pair has always the same Email. Is this guaranteed to be so? If it is, then your table isn't normalized and you can get consistency problems in the future.
However, relying on this would make writing the query easy-peasy:
select
t.id,
t.account,
t.email,
s.primary_account,
s.primary_email
from mytable
join
(
select
id,
stats_mode(account) as primary_account,
stats_mode(email) as primary_email
from mytable
group by id
) s on s.id = t.id
order by id, account;
(It would be even easier, supported Oracle STATS_MODE OVER, but it doesn't yet.)

Get the latest row based on condition

I have a table material -- HERE IS THE FIDDLE
+--------+-----+-------------------+----------------+-----------+
| ID | REV | name | Description | curr |
+--------+-----+-------------------+----------------+-----------+
| 211-32 | 001 | Screw 1.0 | Used in MAT 1 | READY |
| 211-32 | 002 | Screw 2 plus | can be Used-32 | WITHDRAWN |
| 212-41 | 001 | Bolt H1 | Light solid | READY |
| 212-41 | 002 | BOLT H2+Form | Heavy solid | READY |
| 101-24 | 001 | HexHead 1-A | NOR-1 | READY |
| 101-24 | 002 | HexHead Spl | NOR-22 | READY |
| 423-98 | 001 | Nut Repair spare | NORM1 | READY |
| 423-98 | 002 | Nut Repair Part-C | NORM2 | WITHDRAWN |
| 423-98 | 003 | Nut SP-C | NORM2+NORM1 | NULL |
| 654-01 | 001 | Bar | Specific only | WITHDRAWN |
| 654-01 | 002 | Bar rod-S | Designed+Spe | WITHDRAWN |
| 654-01 | 003 | Bar OPG | Hard spec | NULL |
+--------+-----+-------------------+----------------+-----------+
Here each ID can have multiple revisions. I want to take latest revisions (i.e highest of 001,002,003 etc.,). But If the latest revision has curr as either NULL(string) or WITHDRAWN then I have take the previous revision and its corresponding value. If even that's curr is NULL or WITHDRAWN I have to again go to previous revision. If all the revision has the same issue then we can ignore it. so the expected output is
+--------+-----+------------------+---------------+-------+
| ID | REV | name | Description | curr |
+--------+-----+------------------+---------------+-------+
| 211-32 | 001 | Screw 1.0 | Used in MAT 1 | READY |
| 212-41 | 002 | BOLT H2+Form | Heavy solid | READY |
| 101-24 | 002 | HexHead Spl | NOR-22 | READY |
| 423-98 | 001 | Nut Repair spare | NORM1 | READY |
+--------+-----+------------------+---------------+-------+
I have tried below code, but i'm not sure how to take previous revision.
with cte as (
select *,dense_rank() over (partition by id order by rev desc) as DR ,
lead(curr) over (partition by id order by rev desc) LEAD_CURR
from material )
select * from cte where DR = 1 and curr='READY'
union all
select * from cte where LEAD_CURR='READY' and DR=2
union all
select * from cte where LEAD_CURR='READY' and DR=3
This sounds like filtering and then calculating the row number:
select m.*
from (select m.*,
row_number() over (partition by id order by rev desc) as seqnum
from material m
where curr is not null and curr not in ( 'WITHDRAWN', 'NULL' )
) m
where seqnum = 1;
You can also do this using a correlated subquery:
select m.*
from material m
where m.rev = (select max(m2.rev)
from material m2
where m2.id = m.id and
curr is not null and curr not in ( 'WITHDRAWN', 'NULL' )
);
Here is a db<>fiddle.
Note: It is quite non-traditional to store the string 'NULL' in a column, because that can easily be confused with the SQL "constant" NULL.
Also, your question specifically mentions 'WITHDRAWN' and NULL, but it does not say what other values are allowed. Obviously, the logic in the queries above might be equivalent to curr = 'READY' and you can use that. The above logic follows your description of the problem.

SELECT Top values for each records

I have been battling through this query/query design for sometime now and I thought it's time to ask the experts! Here's my table results:
ID | Status | date |
---------------------------------
05 | Returned | 20/6/2018 |
03 | Sent | 12/5/2018 |
01 | Pending | 07/6/2018 |
01 | Engaged | 11/4/2018 |
03 | Contacted | 16/4/2018 |
05 | Surveyed | 04/3/2017 |
05 | No Contact | 05/3/2017 |
How do I get it to return top/newest value for each ID:
ID | Status | date |
---------------------------------
05 | Returned | 20/6/2018 |
03 | Sent | 12/5/2018 |
01 | Pending | 07/6/2018 |
I've tried group by, TOP 1, Distinct and results still not what I wanted. Also, displaying the results by top 5% is won't do either as the ID can be more than just 3 types.
My QUERY below:
INSERT INTO TmpAllcomsEmployee ( StatusID, EmployeeID, CommunicationDate )
SELECT DISTINCT CommunicationLog.StatusID, TmpAllcomsEmployee.EmployeeID,
Max(CommunicationLog.CommunicationDate) AS MaxOfCommunicationDate
FROM CommunicationLog RIGHT JOIN TmpAllcomsEmployee ON
CommunicationLog.EmployeeID = TmpAllcomsEmployee.EmployeeID
GROUP BY CommunicationLog.StatusID, TmpAllcomsEmployee.EmployeeID
ORDER BY Max(CommunicationLog.CommunicationDate) DESC;
One method is a correlated subquery:
select cl.*
from CommunicationLog as cl
where cl.date = (select max(cl2.date)
from CommunicationLog as cl2
where cl2.EmployeeID = cl.EmployeeID
);
This gets the most recent record for each employee in CommunicationLog. You can join in the other table if you really need it. It does not seem unnecessary unless you are using it for filtering.

How to exclude rows that have matching fields in other rows

I have a table in MS Access 2013 that has a number of different columns. As part of the data that is entered into the main table, there are duplicates in certain columns. However when I 'pot up' the volumes of rows based on their status, I need to be able to exclude those with the same values in other columns.
------------------------------------------------------------
HeaderID | Date | Number | EffectiveDate | Reg | Status
------------------------------------------------------------
2 | 01/01/2016| 100001 | 01/12/2015 | 01 | Ready
3 | 01/01/2016| 100001 | 01/12/2015 | 02 | Ready
4 | 02/02/2016| 100002 | 12/11/2015 | R | Pending
5 | 02/02/2016| 100002 | 12/11/2015 | T | Pending
6 | 02/02/2016| 100002 | 12/11/2015 | N | Pending
7 | 15/09/2015| 100003 | 30/11/2015 | 01 | Ready
8 | 14/09/2015| 100004 | 20/02/2016 | 01 | New
I have the basic below code already:
Select
tbl_Progression.Status,
Count(tbl_Progression.HeaderID) AS CountofHeaderID
From tbl_Progression
Group By tbl_Progression.Status
I'm looking to be able to get the results to look like the below using the example data above, whereby the Status is counted by HeaderID but only counts once those records that have the same Date, Number and EffectiveDate (but different Reg) to look like this:
------------------------
Status | CountofHeaderID
------------------------
Pending | 1
Ready | 2
New | 1
Instead of what the current code is doing:
------------------------
Status | CountofHeaderID
------------------------
Pending | 3
Ready | 3
New | 1
MS Access doesn't support COUNT(DISTINCT). You can, however, use a subquery with DISTINCT (or GROUP BY):
Select p.Status, Count(*) as new_CountofHeaderID
From (select distinct p.status, p.Date, p.Number, pEffectiveDate
from tbl_Progression as p
) as p
Group By p.Status;

SQL Server: how do I get data from a history table?

Can you please help me build an SQL query to retrieve data from a history table?
I'm a newbie with only a one-week coding experience. I've been trying simple SELECT statements so far but have hit a stumbling block.
My football club's database has three tables. The first one links balls to players:
BallDetail
| BallID | PlayerID | TeamID |
|-------------------|--------|
| 1 | 11 | 21 |
| 2 | 12 | 22 |
The second one lists things that happen to the balls:
BallEventHistory
| BallID | Event | EventDate |
|--------|------ |------------|
| 1 | Pass | 2012-01-01 |
| 1 | Shoot | 2012-02-01 |
| 1 | Miss | 2012-03-01 |
| 2 | Pass | 2012-01-01 |
| 2 | Shoot | 2012-02-01 |
And the third one is a history change table. After a ball changes hands, history is recorded:
HistoryChanges
| BallID | ColumnName | ValueOld | ValueNew |
|--------|------------|----------|----------|
| 2 | PlayerID | 11 | 12 |
| 2 | TeamID | 21 | 22 |
I'm trying to obtain a table that would list all passes and shoots Player 11 had done to all balls before the balls went to other players. Like this:
| PlayerID | BallID | Event | Month |
|----------|--------|-------|-------|
| 11 | 1 | Pass | Jan |
| 11 | 1 | Shoot | Feb |
| 11 | 2 | Pass | Jan |
I begin so:
SELECT PlayerID, BallID, Event, DateName(month, EventDate)
FROM BallDetail bd INNER JOIN BallEventHistory beh ON bd.BallID = beh.BallID
WHERE PlayerID = 11 AND Event IN (Pass, Shoot) ...
But how to make sure that Ball 2 also gets included despite being with another player now?
Select PlayerID,BallID,Event,datename(month,EventDate) as Month,Count(*) as cnt from
(
Select
Coalesce(
(Select ValueNew from #HistoryChanges where ChangeDate=(Select max(ChangeDate) from #HistoryChanges h2 where h2.BallID=h.BallID and ColumnName='PlayerID' and ChangeDate<=EventDate) and BallID=h.BallID and ColumnName='PlayerID')
,(Select PlayerID from #BallDetail where BallID=h.BallID)
) as PlayerID,
h.BallID,h.Event,EventDate
from #BallEventHistory h
) a
Group by PlayerID, BallID, Event,datename(month,EventDate)
SELECT d.PlayerID, d.BallID, h.Event, DATENAME(mm, h.EventDate) AS Month
FROM BallDetail d JOIN BallEventHistory h ON d.BallID = h.BallID
WHERE h.Event IN ('Pass', 'Shoot') AND d.PlayerID = 11
OR EXISTS (SELECT 1
FROM dbo.HistoryChanges c
WHERE c.ValueOld = 11 AND c.ValueNew = d.PlayerID AND c.ColumnName = 'PlayerID' and c.ChangeDate = h.EventDate)