This question already has an answer here:
Mysqli doesn't allow multiple queries?
(1 answer)
Closed 4 years ago.
I've looked at about a dozen posts about queries not working in PHP, but they all haven't been able to solve my problem - hopefully this is an easy one!
What I am attempting to do here is "rank" the rows based on their total yearly sales, and that's worked pretty well so far. When I run my query in MySQL, it works properly - I get back hundreds of results.
SET #row_number := 0;
SELECT #row_number := #row_number + 1 AS row_number,
TotalRevenue,
CompanyID
FROM (
SELECT CompanyID,
SUM(Sales_Amt) AS TotalRevenue
FROM Sales,
(SELECT #row_number := 0) r
GROUP BY CompanyID
) t
WHERE TotalRevenue > 0
ORDER BY TotalRevenue DESC
Produces:
row_number | TotalRevenue | CompanyID
-----------+--------------+----------
1 | 81130.00 | 333
2 | 72234.00 | 876
3 | 62653.00 | 123
4 | 54408.40 | 999
5 | 44548.00 | 111
However, when I run it via PHP, I get back the error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SELECT #row_number := #row_number + 1 AS row_number,
TotalRevenue' at line 2
Based on other posts here, I've tried:
adding `` around my column names
adding '' around my column names
adding := instead of = when I created #row_number
confirming that all spaces are added in after my query in PHP so that key words are not squished together
setting my mysqli->charset to utf-8
Here's my PHP code, in case you need that as well:
$query = "SET #row_number := 0;
SELECT #row_number := #row_number + 1 AS `row_number`,
TotalRevenue,
CompanyID
FROM (
SELECT CompanyID,
SUM(`Sales_Amt`) AS TotalRevenue
FROM Sales
GROUP BY CompanyID
) t
WHERE TotalRevenue > 0
ORDER BY TotalRevenue DESC";
if (!$result = $mysqli->query($query))
{
print_r($mysqli->error);
}
Hoping this will be something really simple that I am just NOT seeing.
Thanks!
To add another option aside from S. Dev's answer, you can use mysqli multi query;
$query = "SET #row_number := 0;
SELECT #row_number := #row_number + 1 AS `row_number`,
TotalRevenue,
CompanyID
FROM (
SELECT CompanyID,
SUM(`Sales_Amt`) AS TotalRevenue
FROM Sales
GROUP BY CompanyID
) t
WHERE TotalRevenue > 0
ORDER BY TotalRevenue DESC";
if (!$result = $mysqli->multi_query($query))
{
print_r($mysqli->error);
}
This will allow you to preform more than 1 query at one time, keeping it all within the same remit of the query
It also uses slightly less overhead as you are only doing (technically) 1 query as there is only 1 connection rather than 2 separate calls
It could be worth looking into stored procedures to make it so you don't have to set variables, rather, pass them into the query itself
Related
I need to execute these two queries from php, is there a way to merge them together in a single query or I have to use a stored procedure?
SET #rn=0;
UPDATE `nl_emails` SET `row_num`=(#rn:=#rn+1);
Thanks in advance
It doesn't look like it is possible. We could create #rn in the query but it will be local and the value will be lost from one row to another.
Here is another way of doing the what I believe you want to do.
create table nl_emails (id int not null primary key ,row_num int);
insert into nl_emails values(10,10),(20,20),(30,30);
with cte as(
select id, row_num,
row_number() over (order by id)rn
from nl_emails)
update nl_emails join cte on nl_emails.id = cte.id
set nl_emails.row_num = rn;
select * from nl_emails;
id | row_num
-: | ------:
10 | 1
20 | 2
30 | 3
db<>fiddle here
So as question marked with php tag, you can use PHP PDO solution:
<?php
$sql = "SET #rn = 0;
UPDATE nl_emails SET row_num = (#rn:=coalesce(#rn, 0) + 1);";
$pdo->exec($sql);
Test PHP PDO online
You can make in one direct query, but you have to check the performance.
Use:
UPDATE `nl_emails` n1
INNER JOIN (
SELECT (#row_number:=#row_number + 1) AS num,
id
FROM nl_emails, (SELECT #row_number:=0) AS t
) as t1 on n1.id=t1.id
SET n1.`row_num`=t1.num;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=bf7d4a9243eed3c3e3aeb934846294b7
The key part is the cross join used
SELECT (#row_number:=#row_number + 1) AS num,
id
FROM nl_emails, (SELECT #row_number:=0) AS t
;
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;
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;
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 need help to write MySQL query.
I have table full of logs where one of the column is unix timestamp.
I want to group (GROUP BY) those records so that events that were made in close range time (i.e. 5 sec) between each of them are in one group.
For example:
Table:
timestamp
----------
1429016966
1429016964
1429016963
1429016960
1429016958
1429016957
1429016950
1429016949
1429016943
1429016941
1429016940
1429016938
Become to groups like that:
GROUP_CONCAT(timestamp) | COUNT(*)
-----------------------------------------------------------------------------
1429016966,1429016964,1429016963,1429016960,1429016958,1429016957 | 6
1429016950,1429016949 | 2
1429016943,1429016941,1429016940,1429016938 | 4
Of course I can work with the data array afterwards in php, but I think that mysql would do it faster.
I started by using a variable to get the position of each row, where 1 is the highest time column and ending with the lowest, like this:
SET #a := 0;
SELECT timeCol, #a := #a + 1 AS position
FROM myTable
ORDER BY timeCol DESC;
For simplicity, we will call this positionsTable so that the rest of the query will be more readable. Once I created that table, I used a 'time_group' variable that checked if a previous row was within the last 5 seconds. If it was, we keep the same time_group. It sounds ugly, and looks kind of ugly, but it's like this:
SELECT m.timeCol, m.position,
CASE WHEN (SELECT p.timeCol FROM positionsTable p WHERE p.position = m.position - 1) <= m.timeCol + 5
THEN #time_group
ELSE #time_group := #time_group + 1 END AS timeGroup
FROM positionsTable m;
And then ultimately, using that as a subquery, you can group them:
SELECT GROUP_CONCAT(timeCol), COUNT(*)
FROM(
SELECT m.timeCol, m.position,
CASE WHEN (SELECT p.timeCol FROM positionsTable p WHERE p.position = m.position - 1) <= m.timeCol + 5
THEN #time_group
ELSE #time_group := #time_group + 1 END AS timeGroup
FROM positionsTable m) tmp
GROUP BY timeGroup;
Here is an SQL Fiddle example.
http://sqlfiddle.com/#!9/37d88/20
SELECT GROUP_CONCAT(t1.t) as `time`,
COUNT(*)
FROM (SELECT *
FROM table1
ORDER BY t) as t1
GROUP BY CASE WHEN (#start+5)>=t THEN #start
ELSE #start:=t END