find the max for each value in SQL - sql

i have tables like this
table 1
|cl.1|
| -- |
| a |
| b |
| c |
table 2
|cl.1|cl.2|para|
|----|---| --- |
| a | 3 | t |
| a | 3 | f |
| b | 2 | t |
| a | 1 | b |
| c | 4 | t |
| b | 7 | d |
i want to get the max value for each element in table1 from table2
and the different parameter
so the expecited tabel should be like this
|cl.1|max|para|
|----|---| --- |
| a | 3 | t |
| a | 3 | f |
| c | 4 | t |
| b | 7 | d |

You can try to compute all the maximums:
with Maxes as (
select cl1,
max(cl2) as cl2
from Table2
group by cl1)
and then join them with the original Table2, e.g.
with Maxes as (
select cl1,
max(cl2) as cl2
from Table2
group by cl1)
select t.*
from Table2 t join
Maxes m on (t.cl1 = m.cl1 and t.cl2 = m.cl2)

Depends on what features your RDBMS supports.
With Oracle you could do a CROSS APPLY to order table2 by descending cl2 and keep the top values (with ties):
select T1.c1, TM.maximum, TM.para
from Table1 T1
cross apply (
select *
from Table2 T2
where T2.c1 = T1.c1
order by T2.maximum descending
fetch first 1 row with ties
) TM
You can do the same in SQL Server with syntax select top 1 with ties instead of fetch first 1 row with ties.
Another option could be to use Analytical Functions to rank the results per col1 and then keep only the first ones.
select T.c1, T.maximum, T.para
from (
select
T1.c1, T2.maximum, T2.para,
rank() over (partition by T1.c1 order by T2.maximum desc) r
from T1
join T2 on T1.c1 = T2.c2
) T
where T.r = 1
Less stylish and probably(?) less performant would be computing the maximum for each c1 and then doing an equality:
select T1.c1, T2.maximum, T2.para
from T1
join T2 on T1.c1 = T2.c1
where T2.maximum = (select max(maximum) from T2 where c1 = T1.c1)

If you are trying to get the max tl.1 and if for the same values it is equal, you could try:
SELECT *
FROM table2
WHERE cl_2 in ( SELECT MAX(cl_2)
FROM table2
group by cl_1
);
Result:
cl_1 cl_2 para
a 3 t
a 3 f
c 4 t
b 7 d
Tested on MySQL : https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=42a6bc20622a210b18101588540995ec
You could use a join , but it makes no difference:
SELECT t1.cl_1,t2.cl_2,t2.para
FROM table2 t2
INNER JOIN table1 t1 on t2.cl_1=t1.cl_1
WHERE t2.cl_2 in (SELECT MAX(cl_2) FROM table2 group by cl_1 );
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4b2eed9bcee3532cc7c4e7b3862bc3ef

DENSE_RANK can be used to get whole rows that have a maximum of something within a partition.
Because when sorted descending, the top 1 will have rank 1.
select cl_1, cl_2, para
from
(
select cl_1, cl_2, para
, dense_rank() over (partition by cl_1 order by cl_2 desc) as rnk
from table1 t1
join table2 t2 using (cl_1)
) q
where rnk = 1

Use a CTE to get the max values, then select the rows with those values:
with maxes as
(
select t1.[cl.1]
, max(t2.[cl.2]) max_val
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
group by t1.[cl.1]
)
select t1.[cl.1]
, t2.[cl.2]
, t2.para
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
where t2.[cl.2] = (select m.max_val from maxes m where m.[cl.1] = t1.[cl.1])
This can also be achieved by joining the CTE:
with maxes as
(
select t1.[cl.1]
, max(t2.[cl.2]) max_val
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
group by t1.[cl.1]
)
select t1.[cl.1]
, t2.[cl.2]
, t2.para
from table1 t1
inner join table2 t2
on t1.[cl.1] = t2.[cl.1]
inner join maxes m
on t2.[cl.2] = m.max_val

Related

Create table with values from one column and another column without intersection

I have a table like so:
userid | clothesid
-------|-----------
1 | 1
1 | 3
2 | 1
2 | 4
2 | 5
What I want from this table is a table like so:
userid | clothesid
-------|-----------
1 | 4
1 | 5
2 | 3
How can I do this?
I've tried it with one entry as:
select distinct r.clothesid from table r where r.clothes not in (select r1.clothes from table r1 where r1.userid=1);
and this returns 4,5, but I'm not sure where to proceed from here
You can cross join the list of userids and the list of clothesid to generate all combinations, and then use not exists on the original table to identify the missing rows:
select u.userid, c.clothesid
from (select distinct userid from mytable) u
cross join (select distinct clothesid from mytable) c
where not exists(
select 1 from mytable t on t.userid = u.userid and t.clothesid = c.clothesid
)
I think you want:
select (case when t1.clothesid is not null then 2 else 1 end),
coalesce(t1.clothesid, t2.clothesid)
from (select t.*
from t
where t.userid = 1
) t1 full join
(select t.*
from t
where t.userid = 2
) t2
on t1.clothesid = t2.clothesid
where t1.clothesid is null or t2.clothesid is null;
Actually, I think I have a simpler solution:
select (case when min(t.userid) = 1 then 2 else 1 end), clothesid
from t
group by clothesid
having count(*) = 1;
Here is a db<>fiddle.
Left join all the combinations of userid and clothesid to the table and return only the unmatched rows:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
left join tablename t on t.userid = t1.userid and t.clothesid = t2.clothesid
where t.userid is null
Or with the operator EXCEPT:
select t1.userid, t2.clothesid
from (select distinct userid from tablename) t1
cross join (select distinct clothesid from tablename) t2
except
select userid, clothesid
from tablename
See the demo.
Results:
> userid | clothesid
> -----: | --------:
> 1 | 4
> 1 | 5
> 2 | 3

UPDATE Table value from other table like distribution

I have a table 1 where I have to distribute award from other table table 2 but in a manner of First in first serve from other table row.
Table 1
ATTIME | Absent | LeaveType
-----------------------------
2019-01-01| 1 |
2019-01-02| 1 |
2019-01-03| 1 |
2019-01-04| 1 |
2019-01-05| 1 |
2019-01-06| 1 |
Table 2
LeaveType | Total
-------------------
Casual | 3
Sick | 2
I have achieved it by using cursor, but want a set base UPDATE QUERY or any other option which optimize my execution plan,
Final Result will be....
Table 1
ATTIME | Absent | LeaveType
-----------------------------
2019-01-01| 1 | CL
2019-01-02| 1 | CL
2019-01-03| 1 | CL
2019-01-04| 1 | SL
2019-01-05| 1 | SL
2019-01-06| 1 |
In a supported version of SQL Server, you would use a cumulative sum and row_number():
with toupdate as (
select t1.*,
row_number() over (order by attime) as seqnum
from table1 t1
)
update toupdate
set leavetype = t2.leavetype
from (select t2.*,
sum(total) over (order by leavetype) as runningtotal
from table2 t2
) t2
where toupdate.seqnum between t2.runningtotal + 1 - total and t2.runningtotal;
In archaic, unsupported versions of SQL Server, the cumulative sum is more cumbersome. One method uses a correlated subquery:
with toupdate as (
select t1.*,
row_number() over (order by attime) as seqnum
from table1 t1
)
update toupdate
set leavetype = t2.leavetype
from (select t2.*,
(select sum(total)
from table2 tt2
where tt2.leavetype <= t2.leavetype
) as runningtotal
from table2 t2
) t2
where t1.seqnum between t2.runningtotal + 1 - total and t2.runningtotal;

Join with a second table containing multiple records, take the latest

I have two tables:
person_id | name
1 name1
2 name2
3 name3
and a second table:
person_id | date | balance
1 2016-03 1200 ---- \
1 2016-04 700 ---- > same person
1 2016-05 400 ---- /
3 2016-05 4000
Considering that person_id 1 has three record on the second table how can I join the first just by taking the latest record? (that is: balance 400, corresponding to date: 2016-05).
E.g.: query output:
person_id | name | balance
1 name1 400
2 name2 ---
3 name3 4000
if it's possibile prefer the simplicity over the complexity of the solution
A query working for all DB engines is
select t1.name, t2.person_id, t2.balance
from table1 t1
join table2 t2 on t1.person_id = t2.person_id
join
(
select person_id, max(date) as mdate
from table2
group by person_id
) t3 on t2.person_id = t3.person_id and t2.date = t3.mdate
The best way to do this in any database that supports the ANSI standard window functions (which is most of them) is:
select t1.*, t2.balance
from table1 t1 left join
(select t2.*,
row_number() over (partition by person_id order by date desc) as seqnum
from table2 t2
) t2
on t1.person_id = t2.person_id and seqnum = 1;

Select Missing date only if it is Maximum from a table in Oracle

I have 2 tables. If table 1 has dates greater than table 2 only those record has should be populated in Output.
Table 1:
ID Category Date
1 A 3/2/1990
1 A 3/5/2013
1 C 4/3/1979
2 D 4/3/1970
2 D 5/6/2016
3 E 8/8/2016
Table 2:
ID Category Date
1 A 3/2/1990
1 C 4/3/1979
1 C 4/3/1982
1 D 4/3/1982
2 D 5/6/2016
The expected Output is
ID Category Date
1 A 3/5/2013
3 E 8/8/2016
I tried the below query and its giving me incorrect results.
select a.id,a.category,a,Date from table1 a where
a.Date > (select Max(b.Date) from table2 b where a.id=b.id and a.category =b.catgory group by b.id,b.category)
SQL Fiddle Demo
WITH cte AS (
SELECT ID, Category, MAX(Date) as mdate
FROM Table2
GROUP BY ID, Category
)
SELECT T1.* --, T2.*
FROM Table1 as T1
LEFT JOIN cte as T2
ON T1.ID = T2.ID
AND T1.Category = T2.Category
WHERE T1.Date > T2.mdate
OR T2.mdate is NULL
OUTPUT
SELECT T1.*
FROM Table1 AS T1 INNER JOIN Table2 AS T2
ON T1.ID = T2.ID
WHERE T1.Date > T2.mdate;
As per the required output, you need to use left outer join
SELECT T1.*
FROM table1 T1
LEFT OUTER JOIN (
SELECT ID
,category
,MAX(Date) mdate
FROM Table2
GROUP BY ID
,category
) T2 ON (
T1.ID = T2.ID
AND T1.category = T2.category
)
WHERE T1.date > nvl(T2.mdate, '01/01/1900');
Filtering Table2:
SELECT ID, Category,MAX(Date) as Date
FROM Table2
GROUP BY ID,Category;
| ID | Category | Date |
|----|----------|-------------------------|
| 1 | A | March, 02 1990 00:00:00 |
| 1 | C | April, 03 1982 00:00:00 |
| 1 | D | April, 03 1982 00:00:00 |
| 2 | D | May, 06 2016 00:00:00 |
Now using this to create a left join with Table1:
SELECT t1.*
FROM Table1 t1 LEFT JOIN
(SELECT ID, Category,MAX(Date) as Date
FROM Table2
GROUP BY ID,Category) AS t2part
ON t1.ID = t2part.ID
AND t1.Category = t2part.Category
WHERE t1.Date > t2part.Date;
| ID | Category | Date |
|----|----------|-------------------------|
| 1 | A | March, 05 2013 00:00:00 |
Please note that the row with ID=3, category=E wasn't found due to not matching neither ID or Category in the JOIN.
As good practice if the entities should interact there must be some sort of normalization applied so we could make best use of joins through indexes.
fiddle with your provided data and queries.

Counting word occurrence with SQL query

I have two tables.
Table1:
ID SENTENCE
1 The shoes are good shoes.
2 There is a tree.
3 This is nice, nice, nice!
Table2:
ID WORD
1 The
1 shoes
1 are
1 good
1 shoes
2 There
2 is
2 a
2 tree
3 This
3 is
3 nice
3 nice
3 nice
I need to count the occurrence of each word in every sentence from Table1. If any word occurs more than once (>1), then count it else skip it. In the end the resulting table should look like this:
ID SENTENCE CNT
1 The shoes are good shoes. 2
2 There is a tree.
3 This is nice, nice, nice! 3
You can use count() over():
select distinct t1.id,
t1.sentence,
coalesce(t2.cnt, 0) cnt
from table1 t1
left join
(
select t1.id,
t1.sentence,
t2.word,
count(t2.word) over(partition by t1.id, t2.word) cnt
from table1 t1
left join table2 t2
on t1.id = t2.id
) t2
on t1.id = t2.id
and t2.cnt > 1
order by t1.id
See SQL Fiddle with Demo.
Or you can just use count():
select t1.id,
t1.sentence,
coalesce(t2.cnt, 0) cnt
from table1 t1
left join
(
select t1.id,
t1.sentence,
t2.word,
count(t2.word) cnt
from table1 t1
left join table2 t2
on t1.id = t2.id
group by t1.id, t1.sentence, t2.word
having count(t2.word) > 1
) t2
on t1.id = t2.id
order by t1.id
See SQL Fiddle with Demo
SQL DEMO
select t1.id, t1.sentence,
coalesce(t2.cnt,0) as counts
from table1 t1
left join
(select id, word, count(id) cnt
from table2
group by id, word
having count(id) > 1)t2
on t1.id = t2.id
order by t1.id
;
| ID | SENTENCE | COUNTS |
-------------------------------------------
| 1 | The shoes are good shoes. | 2 |
| 2 | There is a tree. | 0 |
| 3 | This is nice, nice, nice! | 3 |
SELECT table1.id, table1.sentence, COUNT(word) as cnt FROM table2 JOIN table1 ON table1.id = table2.id GROUP BY table2.word HAVING COUNT(word) > 1
My answer is for mysql, I am verifying now that it works in sql as well
There are many join examples, so, I will add only word count examples:
Select REGEXP_COUNT('The shoes are good shoes.', ' ')+1 words_count
From dual
/
WORDS_COUNT
-----------
5
SELECT id
, LISTAGG(word, ' ') WITHIN GROUP (ORDER BY id, word) AS words
, count(*) word_cnt
FROM your_table2
GROUP BY id
/
ID WORDS WORD_CNT
---------------------------------------
1 The are good shoes shoes 5
2 There a is tree 4
3 This is nice nice nice 5