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;
Related
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 = ?;
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;
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'=>].
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
This one's been tricky to quantify, so I may not have this question worded properly first time around.
I have a table following a format similar to this:
| id | other_id | timestamp |
| 1 | 1 | 2012-01-01 |
| 2 | 1 | 2012-01-02 |
| 3 | 2 | 2012-01-02 |
What I am attempting to do is, given the record with 'id' 2, and similar records, for which the 'id' column value is known and is unique and the 'other_id' is known corresponding with it, how do I find, for each, the 'id' of the record having the same 'other_id' but the first lower 'id' than the one I already know.
E.g.
$arrKnownIds = array (
0 => array('id'=>2,'other_id'=>1),
1 => array('id'=>3,'other_id'=>2)
);
With this info, I'd like to run a query such that this results:
while($row = mysql_fetch_assoc($result)) {
$arrPreviousIds[$row['other_id']] = $row['id'];
// having in this case values of:
// $row['other_id'] = 2;
// $row['id'] = 1;
}
I can't quite work out if I need to tackle this using UNION, multiple php query statements or if there's another way.
Any thoughts on how to tackle this one are greatly appreciated.
Thanks :)
Edit - The original query takes the following form:
SELECT DISTINCT(`other_id`), MAX(`id`), MAX(`timestamp`)
FROM `event`
GROUP BY `other_id`
ORDER BY `id` DESC, `other_id` ASC
LIMIT 0, 10
// This is intended to get the last 10 unique events and find when they occurred.
// From this, I then try to find when they previously occurred.
How about this?
SELECT t1.id, (SELECT id
FROM tbl t2
WHERE t2.other_id = t1.other_id
AND t2.id < t1.id
ORDER BY t2.id DESC
LIMIT 1)
FROM tbl t1
WHERE t1.id IN (1,2,3)
There are more efficient ways of doing this if you will be dealing with large result sets. Can you explain exactly how you will be using this query?
UPDATE - based on addition of existing query to question here is an updated query to combine the two -
SELECT tmp.*, (SELECT `timestamp`
FROM `event`
WHERE `event`.`other_id` = `tmp`.`other_id`
AND `event`.`id` < `tmp`.`id`
ORDER BY `event`.`id` DESC
LIMIT 1) AS `prev_timestamp`
FROM (
SELECT `other_id`, MAX(`id`) AS `id`, MAX(`timestamp`) AS `timestamp`
FROM `event`
GROUP BY `other_id`
ORDER BY `id` DESC, `other_id` ASC
LIMIT 0, 10
) tmp
I have not tried this but it should give the desired result.