about mysql excute order by 1 and rand(1)? - mysql

In mysql I built a table, which id is int type, name and password is varchar type.
excute
select * from test.new_table order by rand(1);
then the result is:
This is because after set seed for rand the sequence is fixed, I already know.But if excute
select * from test.new_table order by 1 and rand(1);
then the result is:
For such a result I do not understand. In addition, if excute order by 'xxx' the results are arranged.
Not quite understand, hope you to give pointers.

You can view the results of the expression as part of your query:
mysql> select *, 1, rand(1), 1 and rand(1) from new_table order by 1 and rand(1);
+----+------+----------+---+---------------------+---------------+
| id | name | password | 1 | rand(1) | 1 and rand(1) |
+----+------+----------+---+---------------------+---------------+
| 1 | ghi | 111 | 1 | 0.40540353712197724 | 1 |
| 3 | abc | 234 | 1 | 0.1418603212962489 | 1 |
| 5 | 5 | 5 | 1 | 0.04671454713373868 | 1 |
| 7 | 7 | 7 | 1 | 0.6108337804776 | 1 |
| 2 | jkl | 123 | 1 | 0.8716141803857071 | 1 |
| 4 | def | 555 | 1 | 0.09445909605776807 | 1 |
| 6 | 6 | 6 | 1 | 0.9501954782290342 | 1 |
+----+------+----------+---+---------------------+---------------+
See how the boolean expression always results in 1?
As #Barmar described, any expression 1 and n results in either 0 or 1, depending on the value of n being zero or nonzero.
So your expression ORDER BY 1 AND RAND(1) is just like ORDER BY true (a constant expression) which means the ordering is a tie between every row, and MySQL orders them in an arbitrary way.
But arbitrary is not the same as random.

Related

how to check the record occurrence in mysql

kindly suggest me a sql query to sort this.
there is a non normalized table named test.there ar2 two fields on is primary key and it is auto incremented. other field 'name' and it is repetitive as follow.
so i just need to know what insert/update mysql query should i used to get below output in the 'occurrence' field.
eg:- in the 5th row name 'occurance' value is 3 because 'name'= "chanaka" has included 3 times totally in the table with including record 5.
Read up on mysql row number simulation
Here's an example
MariaDB [sandbox]> select id,company_id
-> , if(company_id <> #p ,#rn:=1, #rn:=#rn+1) occurance
-> , #p:=company_id
-> from medication, (select #rn:=0,#p:=0) rn
-> order by company_id, id;
+------+------------+-----------+----------------+
| id | company_id | occurance | #p:=company_id |
+------+------------+-----------+----------------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 1 | 3 | 1 |
| 4 | 2 | 1 | 2 |
| 5 | 2 | 2 | 2 |
| 6 | 2 | 3 | 2 |
| 8 | 2 | 4 | 2 |
| 7 | 3 | 1 | 3 |
+------+------------+-----------+----------------+
8 rows in set (0.00 sec)
You should be able to count "name" and that should get you the amount of
occurrences.
SELECT id, name, count(name)
FROM test
GROUP by name

Distinct order-number sequence for every customer

I have table of orders. Each customer (identified by the email field) has his own orders. I need to give a different sequence of order numbers for each customer. Here is example:
----------------------------
| email | number |
----------------------------
| test#com.com | 1 |
----------------------------
| example#com.com | 1 |
----------------------------
| test#com.com | 2 |
----------------------------
| test#com.com | 3 |
----------------------------
| client#aaa.com | 1 |
----------------------------
| example#com.com | 2 |
----------------------------
Is possible to do that in a simple way with mysql?
If you want update data in this table after an insert, first of all you need a primary key, a simple auto-increment column does the job.
After that you can try to elaborate various script to fill the number column, but as you can see from other answer, they are not so "simple way".
I suggest to assign the order number in the insert statement, obtaining the order number with this "simpler" query.
select coalesce(max(`number`), 0)+1
from orders
where email='test1#test.com'
If you want do everything in a single insert (better for performance and to avoid concurrency problems)
insert into orders (email, `number`, other_field)
select email, coalesce(max(`number`), 0) + 1 as number, 'note...' as other_field
from orders where email = 'test1#test.com';
To be more confident about not assign at the same customer two orders with the same number, I strongly suggest to add an unique constraint to the columns (email,number)
create a column order_number
SELECT #i:=1000;
UPDATE yourTable SET order_number = #i:=#i+1;
This will keep incrementing the column value in order_number column and will start right after 1000, you can change the value or even you can even use the primary key as the order number since it is unique all the time
I think one more need column for this type of out put.
Example
+------+------+
| i | j |
+------+------+
| 1 | 11 |
| 1 | 12 |
| 1 | 13 |
| 2 | 21 |
| 2 | 22 |
| 2 | 23 |
| 3 | 31 |
| 3 | 32 |
| 3 | 33 |
| 4 | 14 |
+------+------+
You can get this result:
+------+------+------------+
| i | j | row_number |
+------+------+------------+
| 1 | 11 | 1 |
| 1 | 12 | 2 |
| 1 | 13 | 3 |
| 2 | 21 | 1 |
| 2 | 22 | 2 |
| 2 | 23 | 3 |
| 3 | 31 | 1 |
| 3 | 32 | 2 |
| 3 | 33 | 3 |
| 4 | 14 | 1 |
+------+------+------------+
By running this query, which doesn't need any variable defined:
SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j
Hope that helps!
You can add number using SELECT statement without adding any columns in table orders.
try this:
SELECT email,
(CASE email
WHEN #email
THEN #rownumber := #rownumber + 1
ELSE #rownumber := 1 AND #email:= email END) as number
FROM orders
JOIN (SELECT #rownumber:=0, #email:='') AS t

How to form a MySQL query with 2 different ORDER BY statements

I have a sort of Facebook-esque Like/Dislike system on my website and am using the following query to grab the likes + dislikes of a specific post:
SELECT DISTINCT * FROM posts WHERE cid='$cid' AND pid=".implode(" OR pid=",$pids)." ORDER BY time DESC
$pid is an array of post ids to be searching for.
zbid is the id of the user who is currently accessing the page. Now, if that user has rated (liked/disliked) the post his result should be returned first, and then after that order the results by time DESC.
How would I go about modifying the query to do this?
If the posts table has the following data:
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 3 | 1 | 3 | 2 | 1 | 2 |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
The current select statement (above) will return the following information (with $pid = array(1);):
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
However, if the person with zbid=4 is accessing the page, it should bump that result (if it exists) up to the top as below:
+----+-----+------+-----+--------+------+
| id | cid | zbid | pid | rating | time |
+----+-----+------+-----+--------+------+
| 4 | 2 | 4 | 1 | -1 | 3 |
+----+-----+------+-----+--------+------+
| 5 | 1 | 5 | 1 | 1 | 8 |
+----+-----+------+-----+--------+------+
| 6 | 1 | 6 | 1 | -1 | 7 |
+----+-----+------+-----+--------+------+
| 2 | 1 | 2 | 1 | -1 | 4 |
+----+-----+------+-----+--------+------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+----+-----+------+-----+--------+------+
The variable $zbid is set to the user's zbid who is accessing the page.
This is a rather rough solution I could come out with, using the information you provided:
Solution 1 - The portable way
-- This query will return User's posts and give it a higher priority in ordering, via post_order field
SELECT
posts.*
,0 as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).") AND
(zbid = $user_zbid)
UNION ALL
-- This query will return everything BUT User's posts and give it a lower priority in ordering
SELECT
posts.*
,1 as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).") AND
(zbid <> $user_zbid)
ORDER BY
post_order -- This clause will put User's posts before the others
,time DESC
Solution 2 - The more performing way (credits to cbranch for the suggestion)
SELECT
posts.*
,IF(zbid = $user_zbid, 0, 1) as post_order
FROM posts
WHERE
(cid='$cid' AND pid=".implode(" OR pid=",$pids).")
ORDER BY
post_order -- This clause will put User's posts before the others
,time DESC
Notes
- As you may have noticed, I removed the DISTINCT from the SELECT, as I couldn't see a reason for them. Since you just extract data from a single table, you shouldn't have duplicates. Obviously, you can still add them back, but remember not to use such clause unless it's really needed.
- The second query will be very expensive to run, as it uses the "not equal to" clauses. This means it won't be using indexes, and it won't be suitable for big amounts of data. In case you have to deal with a big table, this solution will have to be reviewed.
After reviewing Diego's suggestion I came up with the following answer that has worked:
SELECT zbid,pid,rating,0 as post_order,time
FROM posts
WHERE cid='$cid'
AND (pid=".implode(" OR pid=",$pids).")
AND zbid!='$zbid'
UNION
SELECT zbid,pid,rating,1 as post_order,time
FROM posts
WHERE cid='$cid'
AND (pid=".implode(" OR pid=",$pids).")
AND zbid='$zbid'
ORDER BY
post_order DESC,
time DESC

Top 'n' results for each keyword

I have a query to get the top 'n' users who commented on a specific keyword,
SELECT `user` , COUNT( * ) AS magnitude
FROM `results`
WHERE `keyword` = "economy"
GROUP BY `user`
ORDER BY magnitude DESC
LIMIT 5
I have approx 6000 keywords, and would like to run this query to get me the top 'n' users for each and every keyword we have data for. Assistance appreciated.
Since you haven't given the schema for results, I'll assume it's this or very similar (maybe extra columns):
create table results (
id int primary key,
user int,
foreign key (user) references <some_other_table>(id),
keyword varchar(<30>)
);
Step 1: aggregate by keyword/user as in your example query, but for all keywords:
create view user_keyword as (
select
keyword,
user,
count(*) as magnitude
from results
group by keyword, user
);
Step 2: rank each user within each keyword group (note the use of the subquery to rank the rows):
create view keyword_user_ranked as (
select
keyword,
user,
magnitude,
(select count(*)
from user_keyword
where l.keyword = keyword and magnitude >= l.magnitude
) as rank
from
user_keyword l
);
Step 3: select only the rows where the rank is less than some number:
select *
from keyword_user_ranked
where rank <= 3;
Example:
Base data used:
mysql> select * from results;
+----+------+---------+
| id | user | keyword |
+----+------+---------+
| 1 | 1 | mysql |
| 2 | 1 | mysql |
| 3 | 2 | mysql |
| 4 | 1 | query |
| 5 | 2 | query |
| 6 | 2 | query |
| 7 | 2 | query |
| 8 | 1 | table |
| 9 | 2 | table |
| 10 | 1 | table |
| 11 | 3 | table |
| 12 | 3 | mysql |
| 13 | 3 | query |
| 14 | 2 | mysql |
| 15 | 1 | mysql |
| 16 | 1 | mysql |
| 17 | 3 | query |
| 18 | 4 | mysql |
| 19 | 4 | mysql |
| 20 | 5 | mysql |
+----+------+---------+
Grouped by keyword and user:
mysql> select * from user_keyword order by keyword, magnitude desc;
+---------+------+-----------+
| keyword | user | magnitude |
+---------+------+-----------+
| mysql | 1 | 4 |
| mysql | 2 | 2 |
| mysql | 4 | 2 |
| mysql | 3 | 1 |
| mysql | 5 | 1 |
| query | 2 | 3 |
| query | 3 | 2 |
| query | 1 | 1 |
| table | 1 | 2 |
| table | 2 | 1 |
| table | 3 | 1 |
+---------+------+-----------+
Users ranked within keywords:
mysql> select * from keyword_user_ranked order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| mysql | 2 | 2 | 3 |
| mysql | 4 | 2 | 3 |
| mysql | 3 | 1 | 5 |
| mysql | 5 | 1 | 5 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| query | 1 | 1 | 3 |
| table | 1 | 2 | 1 |
| table | 3 | 1 | 3 |
| table | 2 | 1 | 3 |
+---------+------+-----------+------+
Only top 2 from each keyword:
mysql> select * from keyword_user_ranked where rank <= 2 order by keyword, rank asc;
+---------+------+-----------+------+
| keyword | user | magnitude | rank |
+---------+------+-----------+------+
| mysql | 1 | 4 | 1 |
| query | 2 | 3 | 1 |
| query | 3 | 2 | 2 |
| table | 1 | 2 | 1 |
+---------+------+-----------+------+
Note that when there are ties -- see users 2 and 4 for keyword "mysql" in the examples -- all parties in the tie get the "last" rank, i.e. if the 2nd and 3rd are tied, both are assigned rank 3.
Performance: adding an index to the keyword and user columns will help. I have a table being queried in a similar way with 4000 and 1300 distinct values for the two columns (in a 600000-row table). You can add the index like this:
alter table results add index keyword_user (keyword, user);
In my case, query time dropped from about 6 seconds to about 2 seconds.
You can use a pattern like this (from Within-group quotas (Top N per group)):
SELECT tmp.ID, tmp.entrydate
FROM (
SELECT
ID, entrydate,
IF( #prev <> ID, #rownum := 1, #rownum := #rownum+1 ) AS rank,
#prev := ID
FROM test t
JOIN (SELECT #rownum := NULL, #prev := 0) AS r
ORDER BY t.ID
) AS tmp
WHERE tmp.rank <= 2
ORDER BY ID, entrydate;
+------+------------+
| ID | entrydate |
+------+------------+
| 1 | 2007-05-01 |
| 1 | 2007-05-02 |
| 2 | 2007-06-03 |
| 2 | 2007-06-04 |
| 3 | 2007-07-01 |
| 3 | 2007-07-02 |
+------+------------+

Mysql how to create an if statement within a having to find out if my variable is in between two other values

I'm trying to create a stored procedure like followed
...Having (IF(input_val between 1 and 10, 1,0) AS rank
example
|input_val | rank |
--------------------|
| 1 | 1 |
| 11 | 0 |
| 3 | 1 |
| 22 | 0 |
| 4 | 1 |
| 5 | 1 |
A HAVING clause normally follows a GROUP BY clause and is used to test the results of an aggregate function. I don't think that's appropriate for what you want here. How about:
...
CASE WHEN input_val BETWEEN 1 AND 10 THEN 1 ELSE 0 END AS rank