php mysql order by priority and ranking - php

I have the following table :
Properties
id agency_id refno
1 1 AA101
2 3 AA201
3 2 AA501
4 1 AA762
5 3 AA555
agency
agency_id agency_name priority
1 A 30
2 B 10
3 C 20
I have defined the priority of each agency in the agency table:
Now I want to extract the rows from properties table based on the ranking and priority. I want to extract 1st row from 1st agency, 1st row from 2nd agency, 1st row from 3rd agency and so on.
Then 2nd row from 1st agency, 2nd row from 2nd agency, 2nd row from 3rd agency and so on.
Then I want to sort the whole result based on the priority of each agency. I am using the following clauses but its not giving the desired result
select properties.id,
properties.agency_id,
IF(#prev <> properties.agency_id, #cnt := 1, #cnt := #cnt + 1) AS rank, #prev := properties.agency_id,
properties.refno
where properties.agency_id = agency.agency_id
order by agency.priority, rank
i have put the join, and its working fine there is no error. but i need the results as follow:
i have defined the priority of each agency in the agency table. the query is working fine with join. i need the results as follow:
agency2 row1
agency3 row1
agency1 row1
agency2 row2
agency3 row2
agency1 row2
agency2 row3
agency3 row3
agency1 row3
according to the priority defined in the agency table and rank.

Please go through the concpet of a JOIN
The query must join the two tables.
select
properties.id,
properties.agency_id,
IF(#prev <> properties.agency_id, #cnt := 1, #cnt := #cnt + 1) AS rank,
#prev := properties.agency_id,
properties.refno
FROM properties
INNER JOIN agency ON properties.agency_id = agency.agency_id
order by agency.priority, rank

Related

Get position of an ID based on MySQL COUNT result

I am not even sure if this has been answered because I don't even know how to coin the problem. But here is what am trying to do.
I am using COUNT() to create a tabular representation of a data from top to bottom for a 30 day period.
SELECT id FROM table WHERE col = '123' AND date >= DATE_SUB(CURRENT_DATE, INTERVAL DAYOFMONTH(CURRENT_DATE)-1 DAY) AND date <= LAST_DAY(CURRENT_DATE) GROUP BY id ORDER BY COUNT(id) DESC
And I get the result with the most at the top
id | col
==========
id3 | 123
id5 | 123
id2 | 123
id4 | 123
id8 | 123
id5 | 123
id1 | 123
id9 | 123
id7 | 123
This works fine for a tabular view and I can use ol to create a numbering system from 1 - 10. My issue is, I want to be able to tell the position of any given id. Eg. if I want to get the position of id9 in this count result i.e. 8, how do I do that?
If you are using MySQL v8.0 or higher you can use the RANK function:
SELECT COUNT(*), RANK() OVER (ORDER BY COUNT(id) DESC) AS r FROM table GROUP BY id ORDER BY COUNT(id) DESC;
For previous version of mysql, you need to create the variable your self:
SELECT COUNT(*), #rank := #rank + 1 AS r FROM table, (SELECT #rank := 0) temp ORDER BY COUNT(id) DESC;
Note SELECT #rank := 0 initiate the variable.
Updated:
To select a specific id and it's rank, you can use:
SELECT * FROM (
SELECT id, COUNT(*), RANK() OVER (ORDER BY COUNT(id) DESC) AS r FROM table GROUP BY id ORDER BY COUNT(id) DESC
) ranked WHERE id = ?;

MySQL Counting occurrences of an ID

I have the following query which returns event details and a rank based on the number of votes.
SELECT e.guid, e.name, e.desc, e.location, e.votes,
#curRank := #curRank + 1 AS rank
FROM event e, (SELECT #curRank := 0) r
ORDER BY votes DESC
This gives me the required output. However because I want to store more information about who has voted (to determine if a user can vote - as 1 vote per person is allowed). I created a new table called event_votes which looks like:
event_vote_id | event_guid | user_guid
1 abc1 def6
2 ghi4 def6
3 abc1 lmn2
How can I get the first query to work again replacing e.votes (a field which increments) with the number of occurrences of event_guid in event_votes
Expected Result
guid: abc1
name: testevent
desc: example
location: London
votes: 2
rank: 1
I did not test it, but hopefully, it works.
SELECT e.guid, e.name, e.desc, e.location,
(SELECT COUNT(ev.id) FROM event_votes ev WHERE ev.event_guid = e.guid) AS votes,
#curRank := #curRank + 1 AS rank
FROM event e, (SELECT #curRank := 0) r
ORDER BY votes DESC

A sort query in SQL by special criteria

I have a table products, and a table product-languages.
products:
prd_id|prd_name
product-languages:
prd_id|language_id|prd_name
1 |1 |Product_German_name
1 |2 |Product_English_name
1 |4 |Product_French_name
I want to join product-languages on prd_id and then sort it by my priority list of language_id (for example: 2,1,4) - first I want the result of lang_id=2, if this is not available I want to have lang_id=1 as the first result.
Is this possible in SQL? I think my personal order list is the problem, because I would have to check if that lang_id is even available...
Subquery maybe?
Thanks
You can do this in standard SQL via case with something like:
select
*
from
products p,
product_languages l
where
p.prd_id = l.prd_id
order by
(case language_id
when 2 then 1
when 1 then 2
when 4 then 3
end) asc
This is a bit of a pain in MySQL, but you can do it. One method uses variables:
select t.*
from (select t.*,
(#rn := if(#prd_id = prd_id, #rn + 1,
if(#prd_id := prd_id, 1, 1)
)
) as rn
from producct_languages t join
(select #prd_id := -1, #rn := 0) params
order by prd_id, field(language_id, 2, 1, 4)
) t
where rn = 1;

MySQL result set ordered by fixed positions

I've got following, simple table
Item (id, name, date, fixed_position)
(1, 'first entry', '2016-03-09 09:00:00', NULL)
(2, 'second entry', '2016-03-09 04:00:00', 1)
(3, 'third entry', '2016-03-09 05:00:00', NULL)
(4, 'fourth entry', '2016-03-09 19:00:00', NULL)
(5, 'fifth entry', '2016-03-09 13:00:00', 4)
(6, 'sixth entry', '2016-03-09 21:00:00', 2)
The number of items is not fixed, in fact can vary from ~100 to ~1000.
What i want to achieve is to perform a query to return set of Items ordered by date field which takes into consideration fixed_position field, which stands for something like "pinned" results to specific positions. If fixed_position for given entry is not NULL the result should be pinned to n-th position and if fixed_position is NULL the ORDER BY should take precedence.
Desired output of query for brighter explanation:
(2, 'second entry', '2016-03-09 04:00:00', 1) // pinned to 1-st position
(6, 'sixth entry', '2016-03-09 21:00:00', 2) // pinned to 2-nd position
(3, 'third entry', '2016-03-09 05:00:00', NULL) // ORDER BY `date`
(5, 'fifth entry', '2016-03-09 13:00:00', 4) // pinned to 4-th position
(1, 'first entry', '2016-03-09 09:00:00', NULL) // ORDER BY `date`
(4, 'fourth entry', '2016-03-09 19:00:00', NULL) // ORDER BY `date`
I've tried solution posted in Ordering MySql results when having fixed position for some items but even with copy-paste method this doesn't seem to work at all.
What I've tried this far is this query:
SELECT
#i := #i +1 AS iterator,
t.*,
COALESCE(t.fixed_position, #i) AS positionCalculated
FROM
Item AS t,
(
SELECT
#i := 0
) AS foo
GROUP BY
`id`
ORDER BY
positionCalculated,
`date` DESC
Which returns:
iterator | id | name | date | fixed_position | positionCalculated
1 1 first entry 2016-03-09 09:00:00 NULL 1
2 2 second entry 2016-03-09 04:00:00 1 1
6 6 sixth entry 2016-03-09 21:00:00 2 2
3 3 third entry 2016-03-09 05:00:00 NULL 3
4 4 fourth entry 2016-03-09 19:00:00 NULL 4
5 5 fifth entry 2016-03-09 13:00:00 4 4
Does MySQL can perform such task or should I take backend approach and perform PHP's array_merge() on two result sets?
A brute force method to solve this would be to first create a tally table having an amount of rows bigger than the original table:
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
Then you can left join this table to a derived table containing all fixed positions of your original table:
SELECT Tally.rn
FROM (
... tally table query here
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
WHERE t.t.fixed_position IS NULL
The above returns the to-be-filled missing order positions.
Demo here
You can now use the above query as yet another derived table joined to the original table to achieve the desired ordering:
SELECT id, name, `date`, fixed_position, Gaps.rn,
derived.seq, Gaps.seq
FROM (
SELECT id, name, `date`, fixed_position,
#seq1 := IF(fixed_position IS NULL, #seq1 + 1, #seq1) AS seq
FROM Item
CROSS JOIN (SELECT #seq1 := 0) AS v
ORDER BY `date`
) AS derived
LEFT JOIN (
SELECT Tally.rn,
#seq2 := #seq2 + 1 AS seq
FROM (
SELECT #rn := #rn + 1 AS rn
FROM (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t1
CROSS JOIN (
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS t2
CROSS JOIN (SELECT #rn := 0) AS v
) AS Tally
LEFT JOIN (
SELECT fixed_position
FROM Item
) AS t ON Tally.rn = t.fixed_position
CROSS JOIN (SELECT #seq2 := 0) AS v
WHERE t.t.fixed_position IS NULL
ORDER BY rn
) AS Gaps ON (derived.seq = Gaps.seq) AND (derived.fixed_position IS NULL)
ORDER BY COALESCE(derived.fixed_position, Gaps.rn)
Demo here
I've had the same problem (sort by date + inject rows with fixed position values). The above solution seems to work. But you have to know how much values your table has. The line:
SELECT 1 AS x UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
has to get extended if you have more rows because the temporary table is to short. After some google search results and tests in my DB I've figured out a solution that fits your needs and is smarter to understand I think.
SELECT *
FROM
(SELECT
create_date,
fixedposition,
#rownum:=#rownum + 1 AS colposition,
1 AS majorEntry
FROM myTable
JOIN (SELECT #rownum:=0) r
WHERE fixedposition IS NULL
ORDER BY crdate DESC) AS orderedFixed
UNION ALL
(SELECT create_date,
fixedposition,
fixedposition AS colposition,
0 AS majorEntry
FROM myTable
WHERE fixedposition IS NOT NULL
ORDER BY fixedposition ASC)
ORDER BY colposition ASC, majorEntry
So this is how it works: There are two SELECT statements.
The first SELECT searches for all columns without a fixed position and sorts them by date. Additionally it makes a JOIN to add a row counter column.
The second one searches for all rows with a fixed position and returns the sorting by "colposition".
Both select statements gets combined by an UNION ALL. The union gets sorted first by the ASCending colposition and in the second step by the majonEntry-value which indicates that the rows with fixedposition are to be placed before the other rows which have the same position.
If anyone stumbles upon this, I came up with a good solution with the help of dk1's answer. While his answer is good in principle, it has a flaw that every iteration of a pinned row has its position increased by one. For example: post pinned to the 1st position is on the 1st place (+0), post pinned to the 2nd position is on the 3rd place (+1), and post pinned to the 4th position is on the 6th place (+2).
I solved this by adding a second variable to decrease the final position of a pinned post.
Code explanation:
minor = derived table with unpinned posts
major= derived table with pinned posts
#rownum = iterator for posts in minor table
#decrease = iterator for posts in major table
SELECT
final.*
FROM
(
(
# see note #1 about this wrapper query
SELECT * FROM
(
SELECT
minor.*,
(#rownum := #rownum + 1) AS 'rowPosition',
0 AS 'rowDecrease',
0 AS 'majorEntry'
FROM table_with_your_posts minor
JOIN (#rownum := 0) x
WHERE minor.your_fixed_position IS NULL
ORDER BY minor.your_date DESC
) y
ORDER BY y.your_date DESC
)
UNION ALL
(
SELECT
major.*,
major.your_fixed_position AS 'rowPosition',
(#decrease := #decrease + 1) AS 'rowDecrease',
1 AS 'majorEntry'
FROM table_with_your_posts major
JOIN (#decrease := -1) x
WHERE major.your_fixed_position IS NOT NULL
ORDER BY major.your_fixed_position ASC
)
) final
# see note #2 about this ORDER BY clause
ORDER BY final.rowPosition - final.rowDecrease ASC, final.majorEntry DESC
note #1: Now I'm not an expert, but if you don't wrap the query of the minor table in another query and don't order it by the same thing as in the inner query, the sorting in the minor table just doesn't work. Maybe someone can shed more light on that.
note #2: ORDER BY final.rowPosition - final.rowDecrease - final.majorEntry / 2 ASC also works and looks cooler
Note that if you want to further filter your results (good example here would be to only show published posts), you will most likely need to add the filter condition to the WHERE clause of both the derived tables. Otherwise the final positions will be incorrect.
In the end I want to add that this isn't foolproof. For example if there are two pinned posts with the same position, it will shift the results by one. This could be solved by making the column with the fixed position UNIQUE. Other thing that comes to my mind is that if the table has less results than the position value of a pinned post, the post will obviously not be on its desired position (e.g. if the table contains 5 posts and you pin a post to the 8th position, it will probably end up being 5th). And remember that I didn't meaningfully test this, make sure your query is efficient for your needs.

concating renaming duplicate rows in mysql

I have a table like following
ID student_name dept email
1 Mary Wise Eng mary-wise#xxx.cc
2 John Walter Sc john-walter#xxx.cc
3 Sophia Jacob Politics sophia-jacob#xxx.cc
4 Ava William Eng ava-william#xxx.cc
5 Mary Wise Politics mary-wise#xxx.cc
6 John Walter Eng john-walter#xxx.cc
7 John Walter Politics john-walter#xxx.cc
8 Sophia Eng sophia#xxx.cc
9 Emma Eng emma#xxx.cc
10 Sherlock Eng sherlock#xxx.cc
The email ids col is generated by firstname-lastname#xxx.cc
The problem is when the name is same the email id is also same.
I want the email id to be appended with 1, 2, 3 when same name exists.
For example in table above
the mary-wise on 5th row should be mary-wise1#xxx.cc,
6th row should be, john-walter1#xxx.cc,
7th row should be, john-walter2#xxx.cc
How can I update my email column with mysql query as fast as possible.
I tried with php with mysql it takes too long when the table contains million rows.
Thanks
I believe it's better for you to make email column unique and to use ON DUPLICATE KEY UPDATE syntax (more here).
You still need to keep track of a number you want to append to the new value. For this purpose you can create a separate table with auto increment field and just get the new value from there.
The following SQL will enumerate the duplicates:
select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName;
You can put this in an update using join:
update t join
(select t.*,
#rn := if(#StudentName = StudentName, 1, #rn + 1) as seqnum,
#StudentName := StudentName
from table t cross join
(select #rn := 0, #StudentName := '') const
order by StudentName
) toupdate
on t.name = toupdate.name and toupdate.seqnum > 1
set email = concat(replace(t.StudentName, ' ', '-'), toupdate.seqnum - 1, '#xxx.cc);
It would be easy to achieve if you had CTE (maybe switch to postgres 9 if you can):
SELECT
id
, student_name
, concat(
replace(lower(student_name), ' ', '-')
, case
when cnt > 1 then numb
end
,'#xxx.cc'
) as newmail
FROM (
SELECT
count(*) over (partition BY student_name) as cnt
, count(*) over (partition BY student_name order by id) as numb
, id
, student_name
FROM tab1
order by id
) subq
sqlFiddle demo

Categories