SQL SELECT last entry without limiting - mysql

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.

Related

SQL code in order to select second value after first one

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.

Identify the last row of a distinct set of data in the field for an Alias ​Column

How can i identify the last Row of a distinct set of data in the field for an Alias ​​Column (signaling somehow, with "1" for example).
For this example i need to know, when the ordered GROUP "CARS, COLORS, DRINKS, FRUITS" ends.
Check my intended result on this image:
My base query:
SELECT * FROM `MY_DB` ORDER BY `ITEM`, `GROUP` ASC
As a starter: rows of a SQL table are unordered. There is no inherent ordering of rows. For your question to make sense, you need a column that defines the ordering of the rows in each group - I assumed id.
Then: in MySQL 8.0, one option uses window functions:
select t.*,
(row_number() over(partition by grp order by id desc) = 1) as last_group_flag
from mytable t
In earlier versions, you could use a subquery:
select t.*,
(id = (select max(t1.id) from mytable t1 where t1.grp = t.grp)) as last_group_flag
from mytable t
Note: group is a language keyword, hence not a good choice for a column name. I used grp instead in the query.
You need ordering by item column and order by group column to find the last record per distinct group column.
Use row_number as follows:
select t.*,
Case when row_number() over(partition by group
order by item desc) = 1
then 1 else 0 end as last_group_flag
from your_table t

Ranking system gaps in ranking output

I have this project that it ranks different items by their scores, the ranking is okay but it shows gaps when there is a tied score.
Here is the query:
SELECT bgycode, scc_bgyscoretotal, FIND_IN_SET( scc_bgyscoretotal, (
SELECT GROUP_CONCAT(DISTINCT scc_bgyscoretotal
ORDER BY scc_bgyscoretotal DESC ) FROM bgyprofile)
) AS rank
FROM bgyprofile
and it outputs like this:
any way to fix this?
Thanks in advance sorry for the bad english
You basically need Dense_Rank() like functionality (available in MySQL version >= 8.0). In older versions of MySQL, it can be emulated using Session Variables.
In a Derived table, determine ranking of a scc_bgyscoretotal (highest value having rank 1 and so on). Firstly, get unique values of scc_bgyscoretotal, and then determine their ranking using Session Variables.
Now, simply join these Derived table to the main table bgyprofile using scc_bgyscoretotal.
Try the following:
SELECT t2.bgycode,
t2.scc_bgyscoretotal,
dt2.`rank`
FROM bgyprofile AS t2
JOIN
(
SELECT dt1.scc_bgyscoretotal,
#rank_no := #rank_no + 1 AS `rank`
FROM
(
SELECT t1.scc_bgyscoretotal
FROM bgyprofile AS t1
GROUP BY t1.scc_bgyscoretotal
ORDER BY t1.scc_bgyscoretotal DESC
) AS dt1
CROSS JOIN (SELECT #rank_no := 0) AS init1
) AS dt2 ON dt2.scc_bgyscoretotal = t2.scc_bgyscoretotal

getting weird value back when trying to find the max row of a table based on date

I'm trying to do something I thought was relatively simple. Get the last entered value of a user.
I have a table which tracks all their entries called plan_activities_logs. I used this statement to get all the activity regarding a single user:
SELECT created_at as last_active, plan_id, plan_value
from plan_activity_logs where plan_id IN (select id from plans where tile_id = 30);
and it gives me back a table that looks like this:
but when I try to do something like this:
SELECT MAX(created_at) as last_active, plan_id, plan_value from plan_activity_logs where plan_id IN (select id from plans where tile_id = 30);
I get this:
while the date and id are correct the plan value is the wrong value. Any idea what I'm doing wrong?
Any idea what I'm doing wrong?
you are not doing anything wrong. According to the manual
If ONLY_FULL_GROUP_BY is disabled, a MySQL extension to the standard SQL use of GROUP BY permits the select list, HAVING
condition, or ORDER BY list to refer to nonaggregated columns even if
the columns are not functionally dependent on GROUP BY columns. This
causes MySQL to accept the preceding query. In this case, the server
is free to choose any value from each group, so unless they are the
same, the values chosen are nondeterministic, which is probably not
what you want.
In other words, this behavior is in accordance with the specification. The query returns a random non-deterministic value because you are using MySql nonstandard extension to the group by query.
This should work
SELECT created_at as last_active, plan_id, plan_value from plan_activity_logs where plan_id IN (select id from plans where tile_id = 30) order by created_at desc limit 1;
Try this query:
select #rn := 1;
select created_at as last_active,
plan_id,
plan_value
from (
select created_at as last_active,
plan_id,
plan_value,
#rn := #rn + 1 rn
from plan_activity_logs
where plan_id IN (select id from plans where tile_id = 30)
order by created_at desc
) a where rn = 1;

How to delete all records of a table above a certain number for clean up purpose

To illustrate my question, let's take the example of rolling log files. You set a cap in bytes and every bytes that go over it, is deleted to let place to the new entries.
I need something similar for my database and I want to create a query that will be executed daily.
The query will delete all old records in the table T above a number N of records.
Records will be sorted by date descending of course.
There are multi millions records in that table.
If ID is the primary key of T:
delete T where ID not in (select top N ID from T order by date_column desc)
Something like this should work.
DELETE FROM <table> WHERE date < (
SELECT min(date) FROM (
SELECT date FROM table
ORDER BY date DESC
LIMIT N
)
)
Inner select finds the top N newer records.
Then the middle select finds the older date of those one and the outer select deletes everything older than that.
You don't specify RDBMS. For SQL Server
WITH CTE AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Dt DESC) AS Rn
FROM T
)
DELETE FROM CTE
WHERE Rn > 100000
If I have right understand your question, you need something like
DELETE FROM T WHERE ID IN (SELECT TOP 1000 ID FROM T AS T1 WHERE T1.DATE > #DATE )
In this example N = 1000, and the minimum date is #DATE
For ORACLE
delete from T
where rowid not in ( select rowid
from (select rowid from T order by date desc)
where rownum <= N
)
For Oracle (enterprise), you might consider range partitioning a big table by date. You can then drop or truncate the partition(s) you wish very easily (and quickly). Definitely not a generic solution though.