Join tables displaying one column based on max of another - sql

I'm joining some tables and need to return values based upon a max value without actually returning the max value column itself.
Table1
+------------+----------+---------+
| EmployeeID | TaskTime | JobType |
+------------+----------+---------+
| 1000 | 5:14:00 | Read |
| 1000 | 5:42:00 | Write |
| 1000 | 6:14:00 | Write |
+------------+----------+---------+
Table2
+------------+-----------+-----------+---------+
| EmployeeID | ClockType | JobDetail | JobType |
+------------+-----------+-----------+---------+
| 1000 | 5:03:00 | This | Read |
| 1000 | 5:21:00 | That | Write |
+------------+-----------+-----------+---------+
What I want to now return is the JobDetail corresponding to the last ClockType before the TaskTime. So something like this:
+------------+----------+---------+-----------+
| EmployeeID | TaskTime | JobType | JobDetail |
+------------+----------+---------+-----------+
| 1000 | 5:14:00 | Read | This |
| 1000 | 5:42:00 | Write | That |
| 1000 | 6:14:00 | Write | That |
+------------+----------+---------+-----------+
My query I'm using is as follows:
SELECT DISTINCT X.*, t2.JobDetail
FROM
(
SELECT t1.EmployeeID, t1.TaskTime, t1.JobType
FROM Table1 t1
WHERE t1.EmployeeID=10000
) X
LEFT JOIN Table2 t2 on (X.EmployeeID = t2.EmployeeID) and (X.JobType = t2.JobType) and (t2.ClockTime < X.TaskTime)
This returns all rows where ClockTime < TaskTime, as expected. I just cant seem to figure out how to show only the last ClockTime before TaskTime. I tried changing my JOIN like the following but it returns all null values for JobDetail:
LEFT JOIN
(
SELECT row_number() over(partition by ClockTime order by ClockTime desc) rn, JobDetail, ClockTime as time2
FROM Table2) t2
on (X.EmployeeID = t2.EmployeeID) and (X.JobType = t2.JobType) and (t2.ClockTime < X.TaskTime)

You can try using TOP and Apply to get the max values.
select *
from Table1 as t1
cross apply ( select top 1
JobDetail
from Table2 as t2
where t1.EmployeeID = t2.EmployeeID
and t1.JobType = t2.JobType
and t1.TaskTime >= t2.ClockType
order by ClockType desc) as jt
SQL Fiddle
Using Row_Number()
select *
from ( select t1.EmployeeID
, t1.TaskTime
, t1.JobType
, t2.JobDetail
, row_number() over (partition by t1.EmployeeID, t1.JobType, t1.TaskTime
order by t2.ClockType desc) as Rn
from Table1 as t1
join Table2 as t2 on t1.EmployeeID = t2.EmployeeID
and t1.JobType = t2.JobType
and t1.TaskTime >= t2.ClockType) as t
where Rn = 1
SQL Fiddle

Related

Assign unique id based on combination in sql

The data looks like this:
Need to assign id based on the combination of 2 columns and get the id of each value in 2 columns
final output should look like:
I tried with
WITH RNS AS (
SELECT *, ROW_NUMBER() OVER () AS rn
FROM test
),
IDS AS (
SELECT t1.coLA, t1.colB, t1.rn, MIN(COALESCE(t2.rn, t1.rn)) AS id
FROM RNS t1
LEFT JOIN RNS t2 ON t1.rn > t2.rn
AND (t1.colA = t2.colA OR t1.colA = t2.colB OR
t1.colB = t2.colA OR t1.colB = t2.colB)
GROUP BY t1.coLA, t1.colB, t1.rn
ORDER BY t1.rn
)
SELECT colA, colB, DENSE_RANK() OVER (ORDER BY id) AS id
FROM IDS
ORDER BY rn
but not working as expected
Using RECURSIVE CTE in BigQuery, you may try below query
WITH RECURSIVE test AS (
SELECT * EXCEPT(offset)
FROM UNNEST(SPLIT('abaaeghjc', '')) colA WITH OFFSET
JOIN UNNEST(SPLIT('bccdfhikl', '')) colB WITH OFFSET USING (offset)
),
IDS AS (
SELECT *, DENSE_RANK() OVER (ORDER BY colA) id
FROM test t1
WHERE NOT EXISTS (SELECT 1 FROM test t2 WHERE t1.colA = t2.colB)
UNION ALL
SELECT t.*, id FROM IDS i JOIN test t ON i.colB = t.colA
)
SELECT DISTINCT col, id FROM IDS, UNNEST([colA, colB]) col
ORDER BY 1;
Query results:
+-----+----+
| col | id |
+-----+----+
| a | 1 |
| b | 1 |
| c | 1 |
| d | 1 |
| e | 2 |
| f | 2 |
| g | 3 |
| h | 3 |
| i | 3 |
| j | 4 |
| k | 4 |
| l | 1 |
+-----+----+

SQL query For Latest date/time Stamp record for each ID

Please help to sort below list TABLE,
ID NAME DATE TIME STATUS
ID is unique, Name, Date, Time, Status keeps changing in database.
I need output list, having Latest STATUS, DATE AND TIME stamps for each user ID
I would use window functions for this:
select t.*
from (select t.*,
row_number() over (partition by id order by date desc, time desc) as seqnum
from t
) t
where seqnum = 1;
Alternatively, if you have a table with one row per customer, then apply might be best:
select t.*
from customers c cross apply
(select top (1) t.*
from t
where t.id = c.id
order by date desc, time desc
) t;
How about
SELECT T1.*
FROM T T1 INNER JOIN
(
SELECT ID,
CName,
MAX(CDate) CDate,
MAX(CTime) CTime
FROM T
GROUP BY ID,
CName
) T2
ON T1.CDate = T2.CDate
AND
T1.CTime = T2.CTime
AND T1.CName = T2.CName;
Which will return
+---------------------+----------+--------+----+-------+
| CDate | CTime | Status | ID | CName |
+---------------------+----------+--------+----+-------+
| 22/12/2018 00:00:00 | 16:27:57 | 1 | 1 | A |
| 21/12/2018 00:00:00 | 15:41:13 | 4 | 2 | B |
| 20/12/2018 00:00:00 | 12:35:27 | 3 | 2 | C |
| 21/12/2018 00:00:00 | 15:29:46 | 4 | 3 | D |
+---------------------+----------+--------+----+-------+
OR
SELECT T1.*
FROM T T1 INNER JOIN
(
SELECT ID,
MAX(CDate) CDate,
MAX(CTime) CTime
FROM T
GROUP BY ID
) T2
ON T1.CDate = T2.CDate
AND
T1.CTime = T2.CTime;
Which will return
+---------------------+----------+--------+----+-------+
| CDate | CTime | Status | ID | CName |
+---------------------+----------+--------+----+-------+
| 22/12/2018 00:00:00 | 16:27:57 | 1 | 1 | A |
| 21/12/2018 00:00:00 | 15:41:13 | 4 | 2 | B |
| 21/12/2018 00:00:00 | 15:29:46 | 4 | 3 | D |
+---------------------+----------+--------+----+-------+
Demo
SELECT * FROM table
WHERE C_Time =
(SELECT max(C_Time) FROM table t1 WHERE C_Date =
(SELECT max(C_Date) FROM table t2 WHERE t1.ID = t2.ID)
);
This gives you the entries for the highest C_Date and C_Time values for every ID

How can I select for the latest row based on two different attributes?

I need to select the latest activity code (A, V, W, J) for the following transactions (109, 154, 982, 745) for my employees. I need to know what my employee last transaction was (from the list) that had one of those activity codes. There are 2 tables involved with a join on the employee ID.
Table 1:
|Emp_id | STUFF
| 1 | stuff
| 2 | stuff
| 3 | stuff
Table 2:
|Emp_id | date | act_code | trans
| 1 | 1/1/17 | A | 109
| 1 | 3/4/12 | X | 203
| 1 | 2/14/09 | A | 154
| 2 | 1/1/17 | A | 110
| 2 | 6/6/13 | V | 109
| 3 | 12/13/16 | J | 982
| 3 | 11/23/14 | W | 745
| 4 | 12/13/16 | X | 154
| 4 | 11/23/14 | W | 745
What I’d like to return is:
|Emp_id | STUFF | date | act_code | trans
| 1 | stuff | 1/1/17 | A | 109
| 3 | stuff | 12/13/16 | J | 982
Emp 2 would not be selected because the latest trans is not one of the correct values. Emp 4 would not be selected because the latest act_code is not one of the correct values. Anyone have an idea as to how to go about this? Thanks in advance.
Here is one way.
Use ROW_NUMBER() to partition the rows by emp_id and order the rows by date:
SELECT t2.emp_id, t1.stuff, t2.date, t2.act_code, t2.trans,
ROW_NUMBER() over (PARTITION BY t2.emp_id ORDER BY t2.date DESC) RN
FROM Table1 t1
JOIN Table2 t2 on t1.emp_id = t2.emp_id;
Then filter this to only the most recent records (RN = 1) that have values in your lists with an outer select:
SELECT emp_id, stuff, date, act_code, trans
FROM (
SELECT t2.emp_id, t1.stuff, t2.date, t2.act_code, t2.trans,
ROW_NUMBER() over (PARTITION BY t2.emp_id ORDER BY t2.date DESC) RN
FROM Table1 t1
JOIN Table2 t2 on t1.emp_id = t2.emp_id
) A
WHERE RN = 1
AND trans IN (109, 154, 982, 745)
AND act_code IN ('A', 'V', 'W', 'J');
Use first_value to get the latest values of act_code,trans and then check if they are in the specified list.
select * from (
select distinct t1.emp_id,t1.stuff,
max(t2.date) over(partition by t2.emp_id) as latest_date,
first_value(t2.act_code) over(partition by t2.emp_id order by t2.date desc) as latest_act_code,
first_value(t2.trans) over(partition by t2.emp_id order by t2.date desc) as latest_trans
from tbl1 t1
join tbl2 t2 on t1.emp_id=t2.emp_id
) t
where latest_act_code in ('A','V','W','J') and latest_trans in (109, 154, 982, 745)

Oracle SQL Query with joining four tables to get only one row of multiple entries

My tables are set up something like this:
Table 1 Table 2 Table 3
+-------+-----+ +-------+-------+-------+-----+ +-------+-----+
| ID | ... | | ID | T1_ID | T3_ID | ... | | ID | ... |
+-------+-----+ +-------+-------|-------|-----| |-------|-----|
| 101 | ... | | 202 | 101 | 301 | ... | | 300 | ... |
| 102 | ... | | 203 | 101 | 302 | ... | | 302 | ... |
| 104 | ... | | 204 | 101 | 302 | ... | | 314 | ... |
+-------+-----+ | 205 | 101 | 302 | ... | +-------+-----+
| 206 | 104 | 327 | ... |
+-------+-------+-------+-----+
I want to construct a subquery statement that will select only one row of table 2 for an given id of table 1, if table2.t3_id exists in table 3.
The important point is that there maybe exist multiple rows with same t3_id in table 2. So, the foreign key relation between table 2 and table 3 is not unique or does not exist at all.
My idea was the following statement:
inner join
(
SELECT *
FROM (
SELECT t3_id, t1_id, id
FROM table2
WHERE EXISTS
(
SELECT id
FROM table3
)
)
WHERE ROWNUM=1
) tb2 ON tb1.id = tb2.t1_id
This statement returns multiple rows, but I only need one.
How do I do this?
Not tested but should do what you need
SELECT *
FROM table1 t1 JOIN table2 t2
ON ( t1.id = t2.t1_id
AND EXISTS ( SELECT 'x'
FROM table3 t3
WHERE t2.t3_id = t3.id
)
AND NOT EXISTS ( SELECT 'a'
FROM table2 t22
WHERE t22.t1_id = t2.t1_id
AND t22.id < t2.id
)
)
You can get one row of multiple entries by using row_number() to enumerate them and then selecting just one value. Here is an example:
select . . .
from table1 t1 join
(select t2.*, row_number() over (partition by t.id order by t2.id) as seqnum
from table2 t2
) t2
on t2.t1_id = t1.id and t2.seqnum = 1;
EDIT:
For all three tables, you want to do the row_number() all the joins:
select . . .
from (select . . ., row_number() over (partition by t1_id order by id) as seqnum
from table1 t1 join
table2 t2
on t2.t1_id = t1.id join
table3 t3
on t2.t3_id = t3.id
) t
where seqnum = 1;

How to get Previous Value for Null Values

I have the Below Data in my Table.
| Id | FeeModeId |Name | Amount|
---------------------------------------------
| 1 | NULL | NULL | 20 |
| 2 | 1 | Quarter-1 | 5000 |
| 3 | NULL | NULL | 2000 |
| 4 | 2 | Quarter-2 | 8000 |
| 5 | NULL | NULL | 5000 |
| 6 | NULL | NULL | 2000 |
| 7 | 3 | Quarter-3 | 6000 |
| 8 | NULL | NULL | 4000 |
How to write such query to get below output...
| Id | FeeModeId |Name | Amount|
---------------------------------------------
| 1 | NULL | NULL | 20 |
| 2 | 1 | Quarter-1 | 5000 |
| 3 | 1 | Quarter-1 | 2000 |
| 4 | 2 | Quarter-2 | 8000 |
| 5 | 2 | Quarter-2 | 5000 |
| 6 | 2 | Quarter-2 | 2000 |
| 7 | 3 | Quarter-3 | 6000 |
| 8 | 3 | Quarter-3 | 4000 |
Since you are on SQL Server 2012... here is a version that uses that. It might be faster than other solutions but you have to test that on your data.
sum() over() will do a running sum ordered by Id adding 1 when there are a value in the column and keeping the current value for null values. The calculated running sum is then used to partition the result in first_value() over(). The first value ordered by Id for each "group" of rows generated by the running sum has the value you want.
select T.Id,
first_value(T.FeeModeId)
over(partition by T.NF
order by T.Id
rows between unbounded preceding and current row) as FeeModeId,
first_value(T.Name)
over(partition by T.NS
order by T.Id
rows between unbounded preceding and current row) as Name,
T.Amount
from (
select Id,
FeeModeId,
Name,
Amount,
sum(case when FeeModeId is null then 0 else 1 end)
over(order by Id) as NF,
sum(case when Name is null then 0 else 1 end)
over(order by Id) as NS
from YourTable
) as T
SQL Fiddle
Something that will work pre SQL Server 2012:
select T1.Id,
T3.FeeModeId,
T2.Name,
T1.Amount
from YourTable as T1
outer apply (select top(1) Name
from YourTable as T2
where T1.Id >= T2.Id and
T2.Name is not null
order by T2.Id desc) as T2
outer apply (select top(1) FeeModeId
from YourTable as T3
where T1.Id >= T3.Id and
T3.FeeModeId is not null
order by T3.Id desc) as T3
SQL Fiddle
Please try:
select
a.ID,
ISNULL(a.FeeModeId, x.FeeModeId) FeeModeId,
ISNULL(a.Name, x.Name) Name,
a.Amount
from tbl a
outer apply
(select top 1 FeeModeId, Name
from tbl b
where b.ID<a.ID and
b.Amount is not null and
b.FeeModeId is not null and
a.FeeModeId is null order by ID desc)x
OR
select
ID,
ISNULL(FeeModeId, bFeeModeId) FeeModeId,
ISNULL(Name, bName) Name,
Amount
From(
select
a.ID , a.FeeModeId, a.Name, a.Amount,
b.ID bID, b.FeeModeId bFeeModeId, b.Name bName,
MAX(b.FeeModeId) over (partition by a.ID) mx
from tbl a left join tbl b on b.ID<a.ID
and b.FeeModeId is not null
)x
where bFeeModeId=mx or mx is null
SELECT
T.ID,
ISNULL(T.FeeModeId,
(SELECT TOP 1 FeeModeId
FROM TableName AS T1
WHERE ID < T.ID AND FeeModeId IS NOT NULL
ORDER BY ID DESC)) AS FeeModeId,
ISNULL(Name,
(SELECT TOP 1 Name
FROM TableName
WHERE ID < T.ID AND Name IS NOT NULL
ORDER BY ID DESC)) AS Name,
T.Amount
FROM
TableName AS T
try this -
SELECT Id,
CASE
WHEN Feemodeid IS NOT NULL THEN
Feemodeid
ELSE
(SELECT Feemodeid
FROM Table_Name t_2
WHERE t_2.Id = (SELECT MAX(Id)
FROM Table_Name t_3
WHERE t_3.Id < t_1.Id
AND Feemodeid IS NOT NULL))
END Feemodeid,
CASE
WHEN NAME IS NOT NULL THEN
NAME
ELSE
(SELECT NAME
FROM Table_Name t_2
WHERE t_2.Id = (SELECT MAX(Id)
FROM Table_Name t_3
WHERE t_3.Id < t_1.Id
AND NAME IS NOT NULL))
END NAME,
Amount
FROM Table_Name t_1
id name
1 toto
2 NULL
3 NULL
4 titi
5 NULL
6 NULL
7 tutu
8 NULL
9 NULL
SELECT
id_table
,name
FROM
(
SELECT
T_01.id AS 'id_table'
,max(T_02.id) AS 'id_name'
FROM
names AS T_01
cross join
(
SELECT
id
,name
FROM
names
WHERE
name IS NOT NULL
) AS T_02
WHERE
T_02.id <= T_01.id
GROUP BY
T_01.id
) AS tt02
left join names
ON names.id = tt02.id_name
id_table name
1 toto
2 toto
3 toto
4 titi
5 titi
6 titi
7 tutu
8 tutu
9 tutu