SQL Query If Condition for specific text within two rows - sql

I need to write a query, but I'm not exactly sure how to write it. I need to grab all the EmployeeIds based off the status. If it is completed and pending, completed takes precedence. However, if there is only pending and not completed I'll take pending, else don't take any of the rows for that employee. I just need one item per EmployeeId. Technically I would also need to grab the earliest date but I think I would know how to write that part.
RowNumber Status EmployeeId Produce Date
-------------------------------------------------------
1 New 1 Apples 1/1/18
2 Pending 1 BlueBerry 1/2/18
3 New 1 Oranges 1/3/18
4 Pending 2 Bananas 1/1/18
5 New 2 Grapes 1/2/18
6 Complete 2 Limes 1/3/18
So in this example I need the following below
RowNumber Status EmployeeId Produce Date
--------------------------------------------------------
2 Pending 1 BlueBerry 1/2/18
6 Complete 2 Limes 1/3/18
The hardest part for me is trying to figure out how to compare strings. Basically (semi pseudo-code)
Select top 1
t.EmployeeId, t.Produce, t.Date, stat.Status
(Case
If t.Status = 'Complete'
Select 'Complete'
If t.Status = 'Pending'
Select 'Pending'
Else
Dont Add this row ) stat
From
Table t
Where
t.Status = 'Complete' or t.Status = 'Pending'
Order by
t.Date

There's at least two different approaches coming to my mind :
SELECT DISTINCT EmployeeId
FROM Table
WHERE Status = "Complete"
UNION
SELECT DISTINCT t1.EmployeeId
FROM Table t1
LEFT OUTER JOIN Table t2
ON t2.EmployeeId = t1.EmployeeId
AND t2.Status = "Complete"
WHERE t1.Status = "Pending"
AND t2.Status IS NULL
… or using coalesce() if your database engine supports it:
SELECT DISTINCT pend.EmployeeId,
coalesce(comp.Status,pend.Status),
coalesce(comp.RowNumber,pend.RowNumber)
FROM MyTable pend
LEFT OUTER JOIN MyTable comp
ON comp.EmployeeId = pend.EmployeeId
AND comp.Status = 'Complete'
WHERE pend.Status IN ('Complete','Pending');

Is this what you want using row_number() :
select top (1) with ties *
from table t
where Status in ('Completed', 'Pending')
order by row_number() over (partition by EmployeeId
order by (case Status when 'Completed'
then 0
when 'Pending'
then 1
end)
);

You could do this with a UNION ALL coupled with a WHERE NOT EXISTS:
SELECT t1.*
FROM [Table] t1
WHERE [Status] = 'Completed'
UNION ALL
SELECT t2.*
FROM [Table] t2
WHERE [Status] = 'Pending'
AND NOT EXISTS (
SELECT 1
FROM [Table]
WHERE EmployeeId = t2.EmployeeId
AND [Status] = [Completed]
);

You could calculate a ROW_NUMBER that uses a CASE WHEN.
And then filter on that.
For example:
SELECT RowNumber, [Status], EmployeeId, Produce, [Date]
FROM
(
SELECT
RowNumber, [Status], EmployeeId, Produce, [Date],
ROW_NUMBER() OVER (
PARTITION BY EmployeeId
ORDER BY [Date] DESC,
CASE [Status] WHEN 'Complete' THEN 1 WHEN 'Pending' THEN 2 ELSE 9 END
) AS RN
FROM [YourTable] t
WHERE [Status] IN ('Complete','Pending')
) q
WHERE RN = 1
ORDER BY EmployeeId

Related

Postgresql combine IN with NOT IN

I have a table of entities where each can have different statuses. For the sake of keeping history, each status change is reflected by a new row.
Example:
Entity Id Status
123456 1
123456 2
789000 1
Assuming i want to find all rows that have only status 1 (so if they have other statuses they should not be returned), How do I do that?
This query:
select entityid
from tablename
group by entityid
having min(status) = 1 and max(status) = 1
returns all the entityids that you want, so you can use it with the operator IN:
select * from tablename
where entityid in (
select entityid
from tablename
group by entityid
having min(status) = 1 and max(status) = 1
)
Just use not exists:
select t.*
from t
where not exists (select 1
from t t2
where t2.entity_id = t.entity_id and t2.status <> 1
);

Remove matching pairs of rows from query

I am trying to produce a report from an SQL database.
The data is transactions, sometimes because of operator error incorrect records are entered, latter to correct for this the same record is entered but with a negative quantity.
i.e.
ID, DESC , QTY
0 , ITEM1 , 2
1 , ITEM2 , 1
2 , ITEM3 , 2 // This record and
3 , ITEM2 , 1
4 , ITEM3 , -2 // this record cancel out
I would like to have a query that looks at pairs of rows that are identical besides the ID and have an opposite sign on the QTY and does not include them in the result.
Similar to the below.
ID, DESC , QTY
0 , ITEM1 , 2
1 , ITEM2 , 1
3 , ITEM4 , 1
What is the easiest way I can achieve this in a query. I was thinking along the lines of an aggregate SUM function, but I only wanted to remove rows with a QTY of opposite sign but equal magnitude.
This is rather painful. The immediate answer to your question is not exists. However, you need to be careful about duplicates, so I would recommend enumerating the values first:
with t as (
select t.*,
row_number() over (partition by desc, qty order by id) as seqnum
from transactions t
)
select t.*
from t
where not exists (select 1
from t t2
where t2.desc = t.desc and
t2.seqnum = t.seqnum and
t2.qty = - t.qty
);
You could use the left join antipattern to evict records for which another record exists with the same desc and an opposite qty.
select t.*
from mytable t
left join mytable t1 on t1.desc = t.desc and t1.qty = - t.qty
where t1.id is null
Or a not exists condition with a correlated subquery:
select t.*
from mytable t
where not exists (
select 1
from mytable t1
where t1.desc = t.desc and t1.qty = - t.qty
)

Find the latest 3 records with the same status

I need to find the latest 3 records for each user that has a particular status on 'Fail'. At first it seems easy but I just can't seem to get it right.
So in a table of:
ID Date Status
1 2017-01-01 Fail
1 2017-01-02 Fail
1 2017-02-04 Fail
1 2015-03-21 Pass
1 2014-02-19 Fail
1 2016-10-23 Pass
2 2017-01-01 Fail
2 2017-01-02 Pass
2 2017-02-04 Fail
2 2016-10-23 Fail
I would expect ID 1 to be returned as the most recent 3 records are fails, but not ID 2, as they have a pass within their three fails. Each user may have any number of Pass and Fail records. There are thousands of different IDs
So far I've tried a CTE with ROW_NUMBER() to order the attempts but can't think of a way to ensure that the latest three results all have the same status of Fail.
Expected Results
ID Latest Fail Date Count
1 2017-02-04 3
Maybe try something like this:
WITH cte
AS
(
SELECT id,
date,
status,
ROW_NUMBER () OVER (PARTITION BY id ORDER BY date DESC) row
FROM #table
),cte2
AS
(
SELECT id, max(date) as date, count(*) AS count
FROM cte
WHERE status = 'fail'
AND row <= 3
GROUP BY id
)
SELECT id,
date AS latest_fail,
count
FROM cte2
WHERE count = 3
Check This.
Demo : Here
with CTE as
(
select *,ROW_NUMBER () over( partition by id order by date desc) rnk
from temp
where Status ='Fail'
)
select top 1 ID,max(DATE) as Latest_Fail_Date ,COUNT(rnk) as count
from CTE where rnk <=3
group by ID
Ouptut :
I think you can do this using cross apply:
select i.id
from (select distinct id from t) i cross apply
(select sum(case when t.status = 'Fail' then 1 else 0 end) as numFails
from (select top 3 t.*
from t
where t.id = i.id
order by date desc
) ti
) ti
where numFails = 3;
Note: You probably have a table with all the ids. If so, you an use that instead of the select distinct subquery.
Or, similarly:
select i.id
from (select distinct id from t) i cross apply
(select top 3 t.*
from t
where t.id = i.id
order by date desc
) ti
group by i.id
having min(ti.status) = 'Fail' and max(ti.status) = 'Fail' and
count(*) = 3;
Here you go:
declare #numOfTries int = 3;
with fails_nums as
(
select *, row_number() over (partition by ID order by [Date] desc) as rn
from #fails
)
select ID, max([Date]) [Date], count(*) as [count]
from fails_nums fn1
where fn1.rn <= #numOftries
group by ID
having count(case when [Status]='Fail' then [Status] end) = #numOfTries
Example here

SQL Server : select where row was X but is not now X

I have a table like this:
WOTranID WOID Status DateCreated
----------------------------------------------
1 5 Ready 6/6/2015
2 5 Pending 6/5/2015
3 7 Pending 6/9/2015
4 8 Scheduled 6/10/2015
What I need is to select all WOID where the status was pending but is not currently pending.
Thank you for your help.
Edit: Using the example table above I would only like to return WOID 5.
First, select all WOIDs which have had a status of pending, then intersect that with the list of WOIDs whose current status is not pending (using row_number() to select the latest status per WOID).
(Select Distinct WOID
from MyTable
where Status = 'Pending')
intersect
(Select WOID
from
(Select WOID
, Status
, Row_number() over (partition by WOID order by DateCreated desc) as RN
from MyTable) a
where Status <> 'Pending' and RN = 1)
May be this:
select distinct t1.WOID
from TableName t1
where Status <> 'Pending' and
not exists(select * from TableName t2
where t2.WOID = t1.WOID and t2.DateCreated > t1.DateCreated) and
exists(select * from TableName t3
where t3.WOID = t1.WOID and t3.DateCreated < t1.DateCreated and t3.Status = 'Pending')
This answer assumes that you want the next woid that changes from pending. If you want all of them, then you should clarify the question.
In SQL Server 2012+, you can use the lag() function:
select t.*
from (select t.*,
lag(status) over (partition by woid order by datecreated) as prev_status
from table t
) t
where prev_status = 'Pending' and status <> 'Pending';
You can do something similar in earlier versions use cross apply.

Is there something equivalent to putting an order by clause in a derived table?

This is sybase 15.
Here's my problem.
I have 2 tables.
t1.jobid t1.date
------------------------------
1 1/1/2012
2 4/1/2012
3 2/1/2012
4 3/1/2012
t2.jobid t2.userid t2.status
-----------------------------------------------
1 100 1
1 110 1
1 120 2
1 130 1
2 100 1
2 130 2
3 100 1
3 110 1
3 120 1
3 130 1
4 110 2
4 120 2
I want to find all the people who's status for THEIR two most recent jobs is 2.
My plan was to take the top 2 of a derived table that joined t1 and t2 and was ordered by date backwards for a given user. So the top two would be the most recent for a given user.
So that would give me that individuals most recent job numbers. Not everybody is in every job.
Then I was going to make an outer query that joined against the derived table searching for status 2's with a having a sum(status) = 4 or something like that. That would find the people with 2 status 2s.
But sybase won't let me use an order by clause in the derived table.
Any suggestions on how to go about this?
I can always write a little program to loop through all the users, but I was gonna try to make one horrendus sql out of it.
Juicy one, no?
You could rank the rows in the subquery by adding an extra column using a window function. Then select the rows that have the appropriate ranks within their groups.
I've never used Sybase, but the documentation seems to indicate that this is possible.
With Table1 As
(
Select 1 As jobid, '1/1/2012' As [date]
Union All Select 2, '4/1/2012'
Union All Select 3, '2/1/2012'
Union All Select 4, '3/1/2012'
)
, Table2 As
(
Select 1 jobid, 100 As userid, 1 as status
Union All Select 1,110,1
Union All Select 1,120,2
Union All Select 1,130,1
Union All Select 2,100,1
Union All Select 2,130,2
Union All Select 3,100,1
Union All Select 3,110,1
Union All Select 3,120,1
Union All Select 3,130,1
Union All Select 4,110,2
Union All Select 4,120,2
)
, MostRecentJobs As
(
Select T1.jobid, T1.date, T2.userid, T2.status
, Row_Number() Over ( Partition By T2.userid Order By T1.date Desc ) As JobCnt
From Table1 As T1
Join Table2 As T2
On T2.jobid = T1.jobid
)
Select *
From MostRecentJobs As M2
Where Not Exists (
Select 1
From MostRecentJobs As M1
Where M1.userid = M2.userid
And M1.JobCnt <= 2
And M1.status <> 2
)
And M2.JobCnt <= 2
I'm using a number of features here which do exist in Sybase 15. First, I'm using common-table expressions both for my sample data and clump my queries together. Second, I'm using the ranking function Row_Number to order the jobs by date.
It should be noted that in the example data you gave, no user satisfies the requirement of having their two most recent jobs both be of status "2".
__
Edit
If you are using a version of Sybase that does not support ranking functions (e.g. Sybase 15 prior to 15.2), then you need simulate the ranking function using Counts.
Create Table #JobRnks
(
jobid int not null
, userid int not null
, status int not null
, [date] datetime not null
, JobCnt int not null
, Primary Key ( jobid, userid, [date] )
)
Insert #JobRnks( jobid, userid, status, [date], JobCnt )
Select T1.jobid, T1.userid, T1.status, T1.[date], Count(T2.jobid)+ 1 As JobCnt
From (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T1
Left Join (
Select T1.jobid, T2.userid, T2.status, T1.[date]
From #Table2 As T2
Join #Table1 As T1
On T1.jobid = T2.jobid
) As T2
On T2.userid = T1.userid
And T2.[date] < T1.[date]
Group By T1.jobid, T1.userid, T1.status, T1.[date]
Select *
From #JobRnks As J1
Where Not Exists (
Select 1
From #JobRnks As J2
Where J2.userid = J1.userid
And J2.JobCnt <= 2
And J2.status <> 2
)
And J1.JobCnt <= 2
The reason for using the temp table here is for performance and ease of reading. Technically, you could plug in the query for the temp table into the two places used as a derived table and achieve the same result.