Oracle SQL Query - Between Two Timestamp - sql

For a report, i need to track movement of persons from one place to another within a given time range, based on their activities
Activities
Name
TimeStamp
Activity
Peter
10-JAN-23 05:23:06
Gym
Peter
10-JAN-23 07:01:45
Home
Peter
10-JAN-23 08:09:26
Restaurant
Peter
10-JAN-23 09:19:32
Office
Peter
10-JAN-23 16:43:02
Golf
John
10-JAN-23 07:30:26
Home
John
10-JAN-23 08:30:43
Gym
John
10-JAN-23 10:02:06
Theater
John
10-JAN-23 12:00:32
Soccer
John
10-JAN-23 20:23:02
Bar
From the above table, let's say we need to track movement of people from 8AM to 8PM (08:00 to 20:00) the result would be as below.
Name
From
To
Peter
Home
Restaurant
Peter
Restaurant
Office
Peter
Office
Golf
John
Home
Gym
John
Gym
Theater
John
Theater
Soccer
Using BETWEEN in WHERE CLAUSE the activity between the given range can be fetched. But I am unable to get the first 'FROM' place of each person as it falls outside the time range. I have tried with group by and window functions, but still unable to get the desired result. Can someone please help on this ?
Name
From
To
Peter
?????
Restaurant
Peter
Restaurant
Office
Peter
Office
Golf
John
?????
Gym
John
Gym
Theater
John
Theater
Soccer

Use the LAG analytic function in an inner-query and then filter on the time range in an outer query (if you do it the other way round then you will filter out the value before the start of the range before you can find it using LAG):
SELECT name,
prev_activity AS "FROM",
activity AS "TO"
FROM (
SELECT a.*,
LAG(activity) OVER (PARTITION BY name ORDER BY timestamp) AS prev_activity
FROM activites a
)
WHERE timestamp BETWEEN TIMESTAMP '2023-01-10 08:00:00'
AND TIMESTAMP '2023-01-10 20:00:00';
Which, for the sample data:
CREATE TABLE activities ( Name, TimeStamp, Activity ) AS
SELECT 'Peter', TIMESTAMP '2023-01-10 05:23:06', 'Gym' FROM DUAL UNION ALL
SELECT 'Peter', TIMESTAMP '2023-01-10 07:01:45', 'Home' FROM DUAL UNION ALL
SELECT 'Peter', TIMESTAMP '2023-01-10 08:09:26', 'Restaurant' FROM DUAL UNION ALL
SELECT 'Peter', TIMESTAMP '2023-01-10 09:19:32', 'Office' FROM DUAL UNION ALL
SELECT 'Peter', TIMESTAMP '2023-01-10 16:43:02', 'Golf' FROM DUAL UNION ALL
SELECT 'John', TIMESTAMP '2023-01-10 07:30:26', 'Home' FROM DUAL UNION ALL
SELECT 'John', TIMESTAMP '2023-01-10 08:30:43', 'Gym' FROM DUAL UNION ALL
SELECT 'John', TIMESTAMP '2023-01-10 10:02:06', 'Theater' FROM DUAL UNION ALL
SELECT 'John', TIMESTAMP '2023-01-10 12:00:32', 'Soccer' FROM DUAL UNION ALL
SELECT 'John', TIMESTAMP '2023-01-10 20:23:02', 'Bar' FROM DUAL;
Outputs:
NAME
FROM
TO
John
Home
Gym
John
Gym
Theater
John
Theater
Soccer
Peter
Home
Restaurant
Peter
Restaurant
Office
Peter
Office
Golf
fiddle

Use the windowing LAG(activity) or LEAD(activity) OVER (PARTITION BY name ORDER BY timestamp) to show the previous (or next) location. That's your FROM (or TO, whichever you prefer). Done.

Related

BigQuery SQL - how to pivot the top 3 results into columns

Long time lurker first time poster...
I have a flat table like below and I want to bring back the top 3 finishers by event into separate columns.
with races as (
select "200M" as race, "johnson" as name,23.5 as finishtime
union all
select "200M" as race, "smith" as name,24.1 as finishtime
union all
select "200M" as race, "anderson" as name,23.9 as finishtime
union all
select "200M" as race, "jackson" as name,24.9 as finishtime
union all
select "400M" as race, "johnson" as name,47.1 as finishtime
union all
select "400M" as race, "alexander" as name,46.9 as finishtime
union all
select "400M" as race, "wise" as name,47.2 as finishtime
union all
select "400M" as race, "thompson" as name,46.8 as finishtime
)
select * from races
I would like the output to basically look like this:
Race | 1st Place | 2nd Place | 3rd Place
200M | johnson | anderson | smith
400M | thompson | alexander | johnson
I didn't put a tie into the data above but I will have some of those too...
Thanks in advance!
Consider below
select * from (
select race, name,
row_number() over(partition by race order by finishtime) pos
from races
)
pivot (any_value(name) as place for pos in (1,2,3))
if applied to sample data in your question - output is

How to find all records that are older than or equal to the current month of the previous year?

I barely know SQL. I am trying to achieve the following:
For example I have the following table that has 2 fields
NAME VARCHAR(20)
BDAY TIMESTAMP(6)
I need to find all whose birthdays are current month of the previous year and prior. For example if I am running the query on 08/15/2021, I need to find all records that have a month/date of 08/2020 (ignoring the day part) and prior. So some entries may be less than 12 months old (those after the day of query in the current month of prior year) and would get need to get included in the returned rows.
if I have the following records and I am running the query on 8/15/2021
Mark 12/22/2018
Mike 10/15/2019
Joe 07/31/2020
John 08/06/2020
Jill 08/28/2020
Bill 08/31/2020
Jack 09/01/2020
Jeb 08/08/2021
It needs to return the following:
Mark 12/22/2018
Mike 10/15/2019
Joe 07/31/2020
John 08/06/2020
Jill 08/28/2020
Bill 08/31/2020
If I did something like the following,
select * from BDAY where BDAY < add_months(systimestamp,-12) order by BDAY desc;
it would exclude the entries for the current month of previous year that are after the date when the query is run. I can get the desired result using convoluted queries but I was wondering if there was an easier way of doing this, e.g. an oracle function.
You can compare with the first day of next month, ie 11 months ago instead of 12:
select * from bday where bday<trunc(add_months(sysdate ,-11), 'mm');
Full example with test data:
alter session set nls_date_format='mm/dd/yyyy';
with bday(name, bday) as (
select 'Mark',to_date('12/22/2018','mm/dd/yyyy') from dual union all
select 'Mike',to_date('10/15/2019','mm/dd/yyyy') from dual union all
select 'Joe ',to_date('07/31/2020','mm/dd/yyyy') from dual union all
select 'John',to_date('08/06/2020','mm/dd/yyyy') from dual union all
select 'Jill',to_date('08/28/2020','mm/dd/yyyy') from dual union all
select 'Bill',to_date('08/31/2020','mm/dd/yyyy') from dual union all
select 'Jack',to_date('09/01/2020','mm/dd/yyyy') from dual union all
select 'Jeb ',to_date('08/08/2021','mm/dd/yyyy') from dual
)
select * from bday where bday<trunc(add_months(sysdate ,-11), 'mm');
NAME BDAY
---- ----------
Mark 12/22/2018
Mike 10/15/2019
Joe 07/31/2020
John 08/06/2020
Jill 08/28/2020
Bill 08/31/2020
6 rows selected.
DBFIDDLE: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=b9f4889a47f59f6a48d3fb2d0a212617

SQL getting multiple rows in PARTICULAR sql

I have a table abc with following rows:
emp_id_role Group_name Role_name Location_id
12 Insurance Manager Noida
12 Insurance Senior Manager Noida
13 Global Client Services Sw UP
14 Operations Management All Jobs kERALA
and another master table with all the details employee_xyz:
PERSON_ID NAME DOB START_DATE END_DATE SSN
12 DEAN 01-JAN-1990 01-JAN-2017 20-JAN-2017 847474
12 DEAN 01-JAN-1990 21-JAN-2017 03-mar-2018 847474
12 DEAN 01-JAN-1990 04-mar2018 31-DEC-4712 847474
13 SAM 20-JAN-1990 17-JAN-2016 20-JAN-2017 847474
13 SAM 20-JAN-1990 21-JAN-2017 31-DEC-4712 847474
14 JAY 29-dec-1990 21-JAN-2016 31-DEC-4712 847474
I want to fetch the full names from the table employee_xyz for the records in table abc.
When I'm joining these two using the below queries I'm getting more number of rows for an employee than in table abc,
Eg: for employee_id 12 I should get 2 rows as in table abc but I'm getting 9 rows somehow...
Query used is simple :
select * from table_abc abc,
employee_xyz xyz
where xyz.person_id=abc.emp_id_role
and trunc(sysdate) between abc.Start_date and xyz.end_date
and person_id=12;
I suppose you want to select all columns from table_abc with names from employee_xyz.
If your try with select abc.*, xyz.name, you already able to select as two rows since sysdate for person_id= 12 stays in the interval only for third row(start_date:04-mar2018/ end_date:31-DEC-4712) but if you want non-repeating even with more date values stay, you can use distinct : select distinct abc.*, xyz.name as below( where there're two rows in the interval second row's end date converted from '2018-03-03' to '2019-03-03' ) :
with table_abc(emp_id_role, Group_name, Role_name, Location_id) as
(
select 12,'Insurance','Manager','Noida' from dual union all
select 12,'Insurance','Senior Manager','Noida' from dual union all
select 13,'Global Client Services','Sw', 'UP' from dual union all
select 14,'Operations Management','All Jobs','kERALA' from dual
),
employee_xyz(person_id,name, dob, start_date, end_date, ssn) as
(
select 12,'DEAN',date'1990-01-01',date'2017-01-01',date'2017-01-20',847474 from dual union all
select 12,'DEAN',date'1990-01-01',date'2017-01-21',date'2019-03-03',847474 from dual union all
select 12,'DEAN',date'1990-01-01',date'2018-03-04',date'4712-12-31',847474 from dual
)
select distinct abc.*, xyz.name
from table_abc abc join employee_xyz xyz
on xyz.person_id = abc.emp_id_role
where trunc(sysdate) between xyz.Start_date and xyz.end_date
and person_id = 12;
EMP_ID_ROLE GROUP_NAME ROLE_NAME LOCATION_ID NAME
----------- ---------- --------------- ----------- -----
12 Insurance Manager Noida DEAN
12 Insurance Senior Manager Noida DEAN

Oracle SQL Repeated words in the String

I need your suggestions/inputs on of the following task. I have the following table:
ID ID_NAME
------ ---------------------------------
1 TOM HANKS TOM JR
2 PETER PATROL PETER JOHN PETER
3 SAM LIVING
4 JOHNSON & JOHNSON INC
5 DUHGT LLC
6 THE POST OF THE OFFICE
7 TURNING REP WEST
8 GEORGE JOHN
I Need a SQL query to find a repetitive word for every ID. if it exists, i need to get the count of the repeated word.
For instance in ID 2, the word PETER was repeated 3 times and in ID 1 the word TOM was repeated twice. so I need the output something like this:
ID ID_NAME COUNT
------ --------------------------------- --------
1 TOM HANKS TOM JR 2
2 PETER PATROL PETER JOHN PETER 3
3 SAM LIVING 0
4 JOHNSON & JOHNSON INC 2
5 DUHGT LLC 0
6 THE POST OF THE OFFICE 2
7 TURNING REP WEST 0
8 GEORGE JOHN 0
Just an FYI, The table has 560K rows
I tried the below and it didn't work and it is literally looking for every single word.
SELECT RESULT, COUNT(*)
FROM (SELECT
REGEXP_SUBSTR(COL_NAME, '[^ ]+', 1, COLUMN_VALUE) RESULT
FROM TABLE_NAME T ,
TABLE(CAST(MULTISET(SELECT DISTINCT LEVEL
FROM TABLE_NAME X
CONNECT BY LEVEL <= LENGTH(X.COL_NAME) - LENGTH(REPLACE(X.COL_NAME, ' ', '')) + 1
) AS SYS.ODCINUMBERLIST)) T1
)
WHERE RESULT IS NOT NULL
GROUP BY RESULT
ORDER BY 1;
Please let me know your inputs.
The query below counts repeated words and returns the highest count (if a word appears three times and another appears twice, the result will be the number 3). It treats JOHN as different from John (if capitalization shouldn't count as "different" then wrap the input strings within UPPER(...)). It only considers space as a word delimiter; if something else, like dash, is also considered as a delimiter, add to the REGEXP search pattern. Make sure you put a dash right at the end of a square-bracketed matching character list, etc. - the usual "tricks" for matching character lists. More generally, adapt as needed.
The query first breaks each input string into individual words, and counts how many times each word appears. For the count, I only need the words ("tokens") in the GROUP BY clause, I don't need to actually SELECT them, this is why the innermost query may look odd if you aren't forewarned. (Now you are!)
It also seems you want to show null rather than 1 if there are no repeated words, so I wrote the query to accommodate that. (Not sure why 1 wasn't OK.)
with
test_data ( id, id_name ) as (
select 1, 'TOM HANKS TOM JR' from dual union all
select 2, 'PETER PATROL PETER JOHN PETER' from dual union all
select 3, 'SAM LIVING' from dual union all
select 4, 'JOHNSON & JOHNSON INC' from dual union all
select 5, 'DUHGT LLC' from dual union all
select 6, 'THE POST OF THE OFFICE' from dual union all
select 7, 'TURNING REP WEST' from dual union all
select 8, 'GEORGE JOHN' from dual
)
-- end of test data; SQL query begins below this line
select id, id_name, case when max(cnt) >= 2 then max(cnt) end as max_count
from (
select id, id_name, count(*) as cnt
from test_data
connect by level <= 1 + regexp_count(id_name, ' ')
and prior id = id
and prior sys_guid() is not null
group by id, id_name, regexp_substr(id_name, '[^ ]+', 1, level)
)
group by id, id_name
order by id -- if needed
;
Output:
ID ID_NAME MAX_COUNT
-- ----------------------------- ----------
1 TOM HANKS TOM JR 2
2 PETER PATROL PETER JOHN PETER 3
3 SAM LIVING
4 JOHNSON & JOHNSON INC 2
5 DUHGT LLC
6 THE POST OF THE OFFICE 2
7 TURNING REP WEST
8 GEORGE JOHN
8 rows selected.
EDIT:
If you only need to find the returns where the string column has at least one repeated word, and you don't care what the highest "repeated word count" is or how many words are repeated, the solution is simpler and more efficient; you don't need to split the input string into component words and count them.
(The OP indicated in the comments, after long dialogue, that this would suffice.)
In the solution the "match pattern" in regexp_like searches for a string of letters, preceded by either the beginning of the string or a space or a dash and ended by space, comma, period, question mark, exclamation point or dash. Both "markers", for beginning and end of a word, can be modified as needed. Make sure the dash is either the first or last character in [...], anywhere else it has a special meaning.
Then it looks for a repetition of the word. That's what \2 does in the match pattern. It's 2 and not 1 because the "word" is in the second pair of parentheses; I need the first pair for the alternation, EITHER start-of-string OR (space or dash).
Look at the first and the last string for special situations that this query covers correctly. Think of any other possible situations that the query may or may not cover.
with
test_data ( id, id_name ) as (
select 1, 'TOM HANKS TOM-ALAN' from dual union all
select 2, 'PETER PATROL PETER JOHN PETER' from dual union all
select 3, 'SAM LIVING' from dual union all
select 4, 'JOHNSON & JOHNSON INC' from dual union all
select 5, 'DUHGT LLC' from dual union all
select 6, 'THE POST OF THE OFFICE' from dual union all
select 7, 'TURNING REP WEST' from dual union all
select 8, 'GEORGE JOHN-JOHN' from dual
)
-- end of test data; SQL query begins below this line
select id, id_name
from test_data
where regexp_like(id_name, '(^|[ -])([[:alpha:]]+)[ ,.?!-].*\2')
order by id -- if needed
;
ID ID_NAME
-- -----------------------------
1 TOM HANKS TOM-ALAN
2 PETER PATROL PETER JOHN PETER
4 JOHNSON & JOHNSON INC
6 THE POST OF THE OFFICE
8 GEORGE JOHN-JOHN
The next solution find first repeted word and in next step find count of repeating. Edit just now to fix extra subword findings
with s (ID, ID_NAME) as (
select 1, 'TOM HANKS TOM JR' from dual union all
select 10, 'TO TOM TOM TOM TOM TO TO TO STOM HANKS TOM TOMMY' from dual union all
select 2, 'PETER PATROL PETER JOHN PETER' from dual union all
select 3, 'SAM LIVING' from dual union all
select 4, 'qwe JOHNSON & JOHNSON INC' from dual union all
select 5, 'DUHGT LLC' from dual union all
select 6, 'THE POST OF THE OFFICE ' from dual union all
select 7, 'TURNING REP WEST ' from dual union all
select 8, 'GEORGE JOHN ' from dual)
select id,
case when r1 = 0 then 0
else regexp_count(id_name, r3)
- regexp_count(id_name, r3||'\w+') -- exlude word with tail
- regexp_count(id_name, '\w+'||r3) -- exclude words with head
+ regexp_count(id_name, '\w+'||r3||'\w+') -- double calc with head and tail
end as rep_count
from (
select
s.*,
regexp_instr(s.id_name, '(^|\s)(\w+)(\s|$)(.*(\2))+') as r1 ,
regexp_replace(s.id_name, '.*?(^|\s)(\w+)(\s)(.*(\s)\2(\s|$))+.*$', '\2') as r3
from s);
the result is
ID REP_COUNT
---------- ----------
1 2
10 4
2 3
3 0
4 2
5 0
6 2
7 0
8 0

SQL Server 2014 - Return Team name based on most recent date (somewhat dynamically)

My title is misleading because I don't know how to sum it up better than that :)
I have a table that keeps a history of changes made to users and what teams they belong to. It starts with their initial team and date, then adds an entry via a trigger when we change their teams in the UserList table.
Our business, like many, loves month to month data. I don't want to have entries for every single month if they don't change teams. Ill get to why that's a problem.
Here is an example of the data in the TeamHistory Table
UserID|CurrentTeam|ChangeDate
User1-|Team1------|01-01-2016
User1-|Team2------|03-01-2016
When I run a view or query that rolls the data up by person and media type (I can have 4 entries for a single person in a single month - voice, fax, email and voicemail) I then need to add the team that they were working on for that month.
Using that above example, if I ran the data for all of last year, I would expect Jan-May to display Team1. Then from June to Dec, Team 2. The problem is if I join the date field in my view/query with this table and use an = sign, then I only get data for 1-1 and 6-1, clearly because I only have those values in the table to match against. If I tell it to do < or <=, I start encountering duplicates as its just not specific enough.
If we need an example query, I can try to work something up that's not one of these massive views.
So lets assume this is my data:
Userid| Month |Media|Calls
User1-|-01/01/2016|Voice|200
User1-|-01/01/2016|Email|100
User1-|-02/01/2016|Voice|250
User1-|-02/01/2016|Email|120
User1-|-03/01/2016|Voice|250
User1-|-03/01/2016|Email|120
And the TeamHistory table has 2 entries, the team they started on for 1/1/2016 and then they switched for 3/1/2016. How do I join the two data sets, using the date and userid as my variables, to pull in the corresponding Team? Especially when I wont have an actual entry for 2/1/2016?
Id want my final dataset to look like this:
Userid|Team | Month |Media|Calls
User1-|Team1|-01/01/2016|Voice|200
User1-|Team1|-01/01/2016|Email|100
User1-|Team1|-02/01/2016|Voice|250
User1-|Team1|-02/01/2016|Email|120
User1-|Team2|-03/01/2016|Voice|250
User1-|Team2|-03/01/2016|Email|120
Since you're using SQL Server (2012 and newer) you can use the LEAD() function to identify an end date for a given range:
;with cte aS (SELECT 'User1' as UserID, 'Team1' AS CurrentTeam, CAST('2016-01-01' AS DATE) as ChangeDate
UNION SELECT 'User1' as UserID, 'Team2' AS CurrentTeam, CAST('2016-06-01' AS DATE) as ChangeDate
UNION SELECT 'User1' as UserID, 'Team1' AS CurrentTeam, CAST('2016-08-15' AS DATE) as ChangeDate
UNION SELECT 'User2' as UserID, 'Team1' AS CurrentTeam, CAST('2016-02-01' AS DATE) as ChangeDate
UNION SELECT 'User2' as UserID, 'Team2' AS CurrentTeam, CAST('2016-07-01' AS DATE) as ChangeDate
)
SELECT *,COALESCE(LEAD(ChangeDate,1) OVER(PARTITION BY UserID ORDER BY ChangeDate),CAST(GETDATE() AS DATE)) as End_Dt
FROM cte
Returns:
UserID CurrentTeam ChangeDate End_Dt
User1 Team1 2016-01-01 2016-06-01
User1 Team2 2016-06-01 2016-08-15
User1 Team1 2016-08-15 2017-01-05
User2 Team1 2016-02-01 2016-07-01
User2 Team2 2016-07-01 2017-01-05
You could then join those ranges to a calendar table to get the individual months as well as calculate which team they spent more days in for a given month.
The LEAD() function returns the next row's value for a given field, PARTITION BY is used to reset the next row based on some grouping, in this case you want the value per UserID, and ORDER BY is used to specify what the next row should be, in this case from one ChangeDate to the next.
You might try this:
--A simple person table
DECLARE #pers TABLE(Person VARCHAR(100));
INSERT INTO #pers VALUES('Bob'),('Tim');
--a table reflecting your work-data
--attention Tim is changing in July to Team Read and still in July back to Blue
DECLARE #Team TABLE(Person VARCHAR(100),Team VARCHAR(100),ChangeDate DATE);
INSERT INTO #Team VALUES
('Bob','Red' ,{d'2016-04-01'})
,('Tim','Blue',{d'2016-04-13'})
,('Tim','Red' ,{d'2016-07-22'})
,('Bob','Blue',{d'2016-06-15'})
,('Tim','Blue',{d'2016-07-28'})
,('Bob','Red' ,{d'2016-10-15'})
,('Tim','Red' ,{d'2016-12-28'})
;
--A CTE to mock-up a numbers/tally/date-table
WITH FirstOfMonthDays(d) AS
(
SELECT {d'2016-01-01'}
UNION ALL SELECT {d'2016-02-01'}
UNION ALL SELECT {d'2016-03-01'}
UNION ALL SELECT {d'2016-04-01'}
UNION ALL SELECT {d'2016-05-01'}
UNION ALL SELECT {d'2016-06-01'}
UNION ALL SELECT {d'2016-07-01'}
UNION ALL SELECT {d'2016-08-01'}
UNION ALL SELECT {d'2016-09-01'}
UNION ALL SELECT {d'2016-10-01'}
UNION ALL SELECT {d'2016-11-01'}
UNION ALL SELECT {d'2016-12-01'}
)
--I use CONVERT(VARCHAR(6),ChangeDate,112) to get a string of YYYYMM
,Numbered AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY Person, CONVERT(VARCHAR(6),ChangeDate,112) ORDER BY ChangeDate DESC) AS Nr
,t.*
FROM #Team AS t
)
--Pick out the one with Nr=1, these are the last changes per month
,LastChangeInMonth AS
(
SELECT *
FROM Numbered
WHERE Nr=1
)
--The actual query
SELECT fom.d
,p.Person
,(
SELECT TOP 1 t.Team
FROM LastChangeInMonth AS t
WHERE t.Person=p.Person
AND CONVERT(VARCHAR(6),t.ChangeDate,112)<=CONVERT(VARCHAR(6),fom.d,112)
ORDER BY t.ChangeDate DESC
) AS fittingTeam
FROM FirstOfMonthDays AS fom
CROSS JOIN #pers AS p
ORDER BY p.Person,fom.d
Since you are using SQL Server 2014 (please tag your questions correctly!) this would be a bit easier with LEAD()/LAG/(), but the idea was the same...
The result
2016-01-01 Bob NULL
2016-02-01 Bob NULL
2016-03-01 Bob NULL
2016-04-01 Bob Red
2016-05-01 Bob Red
2016-06-01 Bob Blue
2016-07-01 Bob Blue
2016-08-01 Bob Blue
2016-09-01 Bob Blue
2016-10-01 Bob Red
2016-11-01 Bob Red
2016-12-01 Bob Red
2016-01-01 Tim NULL
2016-02-01 Tim NULL
2016-03-01 Tim NULL
2016-04-01 Tim Blue
2016-05-01 Tim Blue
2016-06-01 Tim Blue
2016-07-01 Tim Blue
2016-08-01 Tim Blue
2016-09-01 Tim Blue
2016-10-01 Tim Blue
2016-11-01 Tim Blue
2016-12-01 Tim Red