select column value by custom priority - mysql

Mysql 5.7
I have a table with 3 columns
ID -> Primary Key
STATUS -> User Status
MASTERID -> Foreign Key
I need a select query which will return a Status of the user using below priority
Verified -> 1 has the highest priority
Reset -> 2 the second priority
Created -> 3 has the least priority
ID STATUS MASTERID
1 Created 1
2 Verified 1
3 Reset 1
4 Created 1
select query should return Verified
ID STATUS MASTERID
1 Reset 1
2 Reset 1
select query should return Reset as there is only one distinct status present
ID STATUS MASTERID
1 Reset 1
2 Created 1
3 Verified 1
select query should return Verified as it has the highest priority
above records are an example of one user, likewise I need to fetch the status of each distinct user

Use order by with field():
select t.*
from (select t.*,
row_number() over (partition by masterid order by field(status, 'Verified', 'Reset', 'Created') as seqnum
from t
) t
where seqnum = 1;
If you only want the result for one master id:
select t.*
from t
where masterid = 1
order by field(status, 'Verified', 'Reset', 'Created')
limit 1;
Of course, for the values that you have specified, you could replace field() with status desc. However, this offers the more general solution.
EDIT:
In MySQL 5.7, you can use:
select t.*
from t
where t.id = (select t2.id
from t t2
where t2.masterid = t.masterid
order by field(status, 'Verified', 'Reset', 'Created')
limit 1
);

I am assuming that MASTERID is the user and I have taken the liberty of creating some sample data. But really the OP (and that is you) should be the one creating the sample tables and data on a website such as db-fiddle.com if you are seriously looking for someone to answer your questions in the future. See Why should I provide a Minimal Reproducible Example for a very simple SQL query?.
SELECT MASTERID,
CASE
WHEN RANKING = 1 THEN 'Created'
WHEN RANKING = 2 THEN 'Reset'
WHEN RANKING = 3 THEN 'Verified'
END as STATUS
FROM (
SELECT MASTERID, MAX(FIELD(STATUS, 'Created', 'Reset', 'Verified')) as RANKING
FROM mytable
GROUP bY MASTERID
) SQ;
| MASTERID | STATUS |
| -------- | -------- |
| 1 | Verified |
| 2 | Reset |
| 3 | Verified |
View on DB Fiddle
If you want to also have an associated ID with the returned results, then:
SELECT * FROM mytable t1
WHERE FIELD(STATUS, 'Created', 'Reset', 'Verified') = (
SELECT MAX(FIELD(STATUS, 'Created', 'Reset', 'Verified')) FROM mytable t2
WHERE t2.MASTERID = t1.MASTERID
) AND t1.ID = (
SELECT MIN(t2.ID) FROM mytable t2
WHERE t2.MASTERID = t1.MASTERID AND t2.STATUS = t1.STATUS
)
| ID | STATUS | MASTERID |
| --- | -------- | -------- |
| 2 | Verified | 1 |
| 5 | Reset | 2 |
| 9 | Verified | 3 |
View on DB Fiddle

Related

GROUP_CONCAT ORDER BY With LIMIT Unknown column 'p.id' in 'where clause' [duplicate]

This question already has answers here:
GROUP_CONCAT with limit
(7 answers)
Closed 9 months ago.
Here is my table:
+----+---------+------------+----------+
| id | message | projectID | noteType |
+----+---------+------------+----------+
| 1 | 1 | 125 | update |
| 2 | 2 | 125 | update |
| 3 | 3 | 125 | update |
| 4 | 4 | 125 | update |
| 5 | 5 | 125 | update |
| 6 | 6 | 125 | update |
My query using the suggestion below:
SELECT `p`.`id`, `proName`, `p`.`proType`, `p`.`priority`,
`p`.`busSegment`, `p`.`portfolio`, `p`.`description`,
(SELECT group_concat('<li>', `message`, '</li>') AS temp FROM (SELECT
projectID, message FROM notes where projectID = p.id AND noteType =
'update' ORDER BY id DESC LIMIT 3) three_messages GROUP BY projectID) as
updates
FROM `projects` as `p`
WHERE `p`.`id` = 125
Error:
Error Code: 1054. Unknown column 'p.id' in 'where clause'
All records are returned. For some reason, the LIMIT 3 is not working.
The suggestion query work by it self.
SELECT group_concat('<li>', `message`, '</li>') AS updates
FROM (
SELECT projectID, message
FROM notes where projectID = 125 AND noteType = 'update'
ORDER BY id DESC
LIMIT 3
) three_messages
GROUP BY projectID;
+---------------------+
| updates |
+---------------------+
| 6,5,4 |
+---------------------+
I'm guessing you have more than project id = 125 in your real query and for each one you want 3 results. in that case you may need to do some kind of ranking. I am doing it here with the row_number() function.
here is the fiddle https://www.db-fiddle.com/f/uqSzoth466RX86c5dCkfDR/0
with t as(select a.*,
row_number() over
(partition by project_id order by message desc) as rn
from mytable a
where note_type = 'update')
SELECT project_id,
group_concat('<li>', `message`, '</li>' ORDER BY id DESC) AS updates
from t where rn <=3 group by project_id;
LIMIT applies to the rows after grouping.
You can use it in a subquery and group the results of that subquery:
SELECT group_concat('<li>', `message`, '</li>' ORDER BY id DESC) AS updates
FROM (
SELECT id, projectID, message
FROM notes where projectID = 125 AND noteType = 'update'
ORDER BY id DESC
LIMIT 3
) three_messages
GROUP BY projectID
It does look to me like you want:
group_concat('<li>', `message`, '</li>' ORDER BY id DESC SEPARATOR '')
though, to not put commas between the list items.
I also think it is a bad idea to use id for ordering; if you want newest notes, use a timestamp. Imagine if your database was hacked and you had to restore from a backup. After the restore, you start your system again and notes get added. Then you go through your application logs and recover some number of notes that were lost; if you are imputing order to ids, you have to increase all the ids added after the restore to make room for the recovered ones. It's much better just to be able to insert with a timestamp.

Select all columns, only taking into account the highest scores per user

It's been asked before, but I can't get it to work properly. The selected answer doesn't work with duplicate values. The second answer should be able to handle duplicates according to the poster, but it's not functioning correctly with my data.
What I want to achieve is pretty simple:
I have a database containing all scores of all users. I want to build a highscore table, so I want to select all highscore rows of each user. With highscore row I mean the row for that user where his score is the highest.
Here's a demo I made based on the answer I mentioned at the top:
CREATE TABLE test(
score INTEGER,
user_id INTEGER,
info INTEGER
);
insert into test(score, user_id, info)
values
(1000, 1, 1),
(1000, 1, 2),
(2000, 2, 3),
(2001, 2, 1);
--
SELECT t.*
FROM test t
JOIN (SELECT test.user_id, max(score) as mi FROM test GROUP BY user_id) j ON
t.score = j.mi AND
t.user_id = j.user_id
ORDER BY score DESC, info ASC;
Expected output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
+-------+---------+------+
--> every user_id is present with the row where the user had the highest score value.
Real output:
+-------+---------+------+
| score | user_id | info |
+-------+---------+------+
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
| 1000 | 1 | 2 |
+-------+---------+------+
--> when there are duplicate values, user show up multiple times.
Anyone who can point me in the right direction?
I assume when there are duplicate scores you want the lowest info just like your expected output.
With NOT EXISTS:
select t.* from test t
where not exists (
select 1 from test
where user_id = t.user_id and (
score > t.score or (score = t.score and info < t.info)
)
);
See the demo.
For MySql 8.0+ you can use ROW_NUMBER():
select t.score, t.user_id, t.info
from (
select *, row_number() over (partition by user_id order by score desc, info asc) rn
from test
) t
where t.rn = 1
See the demo.
Results:
| score | user_id | info |
| ----- | ------- | ---- |
| 1000 | 1 | 1 |
| 2001 | 2 | 1 |
If the combination of (user_id, info) is UNIQUE and NOT NULL (or PRIMARY KEY), then you can use a LIMIT 1 subquery in the WHERE clause:
SELECT t.*
FROM test t
WHERE (t.score, t.info) = (
SELECT t2.score, t2.info
FROM test t2
WHERE t2.user_id = t.user_id
ORDER BY t2.score DESC, t2.info ASC
LIMIT 1
)
ORDER BY t.score DESC, t.info ASC;
The result will be:
| score | user_id | info |
|-------|---------|------|
| 2001 | 2 | 1 |
| 1000 | 1 | 1 |
demo on sqlfiddle
SELECT info FROM test HAVING MAX(score) was used to keep the info field relevant with the row containing the MAX(score).
SELECT MAX(score) score, user_id, (SELECT info FROM test HAVING MAX(score)) AS info FROM test GROUP BY user_id ORDER BY score DESC;

MySQL - select row by user_id based on column value of other rows with the same user_id

I have a complicated request, so I have simplified it down, and hope I explain it well.
I have a table of Subscriptions:
ID | Timestamp | User Id | Status
-----+---------------------+---------+---------
1 | 2013-06-14 16:39:23 | 1 | inactive
2 | 2013-11-20 10:18:17 | 1 | active
3 | 2013-06-14 16:39:23 | 2 | inactive
4 | 2014-03-01 17:18:26 | 3 | active
I want to Query this table to find subscriptions which have a status of inactive, and who do NOT also have an active subscription.
For example, this should return Subscription ID 3, but not Subscription ID 1 - Because the User of ID 1 has an active subscription (being Subscription ID 2).
following query should do the trick:
select a.id from
table as a
inner join
(
select user_id, max(id)
from table
group by user_id
) as b
on (a.id=b.id and a.status='inactive')
Try the following and see if that works
SELECT *
FROM Table t
WHERE t.Status = 'inactive'
AND NOT EXISTS(SELECT 1 FROM Table t2
WHERE t2.user_id = t.user_id
AND t2.status <> 'inactive')

Different ORDER BY direction for MySql query results

I am trying to do some ordering on a mysql query that I can't figure out.
id | status | created_at
------------------------
1 | open | 1348778070
2 | closed | 1348711241
3 | open | 1348839204
4 | closed | 1348738073
5 | banned | 1348238422
How do I order the above table so that the 'open' records are first, in ASC order; and then the non-open records are second in DESC order? In another word, have a dynamic second level ordering direction based on some condition?
I have tried a UNION of two SELECT queries with ordering within them, which doesn't work because UNION by default produces an unordered set of rows.
Also I've tried a pseudo column that subtracts the created_at timestamp from a large number, for the closed status records, so I can just ORDER BY ASC to get the result as per below...
SELECT table.*, (table.created_at) as tmp_order FROM table
WHERE table.status = 'open'
UNION
SELECT table.*, (999999999 - table.created_at) as tmp_order FROM table
WHERE table.status = 'closed'
ORDER BY tmp_order ASC
This works but I feel there has to be a better way. Ideally a solution would not include a random big number as above
UPDATED
SELECT *
FROM tmp_order
ORDER BY FIELD(status, 'open') DESC,
CASE
WHEN status = 'open'
THEN created_at
ELSE (999999999 - created_at)
END
or
SELECT *
FROM tmp_order
ORDER BY FIELD(status, 'open') DESC,
CASE
WHEN status = 'open'
THEN created_at END,
CASE
WHEN status <> 'open'
THEN created_at END DESC
Output:
| ID | STATUS | CREATED_AT |
----------------------------
| 1 | open | 1348778070 |
| 3 | open | 1348839204 |
| 4 | closed | 1348738073 |
| 2 | closed | 1348711241 |
| 5 | banned | 1348238422 |
Here is SQLFiddle demo.
Try:
SELECT id, status,
if (status = 'open', created_at, 999999999 - created_at) as tmp_order
FROM table
ORDER BY status, tmp_order
This is how I would solve it:
SELECT
id, status, created_at
FROM
yourtable
ORDER BY
status DESC,
CASE WHEN status='open' THEN created_at END,
CASE WHEN status='closed' THEN created_at END DESC
In your case, you can probably do it in one query, but the general solution, which works for any two different and unrelated. orderings is to use two unioned subqueries each with their own ordering;
SELECT * FROM (
SELECT *
FROM table
WHERE table.status = 'open'
ORDER BY created_at DESC) x
UNION ALL
SELECT * FROM (
SELECT *
FROM table
WHERE table.status = 'closed'
ORDER BY created_at) y

MySQL Query to get the record for a specific date

I have the following table named staff_status with structure and records:
----------------------------------------------------
| id (INT) | status (VARCHAR) | status_date (DATE) |
----------------------------------------------------
| 1 | Working | 2009-05-03 |
| 2 | Working | 2009-07-21 |
| 1 | Leave | 2010-02-01 |
| 1 | Working | 2010-02-15 |
----------------------------------------------------
Now I want to query this to get the status of the staff on a specific date. Example: status of id = 1 on 2010-02-10 should return Leave while on 2010-03-01 should return Working
What I have tried without success:
SELECT t1.status FROM staff_status t1 INNER JOIN (SELECT * FROM staff_status WHERE id = 1 AND status_date < '2010-02-10') t2 ON (t1.id = t2.id AND t1.status_date < t2.status_date);
You could try something like
SELECT s.*
FROM staff_status s INNER JOIN
(
SELECT id,
MAX(status_date) status_date
FROM staff_status
WHERE status_date < '2010-02-10'
AND id = 1
) m ON s.id = m.id
AND s.status_date = m.status_date
Additionaly you could try an ORDER BY status_date DESC LIMIT 1
from 13.2.8. SELECT Syntax
Something like
SELECT *
FROM staff_status
WHERE id = 1
AND status_date < '2010-02-10'
ORDER BY status_date DESC
LIMIT 1
First, you'll need the MAX() of the dates per id:
SELECT id, MAX(status_date)
FROM staff_status
WHERE status_date < "2010-02-10" GROUP BY id
...but MySQL doesn't guarantee that the status will be from the row of the MAX(status_date) (in fact, this is almost never the case). So you'll have to take the information you found above, and pull out those records from the original table, matching on id and status_date:
SELECT id, status
FROM staff_status
WHERE
(id, status_date)
IN
(
SELECT id, MAX(status_date)
FROM staff_status
WHERE status_date < "2010-02-10" GROUP BY id
);
This generates a list of ids and statuses for the most recent date found before 2010-02-10:
+------+---------+
| id | status |
+------+---------+
| 2 | Working |
| 1 | Leave |
+------+---------+
2 rows in set (0.01 sec)
Surely simply:
SELECT status FROM staff_status WHERE status_date = '2010-02-10'
Would return you "leave"?
try this:
select status
from staff_status
where status_date<='2010-03-01'
and id=1
order by status_date desc
limit 1
try this:
SELECT IFNULL((SELECT status
FROM staff_status
WHERE id = 1 AND
status_date = '2010-02-10'),
"Leave") AS status;