Finding max of a column while doing inner join of two tables - sql

I have two tables as follows:
Table A
=====================
student_id test_week
-------- ---------
s1 2018-12-01
s1 2018-12-08
Table B
======================
student_id last_updated remarks
-------- ------------ --------
s1 2018-12-06 Fail
s1 2018-12-10 Pass
Above two tables, I want to fetch following columns:
student_id, last(test_week) and remarks such that
last_updated>=test_week -1 and last_updated<=test_week-15,
i.e. last_updated should be within two weeks of last(test_week), so following will be the result for above entries:
s1 2018-12-08 Pass
I have written like following:
select a.student_id, test_week, remarks
from A inner join B
on A.student_id = B.student_id
and DATEDIFF(last_updated, test_week)>=1
and DATEDIFF(last_updated, test_week)<=15;
But how I will handle the last(test_week), that I am not getting.

If you need the only record related to the last test_week then you can do the following. If I understood this right.
select top 1 a.student_id, test_week, remarks
from A inner join B
on A.student_id = B.student_id
and DATEDIFF(last_updated, test_week)>=1
and DATEDIFF(last_updated, test_week)<=15
order by last_week desc;

You can try to use window function row_number(). The following query will give the max(test_week) for every student_id.
select * from (
select id, test_week, remarks, row_number()
over (partition by id order by test_week desc) as rn
from (
select a.id, test_week, remarks from A join B on A.id = B.id and last_updated - test_week >=1 and last_updated - test_week <=15)tb1
)tb2 where rn=1;
Note : The above query is supported in postgresql, you might want to convert it into equivalent Mysql query

Related

Return only one row based on search

Query
select
a.id,
a.ba,
b.status,
b.custid
from balist as a
inner join customer as b
on a.ba = b.ba
I have a table "balist" that has a list of (ba) and i inner join table "customer" on (ba) and right now by output is like the following
id
ba
status
custid
1
ba-1234455
A
123-321-123-321a
2
ba-1234455
I
123-321-123-321a
3
ba-1234457
A
123-321-123-321b
4
ba-1234458
A
123-321-123-321c
5
ba-1234459
I
123-321-123-321d
and I want to return all A and I status but remove the row that has status I that also have a A status. Like the following.
I have a table customer like the following
id
ba
status
custid
1
ba-1234455
A
123-321-123-321a
3
ba-1234457
A
123-321-123-321b
4
ba-1234458
A
123-321-123-321c
5
ba-1234459
I
123-321-123-321d
You could use a row_number() to filter your resulting rows eg
SELECT
id,ba,status,custid
FROM (
SELECT
a.id,
a.ba,
b.status,
b.custid,
ROW_NUMBER() OVER (
PARTITION BY a.ba
ORDER BY b.status ASC
) as rn
FROM
balist as a
INNER JOIN
customer as b ON a.ba = b.ba
)
WHERE rn=1
Let me know if this works for you.

Filtering Join in Oracle DB

Problem:
Each KEY in Table A should have one RF record and one SJ record however I have some duplicated SJ records.
Objective:
I wish to use the SJ records in Table B to identify which SJ record in Table A to keep.
Info:
Table A and Table B share a KEY and SEQ_NBR field.
Inputs:
Table A looks as follows
KEY ID_TYPE SEQ_NBR BUS_NAME
1234 RF 1 COMP_A
1234 SJ 2 COMP_B
1234 SJ 4 COMP_C
5678 RF 1 COMP_L
5678 SJ 2 COMP_M
5678 SJ 3 COMP_N
Table B looks as follows
KEY SEQ_NBR BUS_NAME
1234 2 COMP_B
5678 3 COMP_N
Desired Outcome:
My output would look as follows
KEY ID_TYPE SEQ_NBR BUS_NAME
1234 RF 1 COMP_A
1234 SJ 2 COMP_B
5678 RF 1 COMP_L
5678 SJ 3 COMP_N
Here is one way:
select key, id_type, seq_nbr, bus_name
from (
select a.*,
row_number() over (partition by a.key, a.id_type
order by b.key) as rn
from a left outer join b on a.key = b.key and a.seq_nbr = b.seq_nbr
)
where rn = 1
;
The left outer join adds columns from table b to those of table a. We need that for a single purpose: as we partition by key and id_type, we have partitions of either a single row or (two or more) rows. In the latter case, only one row has a non-null value in b.key. If we order by b.key, the row with non-null b.key will get row number = 1 (and we don't care about the rest).
Then the outer query simply keeps all the rows with row number = 1 and ignores the rest.
An alternative solution, using the union all of the two tables (slightly modified as needed) and basic aggregation using the last aggregate function:
select key, id_type,
min(seq_nbr) keep (dense_rank last order by source) as seq_nbr,
min(bus_name) keep (dense_rank last order by source) as bus_name
from (
select 'A' as source, a.* from a
union all
select 'B', key, 'SJ', seq_nbr, bus_name from b
)
group by key, id_type
;
You can test both to see which is more efficient on your data (if performance is important).
Here goes your code:
select * from tablea a
where exists
(select 1 from tableb b where b.key=a.key and b.seq_nbr=a.seq_nbr)
or not exists (select tablea.id_type from tablea inner join tableb on tablea.key=tableb.key and tablea.SEQ_NBR=tableb.SEQ_NBR and tablea.id_type=a.id_type)
If I understand correctly, you can count the number of duplicates. Then use left join and filter based on both the count and the match:
select a.*
from (select a.*,
count(*) over (partition by key, id_type) as cnt
from a
) a left join
b
on b.key = a.key and
b.seq_nbr = a.seq_nbr and
b.bus_name = a.bus_name
where cnt = 1 or b.key is not null;

Grouping the data and showing 1 row per group in postgres

I have two tables which look like this :-
Component Table
Revision Table
I want to get the name,model_id,rev_id from this table such that the result set has the data like shown below :-
name model_id rev_id created_at
ABC 1234 2 23456
ABC 5678 2 10001
XYZ 4567
Here the data is grouped by name,model_id and only 1 data for each group is shown which has the highest value of created_at.
I am using the below query but it is giving me incorrect result.
SELECT cm.name,cm.model_id,r.created_at from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by cm.name,cm.model_id,r.created_at
ORDER BY cm.name asc,
r.created_at DESC;
Result :-
Anyone's help will be highly appreciated.
use max and sub-query
select T1.name,T1.model_id,r.rev_id,T1.created_at from
(
select cm.name,
cm.model_id,
MAX(r.created_at) As created_at from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by cm.name,cm.model_id
) T1
left join revision r
on T1.created_at =r.created_at
http://www.sqlfiddle.com/#!17/68cb5/4
name model_id rev_id created_at
ABC 1234 2 23456
ABC 5678 2 10001
xyz 4567
In your SELECT you're missing rev_id
Try this:
SELECT
cm.name,
cm.model_id,
MAX(r.rev_id) AS rev_id,
MAX(r.created_at) As created_at
from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by 1,2
ORDER BY cm.name asc,
r.created_at DESC;
What you were missing is the statement to say you only want the max record from the join table. So you need to join records, but the join will bring in all records from table r. If you group by the 2 columns in component, then select the max from r, on the id and created date, it'll only pick the top out the available to join
I would use distinct on:
select distinct on (m.id) m.id, m.name, r.rev_id, r.created_at
from model m left join
revision r
on m.model_id = r.model_id
order by m.id, r.rev_id;

pad database out with NULL criteria

If I have the following sample table (order by ID)
ID Date Type
-- ---- ----
1 01/01/2000 A
2 22/04/1995 A
2 14/02/2001 B
Where you can immediate see that ID=1 does not have a Type=B, but ID=2 does. What I want to do, if fill in a line to show this:
ID Date Type
-- ---- ----
1 01/01/2000 A
1 NULL B
2 22/04/1995 A
2 14/02/2001 B
where there could potentially be 100's of different types, (so may need to end up inserting 100's rows per person if they lack 100's Types!)
Is there a general solution to do this?
Could I possibly outer join the table on itself and do it that way?
You can do this with a cross join to generate all the rows and a left join to get the actual data values:
select i.id, s.date, t.type
from (select distinct id from sample) i cross join
(select distinct type from sample) t left join
sample s
on s.id = i.id and
s.type = t.type;

SQL display two results side-by-side

I have two tables, and am doing an ordered select on each of them. I wold like to see the results of both orders in one result.
Example (simplified):
"SELECT * FROM table1 ORDER BY visits;"
name|# of visits
----+-----------
AA | 5
BB | 9
CC | 12
.
.
.
"SELECT * FROM table2 ORDER BY spent;"
name|$ spent
----+-------
AA | 20
CC | 30
BB | 50
.
.
.
I want to display the results as two columns so I can visually get a feeling if the most frequent visitors are also the best buyers. (I know this example is bad DB design and not a real scenario. It is an example)
I want to get this:
name by visits|name by spent
--------------+-------------
AA | AA
BB | CC
CC | BB
I am using SQLite.
Select A.Name as NameByVisits, B.Name as NameBySpent
From (Select C.*, RowId as RowNumber From (Select Name From Table1 Order by visits) C) A
Inner Join
(Select D.*, RowId as RowNumber From (Select Name From Table2 Order by spent) D) B
On A.RowNumber = B.RowNumber
Try this
select
ISNULL(ts.rn,tv.rn),
spent.name,
visits.name
from
(select *, (select count(*) rn from spent s where s.value>=spent.value ) rn from spent) ts
full outer join
(select *, (select count(*) rn from visits v where v.visits>=visits.visits ) rn from visits) tv
on ts.rn = tv.rn
order by ISNULL(ts.rn,tv.rn)
It creates a rank for each entry in the source table, and joins the two on their rank. If there are duplicate ranks they will return duplicates in the results.
I know it is not a direct answer, but I was searching for it so in case someone needs it: this is a simpler solution for when the results are only one per column:
select
(select roleid from role where rolename='app.roles/anon') roleid, -- the name of the subselect will be the name of the column
(select userid from users where username='pepe') userid; -- same here
Result:
roleid | userid
--------------------------------------+--------------------------------------
31aa33c4-4e66-4da3-8525-42689e46e635 | 12ad8c95-fbef-4287-9834-7458a4b250ee
For RDBMS that support common table expressions and window functions (e.g., SQL Server, Oracle, PostreSQL), I would use:
WITH most_visited AS
(
SELECT ROW_NUMBER() OVER (ORDER BY num_visits) AS num, name, num_visits
FROM visits
),
most_spent AS
(
SELECT ROW_NUMBER() OVER (ORDER BY amt_spent) AS num, name, amt_spent
FROM spent
)
SELECT mv.name, ms.name
FROM most_visited mv INNER JOIN most_spent ms
ON mv.num = ms.num
ORDER BY mv.num
Just join table1 and table2 with name as key like bellow:
select a.name,
b.name,
a.NumOfVisitField,
b.TotalSpentField
from table1 a
left join table2 b on a.name = b.name