SELECT Top values for each records - sql

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.

Related

Oracle SQL - Where Not Exists and multiple joins

I'm in need of some assistance to anyone familiar with Oracle SQL. I'm trying to use the Where Not Exists sub query, and is working fine with specific where clauses for specific customers, and then using UNION to join any other customer thereafter. I'm trying to use the SQL in a way where I'm not using UNION to join multiple customers, as there's 100's. I just don't know how to go about it.
I think I need to join the MAIN_ITEM table to the LOCATION table somehow, as it links the items in INVENTORY_LOCATIONS with a zone_code where the location_code can be compared against the table ZONE, showing any mismatches where location_code in INVENTORY_LOCATIONS does not exist in table ZONE. I'm not really sure if I"m explaining this properly, but hopefully my example below clears it up.
Many thanks in advance.
Current Query
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty
from inventory_locations a left join main_customer c
on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
where not exists (select 1 from zone b where b.location_code = a.location_code and b.zone_code='PM')
and a.company='M1'
and a.customer='100068'
UNION
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty
from inventory_locations a left join main_customer c
on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
where not exists (select 1 from zone b where b.location_code = a.location_code and b.zone_code='Z1')
and a.company='M1'
and a.customer='100012'
Table 1 - INVENTORY_LOCATIONS A
COMPANY | WARE_CODE |CUSTOMER | LOCATION_CODE |INVT1 | INVT2 | INVT3 | INVT_QTY
--------------------------------------------------------------------------------------
M1 | 01 | 100012 | 0101A |000052 | T100 | 000001001 | 60
M1 | 01 | 100012 | 0602A |000053 | T101 | 000001002 | 60
M1 | 01 | 100068 | 0601A |CANDY | T200 | 000001080 | 25
M1 | 01 | 100068 | 0102A |CANDY2 | T202 | 000001081 | 25
Table 2 - ZONE B
COMPANY | WARE_CODE |ZONE_CODE | LOCATION_CODE
--------------------------------------------------------
M1 | 01 |PM | 0101A
M1 | 01 |PM | 0102A
M1 | 01 |Z1 | 0601A
M1 | 01 |Z1 | 0602A
Table 3 - MAIN_ITEM D
COMPANY | WARE_CODE | CUSTOMER | ITEM_CODE | ZONE_CODE
----------------------------------------------------------------
M1 | 01 | 100012 | 000052 | PM
M1 | 01 | 100012 | 000053 | PM
M1 | 01 | 100068 | CANDY | Z1
M1 | 01 | 100068 | CANDY2 | Z1
Current results with above query.
COMPANY | CUSTOMER | CUSTOMER_NAME | LOCATION_CODE | INVT1 | INVT2 | INVT3 | INVT_QTY
---------------------------------------------------------------------------------------------------
M1 | 100012 | TEST COMP 1 | 0602A | 000053 | T101 | 000001002 | 60
M1 | 100068 | TEST COMP 2 | 0102A | CANDY2 | T202 | 000001081 | 25
Expected results with a query that doesn't use UNION to join multiple customers.
COMPANY | CUSTOMER | CUSTOMER_NAME | LOCATION_CODE | INVT1 | INVT2 | INVT3 | INVT_QTY
-------------------------------------------------------------------------------------------------
M1 | 100012 | TEST COMP 1 | 0602A | 000053 | T101 | 000001002 | 60
M1 | 100068 | TEST COMP 2 | 0102A | CANDY2 | T202 | 000001081 | 25
Thank you for taking the time to read and assist. Greatly appreciated.
I don't fully understand the semantics of your tables and am not sure of the primary keys which means the join conditions could need to be corrected.
However, my interpretation of your goal is to find which inventory_location rows imply a combination of location code and zone that is not in the zone table.
So I would do as follows:
Take the inventory_location table, and add on the customer_name and zone_code with joins. I am assuming each row has only one customer and only one zone. A "with" clause is convenient to treat this as if it were a single table.
Then take the location and zone code combinations and see which ones are missing from the zone table with a "where not exists" clause.
I apologize in advance for any typos/syntax errors. Without actually executing it, I think it would produce your requested output.
with inv_loc as (
select a.company, a.customer, c.customer_name, a.location_code, a.invt1, a.invt2, a.invt3, a.invt_qty, d.zone_code
from inventory_locations a
left join main_customer c on a.company=c.company and a.customer=c.customer and a.ware_code=c.ware_code
left join main_item d on d.company = a.company and d.customer = a.customer and d.ware_code = a.ware_code and d.item_code = a.invt1
)
select
company, customer, customer_name, location_code, invt1, invt2, invt3, invt_qty
from inv_loc i
where not exists (
select 1 from zone b
where b.location_code = i.location_code and b.zone_code =i.zone_code
)

Select Top 1 with multiple Group By

I've got a table in SQL Server 2016 (I believe it was originally from 2008 or 2012 and it's just moved to the 2016 cluster) with patient events, the type of event, and the severity of the event (called a grade). There are several instances where the same patient will have multiple events occur but with varying grades. So, a sample of data will look something like this:
| Pt_id | Event | Grade |
+-------+----------------+-------+
| 01 | Pain | 2 |
| 01 | Pain | 4 |
| 01 | Nausea | 2 |
| 02 | Headache | 2 |
| 02 | Headache | 3 |
| 03 | Blurred Vision | 3 |
| 03 | Blurred Vision | 4 |
| 03 | Bluured Vision | 3 |
| 03 | Nausea | 4 |
| 03 | Nausea | 2 |
I'm trying to get the highest grade for each of the different events per patient. My desired output for that data would be as follows:
| Pt_id | Event | Grade |
+-------+----------------+-------+
| 01 | Pain | 4 |
| 01 | Nausea | 2 |
| 02 | Headache | 3 |
| 03 | Blurred Vision | 4 |
| 03 | Nausea | 4 |
I've tried using the the Top 1 incorporated into the query, the ROW_Number, Partition, and everything else Google has thrown at me but I get either too restricted of results (I'm getting around 30 rows but I actually went through the excel (I'm trying to do some QA here) and I should have just under 400 rows. I think that when I do these functions I'm missing something and it's grouping either all Pt_ids and just picking 1 row for all the Events for that Pt_id or it's doing that with the Event - and no matter what I try it won't give me one row per patient, per event, with the highest grade for that event and patient.
Although I've used SQL throughout the years, it's never been my primary function so your assistance is greatly appreciated!
Isn't this enough with use of GROUP BY with MAX() ?
SELECT Pt_id, Event, MAX(Grade)
FROM table t
GROUP BY Pt_id, Event;
If the table has more column other than only 3 columns, then use ROW_NUMBER() with TIES clause :
SELECT TOP (1) WITH TIES t.*
FROM table t
ORDER BY ROW_NUMBER() OVER (PARTITION BY Pt_id, Event ORDER BY Grade DESC);
use row_number window function
with cte as
(
select *,
row_number() over(partition by Pt_id ,Event by order by Grade desc) rn
from your_table
)select * from cte where rn=1
You have to use order by Grade descfor getting max value

How to select number of days from history table?

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

Sum of count from external table

I need to search for the sum of the games made by specific developers. I have two tables:
_____________________________
|____________GAMES____________|
| Id | Title | id_dev | hits |
| 01 | abc | 1 | 20 |
| 02 | xyz | 2 | 15 |
| 03 | cde | 1 | 9 |
_______________
|__DEVELOPERS___|
| Id | Title |
| 01 | poi |
| 02 | asd |
| 03 | qwe |
I want result formatted like Developers title 40, where 40 is the sum of all hits of the games with the ID of this developer. How can I go about this?
SELECT developers.title, COUNT(count) AS total FROM (SELECT COUNT(games.hits) AS count
FROM games
GROUP BY id_dev
HAVING count > 1) as A
FROM developers
JOIN games
WHERE developers.id = games.id_dev
This is a simple join and aggregate, so you are overcomplicating things:
select d.id, d.title, sum(g.hits)
from games g join
developers d
on g.id_dev = d.id
group by d.id, d.title;

Order sql results using a Count() value from another table

assuming we have a users table like this:
ID | USERNAME | STATUS |
01 | Aname | 1 |
02 | Bname | 1 |
03 | Cname | 2 |
04 | Dname | 1 |
there are also two other tables, where, for each user, records the items that the users "has" for istance, video and photos, lets assume the video table is like this:
ID | USER | VIDEO |
01 | 01 | aaaa |
02 | 01 | bbbb |
03 | 02 | cccc |
04 | 02 | dddd |
05 | 03 | eeee |
06 | 03 | ffff |
07 | 03 | gggg |
the same for the photos:
ID | USER | PHOTO |
01 | 01 | aaaa |
02 | 02 | cccc |
03 | 02 | dddd |
04 | 03 | eeee |
05 | 03 | ffff |
06 | 03 | gggg |
now, i'm trying to order the results of the first table, on the STATUS field, so the users with status 2 will go at the end (or beginning) of the results, but i would like to order the others by the fact that they have, or have not videos or photos. Actually for all the users in the status 1 I check if they have video AND photos and display a message like "warning, you have no videos" or something, i would like also to order all the users on the status basis AND on the fact that they have or not photos or video... something like:
SELECT id,username,status,NumOfPhotos,NumOfVideos order by status ASC, numOfPhotos ASC, NumOfVideos DESC
where NumOfPhotos, for example is SELECT COUNT(*) FROM PHOTOS WHERE USER = USERS.ID
Is that possible? is that possible in a single statement?
This is an example on SQL Server, but something similar should work well on other RDBMS
SELECT
id, username, status, COALESCE(NumOfPhotos,0) AS NumOfPhotos,COALESCE(NumOfVideos,0) AS NumOfVideos
FROM dbo.Users u
LEFT JOIN (SELECT [USER], COUNT(*) AS NumOfPhotos FROM dbo.Photos GROUP BY [USER]) ph ON ph.[USER] = u.ID
LEFT JOIN (SELECT [USER], COUNT(*) AS NumOfVideos FROM dbo.Videos GROUP BY [USER]) vd ON vd.[USER] = u.ID
ORDER BY status ASC, numOfPhotos ASC, NumOfVideos DESC
SQLFiddle