Oracle SQL: GROUP BY HAVING multiple criteria - sql

I have the following query:
SELECT PERSON_ID
FROM TABLE
WHERE YEAR > 2013
AND ACTION = 'TERM'
GROUP BY PERSON_ID
HAVING COUNT(ACTION) = 1
This query is returning PERSON_IDs with one TERM action among other actions.
How can I modify my HAVING clause to have the query return PERSON_IDs with one TERM action and no other actions? I tried moving the AND ACTION = 'TERM' below the HAVING line, but, as there is no GROUP BY operation in that line, I am getting an error.

Here is one method:
SELECT PERSON_ID
FROM TABLE
WHERE YEAR > 2013
GROUP BY PERSON_ID
HAVING COUNT(ACTION) = 1 AND MIN(ACTION) = 'TERM'

Related

How can I count duplicates that fall within a date range? (SQL)

I have a table that contains Applicant ID, Application Date and Job Description.
I am trying to identify duplicates, defined as when the same Applicant ID applies for the same Job Description within 3 days of their other application.
I have already done this for the same date, this way:
CREATE TABLE Duplicates
SELECT
COUNT (ApplicantID) as ApplicantCount
ApplicantID
ApplicationDate
JobDescription
FROM Applications
GROUP BY ApplicantID,ApplicationDate,JobDescription
-
DELETE FROM Duplicates WHERE ApplicantCount <2
SELECT COUNT(*) FROM Duplicates
I'm now trying to make it so it doesn't have to match exactly on the ApplicationDate, but falls within a range. How do you do this?
You can use lead()/lag(). Here is an example that returns the first application when there is a duplicate:
SELECT a.*
FROM (SELECT a.*,
LEAD(ApplicationDate) OVER (PARTITION BY ApplicantID, JobDescription) as next_ad
FROM Applications a
) a
WHERE next_ad <= ApplicationDate + INTERVAL 3 DAY;
You can also phrase this using exists:
select a.*
from applications a
where exists (select 1
from applications a2
where a2.ApplicantID = a.ApplicantID and
a2.JobDescription = a.JobDescription and
a2.ApplicationDate > a.ApplicationDate and
a2.ApplicationDate <= a.ApplicationDate + interval 3 day
);

SQL - Count new entries based on last date

I have a table with the follow structure
ID ReportDate Object_id
What I need to know, is the count of new and count of old (Object id's)
For example: If I have the data below:
I want the following output grouped by ReportDate:
I thought a way doing it using a Where clause based on date, however i need the data for all the dates I have in the table. To see the count of what already existed in the previous report and what is new at that report. Any Ideas?
Edit: New/Old definition- New would be the records that never appeared before that report run date and appeared on this one, whereas old is the number of records that had at least one match in previous dates. I'll edit the post to include this info.
managed to do it using a left join. Below is my solution in case it helps anyone in the future :)
SELECT table.ReportRunDate,
-1*sum(table.ReportRunDate = new_table.init_date) as count_new,
-1*sum(table.ReportRunDate <> new_table.init_date) as count_old,
count(*) as count_total
FROM table LEFT JOIN
((SELECT Object_ID, min(ReportRunDate) as init_date
FROM table
GROUP By OBJECT_ID) as new_table)
ON table.Object_ID = new_table.Object_ID
GROUP BY ReportRunDate
This would work in Oracle, not sure about ms-access:
SELECT ReportDate
,COUNT(CASE WHEN rnk = 1 THEN 1 ELSE NULL END) count_of_new
,COUNT(CASE WHEN rnk <> 1 THEN 1 ELSE NULL END)count_of_old
FROM (SELECT ID
,ReportDate
,Object_id
,RANK() OVER (PARTITION BY Object_id ORDER BY ReportDate) rnk
FROM table_name)
GROUP BY ReportDate
Inner query should rank each occurence of object_id based on the ReportDate so the 1st occurrence of certain object_id will have rank = 1, the next one rank = 2 etc.
Then the outer query counts how many records with rank equal/not equal 1 are the within each group.
I assumed that 1 object_id can appear only once within each reportDate.

High performance TSQL to retrieve data

I have two tables with below structure
Person(ID, Name, ...)
Action(ID, FirstPersonId, SecondPersonId, Date)
I wanna retrieve this data for each person:
Number of action that a person be on second person from last action
that be on first person
Current query
Select Result.Id ,
(Select Count(*)
From Action
Where SecondPersonId = Result.Id
AND Date > Result.LastAction)
From
(Select ID ,
(
Select Top 1 Date
From Action
Where Action.FirstPersonId = Person.Id
) as LastAction
From Person ) As Result
this query has bad performance and i need very better one.
with lastActionPerson as -- last action for every first person
(select FirstPersonId , max([Date]) as LastActionDate
from Action
)
select a.SecondPersonId ,count(*)
from lastActionPerson lap
join Action a
on a.SecondPersonId = lap.FirstPersonId -- be on second person
and a.[Date] > lap.lastActionDate
-- you could continue to right join person table to show the person without actions
group by a.SecondPersonId

Select all unique names based on most recent value in different field

I have an access database with a table called SicknessLog. The fields are ID, StaffName, [Start/Return], DateStamp.
When a member of staff is off work for sickness then a record is added to the table and the value in the [Start/Return] field is 1. When they return to work a new record is added with the same details except the [Start/Return] field is 0.
I am trying to write a query that will return all distinct staff names where the most recent record for that person has a value of 1 (ie, all staff who are still off sick)
Does anyone know if this is possible? Thanks in advance
Here's one way, all staff that has been sick where it does not exist an event after that where that staff is "nonsick":
select distinct x.staffname
from sicknesslog x
where Start/Return = 1
and not exists (
select 1
from sicknesslog y
where x.StaffName = y.StaffName
and y.DateStamp > x.DateStamp
and y.Start/Return = 0
)
You can use group by to achieve this.
select staffname ,max(datestamp) from sicknesslog where start/return = 1 group by staffname
it will return all latest recored for all staff. If ID column is autogenerated PK then you can use it in max function.
select staffname,MAX(datestamp)
from sicknesslog
where [start/return]=1
group by staffname
order by max(datestamp) desc,staffname
This will retrieve latest records who is sick and off to work
This should be close:
select s.StaffName, s.DateStamp, s.[Start/Return]
from SicknessLog s
left join (
select StaffName, max(DateStamp) as MaxDate
from SicknessLog
group by StaffName
) sm on s.StaffName = sm.StaffName and s.DateStamp = sm.MaxDate and s.[Start/Return] = 1

Multi-level aggregation from a table

I have a table metrics which has the following columns :
stage name
---------------------
new member
new member
old member
new visitor
old visitor
Now I can find out how many new or old members are there by running a query like this :
select stage, count(*) from metrics where name = 'member' group by stage;
This will give me the following result:
stage count
-----------
new 2
old 1
But along with this I want output like this :
total stage count
------------------
3 new 2
3 old 1
Total is sum of all rows statisfying where clause above. How do I need to modify my previous query to get the result I need? Thanks.
You can do something like this:
with t as
(select stage from metrics where name = 'member')
select
(select count(*) from t) as total,
stage, count(*)
from t
group by stage
Check it: http://sqlfiddle.com/#!15/b97a4/9
This is compact variant and includes the 'member' constant only once.
The window-function using variant:
with member as (
select stage, count(*)
from metrics where name = 'member'
group by stage
)
select sum(count) over () as total, member.*
from member
http://sqlfiddle.com/#!15/b97a4/18
This can do what you want:
SELECT t2.totalCount,
t1.stage,
t1.stageCount
FROM
(SELECT stage,
COUNT(*) stageCount
FROM metrics
WHERE name = 'member'
GROUP BY stage
) t1,
(SELECT COUNT(*) AS totalCount FROM metrics WHERE name = 'member'
) t2;
See sqlfiddle http://sqlfiddle.com/#!2/0240b/5.
An advantage of this approach in comparison to using the subquery is that the sql finding the total count will not be run for each row of the sql defining the count by stage.
Try this code:
select (select count(*) as total from metrics where name = 'member' group by name),stage, count(*) from metrics where name = 'member' group by stage;