Mysql LIMIT in subquery - mysql

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

Related

combine 3 queries to one, SELECT / COUNT / INSERT

I need help to optimize my 3 queries into one.
I have 2 tables, the first has a list of image processing servers I use, so different servers can handle different simultaneous job loads at a time, so I have a field called quota as seen below.
First table name, "img_processing_servers"
| id | server_url | server_key | server_quota |
| 1 | examp.uu.co | X0X1X2XX3X | 5 |
| 2 | examp2.uu.co| X0X1X2YX3X | 3 |
The second table registers if there is a job being performed at this moment on the server
Second table, "img_servers_lock"
| id | lock_server | timestamp |
| 1 | 1 | 2020-04-30 12:08:09 |
| 2 | 1 | 2020-04-30 12:08:09 |
| 3 | 1 | 2020-04-30 12:08:09 |
| 4 | 2 | 2020-04-30 12:08:09 |
| 5 | 2 | 2020-04-30 12:08:09 |
| 6 | 2 | 2020-04-30 12:08:09 |
Basically what I want to achieve is that my image servers don't go past the max quota and crash, so the 3 queries I would like to combine are:
Select at least one server available that hasn't reached it's quota and then insert a lock record for it.
SELECT * FROM `img_processing_servers` WHERE
SELECT COUNT(timestamp) FROM `img_servers_lock` WHERE `lock_server` = id
! if the count is < than quota, go ahead and register use
INSERT INTO `img_servers_lock`(`lock_server`, `timestamp`) VALUES (id_of_available_server, now())
How would I go about creating this single query?
My goal is to keep my image servers safe from overload.
Join the two tables and put that into an INSERT query.
INSERT INTO img_servers_lock(lock_server, timestamp)
SELECT s.id, NOW()
FROM img_processing_servers s
LEFT JOIN img_servers_lock l ON l.lock_server = s.id
GROUP BY s.id
HAVING IFNULL(COUNT(l.id), 0) < s.server_quota
ORDER BY s.server_quota - IFNULL(COUNT(l.id), 0) DESC
LIMIT 1
The ORDER BY clause makes it select the server with the most available quota.
OK, so I encountered just a small addition that was giving me a bug and it was that the s.server_quota had to be added to GROUP BY for it to work in the HAVING
INSERT INTO img_servers_lock(lock_server, timestamp)
SELECT s.id, NOW()
FROM alpr_servers s
LEFT JOIN img_servers_lock l ON l.lock_server = s.id
GROUP BY s.id, s.server_quota
HAVING IFNULL(COUNT(l.id), 0) < s.server_quota
ORDER BY s.server_quota - IFNULL(COUNT(l.id), 0) DESC
LIMIT 1
Thanks again Barmar!

SQL Query to conditionally retrieve records based on a key value list

I have an API that communicates with a database with the following tables:
QUESTS
+----------+------------+
| quest_id | quest_name |
+----------+------------+
| 1 | Q001 |
| 2 | Q002 |
| 3 | Q003 |
| 4 | Q004 |
| 5 | Q005 |
+----------+------------+
SKILLS
+----------+------------+
| skill_id | skill_name |
+----------+------------+
| 1 | S001 |
| 2 | S002 |
| 3 | S003 |
| 4 | S004 |
| 5 | S005 |
+----------+------------+
SKILL_PREREQUISITES
+----------+-----------------------+-------------+
| quest_id | prerequisite_skill_id | skill_value |
+----------+-----------------------+-------------+
| 1 | 2 | 50 |
| 1 | 1 | 45 |
| 4 | 2 | 25 |
| 4 | 3 | 60 |
| 5 | 4 | 50 |
+----------+-----------------------+-------------+
Quests correspond to levels in a game, and the skills are acquired by players while playing the game. The SKILL_PREREQUISITE table maintains the skill pre-requisites a player needs to satisfy before being able to participate in a quest.
Problem
An endpoint of the API receives a list of skills a player has, along with the skill level (skill_value) of the corresponding skill like so:
[
{
"key": 1, //skill ID
"value": 45 //skill value
},
{
"key": 2,
"value": 60
}
...
]
Now my use case is to use these values and query the database to obtain a list of Quests the player is eligible to participate in based on the skill_id as well as the skill_value
Example
Assume the API receives the following skill-skill value mapping:
skill-value map: [{1,50}, {2,60}, {4,50}]
Based on this, the player with the above skill set can participate in quest 1 and 5 but not in 4 since 4 requires skill 3 with 60 points.
Attempt at a solution
I have managed to write a query (thanks to a previous question I posted!) to identify the quests that correspond to the skill IDs, but I have no idea how to filter this further within the query, based on the skill value.
I am wondering if this is even possible at this point and whether I might have to do further processing on the server to get the required result.
Fiddle for my attempt at a solution: https://www.db-fiddle.com/f/s2umHS1wz3Q8ibwUhKDas6/1
The challenge here is that you need to pass these values retrieved from an API response to your SQL statement as input and generate output by dynamically creating no of comparisons based on the input.
Now, if i would've familiar with your back-end platform than i would've given more apt solution but as i don't aware with Node.js, my solution will only include required SQL statements and the remaining part you need to DIY.
First thing you need to do is to parse this API response and store these values into a Data Structure.
Now, create a Temporary table in from your Node.js code and store these input values in this table.
CREATE TEMPORARY TABLE Input (id INT, value INT);
Add data from that Data structure to this table.
Now, run the following query & you'll get what you want:
SELECT skp.quest_id
FROM SKILL_PREREQUISITES skp
GROUP BY quest_id
HAVING COUNT(skp.quest_id) =
( SELECT COUNT(quest_id)
FROM Input i
JOIN SKILL_PREREQUISITES sp
ON sp.prerequisite_skill_id = i.id
AND sp.skill_value <= i.value
WHERE skp.quest_id = sp.quest_id
)
Demo Fiddle
You can phrase the query by comparing both the skill and the value:
SELECT q.quest_id
FROM QUESTS q LEFT JOIN
SKILL_PREREQUISITES p
ON p.quest_id = q.quest_id
GROUP BY q.quest_id
HAVING SUM( p.prerequisite_skill_id = 1 and 50 >= p.skill_value) > 0 AND
SUM( p.prerequisite_skill_id = 2 and 60 >= p.skill_value) > 0 AND
SUM( p.prerequisite_skill_id = 4 and 50 >= p.skill_value) > 0 ;
Because you need quests that have prerequisites, a LEFT JOIN is not necessary. In fact, no JOIN is necessary at all:
SELECT p.quest_id
FROM SKILL_PREREQUISITES p
WHERE p.prerequisite_skill_id IN (1, 2, 4)
GROUP BY p.quest_id
HAVING SUM( p.prerequisite_skill_id = 1 and 50 >= p.skill_value) > 0 AND
SUM( p.prerequisite_skill_id = 2 and 60 >= p.skill_value) > 0 AND
SUM( p.prerequisite_skill_id = 4 and 50 >= p.skill_value) > 0 ;
The filtering before the GROUP BY is optional, but it might improve performance.
EDIT:
I think I answered the wrong question above. I think you want all the skills that are available given the user's pre-requisites. That would be:
SELECT q.quest_id
FROM QUESTS q LEFT JOIN
SKILL_PREREQUISITES p
ON p.quest_id = q.quest_id
GROUP BY q.quest_id
HAVING COALESCE(SUM( (p.prerequisite_skill_id = 1 and 50 >= p.skill_value) +
(p.prerequisite_skill_id = 2 and 60 >= p.skill_value) +
(p.prerequisite_skill_id = 4 and 50 >= p.skill_value)
)) = COUNT(p.prerequisite_skill_id);

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 HAVING MIN() does not return minimum value

I'm running stuck on a MySQL query that doesn't do what I want. From a list of product images, I need to get the one with the lowest order number. I'm using the query below to do so:
SELECT id FROM media
WHERE media.productgroup_id = 102
AND media.product_id= 902
AND media.type = 0
HAVING MIN(media.order)
Database table media contains:
id | productgroup_id | product_id | order | type
47215 | 102 | 902 | 2 | 0
47216 | 102 | 902 | 1 | 0
47217 | 102 | 902 | 3 | 0
Running the query above returns me id 47215, where I was expecting 47216. So it returns the minimum id, not the minimum order.
What mistake do I make?
Order by the order and take the first element
SELECT id
FROM media
WHERE media.productgroup_id = 102
AND media.product_id= 902
AND media.type = 0
order by media.order asc
limit 1
having is used for groups which you did not build.
If your criteria were more complex than just min, you could use a subquery to solve this as well. That being said, #jeurgen-d's answer is probably a faster one for your situation. I just want to point out there is more than one way to skin this cat.
SELECT id
FROM media
INNER JOIN (
SELECT min(id)
FROM media
WHERE
media.productgroup_id = 102
AND media.product_id= 902
AND media.type = 0
) as m2
ON media.id = m2.id

Using SQL to get distinct rows, but also the whole row for those

Ok so its easier to give an example and hopefully some has a solution:
I have table that holds bids:
ID | companyID | userID | contractID | bidAmount | dateAdded
Below is an example set of rows that could be in the table:
ID | companyID | userID | contractID | bidAmount | dateAdded
--------------------------------------------------------------
10 | 2 | 1 | 94 | 1.50 | 1309933407
9 | 2 | 1 | 95 | 1.99 | 1309933397
8 | 2 | 1 | 96 | 1.99 | 1309933394
11 | 103 | 1210 | 96 | 1.98 | 1309947237
12 | 2 | 1 | 96 | 1.97 | 1309947252
Ok so what I would like to do is to be able to get all the info (like by using * in a normal select statement) the lowest bid for each unique contractID.
So I would need the following rows:
ID = 10 (for contractID = 94)
ID = 9 (for contractID - 95)
ID = 12 (for contractID = 96)
I want to ignore all the others. I thought about using DISTINCT, but i haven't been able to get it to return all the columns, only the column I'm using for distinct.
Does anyone have any suggestions?
Thanks,
Jeff
select *
from mytable main
where bidAmount = (
select min(bidAmount)
from mytable
where contractID = main.contractID)
Note that this will return multiple rows if there is more than one record sharing the same minimum bid.
Didn't test it but it should be possible with this query although it might not be really fast:
SELECT * FROM bids WHERE ID IN (
SELECT ID FROM bids GROUP BY contractID ORDER BY MIN(bidAmount) ASC
)
This would be the query for MySQL, maybe you need to adjust it for another db.
You could use a subquery to find the lowest rowid per contractid:
select *
from YourTable
where id in
(
select min(id)
from YourTable
group by
ContractID
)
The problem is that distinct does not return a specific row - it return distinct values, which ( by definition ) could occur on multiple rows.
Subqueries are your answer, and somewhere in the suggestions above is probably the answer. Your subquery need to return the ids or the rows with the minimum bidvalue. Then you can select * from the rows with those ids.