A sort query in SQL by special criteria - php

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;

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 = ?;

Select unique pairs of users from a table at random

I want to create random user pairs between our database users.
I have the following user table:
Table: tbl_users
user_id | name
--------+--------------
1 | Jay
2 | Ram
3 | John
4 | Kevin
5 | Jenny
6 | Tony
I want to generate a random result like this:
from_id | to_id
--------+---------
1 | 6
5 | 3
2 | 4
Can this be done in MySQL only?
This is indeed a duplicate of a previous question, so the answer is there.
However, even if it is indeed possible in MySQL doing this there is not really recommended. PHP is a much better tool for handling this, as what you're doing is actually manipulating data as per some business rule. It'll be a lot easier to maintain by doing it in PHP, and I suspect that it'll be less resource-intensive as well.
A possible way to do this, which I'd prefer. Is to do a random sort in SQL, and then pair up two and two rows against each other. Something like this:
$grouping = {};
// Fetching both rows to ensure that we actually have an even number paired up.
while ($row = $res->fetch_array () && $row2 = $res->fetch_array ()) {
$grouping[] = {$row['name'], $row2['name']};
}
If you want to allow for an unmatched user to be listed, simply move the second fetch to the inside of the loop. Then deal with the potentially missing result there.
You can use the following code to generate your list:
select max(from_id) as from_id,
max(to_id) as to_id
from (
select
case when rownum mod 2 = 1 then user_id else null end as from_id,
case when rownum mod 2 = 0 then user_id else null end as to_id,
(rownum - 1) div 2 as pairnum
from (
select user_id, #rownum := #rownum + 1 as rownum
from
(select #rownum := 0) as init,
(select user_id from tbl_user order by rand()) as randlist
) as randlistrownum
) as randlistpairs
group by pairnum;
Step by step, this will:
order the userlist in random order
assign a rownumber to it (otherwise the order will have no meaning)
assign two consecutive rows the same pairnum (rownum = 1 and rownum = 2 get the value pairnum = 0, the next two rows will get pairnum = 1 and so on)
the first row of these paired rows will get the values from_id = user_id and to_id = null, the other row will be to_id = user_id and from_id = null
group by these pairs together to make them into one row
if you have an odd number of users, one user will end up with to_id = null, because it has no partner
A little more compact if you prefer shorter code:
select max(case when rownum mod 2 = 1 then user_id else null end) as from_id,
max(case when rownum mod 2 = 0 then user_id else null end) as to_id
from (
select user_id, #rownum := #rownum + 1 as rownum, (#rownum - 1) div 2 as pairnum
from
(select #rownum := 0) as init,
(select user_id from tbl_user order by rand()) as randlist
) as randlistpairs
group by pairnum;

Codeigniter 3.0 query bug

Duplicate this table: User_Posts
ID | Upvotes | Downvotes | CAT |
___________________________________
42134 | 5 | 3 | Blogs|
------------------------------------
12342 | 7 | 1 | Blogs|
-------------------------------------
19344 | 6 | 2 | Blogs|
------------------------------------
I need to get the rank of an item within it's category. Therefore ID: 19344 will have Rank position 2, with 4 upvotes, behind 12342 with 6 upvotes. Rank is determined by (upvotes-downvotes) count within it's category.
So I wrote this MySQL query.
SELECT rank FROM (SELECT *, #rownum:=#rownum + 1 AS rank
FROM User_Posts where CAT= 'Blogs' order by
(Upvotes-Downvotes) DESC) d,
(SELECT #rownum:=0) t2 WHERE POST_ID = '19344'
Returns to me (Rank = 2) when run directly in mysql. This is the correct result
However when I try to build it out through code-igniter's query builder I get the
$table = 'User_Posts';
$CAT= 'Blogs';
$POST_ID = '19344';
$sql = "SELECT rank FROM (SELECT *, #rownum:=#rownum + 1 AS
rank FROM $table where CAT= ?
order by (Upvotes-Downvotes) DESC) d,
(SELECT #rownum:=0) t2 WHERE POST_ID= ?";
$query= $this->db->query($sql, array($CAT,$POST_ID))->row_array();
returns to me an empty result: array(rank=>);
so then my question is... but why?
I will also accept an answer will an alternative way to run this query from code-igniters query builder, but ideally I would like to know why this thing is broken.
I've had a similar issue in the past, turns out I had to initialize the variable with a separate query first, I am not sure if this is still the case, but give it a try anyway.
//initialize the variable, before running the ranking query.
$this->db->query('SELECT 0 INTO #rownum');
$query= $this->db->query($sql, array($CAT,$POST_ID))->row_array();
Exactly I don't know why your code is not working. I wrote another solution it will work. Try below code.
$select="FIND_IN_SET( (upvote-downvote), (SELECT GROUP_CONCAT( (upvote-downvote) ORDER BY (upvote-downvote) DESC ) as total FROM (User_Posts))) as rank";
$this->db->select($select,FALSE);
$this->db->from('(User_Posts)',FALSE);
$this->db->where('ID',19344);
$this->db->where('CAT','Blogs');
$query = $this->db->get();
Write a Stored Function to do the query. Then have Codeigniter merely do
query("SELECT PostRank(?,?)", $CAT, $POST_ID);
Restriction: Since you cannot do PREPARE inside a Stored Function, this function will necessarily be specific to one table, User_Posts.
I'm not entirely sure if this is the problem, but I'd be initialising #rownum in the subquery:
SELECT rank
FROM (
SELECT *,
#rownum:=#rownum + 1 AS rank
FROM $table
JOIN (SELECT #rownum := 0) init
WHERE CAT= ?
ORDER BY (Upvotes-Downvotes) DESC
) d
WHERE post_id = ?
Otherwise I'd be worried that #rownum is undefined (NULL) and stays that way while rank is calculated (NULL + 1 = NULL), only being assigned the value of 0 afterwards. Thus rank is returned as NULL and you get ['rank'=>].
Running this again in a constant connection (directly in MySQL) would then give you the correct result as #rownum would start from the value 0 from the previous query and rank would be calculated correctly.
I'm guessing codeigniter starts a new connection/transaction each time the query is run and #rownum starts at NULL each time, giving ['rank'=>].

Calculate ranking in mysql using update query

We have calculated ranking using following query.
set #rank = 0;
Update rank_table
set position= (select #rank := #rank + 1)
order by points DESC, points_new DESC, points_old DESC;
And it calculates ranking in sequence.
eg.
I want that id's having same criteria should assign same ranking.
so let say,
here id 1 and 2 have same criteria then should have position=1 and then id 3 should have position=3.
Not like current flow.
Currently -> Id 1:position 1 , Id 2:position 2 , Id 3:position 3
but as Id 1 and 2 have same data it should be..
Id 1:position 1 , Id 2:position 1 , Id 3:position 3
Thanks for your help.
What you want is technically rank() rather than row_number(). This is something of a pain, but possible:
set #rn := 0;
set #rank := 0;
set #p := -1;
Update rank_table
set position = if(#rn := #rn + 1,
if(#p = points, #rank,
if(#p := points, #rank := #rn, #rank := #rn)
),
NULL -- never should happen
)
order by points DESC, points_new DESC, points_old DESC;
This is tricky because you need to calculate both the rank and the row number -- the rank stays the same and then it must "jump" to the row number.

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