Get previous to max(value) when groupping in SQLite - sql

I have one table which contains trhese colums:
APPOINTSMENTS
|id
|worker_id
|post_id
|date
I can find last appointments for each worker with this query:
SELECT * FROM (
SELECT id,worker_id,post_id,max(date) as date
FROM appointments
GROUP BY worker_id
)
How to find previous appointment (previous to last one) for each worker? How to do it with procedure (but sqlite not supports it, but anyway).

First get the highest date for each worker.
SELECT
worker_id, MAX(date) AS max_date
FROM appointments
GROUP BY worker_id
Then you can use this query as subquery in the next step.
Get the highest date for each worker that is lower than the actual highest date.
SELECT
a.id, a.worker_id, a.post_id, MAX(a.date) AS second_highest_date
FROM appointments a
JOIN (
SELECT
worker_id, MAX(date) AS max_date
FROM appointments
GROUP BY worker_id
) md
ON a.worker_id = md.worker_id
AND a.date < md.max_date
GROUP BY a.worker_id

Here is one method for doing this:
select a.*
from appointments a
where 2 = (select count(*)
from appointments a2
where a2.worker_id = a.worker_id and
a2.date >= a.date
);
Assuming that the dates are unique for each "worker", this will work for the "nth" date. The same logic can work for dates that are not unique, but you would have to clarify exactly what you want in that case.

Related

Return group of columns with highest date

I have a table called events. I need to return all the group of distinct rows with maximum value for time_of_event in a month. For example, if you take October 2018 as time of event, I need to return all the distinct group of rest of the columns who has the highest date in Oct 2018 for column time_of_event.
OK. The key thing is to identify the relevant time_of_event for each month. Then we can join that to the list to get all events.
To identify the latest time_of_event, you can use a group-by statement e.g.,
SELECT MAX(time_of_event) AS Last_time_of_event
FROM events_new
GROUP BY YEAR(time_of_event), MONTH(time_of_event)
The above provides a list of all the last date/time of the events. If you want to filter the result set, feel free to do so (e.g., add a WHERE time_of_event >= '20180101' AND time_of_event < '20190101')
We can then use this as a sub-query or CTE to link to the original table to get all the events e.g.,
; WITH LTOE AS
(SELECT MAX(time_of_event) AS Last_time_of_event
FROM events_new
GROUP BY YEAR(time_of_event), MONTH(time_of_event)
)
SELECT DISTINCT en.User_ID, en.Date_Joined, en.Time_of_event, en.Event
FROM events_new en
INNER JOIN LTOE on en.Last_time_of_event = en.time_of_event
(edit: fixed typo in GROUP BY and added DISTINCT)
You could use the DENSE_RANK windowing function. Something like this
with e_cte as (
select *, dense_rank() over (partition by year(time_of_event), month(time_of_event)
order by time_of_event desc) dr
from [events])
select distinct [user_id], date_joined, time_of_event
from e_cte
where dr=1;
I think I would just use window functions and select distinct:
select distinct en.*
from (select e.*, max(time_of_event) over () as max_time_of_event
from events_new en
where time_of_event >= '2018-10-01' and
time_of_event < '2018-11-01'
) en
where time_of_event = max_time_of_event

To recieve one record per id and the one with the latest date

I have the following table stamps with the columns:
Worker
Date
Transferred
Balance
I want out one row per worker,
the record with the latest day, and also have value 1 in Transferred
I have tried a lot of possibilities but none works the way I want to.
SELECT DISTINCT OUT.WORKER,OUT.DATE,OUT.TRANSFERRED,OUT.BALANCE
FROM (
SELECT WORKER,DATE,TRANSFERRED,BALANCE
FROM STAMPS
ORDER BY DATE DESC
) AS OUT
GROUP BY WORKER
You say you want the latest day (presumably the latest for a given worker), so you need the max function.
select s.Worker,
s.Date,
s.Transferred,
s.Balance
from
(select worker,
max(date) as date
from stamps
where transferred = 1
group by Worker) as max_dates,
join stamps s
on s.worked = max_dates.worker
and s.date = max_dates.date
The typical way is to use window functions:
SELECT S.WORKER, S.DATE, S.TRANSFERRED, S.BALANCE
FROM (SELECT S.*,
ROW_NUMBER() OVER (PARTITION BY WORKER ORDER BY DATE DESC) AS SEQNUM
FROM STAMPS S
) S
WHERE SEQNUM = 1;
With the right indexes, a correlated subquery often has the best performance:
select s.*
from stamps s
where s.date = (select max(s2.date)
from stamps s2
where s2.worker = s.worker
);
The appropriate index is on stamps(worker, date).
SELECT * FROM dbo.STAMPS[enter image description here][1]
SELECT subResult.WORKER,
subResult.Date,
subResult.Transferred,
subResult.Balance
FROM
(
SELECT WORKER,
DATE,
TRANSFERRED,
BALANCE,
ROW_NUMBER() OVER(PARTITION BY Worker ORDER BY date DESC) AS rowNum
FROM STAMPS
WHERE Transferred=1
) AS subResult
WHERE subResult.rowNum=1

Calculating business days in Teradata

I need help in business days calculation.
I've two tables
1) One table ACTUAL_TABLE containing order date and contact date with timestamp datatypes.
2) The second table BUSINESS_DATES has each of the calendar dates listed and has a flag to indicate weekend days.
using these two tables, I need to ensure business days and not calendar days (which is the current logic) is calculated between these two fields.
My thought process was to first get a range of dates by comparing ORDER_DATE with TABLE_DATE field and then do a similar comparison of CONTACT_DATE to TABLE_DATE field. This would get me a range from the BUSINESS_DATES table which I can then use to calculate count of days, sum(Holiday_WKND_Flag) fields making the result look like:
Order# | Count(*) As DAYS | SUM(WEEKEND DATES)
100 | 25 | 8
However this only works when I use a specific order number and cant' bring all order numbers in a sub query.
My Query:
SELECT SUM(Holiday_WKND_Flag), COUNT(*) FROM
(
SELECT
* FROM
BUSINESS_DATES
WHERE BUSINESS.Business BETWEEN (SELECT ORDER_DATE FROM ACTUAL_TABLE
WHERE ORDER# = '100'
)
AND
(SELECT CONTACT_DATE FROM ACTUAL_TABLE
WHERE ORDER# = '100'
)
TEMP
Uploading the table structure for your reference.
SELECT ORDER#, SUM(Holiday_WKND_Flag), COUNT(*)
FROM business_dates bd
INNER JOIN actual_table at ON bd.table_date BETWEEN at.order_date AND at.contact_date
GROUP BY ORDER#
Instead of joining on a BETWEEN (which always results in a bad Product Join) followed by a COUNT you better assign a bussines day number to each date (in best case this is calculated only once and added as a column to your calendar table). Then it's two Equi-Joins and no aggregation needed:
WITH cte AS
(
SELECT
Cast(table_date AS DATE) AS table_date,
-- assign a consecutive number to each busines day, i.e. not increased during weekends, etc.
Sum(CASE WHEN Holiday_WKND_Flag = 1 THEN 0 ELSE 1 end)
Over (ORDER BY table_date
ROWS Unbounded Preceding) AS business_day_nbr
FROM business_dates
)
SELECT ORDER#,
Cast(t.contact_date AS DATE) - Cast(t.order_date AS DATE) AS #_of_days
b2.business_day_nbr - b1.business_day_nbr AS #_of_business_days
FROM actual_table AS t
JOIN cte AS b1
ON Cast(t.order_date AS DATE) = b1.table_date
JOIN cte AS b2
ON Cast(t.contact_date AS DATE) = b2.table_date
Btw, why are table_date and order_date timestamp instead of a date?
Porting from Oracle?
You can use this query. Hope it helps
select order#,
order_date,
contact_date,
(select count(1)
from business_dates_table
where table_date between a.order_date and a.contact_date
and holiday_wknd_flag = 0
) business_days
from actual_table a

Days Since Last Help Ticket was Filed

I am trying to create a report to show me the last date a customer filed a ticket.
Customers can file dozens of tickets. I want to know when the last ticket was filed and show how many days it's been since they have done so.
The fields I have are:
Customer,
Ticket_id,
Date_Closed
All from the Same table "Tickets"
I'm thinking I want to do a ranking of tickets by min date? I tried this query to grab something but it's giving me all the tickets from the customer. (I'm using SQL in a product called Domo)
select * from (select *, rank() over (partition by "Ticket_id"
order by "Date_Closed" desc) as date_order
from tickets ) zd
where date_order = 1
This should be simple enough,
SELECT customer,
MAX (date_closed) last_date,
ROUND((SYSDATE - MAX (date_closed)),0) days_since_last_ticket_logged
FROM emp
GROUP BY customer
select Customer, datediff(day, date_closed, current_date) as days_since_last_tkt
from
(select *, rank() over (partition by Customer order by "Date_Closed" desc) as date_order
from tickets) zd
join tickets t on zd.date_closed = t.date_closed
where zd.date_order = 1
Or you can simply do
select customer, datediff(day, max(Date_closed), current_date) as days_since_last_tkt
from tickets
group by customer
To select other fields
select t.*
from tickets t
join (select customer, max(Date_closed) as mxdate,
datediff(day, max(Date_closed), current_date) as days_since_last_tkt
from tickets
group by customer) tt
on t.customer = tt.customer and tt.mxdate = t.date_closed
I would do this with a simple sub-query to select the last closed date for the customer. Then compare this to today with datediff() to get the number of days since last closed.
Select
LastTicket.Customer,
LastTicket.LastClosedDate,
DateDiff(day,LastTicket.LastClosedDate,getdate()) as DaysSinceLastClosed
From
(select
tickets.customer
max(tickets.dateClosed) as LastClosedDate
from tickets
Group By tickets.Customer) as LastTicket
Based on the responses this is what I did:
select "Customer",
Max("date_closed") "last_date,
round(datediff(DAY, CURRENT_DATE, max("date_closed")), 0) as "Closed_date"
from tickets
group by "Customer"
ORDER BY "Customer"

SQL Count Of Open Orders Each Day Between Two Dates

I've tried searching but it's likely I'm using the wrong keywords as I can't find an answer.
I'm trying to find the number of orders that are open between two dates and by employee. I have one table that shows a list of employees, another that shows a list of orders that contains an open and close date and also a dates table if that helps.
The employee and order tables joined will return something like:
employee order ref opened closed
a 123 01/01/2012 04/01/2012
b 124 02/01/2012 03/01/2012
a 125 02/01/2012 03/01/2012
And I need to transform this data into:
Date employee Count
01/01/2012 a 1
02/01/2012 a 2
02/01/2012 b 1
03/01/2012 a 2
03/01/2012 b 1
04/01/2012 a 1
I'm pulling the data from SQL server.
Any ideas?
Thanks
Nick
Join Dates to the result of the join between Employees and Orders, then group by dates and employees to obtain the counts, something like this:
SELECT
d.Date,
o.Employee,
COUNT(*) AS count
FROM Employees e
INNER JOIN Orders o ON e.ID = o.Employee
INNER JOIN Dates d ON d.Date BETWEEN o.Opened AND o.Closed
GROUP BY
d.Date,
o.Employee
My favorite way to do this counts the number of cumulative opens and the number of cumulative closes over time.
with cumopens as
(select employee, opened as thedate,
row_number() over (partition by employee order by opened) as cumopens,
0 as cumcloses
from eo
),
cumcloses as
(select employee, closed as thedate, 0 as cumopens,
row_number() over (partition by employee order by closed ) as cumcloses
from eo
)
select employee, c.thedate, max(cumopens), max(cumcloses),
max(cumopens) - max(cumcloses) as stillopened
from ((select *
from cumopens
) union all
(select *
from cumcloses
)
) c
group by employee, thedate
The only problem with this approach is that only dates where there is employee activity get reported. This works in your case.
The more general solution requires a sequence numbers to generate dates. For this, I often create one from some existing table with enough rows:
with nums as
(select row_number() over (partition by null order by null) as seqnum
from employees
)
select employee, dateadd(day, opened, seqnum) as thedate, count(*)
from eo join
nums
on datediff(day, opened, closed) < seqnum
group by employee, dateadd(day, opened, seqnum)
order by 1, 2
SELECT opened,employee,count(*)
FROM employee LEFT JOIN orders
WHERE opened < firstDate and opened > secondDate
GROUP BY opened,employee
or you can change the first condition in
WHERE opened BETWEEN firstDate and secondDate
Calling the result column count was a bit odd because it seems to be in fact a row number.
You can do that by using ROW_NUMBER.
The other interesting part is that you also want open date and close date as separate rows. Using a simple UNION will solve that.
WITH cte
AS (SELECT Row_number() OVER ( PARTITION BY employee
ORDER BY order_ref) count,
employee,
opened,
closed
FROM orders)
SELECT employee, opened date, count
FROM cte
UNION ALL
SELECT employee, closed date, count
FROM cte
ORDER BY Date,
employee
DEMO