I am testing out how to use a count-variable to have a column in my SELECT output be dedicated to simply listing the row number. So if I had 3 rows outputed in the query, then they would be numbered {1,2,3} in their "rank" column as I have written....
Furthermore, I am wanting to select only one row, where that row's "rank" column is 1. The following SQL statement works (making sure I SET #rownum=0 everytime) however, it doesn't give me the appropriate output. So looking for rank=1 I get the row where rank is actually 2. If I look for rank=2 then I get where its really 3. and so on. If I then search for rank=0, I DO NOT get where the rank is 1 Surprisingly. So I'm lost
SET #rownum=0;
SELECT #rownum := #rownum + 1 AS rank,
r.id, c.fname, r.pickupCity, r.pickupState
FROM request r
INNER JOIN client c ON r.client = c.id
INNER JOIN pickup p ON r.id = p.request
INNER JOIN driver d ON d.id = p.driver
WHERE date = '2018-04-18' AND d.id = 1
HAVING rank = 1;
You are missing a really important point: SQL tables represent unordered sets. You should expect no correspondence between ids and row numbers without an order by. Period. And this is even more notable in queries that use joins.
I would suggest using an order by in a subquery, like this:
SELECT x.*, #rownum := #rownum + 1 AS rank
FROM (SELECT r.id, c.fname, r.pickupCity, r.pickupState
FROM request r JOIN
client c
ON r.client = c.id
pickup p
ON r.id = p.request
driver d
ON d.id = p.driver
WHERE date = '2018-04-18' AND d.id = 1
ORDER BY r.id
) x CROSS JOIN
(SELECT #rownum := 0) params
HAVING rank = 1;
That said, another way to write the query would use limit and offset:
SELECT r.id, c.fname, r.pickupCity, r.pickupState
FROM request r JOIN
client c
ON r.client = c.id
pickup p
ON r.id = p.request
driver d
ON d.id = p.driver
WHERE date = '2018-04-18' AND d.id = 1
ORDER BY r.id
LIMIT 1 OFFSET 0;
You can change the OFFSET value to get rows other than the first.
Related
I am trying to obtain the row number (i.e. rank) for the following select statement (which includes a column JOIN) but without declaring a SET variable at the beginning.
Reason for this is because I am using a WordPress/MySQL plugin which can only emulate single statement code. The common hack of declaring a prior variable to 0 then incrementing is not recognized.
Is there another way to obtain the row number using the select & join below?
SELECT s.id
, s.item
, s.state
, c.job_count
FROM wp_state_tab s
LEFT
JOIN wp_custom_tab c
ON c.state_id = s.id
WHERE c.date = CURDATE()
ORDER
BY c.job_count DESC
Sample Data Output
MySQL version is 5.6.40-84.0-log
MySQL can be fiddly about variables -- good thing they are deprecated. With ORDER BY or GROUP BY, you often have to use a subquery:
SELECT (#rn := #rn + 1) as seqnum, sc.*
FROM (SELECT s.id, s.item, s.state, c.job_count
FROM wp_state_tab s LEFT JOIN
wp_custom_tab c
ON c.state_id = s.id
WHERE c.date = CURDATE()
ORDER BY c.job_count DESC
) sc CROSS JOIN
(SELECT #rn := 0) params;
You can use a subquery for the iteration of a newly defined row number without explicitly declaring variable as :
select #i := #i + 1 as rownum,
s.id, s.item, s.state, c.job_count
from wp_state_tab s
left join wp_custom_tab c
on c.state_id = s.id and c.date = CURDATE()
join (select #i:=0) t2
order by job_count desc;
i'm trying to get ranking based on rating percentage so mysql query like
select c.id , sum((r.value * 20))/ count(r1.pagetypeid) as score, #curRank := #curRank + 1 AS rank from (SELECT #curRank := 0) cr, rating as r
inner join rateelement as r1 on r.elementid = r1.id
inner join ratesubscription as r2 on r.subscriptionid = r2.id
inner join consultant as c on r2.consultantid = c.id
where r1.displayorder not in (6) and r2.agencyid = 38
group by c.id order by score desc
but it returns wrong raking indexes
what's wrong with the query?
Ranking with variables often has issues with group by -- and even order by in the most recent versions of MySQL. So, use a subquery:
select x.*, (#curRank := #curRank + 1) AS rank
from (select c.id, sum((r.value * 20))/ count(r1.pagetypeid) as score
from rating r inner join
rateelement r1
on r.elementid = r1.id inner join
ratesubscription r2
on r.subscriptionid = r2.id inner join
consultant c
on r2.consultantid = c.id
where r1.displayorder not in (6) and r2.agencyid = 38
group by c.id
order by score desc
) x cross join
(SELECT #curRank := 0) cr;
I have a query like this:
SELECT
p.title,
a.title
FROM
pages p
LEFT JOIN
articles_pages ap on ap.p_id = p.id
LEFT JOIN
articles a ON a.id = p.a_id
WHERE
[...]
LIMIT 10
How can I limit to only 3 articles for each page?
There are few ways to do that. You can use sub-queries but I wouldn't recommend that as its slower and not scalable. So.. I like the one I show you bellow:
I would create a variable and save on it the row number(#num).
We also need another variable to save the "page" id as its necessary to restart the row count when a different page appears.
Finally we filter by #num with as much rows you want. Remember matchNO is a calculated field so you cant filter in a where statment, use having instead.
set #num := 0, #page := 0;
SELECT p.title, a.title,IF(#page = p.id,#num:=#num+1,#num:=1) as matchNO,#page:=p.id
FROM pages p
LEFT JOIN articles_pages ap on ap.p_id = p.id
LEFT JOIN articles a ON a.id = p.a_id
WHERE [...]
having matchNO <= 3
LIMIT 10
The row number is necessary to figure out in what row you are.
The page variable tell you the page in the last row so you can compare and then set #num to 1 again when necesary.
I hope it helps
Another possible way to do it. Whether this would be better or worse than the others would depend on the relative quantities of pages and articles.
You could use a sub query to get 3 articles per page and then join that back to your tables. You can cheat a bit and use GROUP_CONCAT to get the ids of all the articles per per, then use SUBSTRING_INDEX to get the first 3 (you can add an order clause to GROUP_CONCAT if you want), and then JOIN this using FIND_IN SET.
SELECT
p.title,
a.title
FROM pages p
LEFT OUTER JOIN
(
SELECT ap.p_id
SUBSTRING_INDEX(GROUP_CONCAT(ap.a_id), ',', 3) AS three_articles_per_page
FROM articles_pages ap
GROUP BY ap.p_id
) sub0 ON p.id = sub0.p_id
LEFT JOIN articles_pages ap on ap.p_id = p.id AND FIND_IN_SET(ap.aid, sub0.three_articles_per_page)
LEFT JOIN articles a ON a.id = p.a_id
WHERE
[...]
You can make use of user variables to generate row number and then filter based on it.
select p.title, a.title
from (
select
t.*,
#rn := if(
#id = id, #rn + 1,
if(
#id := id, 1, 1
)
) rn
from (
select *
from pages
order by id, a_id
) t cross join (select #rn := 0, #id:= -1) t2
) p left join articles a on a.id = p.a_id
where p.rn <= 3
order by p.id, p.a_id;
Demo
I've got this query:
SELECT *, m.id AS mooringid
FROM mooring m JOIN customer c ON m.assignedTo = c.id
WHERE m.Number = :var OR (CONCAT(c.TitleName,' ',c.Surname) LIKE CONCAT('%', :var, '%')) OR m.MooringArea = :var
ORDER BY c.Surname limit 0,250
That is supposed to get elements assigned to a customer from another table, the only way I saw that I could do this is by "infusing" the customer details with the element, returning the elements but I want to limit the amount of customers returned but allow infinite elements, however, this limits the elements rather than the customer and that just doesn't work in my situation.
Is this possible? Am I missing something?
One way to tackle the issue is to do a subquery on customers and extract the number you need. Something like:
from (select c.*
from customers c
limit 100
) c
But, in your case, you have a lot of secondary filtering going on (with the join conditions and the where). Instead, add a customer counter to each row and use that for selecting a certain number of customers:
select t.*
from (SELECT *, m.id AS mooringid,
#rn := if(#cid = c.id, #rn + 1, 1) as rn,
#cid = c.id
FROM mooring m JOIN
customer c
ON m.assignedTo = c.id cross join
(select #rn := 0, #cid := -1) const
WHERE m.Number = :var OR
(CONCAT(c.TitleName,' ',c.Surname) LIKE CONCAT('%', :var, '%')) OR
m.MooringArea = :var
ORDER BY c.Surname
) t
order by c.SurName
where rn <= 10;
I'm trying to follow this example: MySQL - ranking by count() and GROUP BY but my rank column keeps returning nil.
Here's my query:
SELECT
#rownum := #rownum+1 AS rank,
q.id,
q.Name,
q.count
FROM
(SELECT
Accounts.id,
Accounts.Name,
COUNT(Accounts.Name) AS count
FROM
player_to_team_histories
INNER JOIN
team_histories ON team_histories.id = player_to_team_histories.team_history_id
INNER JOIN
teams ON teams.id = team_histories.team_id
INNER JOIN
accounts ON accounts.id = teams.account_id
WHERE
accounts.AccountTypeId = 1 AND player_id IN (SELECT
player_id
FROM
player_to_team_histories
WHERE
player_to_team_histories.not_valid IS NULL AND team_history_id = (SELECT
team_history_id
FROM
player_to_team_histories
INNER JOIN
team_histories ON team_histories.id = player_to_team_histories.team_history_id
WHERE
player_to_team_histories.id = 574651))
GROUP BY Accounts.Name
ORDER BY count DESC)q
Every column except rank is returning as expected, and rank is returning null for every row.
You have to initialize your rownum to 0 before starting to increment it, either by
SET #rownum := 0;
before the query, or by a separate SELECT clause after your first FROM:
SELECT ...
FROM
(SELECT #rownum := 0 ) counter,
(SELECT
... )