SQL code in order to select second value after first one - mysql

Help needed. Could someone help to generate code which would take only second value of IncurredAmount after first one from the same policid.
SELECT claims.claimid, claims.policyid, claims.IncurredAmount
FROM claims
GROUP BY claims.claimid, claims.policyid, claims.IncurredAmount
HAVING (((claims.policyid)=62));
That's what I have. I tried to take one policyid (62) in order to have less entries. But there I stuck. have no clue what clause can be used in order to take only second entries for all entries.

Try this, though whether it will work depends on the version of your database:
SELECT claimid, policyid, IncurredAmount
FROM (
SELECT *,
row_number() over (partition by policyid order by claimid) rn
FROM [MyTable]
) t
WHERE t.rn = 2

A solution exists for the old MySql versions (pre 8.0)
select *
from claims t
where exists (
select 1
from claims t2
where t2.policyid = t.policyid
and t2.claimid <= t.claimid
having count(distinct t2.claimid) = 2
)
order by policyid, claimid
db<>fiddle here
Although it's more equivalent to a DENSE_RANK.
I.e. if there's more with the 2nd lowest claimid then it'll get more than 1.

Related

Adding Serial Number in mysql successfully but unknown column when use in where clause

I have a table in which I fetch results according to winning in descending order. But I need id for Load More function.
I have successfully added serial number but for load more function i want to use serial number id to get the result and not repeating the result. But when I use into where clause I get error unknown column.
Any help would be appreciated.
I have deleted the stuff, as I post my answer.
try this
select * from (
SELECT rank()over(order by pic_wins desc) as serial_number,
pic_id,
pic_caption,
pic_image,
pic_wins
FROM pics
WHERE pic_status = '1'
order by pic_wins desc
) as foo
where serial_number=1;
you can't use alias in where clause but you can use it an sub query
And if you want continuous series in serial_number use dense_rank()
Thanks for everyone, I got my answer to little modify #dgnk answer
select * from ( SELECT #s:=#s+1 serial_number, pic_id, pic_caption, pic_image, pic_wins FROM pics, (SELECT #s:= 0) AS s WHERE pic_status = '1' order by pic_wins desc ) as foo where serial_number > 8

MySQL Return latest and longest streak of rows with certain conditions

Please have a look at this fiddle.
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/7
I'm trying to accomplish 2 things:
How do I get the length and date of the LATEST STREAK OF CORRECT GUESSES (meaning Result = Guess) without any skipped dates? In this case, it would be 4, starting from 2021-01-05 to 2021-01-08. (Although 2021-01-03 is correct, because there was no guess on 2021-01-04, it should not be included).
How do I get the length and date of the LONGEST STREAK OF CORRECT GUESSES OF ALL TIME? Again meaning Result = Guess, but can be anywhere in the table. Let's say it's 10 from 3 months ago.
To further complicate things, guesses can be made by multiple users AND there will be multiple results (for different game categories for example) on the same day. So the table above is for one user and one game category. I think I can handle this if I can get some guidance on the goals up above.
This is beyond my understanding. Any and all help would be appreciated.
EDIT: I've changed the table to show that the date is not always sequential. Also, I was informed that I should be using MySQL 8.0 for this task as using variables is not good practice for this problem.
Edit: Using the window functions, starting to get somewhere:
Please check the fiddle. It's pretty close to what I'm trying to get to, but the '4' in the total column should be a 1. In other words, the "sum" should restart. Not sure how to achieve this, because it's clear that the window function will group based on the conditions, breaking the order and thus the streak.
Updated: I've updated the fiddle per #The Impaler's request. The table here is more representative to what I'm actually working with (still not exact, but much closer).
Since this new fiddle is more representative, I'll also explain my final goal. I'd also like to get the streak for each game_type. The way I've been comparing game_type result on a given day to "community" (basically all the users) guess is by summing all the 0's and 1's for each game_type on that date from all the users and then using whichever greater as the 'guess'. This way, I can get how the "community" is doing as a whole. This works for individual dates, but to do a streak, I'm not sure.
Update 2
So this is as far as I've got:
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/11
I tried to do a nested window function but that's not allowed. I have the proper groupings and column for when guess = result. Now I need help figuring out the streak within the groups.
This is a typical "Gaps & Islands" problem. Once you assemble the islands the query becomes easy.
For example, for a single user, as stated in the fiddle you can get the LONGEST STREAK by doing:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by streak_length desc
limit 1;
Result:
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
To get the LATEST STREAK you just need to change the ORDER BY clause at the end as shown below:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by ending_day desc
limit 1;
Result (same result as before):
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
See running example at DB Fiddle.
Note: You can remove the LIMIT clause at the end to see all the islands, not just the selected one.
For multi-users it's just a matter of modifying the windows (adding partitioning) and the rest of the query remains the same. If you provide a fiddle for multi-users I can add the solution as well.
So, it took a while, but thanks to #The Impaler providing me the basis and the link below, I was able to solve the problem.
https://www.red-gate.com/simple-talk/sql/t-sql-programming/efficient-solutions-to-gaps-and-islands-challenges/
Here is the full solution:
with GAME_LOG as (
select
*,
guess = result as correct,
lag(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as previous_game_result,
lead(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as next_game_result,
row_number() over(partition by user_id, game_type order by dayt DESC) as ilocation
from mytable
),
CTE_ISLAND_START as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_start_time,
ilocation as island_start_location
from GAME_LOG
where correct = 1 AND
(previous_game_result <> 1 OR previous_game_result is null)
),
CTE_ISLAND_END as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_end_time,
ilocation as island_end_location
from GAME_LOG
where correct = 1 AND
(next_game_result <> 1 OR next_game_result is null)
)
select
CTE_ISLAND_START.user_id,
CTE_ISLAND_START.game_type,
CTE_ISLAND_START.island_start_time as streak_end,
CTE_ISLAND_END.island_end_time as streak_start,
cast(CTE_ISLAND_END.island_end_location as signed) -
cast(CTE_ISLAND_START.island_start_location as signed) + 1 as streak
from CTE_ISLAND_START
inner join CTE_ISLAND_END
on CTE_ISLAND_START.inumber = CTE_ISLAND_END.inumber AND
CTE_ISLAND_START.user_id = CTE_ISLAND_END.user_id AND
CTE_ISLAND_START.game_type = CTE_ISLAND_END.game_type
This will give all the streaks for each user_id, each game_type, as well as the start and end dates of the streak.
You can simply add a WHERE clause to filter by game_type and user_id.
Here's the fiddle with slightly updated dataset.
Fiddle

Find older records in a group in a SQL query

I'm trying to find older records within a group of a SQL table, so that I can delete them. But I'm having trouble even selecting them.
Table looks like this:
What I want to do is keep only the top 3 newest changes on each issue, and I want to delete the older records.
What is the SQL required to find the ones not in the top 3 most recent for each issue? Thanks!
What is the SQL required to find the ones not in the top 3 most recent for each issue?
If you are running MySQL 8.0, use row_number():
select *
from (
select
t.*,
row_number() over(partition by issue_id order by change_date desc) rn
from mytable t
) t
where rn > 3
In ealier versions, you can emulate this with a correlated subquery:
select *
from mytable t
where (
select count(*)
from mytable t1
where t1.issue_id = t.issue_id and t1.change_date > t.change_date
) >= 3

Find MAX of a summed value on multiple group by clauses

As the title indicates, I am trying to find the maximum summed value in column C for an object in column A based on a subset of column B over a period of time (let's say column D). My current query looks something like this in which I return the summed values greater than 10,000.
select id_a, id_b, sum(column_c) from master_table where id_b in (1,2,3,4,5)
and ymdh >= '2017-11-01' group by 1,2 having sum(column_c) > 10000 order by 2,3
desc;
What I'm trying to get returned is the greatest value from sum(column_c). I tried using both the max() and distinct() functions. Specifically using max(sum(imps)), but aggregate function calls many not be nested. Would anyone be able to provide guidance here?
You can use a FROM ( select ) T
select max(my_sum)
from (
select id_a
, id_b
, sum(column_c) my_sum
from master_table
where id_b in (1,2,3,4,5)
and ymdh >= '2017-11-01'
group by 1,2 having my_sum > 10000
order by 2,3 desc;
) T
Does this do what you want?
select id_a, id_b, sum(column_c)
from master_table
where id_b in (1,2,3,4,5) and
ymdh >= '2017-11-01'
group by id_a, id_b
having sum(column_c) > 10000
order by sum(column_c) desc
limit 1;
That is, use order by and limit to get the value you want. (This query includes the group by keys as well, but that is not necessary.)
scaisEdge has the answer (and my +1) - but I just wanted to add a bit about the thought process when designing an SQL statement like you're working on.
Don't feel you need to compose the whole thing - that it's one big statement, or that it's one single query.
Instead, you'll often need to break up the problem into steps, solve the individual steps, and then use those steps as sources for a query - because you don't have to use tables in the FROM clause; you can use your own subqueries instead.
So for this problem? You've got the first step done - you figured out how to write the query that gets the Sum over a particular grouping:
select someCol, sum(otherCol) as groupSum from myTable
group by someCol
Great! Now, you can effectively use this like it's a table:
select someCol, groupSum
from
(
select someCol, sum(otherCol) as groupSum from myTable
group by someCol
) mySubquery
And in your case, you want to get the maximum sum?
select max(groupSum)
from
(
select someCol, sum(otherCol) as groupSum from myTable
group by someCol
) mySubquery
Not only will this help while composing the full SQL statement, it'll actually help the person trying to read/debug it down the line, especially if you name your subqueries/columns well:
select max(totalHitsForWeek) as maxWeeklyUsage
from
(
select week, sum(hits) as totalHitsForWeek
from requestsTable
) hitsPerWeekSubquery
Hope that helps add to scaisEdge's answer! :-)

SQL SELECT last entry without limiting

I have a table with 3 fields:
id
note
created_at
Is there a way in the SQL language especially Postgres that I can select the value of the last note without having to LIMIT 1?
Normal query:
select note from table order by created_at desc limit 1
I'm interested in something avoiding the limit since I'll need it as a subquery.
Simple version with EXISTS semi-join:
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1 WHERE t1.created_at > t.created_at);
"Find a note where no other note was created later."
This shares the weakness of #Hogan's version that it can return multiple rows if created_at is not UNIQUE - like #Ollie already pointed out. Unlike #Hogan's query (max() is only defined for simple types) this one can be improved easily:
Compare row types
SELECT note FROM tbl t
WHERE NOT EXISTS
(SELECT 1 FROM tbl t1
WHERE (t1.created_at, t1.id) > (t.created_at, t.id));
Assuming you want the greatest id in case of a tie with created_at, and id is the primary key, therefore unique. This works in PostgreSQL and MySQL.
SQL Fiddle.
Window function
The same can be achieved with a window function in PostgreSQL:
SELECT note
FROM (
SELECT note, row_number() OVER (ORDER BY created_at DESC, id DESC) AS rn
FROM tbl t
) x
WHERE rn = 1;
MySQL lacks support for window functions. You can substitute with a variable like this:
SELECT note
FROM (
SELECT note, #rownum := #rownum + 1 AS rn
FROM tbl t
,(SELECT #rownum := 0) r
ORDER BY created_at DESC, id DESC
) x
WHERE rn = 1;
(SELECT #rownum := 0) r initializes the variable with 0 without an explicit SET command.
SQL Fiddle.
If your id column is an autoincrementing primary key field, it's pretty easy. This assumes the latest note has the highest id. (That might not be true; only you know that!)
select *
from note
where id = (select max(id) from note)
It's here: http://sqlfiddle.com/#!2/7478a/1/0 for MySQL and here http://sqlfiddle.com/#!1/6597d/1/0 for postgreSQL. Same SQL.
If your id column isn't set up so the latest note has the highest id, but still is a primary key (that is, still has unique values in each row), it's a little harder. We have to disambiguate identical dates; we'll do this by choosing, arbitrarily, the highest id.
select *
from note
where id = (
select max(id)
from note where created_at =
(select max(created_at)
from note
)
)
Here's an example: http://sqlfiddle.com/#!2/1f802/4/0 for MySQL.
Here it is for postgreSQL (the SQL is the same, yay!) http://sqlfiddle.com/#!1/bca8c/1/0
Another possibility: maybe you want both notes shown together in one row if they were both created at the same exact time. Again, only you know that.
select group_concat(note separator '; ')
from note
where created_at = (select max(created_at) from note)
In postgreSQL 9+, it's
select string_agg(note, '; ')
from note
where created_at = (select max(created_at) from note)
If you do have the possibility for duplicate created_at times and duplicate id values, and you don't want the group_concat effect, you are unfortunately stuck with LIMIT.
I'm not 100% on Postgres (actually never used it) but you can get the same effect with something like this - if the created_at is unique ... (or with any column which is unique):
SELECT note FROM table WHERE created_at = (
SELECT MAX(created_at) FROM table
)
I may not know how to answer on this platform but what I have suggested is working
SELECT * FROM table GROUP BY field ORDER BY max(field) DESC;
You can get the last value of the field without limiting, usually in JOINED query we get the last update time with no limiting of output like this way, such as last message time without limiting it.