select last row only if a where condition is met - mysql

I have a column for messages and I am trying to figure out how I should display messages in "sent" folder.
I run the following query:
SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1
I want to introduce a condition so that result is returned only when the last row with status = 1 also has user_from = $user (If the last row has user_to = $user, then nothing should be returned).
What should my query be?

you can use a subquery
select `text`, `created` from T1
( SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1) T1
where `user_from` = $user and `user_to` != $user

If I understand your question correctly, do you want to do this:
SELECT
`text`,
`created`
FROM
`messages`
WHERE
`status` = 1
AND `user_from` = $user
AND `user_to` != $user
ORDER BY `created` DESC
LIMIT 1
Of course you need to replace $user with your string, or use a prepared statement to insert the variable into the query.

Select the latest message with status=1 and then use it as a nested query and check your conditions to output this one row result or not:
select `text`, `created`
from
(
SELECT
`text`,
`created`,
user_from,
user_to
FROM
`messages`
WHERE
`status` = 1
ORDER BY `created` DESC
LIMIT 1
) t
where (user_from = $user)
and (not (user_to = $user))

Related

improve mysql select query with order by option

I have following table with around 10 million records.
and using following query to retrieve data, but it is taking more than 4, 5 seconds to hand over the response.
Is any way to improve query...?
CREATE TABLE `master` (
`organizationName` varchar(200) NOT NULL DEFAULT '',
`organizationNameQuery` varchar(200) DEFAULT NULL,
`organizationLinkedinHandle` varchar(200) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '',
`organizationDomain` varchar(110) NOT NULL DEFAULT '',
`source` varchar(10) NOT NULL DEFAULT '',
`modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY `master_inx` (`organizationName`(80),`organizationDomain`(80),`organizationLinkedinHandle`(80),`organizationNameQuery`(80),`source`),
KEY `organizationDomain` (`organizationDomain`),
KEY `domainWithModified` (`organizationDomain`,`modified`),
KEY `modifiedInx` (`modified`)
);
Query:
SELECT *
FROM (SELECT *
FROM Organizations.master
where ( ( organizationDomain like 'linkedin.com'
|| organizationNameQuery = 'linkedin.com')
and source like 'MY_SOURCE') ) M
ORDER BY M.modified DESC limit 1;
1 row in set (4.69 sec)
UPDATE
I found by breaking OR operator i am getting result faster.
For example:
SELECT *
FROM (SELECT *
FROM Organizations.master
where ( ( organizationDomain like 'linkedin.com')
and source like 'MY_SOURCE') ) M
ORDER BY M.modified DESC limit 1;
1 row in set (0.00 sec)
SELECT *
FROM (SELECT *
FROM Organizations.master
where ( (organizationNameQuery = 'linkedin.com')
and source like 'MY_SOURCE') ) M
ORDER BY M.modified DESC limit 1;
1 row in set (0.00 sec)
Use OR, not || in that context.
The performance villain is OR. Turn the OR into UNION:
( SELECT *
FROM Organizations.master
WHERE organizationDomain = 'linkedin.com'
AND source = 'MY_SOURCE'
ORDER BY modified DESC limit 1
) UNION ALL
( SELECT *
FROM Organizations.master
WHERE organizationNameQuery = 'linkedin.com'
AND source = 'MY_SOURCE'
ORDER BY modified DESC limit 1
}
ORDER BY modified DESC LIMIT 1;
Notes:
This formulation is likely to take about 0.00s to run.
The ORDER BY and LIMIT shows up 3 times.
If you need OFFSET, things get a little tricky.
Change back to LIKE if you allow users to enter wildcards.
A leading wildcard would not be efficient.
UNION ALL is faster than UNION (aka UNION DISTINCT).
It needs two new composite indexes; the order of the 2 columns is not critical:
INDEX(organizationDomain, source),
INDEX(organizationNameQuery, source)
As I checked the query I think you can remove the like operator and use =.
SELECT * FROM (
SELECT * FROM Organizations.master
where ( (organizationDomain = 'linkedin.com' ||
organizationNameQuery = 'linkedin.com')
and source = 'MY_SOURCE')
) M
ORDER BY M.modified DESC limit 1

How to fetch rows with prefered where clause, but also with a fallback?

I have a query (transaction) like this for fetching items from a table. I have a where clause of confirmed = '1', but would like to fallback to ignoring that where clause when no rows are found with the clause.
How can I do this in MySQL Server?
START TRANSACTION;
SELECT #id := `id`,`item`
FROM `queue_items`
WHERE `processed_at` IS NULL AND `completed_at` IS NULL AND confirmed = '1' ORDER BY `id` ASC
LIMIT 1
FOR UPDATE;
UPDATE `queue_items` SET `processed_at` = #processedAt, `worker_id` = #workerId WHERE `id` = #id;
COMMIT;
You could use a conditional sort:
SELECT #id := `id`, `item`
FROM `queue_items`
WHERE `processed_at` IS NULL AND `completed_at` IS NULL
ORDER BY (confirmed = 1) DESC, `id`
LIMIT 1

Set MYSQL WHERE condition from previous SELECT

I have the following MySQL query
SELECT `category`
FROM `jeopardy_questions`
WHERE `amount` = "$2,000"
GROUP BY `category`
HAVING COUNT(*) > 4
ORDER BY RAND() LIMIT 1
This will grab me a random category where there is at least 5 questions in that category.
Now I want to grab all the rows for that category. So how can I do a second SELECT WHERE category is equal to the category returned from the previous query?
I tried the following but I believe the RAND() is causing it to crash/timeout.
SELECT *
FROM `jeopardy_questions`
WHERE `category` = (
SELECT `category`
FROM `jeopardy_questions`
WHERE `amount` = "$2,000"
GROUP BY `category`
HAVING COUNT(*) > 4
ORDER BY RAND() LIMIT 1
)
You can use the above query as a subquery. Something like this:
SELECT *
FROM `jeopardy_questions`
WHERE `category` = (
SELECT `category`
FROM `jeopardy_questions`
WHERE `amount` = "$2,000"
GROUP BY `category`
HAVING COUNT(*) > 4
ORDER BY RAND() LIMIT 1
)

Every derived table must have its own alias - error from combination descending MySQL

I want to order one mysql table by two strtotime timestamps from two different columns. I've got the following mysql command:
SELECT * FROM (
(SELECT '1' AS `table`, `vid_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `vid_req` = '1')
UNION
(SELECT '2' AS `table`, `ost_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `ost_req` = '1')
)
ORDER BY `timestamp` DESC
This gives me an error:
#1248 - Every derived table must have its own alias
I want to combine vid_req_timestamp and ost_req_timestamp and make those descending. And it's important to know where the timestamp came from (somehow).
In this case, the derived table that requires an alias is the one that you are SELECTing * from.
Indentation helps make that clearer.
SELECT * FROM
(
(SELECT '1' AS `table`, `vid_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `vid_req` = '1')
UNION
(SELECT '2' AS `table`, `ost_req_timestamp` AS `timestamp`, `title` FROM `movies` WHERE `ost_req` = '1')
) AS `some_table_name_lol_this_is_an_alias`
ORDER BY `timestamp` DESC

MySQL Group By get title column with smallest date value

I have a query like:
SELECT *
FROM table
GROUP BY sid
ORDER BY datestart desc
LIMIT 10
which returns the last 10 sid groups.
For each of these groups, I need the title column of the row with the lowest datestart value
I tried using
SELECT *, min(datestart)
but that didn't return the row with the smallest datestart value, just the lowest datestart. I need the title from the lowest datestart.
(Relevant) Table Structure:
CREATE TABLE `table` (
`title` varchar(1000) NOT NULL,
`datestart` timestamp NOT NULL default CURRENT_TIMESTAMP,
`sid` bigint(12) unsigned NOT NULL,
KEY `datestart` (`datestart`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Any ideas?
Updated answer
select t1.* from `table` as t1
inner join (
select sid,min(datestart) as elder
from `table`
group by sid
order by elder desc limit 10) as t2
on t1.sid = t2.sid and t1.datestart = t2.elder
Use a composite index on (sid,datestart)
Try this query. You will get expected results. If it don't work change Table_2.datestart > Table_1.datestart by Table_2.datestart < Table_1.datestart
SELECT title, datestart
FROM `table` AS Table_1
LEFT JOIN `table` AS Table_2 ON (Table_2.sid = Table_1.sid AND Table_2.datestart > Table_1.datestart)
Table_2.sid IS NULL;
Edited query
SELECT Table_1.title, Table_1.datestart
FROM `table` AS Table_1
LEFT JOIN `table` AS Table_2 ON (Table_2.sid = Table_1.sid AND Table_2.datestart > Table_1.datestart)
Table_2.sid IS NULL;