SQL - Retrieving latest message for a CASE WHEN statement - mysql

I have a problem here trying to get one of my CASE WHEN statement to query each row for something called is_op as it's returning the same number for all rows. Here is the code:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid` FROM `bulletin_message` WHERE `bid` = 1 ORDER BY `mid` ASC LIMIT 1) = 5 THEN 1 ELSE 0 END AS `is_op`,
CASE WHEN `bulletin_message`.`uid` = 5 THEN 1 ELSE 0 END AS `is_me`
FROM`bulletin_message`
WHERE `bid` = 1
GROUP BY `mid`
ORDER BY `mid` ASC
As you can see I'm trying to select messages with the condition bid must equal to 1 and uid must equal to 5. While is_me returns the correct value for each row, is_op isn't reflecting the correct value for all the rows at all. It displays 1 at the result of the statement, rather than showing if a user is an OP or not based on the oldest value of mid or created_at. I don't think I am correctly querying each row like is_me statement.
This is all the data of the table:
mid = message uid; bid = bulletin/thread uid; uid = user uid
| mid | bid | uid | message | created_at |
---------------------------------------------------
| 3 | 1 | 5 | ... | ... |
| 5 | 1 | 6 | ... | ... |
| 6 | 2 | 7 | ... | ... |
| 9 | 1 | 5 | ... | ... |
| 10 | 1 | 7 | ... | ... |
| 11 | 1 | 6 | ... | ... |
What can be done to improve this line of code so that it can query each row? Thank you!
Edit: OP is Original Poster, sorry for not clarifying that! It's usually the person who post the first in each bid.

The problem is your subquery is based on a fixed predicate, ``bid= 1, so it is bound to return the same value for all rows.
Something like this would make more sense:
SELECT `mid`, `message`, `created_at`,
CASE WHEN (SELECT `uid`
FROM `bulletin_message` AS t2
WHERE t1.`bid` = t2.`bid`
ORDER BY `mid` ASC LIMIT 1) = t1.`uid`
THEN 1
ELSE 0
END AS `is_op`
FROM`bulletin_message` AS t1
ORDER BY `mid` ASC
The subquery is correlated using bid field: it returns the OP of the current thread.

Related

fetch records from table ordered by column value

I have a table with a column to maintain the state of the record. i.e.
-----------------------------
| id | desc | state |
-----------------------------
| 1 | desc 1 | Complete |
| 2 | desc 2 | Open |
| ... | ... | ... |
-----------------------------
I want fetch the records in the order of 'Open' followed by 'Complete'. Can I get this done using one SQL query? If so, how should I write it?
Yes, you could do this with the ORDER BY statement and FIELD function:
SELECT * FROM table1 ORDER BY FIELD(state, 'Open', 'Complete')
Try something like this:
select *
from table_name
order by decode (state, 'Open', 1, 'Complete', 2, 3)

how to sort results by specific values in mysql

We have a DB called transaction. It has user_id, date, value and so on. I use pagination in my query also. I have thousands of record in my table which has user_id equal to 2 or other value. put the user_id = 2 at the very last page.
I want to sort the result like this:
sort the results by date but if the user_id= 2 , put all results associated with the user_id= 2 at the end.
to be more clear, I show you what I want in the below.
-------------------------------------
| ID | user_id | date | ......
-------------------------------------
| 1 | 10 | 2018-10-20 |
-------------------------------------
| 2 | 11 | 2018-10-21 |
-------------------------------------
| 3 | 2 | 2018-10-22 |
-------------------------------------
| 4 | 2 | 2018-10-23 |
the results have to be like this:
first: ID = 2, second: ID = 1, third: ID = 4, last: ID = 3
tip *:
I use field function but unfortunately in vain.
ORDER BY FIELD(user_id, 2) DESC, date DESC
You may try using a CASE expression in your ORDER BY clause:
SELECT *
FROM yourTable
ORDER BY
CASE WHEN user_id = 2 THEN 1 ELSE 0 END,
date DESC;
I'm not sure if you want each group sorted by date ascending or descending. If you want ascending date order, then remove the DESC keyword at the end of my query.

Getting the most recent row and linking it with another table?

Im trying to get the most recent row of a table
user_quiz:
+--------+-----------+-------------+-------------------+------------+
|quiz_id |userid | module_id |number_of_questions| user_score |
+--------+-----------+-------------+-------------------+-------- ---+
| 1 | 1 | 1 | 5 | 5 |
| 2 | 2 | 2 | 10 | 9 |
| 3 | 1 | 1 | 10 | 9 |
+--------+-----------+-------------+-------------------+------------+
I have used the query:
SELECT * FROM user_quiz WHERE userid = 1 ORDER BY quiz_id DESC LIMIT 1
which correctly retrieves the last row.
However I want to link the module_id with another table:
module:
+---------+------------+
|module_id|module_name |
+---------+------------+
| 1 | Forces |
| 2 | Electricity|
+---------+------------+
And retrieve the module name.
The result of the query will be used to print out the users most recent quiz:
Most recent quiz: Forces - Number of questions: 10 - User Score: 9
Is this possible using just one query?
You just need a JOIN:
SELECT uq.*, m.module_name
FROM user_quiz uq JOIN
modules m
ON uq.module_id = m.module_id
WHERE uq.userid = 1
ORDER BY uq.quiz_id DESC
LIMIT 1;
A more simple query to achieve the same would be
SELECT
user_quiz.quiz_id,
user_quiz.number_of_questions,
user_quiz.user_score,
modules .module_name
FROM user_quiz JOIN modules
ON user_quiz.module_id = modules.module_id
WHERE user_quiz.userid = 1
ORDER BY user_quiz.quiz_id DESC
LIMIT 1
If you want to get the same results for all the users, you could use a bit more sophisticated query
SELECT
user_quiz_virtual_table.userid,
user_quiz_virtual_table.quiz_id,
user_quiz_virtual_table.number_of_questions,
user_quiz_virtual_table.user_score,
modules.module_name
FROM (
SELECT
user_quiz.userid
user_quiz.quiz_id,
user_quiz.module_id
user_quiz.number_of_questions,
user_quiz.user_score
FROM user_quiz
ORDER BY user_quiz.quiz_id DESC
GROUP BY userid
) AS user_quiz_virtual_table
JOIN modules ON user_quiz_virtual_table.module_id = modules.module_id

Mysql LIMIT in subquery

I have this mysql statement but I receive LIMIT in subquery error
SELECT id
FROM articles
WHERE section=1 AND id NOT IN
(
SELECT id
FROM articles
WHERE is_top_story=1 ORDER BY timestamp DESC LIMIT 2
)
I want to select all id-s from table where section=1 and id-s not in my inner(second) statement
+--id--+section+-is_top_story-+--timestamp--+
| 54 | 1 | 1 | 130 |
| 70 | 2 | 0 | 129 |
| 98 | 3 | 1 | 128 |
| 14 | 1 | 1 | 127 |
| 58 | 4 | 0 | 126 |
| 13 | 3 | 1 | 125 |
| 64 | 1 | 1 | 124 |
| 33 | 1 | 1 | 123 |
My sql should return 64 and 33(they are with section=1 and is_top_story=1), because 54 and 14 (are in inner statment)
If any can give me some code I will be very grateful
Thanks
How about this:
SELECT a.id, a.times
FROM articles AS a
LEFT JOIN (
SELECT id
FROM articles
WHERE is_top_story =1
ORDER BY times DESC LIMIT 2) AS ax
USING (id)
WHERE section = 1 AND ax.id IS NULL;
Join is a usual workaround when you need limits in subqueries; need for excluding logic just adds these 'left join - joined.id IS NULL` parts to query. )
UPDATE: Got a bit confused by your example. The original query you've quoted was "take some articles with section = 1, then take out those that belong to the 2 most recent top_stories". But in your example the section should also be taken into account when selecting these stories-to-ignore-...
It's actually quite easy to update my query with that condition as well: just replace
WHERE is_top_story = 1
with
WHERE is_top_story = 1 AND section = 1
... but I think it might be even better solved at the client code. First, you send a simple query, without any joins whatsoever:
SELECT id, is_top_story
FROM articles
WHERE section = 1
ORDER BY times DESC;
Then you just walk through the fetched rowset, and just ignore two first elements with 'is_top_story' flag on, like that:
...
$topStoriesToIgnore = 2;
foreach ($rowset as $row) {
if ($row->is_top_story && $topStoriesToIgnore-- > 0) {
continue;
}
// actual processing code goes here
}
I don't know if this is that you want, the question is a little confuse. I don't understand why you use a subquery for the same table. Anyway LIMIT is the same of "TOP" for MSSQL, so LIMIT 2 should be only returns two records.
If this is not that you want please comment and I will edit my answer:
SELECT id
FROM articles
WHERE section=1 AND is_top_story != 1
ORDER BY timestamp DESC

SELECT N rows before and after the row matching the condition?

The behaviour I want to replicate is like grep with -A and -B flags .
eg grep -A 2 -B 2 "hello" myfile.txt will give me all the lines which have "hello" in them, but also 2 lines before and 2 lines after it.
Lets assume this table schema :
+--------+-------------------------+
| id | message |
+--------+-------------------------+
| 1 | One tow three |
| 2 | No error in this |
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
| 8 | social game |
| 9 | stackoverflow |
|10 | stackexchange |
+------------+---------------------+
Now a query like :
Select * from theTable where message like '%hello%' will result in :
5 | hello world
How can I put another parameter "N" which selects N rows before, and N rows after the matched record i.e. for N = 2, the result should be :
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
For simplicity assume 'like %TERM%' matches only 1 row .
Here the result is supposed to be sorted on auto-increment id field.
Right, this works for me:
SELECT child.*
FROM stack as child,
(SELECT idstack FROM stack WHERE message LIKE '%hello%') as parent
WHERE child.idstack BETWEEN parent.idstack-2 AND parent.idstack+2;
Don't know if this is at all valid MySQL but how about
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id <= t.id
ORDER BY
ID DESC
LIMIT 3
UNION ALL
SELECT t.*
FROM theTable t
INNER JOIN (
SELECT id FROM theTable where message like '%hello%'
) id ON id.id > t.id
ORDER BY
ID
LIMIT 2
Try this simple one (edited) -
CREATE TABLE messages(
id INT(11) DEFAULT NULL,
message VARCHAR(255) DEFAULT NULL
);
INSERT INTO messages VALUES
(1, 'One tow three'),
(2, 'No error in this'),
(3, 'My testing message'),
(4, 'php module test'),
(5, 'hello world'),
(6, 'team spirit'),
(7, 'puzzle game'),
(8, 'social game'),
(9, 'stackoverflow'),
(10, 'stackexchange');
SET #text = 'hello world';
SELECT id, message FROM (
SELECT m.*, #n1:=#n1 + 1 num, #n2:=IF(message = #text, #n1, #n2) pos
FROM messages m, (SELECT #n1:=0, #n2:=0) n ORDER BY m.id
) t
WHERE #n2 >= num - 2 AND #n2 <= num + 2;
+------+--------------------+
| id | message |
+------+--------------------+
| 3 | My testing message |
| 4 | php module test |
| 5 | hello world |
| 6 | team spirit |
| 7 | puzzle game |
+------+--------------------+
N value can be specified as user variable; currently it is - '2'.
This query works with row numbers, and this guarantees that the nearest records will be returned.
Try
Select * from theTable
Where id >=
(Select id - variableHere from theTable where message like '%hello%')
Order by id
Limit (variableHere * 2) + 1
(MS SQL Server only)
The most reliable way would be to use the row_number function that way it doesn't matter if there are gaps in the id. This will also work if there are multiple occurances of the search result and properly return two above and below each result.
WITH
srt AS (
SELECT ROW_NUMBER() OVER (ORDER BY id) AS int_row, [id]
FROM theTable
),
result AS (
SELECT int_row - 2 AS int_bottom, int_row + 2 AS int_top
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
WHERE ([message] like '%hello%')
)
SELECT theTable.[id], theTable.[message]
FROM theTable
INNER JOIN srt
ON theTable.id = srt.id
INNER JOIN result
ON srt.int_row >= result.int_bottom
AND srt.int_row <= result.int_top
ORDER BY srt.int_row
Adding an answer using date instead of an id.
The use-case here is an on-call rotation table with one record pr week.
Due to edits the id might be out of order for the purpose intended.
Any use-case having several records pr week, pr date or other will of course have to be mended.
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| startdate| datetime | NO | | NULL | |
| person | int(11) | YES | MUL | NULL | |
+----------+--------------+------+-----+---------+----------------+
The query:
SELECT child.*
FROM rota-table as child,
(SELECT startdate
FROM rota-table
WHERE YEARWEEK(startdate, 3) = YEARWEEK(now(), 3) ) as parent
WHERE
YEARWEEK(child.startdate, 3) >= YEARWEEK(NOW() - INTERVAL 25 WEEK, 3)
AND YEARWEEK(child.startdate, 3) <= YEARWEEK(NOW() + INTERVAL 25 WEEK, 3)