Mysql Retrieve Specific Number of grouped rows from a join - php

So I created this query
SELECT a.email
, a.password
, a.server
, a.status
, a.step
, b.*
FROM mail a
JOIN automation_logs b
ON b.accountid = a.accountid
WHERE a.status <0
ORDER
BY b.created DESC;
What it's used for is essentially I'm trying to get debug information from our log table for email accounts that are errored out on our api. The automation_logs table essentially is text dump of error messages that were produced by our API.
This query gives me what I want but gives me too much. I only want the last five most recent (read: created field is a timestamp) error messages for any of the accounts as some of these accounts have existed for years and old errors have been fixed and aren't necessary to know what's broken and just pad the results.
Is this possible with a single query?

Following the advice listed here: Thank you Marshal Tigerus
I was able to get it to work with the following sql:
SET #currcount = NULL, #currvalue = NULL;
SELECT a.email,a.password,a.serverid,a.status,a.step,b.message,b.created FROM mail as a JOIN (
SELECT * FROM (
SELECT
*,
#currcount := IF(#currvalue = accountid, #currcount + 1, 1) AS num,
#currvalue := accountid AS cur_id
FROM automation_logs
WHERE message NOT LIKE ""
ORDER BY accountid, created DESC
) as limitedresults WHERE num <= 5
) as b ON a.accountid = b.accountid WHERE a.status < 0 ORDER BY b.created DESC

Related

Single query that allows alias with it's own limit

I would like to better optimize my code. I'd like to have a single query that allows an alias name to have it's own limit and also include a result with no limit.
Currently I'm using two queries like this:
// ALL TIME //
$mikep = mysqli_query($link, "SELECT tasks.EID, reports.how_did_gig_go FROM tasks INNER JOIN reports ON tasks.EID=reports.eid WHERE `priority` IS NOT NULL AND `partners_name` IS NOT NULL AND mike IS NOT NULL GROUP BY EID ORDER BY tasks.show_date DESC;");
$num_rows_mikep = mysqli_num_rows($mikep);
$rating_sum_mikep = 0;
while ($row = mysqli_fetch_assoc($mikep)) {
$rating_mikep = $row['how_did_gig_go'];
$rating_sum_mikep += $rating_mikep;
}
$average_mikep = $rating_sum_mikep/$num_rows_mikep;
// AND NOW WITH A LIMIT 10 //
$mikep_limit = mysqli_query($link, "SELECT tasks.EID, reports.how_did_gig_go FROM tasks INNER JOIN reports ON tasks.EID=reports.eid WHERE `priority` IS NOT NULL AND `partners_name` IS NOT NULL AND mike IS NOT NULL GROUP BY EID ORDER BY tasks.show_date DESC LIMIT 10;");
$num_rows_mikep_limit = mysqli_num_rows($mikep_limit);
$rating_sum_mikep_limit = 0;
while ($row = mysqli_fetch_assoc($mikep_limit)) {
$rating_mikep_limit = $row['how_did_gig_go'];
$rating_sum_mikep_limit += $rating_mikep_limit;
}
$average_mikep_limit = $rating_sum_mikep_limit/$num_rows_mikep_limit;
This allows me to show an all-time average and also an average over the last 10 reviews. Is it really necessary for me to set up two queries?
Also, I understand I could get the sum in the query, but not all the values are numbers, so I've actually converted them in PHP, but left out that code in order to try and simplify what is displayed in the code.
All-time average and average over the last 10 reviews
In the best case scenario, where your column how_did_gig_go was 100% numeric, a single query like this could work like so:
SELECT
AVG(how_did_gig_go) AS avg_how_did_gig_go
, SUM(CASE
WHEN rn <= 10 THEN how_did_gig_go
ELSE 0
END) / 10 AS latest10_avg
FROM (
SELECT
#num + 1 AS rn
, tasks.show_date
, reports.how_did_gig_go
FROM tasks
INNER JOIN reports ON tasks.EID = reports.eid
CROSS JOIN ( SELECT #num := 0 AS n ) AS v
WHERE priority IS NOT NULL
AND partners_name IS NOT NULL
AND mike IS NOT NULL
ORDER BY tasks.show_date DESC
) AS d
But; Unless all the "numbers" are in fact numeric you are doomed to sending every row back from the server for php to process unless you can clean-up the data in MySQL somehow.
You might avoid sending all that data twice if you establish a way for your php to use only the top 10 from the whole list. There are probably way of doing that in PHP.
If you wanted assistance in SQL to do that, then maybe having 2 columns would help, it would reduce the number of table scans.
SELECT
EID
, how_did_gig_go
, CASE
WHEN rn <= 10 THEN how_did_gig_go
ELSE 0
END AS latest10_how_did_gig_go
FROM (
SELECT
#num + 1 AS rn
, tasks.EID
, reports.how_did_gig_go
FROM tasks
INNER JOIN reports ON tasks.EID = reports.eid
CROSS JOIN ( SELECT #num := 0 AS n ) AS v
WHERE priority IS NOT NULL
AND partners_name IS NOT NULL
AND mike IS NOT NULL
ORDER BY tasks.show_date DESC
) AS d
In future (MySQL 8.x) ROW_NUMBER() OVER(order by tasks.show_date DESC) would be a better method than the "roll your own" row numbering (using #num+1) shown before.

mySQL: Sub SELECT statement within UPDATE command

I'm trying to update a column in visitors. I'm also using a sub select statement for the SET part of the update query.
UPDATE
visitors AS v
SET
v.IsFirstVisit = (SELECT COUNT(*) FROM visitors AS v2 WHERE ..... LIMIT 1)
However, mySQL returns this error message
#1093 - You can't specify target table 'v' for update in FROM clause
I have no clue why I can't access the 'v' object within the inner select statement. I also don't want to use multiple statements as this would cause a performance issue.
Question: How can I use the 'v' object within the inner select?
Update:
This is the entire query
UPDATE
visitors AS v
SET
IsFirstVisit = (SELECT Count(*) FROM visitors AS v2 WHERE v2.VisitorId < v.VisitorId AND v2.IP = v.IP AND v2.DateTime > v.DateTime [TODO:SUBTRACT30MINUTES] LIMIT 1)
WHERE
VisitorId = "991"
i guess you looking for this
UPDATE
visitors
SET
IsFirstVisit = (SELECT COUNT(*) FROM visitors WHERE ..... LIMIT 1)
edit:
try this
UPDATE
visitors
SET
IsFirstVisit = (SELECT Count(*) FROM visitors v2 inner join visitors v
ON v.VisitorId = v2.VisitorId WHERE v2.IP = v.IP AND v2.DateTime > v.DateTime AND v2.VisitorId < v.VisitorId [TODO:SUBTRACT30MINUTES] LIMIT 1)
WHERE
VisitorId = "991"
The inner join in UPDATE statement won't be a bad idea.
UPDATE
visitors inner join (SELECT COUNT(*) as test FROM visitors v) as v
SET
isfistvisit = v.test;
Another workaround which Im not a big fan of it.
update visitors
set isfistvisit = (
select count(*) from (
select count(*) from visitors
) as x
)
Demo

MySQL returning value only for ID 1

I got the following query:
SELECT ur.repair_id, ur.repair_complete, ur.repair_noted_by_client, ur.repair_problem, ur.added_at, ur.added_by, ur.repaired_at, ur.repaired_by,
aa.account_fullname AS added_by_name,
ar.account_fullname AS repaired_by_name
FROM units_repairs AS ur
LEFT JOIN (SELECT account_id, account_fullname FROM accounts LIMIT 1) AS aa ON aa.account_id = ur.added_by
LEFT JOIN (SELECT account_id, account_fullname FROM accounts LIMIT 1) AS ar ON ar.account_id = ur.repaired_by
WHERE ur.unit_id = 1
It return the fullname only if the account_id = 1. If let say repaired_by = 2 then it say NULL...
Thanks, I don't know what I am missing.
You are joining with inner query:
SELECT account_id, account_fullname FROM accounts LIMIT 1
where you have LIMIT 1, which gives you only one row (which probably has repaired_by = 1), and then you want to filter and get only rows where repaired_by is 2...and you don't have that one.
Probably there are no records that satisfy the condition repaired_by = 2 in the line,
LEFT JOIN (SELECT account_id, account_fullname FROM accounts LIMIT 1) AS ar ON ar.account_id = ur.repaired_by
Hence, ar.account_id = ur.repaired_by returns NULL.

unique field from mysql database to php

I have a sample database like this, in which, id is always unique, but the user_id is not unique.
id,user_id,message,msg_datetime
111,u1, msg from u1,time present here
112,u2, msg from u2,time present here
113,u3, msg from u3,time present here
114,u2, msg from u2,time present here
115,u7, msg from u7,time present here
116,u2, msg from u2,time present here
117,u1, msg from u1,time present here
118,u5, msg from u5,time present here
so i want to grab only those unique users who have messaged and order them in DESC by msg_datetime.
This is the query i have.
select id,DISTINCT user_id,msg_datetime ORDER BY msg_datetime DESC but i am getting an error as:
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 'DISTINCT. any help here? what is the correct syntax for what i am trying to achieve?
I want to show only one entry for each user, it does not matter which ID i am showing but only 1 per user.
If you don't care which record with the same user_id query should return, then
SELECT id,user_id,msg_datetime FROM table_1 GROUP BY user_id ORDER BY msg_datetime DESC
If you want to display, for instance, the last record for each user, you need
SELECT a.user_id, a.last_time, b.id
FROM
(SELECT user_id, MAX(msg_datetime) as last_time
FROM table1)a
INNER JOIN table1 b ON (b.user_id = a.user_id AND b.msg_datetime = a.last_time)
ORDER BY a.last_time;
SELECT syntax:
SELECT (fieldlists)
FROM (table)
[WHERE (conditions)]
ORDER BY (something)
You kindof forgot to say which table you want the data from.
You misunderstand how DISTINCT works. It works on rows not fields. What you want is a groupwise maximum, also known as greatest-n-per-group.
Here's one way to do it in MySQL:
SELECT id, user_id, message, msg_datetime
FROM (
SELECT
id, user_id, message, msg_datetime,
#rn := CASE WHEN #prev_user_id = user_id
THEN #rn + 1
ELSE 1
END AS rn,
#prev_user_id := user_id
FROM (SELECT #prev_user_id := NULL) vars, Table1 T1
ORDER BY user_id, msg_datetime DESC
) T2
WHERE rn = 1
ORDER BY msg_datetime

Mysql Limit column value repetition N times

I have two tables
Customer (idCustomer, ecc.. ecc..)
Comment (idCustomer, idComment, ecc.. ecc..)
obviously the two table are joined together, for example
SELECT * FROM Comment AS co
JOIN Customer AS cu ON cu.idCustomer = co.idCustomer
With this I select all comment from that table associated with is Customer, but now I wanna limit the number of Comment by 2 max Comment per Customer.
The first thing I see is to use GROUP BY cu.idCustomer but it limits only 1 Comment per Customer, but I wanna 2 Comment per Customer.
How can I achieve that?
One option in MySQL is server-side variables. For example:
set #num := 0, #customer := -1;
select *
from (
select idCustomer
, commentText
, #num := if(#customer = idCustomer, #num + 1, 1)
as row_number
, #customer := idCustomer
from Comments
order by
idCustomer, PostDate desc
) as co
join Customer cu
on co.idCustomer = cu.idCustomer
where co.row_number <= 2
This version doesn't require the SET operation:
select *
from (select idCustomer
, commentText
, #num := if(#customer = idCustomer, #num + 1, 1) as row_number
, #customer = idCustomer
from Comments
JOIN(SELECT #num := 0, #customer := 1) r
order by idCustomer, PostDate desc) as co
join Customer cu on co.idCustomer = cu.idCustomer
where co.row_number <= 2
SELECT * FROM Comments AS cm1
LEFT JOIN Comments AS cm2 ON cm1.idCustomer = cm2.idCustomer
LEFT JOIN Customer AS cu ON cm1.idCustomer = cu.idCustomer
WHERE cm1.idComment != cm2.idComment
GROUP BY cm1.idCustomer
However, if you are going to change the number of comments it's better to use Andomar's solution.
There is no need to use cursor, which is very slow. See my answer to Complicated SQL Query About Joining And Limitting. DENSE_RANK will do the trick without all cursor intricacies.
If you are using a scripting language such as PHP to process the results, you could limit the number of results shown per customer after running the query. Set up an array to hold all the results, set up another array to hold the number of results per customer and stop adding the query results to the result set after the count exceeds your limit like so:
$RESULTS = array();
$COUNTS = array();
$limit = 2;
$query = "SELECT customer_id, customer_name, customer_comment FROM customers ORDER BY RAND()";
$request = mysql_query($query);
while ($ROW = mysql_fetch_assoc($request))
{
$c = $ROW['customer_id'];
$n = $COUNTS[$c];
if ($n<$limit)
{
$RESULTS[] = $ROW;
$COUNTS[$c]++;
}
}
This guarantees only two comments per customer will be shown pulled randomly or however you want, the rest gets thrown out. Granted you are pulling ALL the results but this is (probably) faster than doing a complex join.

Categories