Slow queries with MySQL large database - php

I have a table ipaddresses that contains IP ranges. Columns are: ipStart, ipEnd
Example of
ipStart: 3579374832
ipEnd: 3579374839
Now I want to select 300ish IP addresses from another table: visit_count. And based on those 300 addresses I want to check if the ip address is in the range of any of IPs in the table ipaddresses
My current code:
$results = $database->query("SELECT user_id,
DATE_FORMAT(last_visit, '%Y-%m-%d') as datumet,
cookieId, ipaddress
FROM `visit_count`
WHERE last_visit BETWEEN
'$firstDayOfMonth' AND '$lastDayOfMonth'
AND user_id = '$userId'
GROUP BY cookieId
ORDER BY last_visit ASC");
$rows = $database->loadObjectList($results);
foreach ( $rows as $row ) {
$ipaddressLong = ip2long($row->ipaddress);
// This is where its very very slow
$selO = $database->query("SELECT ipStart, ipEnd FROM `ipaddresses`
WHERE '$ipaddressLong' BETWEEN `ipStart` AND `ipEnd`");
$rowO = $database->getrow($selO);
}
The result is a really slow query that consumes a lot of cpu.
ipaddresses has indexes for both ipStart and ipEnd and contains around 50k rows.
How can I make this go faster?

A quick and dirty change to a single query using a JOIN:-
SELECT a.ipStart, a.ipEnd
FROM `ipaddresses` a
INNER JOIN
(
SELECT user_id, DATE_FORMAT(last_visit, '%Y-%m-%d') as datumet, cookieId, inet_aton(ipaddress ) AS ipaddressLong
FROM `visit_count`
WHERE last_visit BETWEEN '$firstDayOfMonth' AND '$lastDayOfMonth'
AND user_id = '$userId'
GROUP BY cookieId
) b
ON b.ipaddressLong BETWEEN a.ipStart AND a.ipEnd
Note that this could probably be simplified. However it depends on your use of GROUP BY cookieId . At the moment this is going to get one row per cookie id, but which row is undefined (ie, could be any user_id, ip address and last visit date that goes with that cookie id).

You can simplify the first select. You don't need to select any fields beyond ipaddress. You also don't need to order or group the result. If you want unique ipaddresses, just use distinct
select distinct ipaddress
from visit_count
where last_visit between '$firstDayOfMonth' and '$lastDayOfMonth'
and user_id = '$userId'
300 selects don't seem to be much, even with the second table having 50k rows. On my machine, even without indexes, it takes only a fraction of a second.
If you want to simplify it anyway, you can join the two tables with
select ipstart, ipend
from ipaddresses i
join visit_count v on inet_aton(v.ipaddress) between i.ipstart and i.ipend
where v.last_visit between '$firstDayOfMonth' and '$lastDayOfMonth'
and v.userid = '$userId'

1) Use better query like other suggested to you
2) Have you defined indexes for the field you need ?
3) Dont know if you use mysql or mysqli...anyway use fetch_assoc that is faster then fetch_array
with this 3 trick you should be ok

Related

Sum row contents from a joined table

I have two tables here.
log (stands for logbook)
act (stands for actions)
log
contains columns: logid, userid, actid
act
contains columns: actid, amount
Amount stands for an amount of points (e.g. 50 of 700 or 5) the user (userid) receives when the corresponding actid is inserted in the logbook (log).
What I'm trying to achieve is that when you search for userid 1, that all rows from log where userid = 1 are selected. Then, from these selected log-rows, the actids should be looked up in the table act, and the amounts should be summed.
In short:
select log-rows where userid = 1
take all the actids from these rows
find the matching amount in the act table
sum all these amounts
I started with this query:
SELECT log.logid, log.userid, log.actid, act.actid, act.amount
FROM log
JOIN act
ON act.actid = log.actid
WHERE log.userid = '1'
This got me a table with (among other things)
userid | amount
---1----|--20---
---1----|--40---
---1----|---5---
---1----|--10---
Now I would like to sum all these amounts and echo the total, but unfortunately I can't find a working query for this.
EDIT:
I used the query that Naruto provided me to sum these amounts. This works like a charm now! The query:
SELECT log.userid, sum(act.amount) FROM log JOIN act ON act.actid = log.actid WHERE log.userid = '1'
The next thing that I'm trying is to get not only the summed amount from the user with userid = 1, but also from the other users in the user-table. After that, I'd like to use these summed amounts in the following query:
$result = $mysqli->query("SELECT * FROM `db`.`user` ORDER BY `summed_amount` DESC");
In which summed_amount is the summed amount generated by Naruto's query.
MySQL has an Aggregate function called sum() which can sum all the records.
So your query will be
SELECT log.userid, sum(act.amount) FROM log JOIN act ON act.actid = log.actid WHERE log.userid = '1'
http://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_sum
To get sum of all users by a single query use group by clause.Here it will calculate sum of amaount for each user and return the result.
SELECT log.userid, sum(act.amount) FROM log JOIN act ON act.actid = log.actid group by log.userid
Edit:- To get order by desc
SELECT log.userid, sum(act.amount) FROM log JOIN act ON act.actid = log.actid group by log.userid order by sum(act.amount) desc

Picking which data to view in group by

So yesterday, I am trying to sort data in groups made by Group by
I must select which data I want to show in those group
There is list of debts and each person may be in debt in the past but never have more than 1 unpaid debt
I need to know how many times how many times each user have been in debt before this last debt.
This is the column in the data base
Table "Users"
uid | name | date_of_birth
Table "Debt"
uid | debt_duration | paid_count | created_date
I end up with a hack like this in php
$res = mysql_query( "
SELECT * FROM Debt
JOIN Users
WHERE Users.uid = Debt.uid
ORDER BY created_date
GROUP BY Debt.uid");
while( $row = mysql_fetch_array( $res ) ){
$uid = $row['uid'];
$r = mysql_fetch_array( mysql_query("SELECT COUNT(*) FROM Debt WHERE uid = $uid") );
$previous_debts_count = $r[0];
}
This script is quite heavy but fortunately my client doesn't complain.
The script run at around 3 seconds top
But I need to know better ways to do this
sorry for the strange formatting, I am new here ...
I think the query you want is this:
SELECT Users.uid, COUNT(*) as cnt
FROM Debt JOIN
Users
ON Users.uid = Debt.uid
GROUP BY Debt.uid
ORDER BY created_date ;
Just loop through the results and don't use multiple queries for this. Check that Users.uid is the primary key on the users table. And add an index on debt(uid) to improve performance.
First of all, you should never use * in an SQL statement.
It makes the query highly vulnerable to SQL injection.
And I recommend you to use a PDO or a PHP framework.
Try this:
SELECT COUNT(Debt.uid) AS users
FROM Debt
LEFT JOIN Users
ON Users.uid = Debt.uid
GROUP BY Debt.uid

select 3 rows from mysql table, then get each row's specific column

Select 3 rows from table1
Get a specific column data out of each row.
Then use that each column data obtained , to make a query again to get data from table2.
Store the data obtained in step 4 into a variable for each row.
Then put them in json array (table 1 , 3 rows + table 2's data(each of them).
I am building a rank table, it displays top 3 users with their rank name.
For example:
User1 has 2000 points , user 2 has 4000points , user 3 has 10k points , so the top 3 user is :
user 3 > user 2 > user 1
So , i want the php to go to 'users' table and get the top 3 members using this:
$query = mysql_query("SELECT * FROM users ORDER BY pts DESC LIMIT 3");
$rows = array();
while($r = mysql_fetch_assoc($query)) {
$rows[] = $r;
}
Table structure for 'user':
1.username(varchar)
2.pts(int)
After the rows are put into an array , how can i get 'points' for each of the row in that array.
Then go to 'rank' table to get their ranknames.
Table structure for 'rank':
1.rank(varchar)
2.pts(int)
Inside rank table there is 'pts' to let php choose compare which rank the user is at based on the points from each row of the array.
Normally i would use this if its only for 1 user , but for multiple users , im not sure:
$result = mysql_query("SELECT * FROM rank WHERE pts <= '$upts' ORDER BY pts DESC LIMIT 1")
or die(mysql_error());
Then after getting the rank for the top 3 users , php will now add the ranks to each of the user(row) in that array(of course , add it to the rank owner, not just simply place it in).
Then JSON encode it out.
How can i do this?
I am not sure if this is what you want. That is combine the two query into one query. Please take a look at http://sqlfiddle.com/#!2/ad419/8
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts group by user.id
UPDATED:
For extracting top 3, could do as below;
SELECT user.username,user.pts,rank.rank
FROM user LEFT JOIN rank
ON user.pts <=rank.pts
GROUP BY user.id
ORDER BY pts DESC LIMIT 3
If i understand correctly, you need to get values from Rank and Users tables. In order to do that in just one query You need to add FK (Foreign Key) to the Rank table that points to a specific user in the Users table.
So you need to add userId to the Rank table and then you can run:
SELECT r.rank, u.points from users u,rank r where u.userId = r.userId
This is roughly what you need.
Not quite the answer to your exact question, but this might be of use to you: How to get rank using mysql query. And may even mean that you don't require a rank table. If this doesn't help, I'll check back later.
Use this query
$query = "SELECT
u.pts,
r.rank
FROM users as u
left join ranks as r
on r.pts = u .pts
ORDER BY pts DESC
LIMIT 3";
This will bring what you required without putting into an array
$rec = mysql_query($query);
$results = arrau();
while($row = mysql_fetch_row($rec)){
$results[] = $row;
}
echo json_encode($results);
It looks like what you're trying to do is retrieve the rank with the highest point requirement that the user actual meets, which isn't quite what everyone else is giving here. Fortunately it is easily possible to do this in a single query with a nice little trick:
SELECT
user.username,
SUBSTRING_INDEX(GROUP_CONCAT(rank.rank ORDER BY pts DESC),",",1) AS `rank`
FROM user
LEFT JOIN rank ON user.pts >= rank.pts
GROUP BY user.id
ORDER BY pts DESC
LIMIT 3
Basically what the second bit is doing is generating a list of all the ranks the user has achieved, ordering them by descending order of points and then selecting the first one.
If any of your rank names have commas in then there's another little tweak we need to add on, but I wouldn't have thought they would so I've left it out to keep things simple.

Counting the number of results from a large database

I got a two database tables, one has 165 entries. For each of those, I got to browse the 1-million-entry table and see how many times is each of those 165 entries mentioned.
"odds_provider" has 165 entries, "bettingoffer" has a million entries.
$SQL = "SELECT
odds_provider.id AS id,
odds_provider.name AS name,
COUNT(bettingoffer.odds_providerFK) AS betcount
FROM odds_provider
INNER JOIN
bettingoffer
ON bettingoffer.odds_providerFK = odds_provider.id
WHERE
odds_provider.active = 'yes'
GROUP BY
odds_provider.id,
odds_provider.name
ORDER BY betcount DESC";
$result = mysql_query($SQL);
while ($db_field = mysql_fetch_assoc($result)) {
echo $db_field['id'] , " " , $db_field['name'] , " " , $db_field['betcount'] , "</BR>";
}
It does what's intended but it takes forever. Is there a faster way?
This one should be faster, especially if there not many inactive ids.
SELECT id, name, betcount
FROM (
SELECT
odds_providerFK as id,
COUNT(*) as betcount
FROM bettingoffer
WHERE active = 'yes'
GROUP BY odds_providerFK
ORDER BY betcount DESC) as counts
USING (id);
You should be able to join the two tables using SQL. It would be best to not do a COUNT(*) though. You can get better performance by specifying the specific column you would want to count.
SELECT
op.id,
op.name,
COUNT(bo.odds_providerFK) AS bet_offering_count
FROM
odds_provider op
INNER JOIN
bettingoffer bo
ON bo.odds_providerFK = op.id
WHERE
op.active = 'yes'
GROUP BY
op.id,
op.name
You can do it in much better way using SQL Join queries. You don't have to search the 2nd table for each individual record in 1st table. You can establish a JOIN relationship between the tables.
It would be more helpful if you would have pasted the schema definition of both of your tables, but in general this is how JOIN queries works.
Kindly refer to these tutorials about how JOIN queries work.

SQL position of row(ranking system) WITHOUT same rank for two records

so I'm trying to create a ranking system for my website, however as a lot of the records have same number of points, they all have same rank, is there a way to avoid this?
currently have
$conn = $db->query("SELECT COUNT( * ) +1 AS 'position' FROM tv WHERE points > ( SELECT points FROM tv WHERE id ={$data['id']} )");
$d = $db->fetch_array($conn);
echo $d['position'];
And DB structure
`id` int(11) NOT NULL,
`name` varchar(150) NOT NULL,
`points` int(11) NOT NULL,
Edited below,
What I'm doing right now is getting records by lets say
SELECT * FROM tv WHERE type = 1
Now I run a while loop, and I need to make myself a function that will get the rank, but it would make sure that the ranks aren't duplicate
How would I go about making a ranking system that doesn't have same ranking for two records? lets say if the points count is the same, it would order them by ID and get their position? or something like that? Thank you!
If you are using MS SQL Server 2008R2, you can use the RANK function.
http://msdn.microsoft.com/en-us/library/ms176102.aspx
If you are using MySQL, you can look at one of the below options:
http://thinkdiff.net/mysql/how-to-get-rank-using-mysql-query/
http://www.fromdual.ch/ranking-mysql-results
select #rnk:=#rnk+1 as rnk,id,name,points
from table,(select #rnk:=0) as r order by points desc,id
You want to use ORDER BY. Applying on multiple columns is as simple as comma delimiting them: ORDER BY points, id DESC will sort by points and if the points are the same, it will sort by id.
Here's your SELECT query:
SELECT * FROM tv WHERE points > ( SELECT points FROM tv WHERE id ={$data['id']} ) ORDER BY points, id DESC
Documentation to support this: http://dev.mysql.com/doc/refman/5.0/en/sorting-rows.html
Many Database vendors have added special functions to their products to do this, but you can also do it with straight SQL:
Select *, 1 +
(Select Count(*) From myTable
Where ColName < t.ColName) Rank
From MyTable t
or to avoid giving records with the same value of colName the same rank, (This requires a key)
Select *, 1 +
(Select Count(Distinct KeyCol)
From myTable
Where ColName < t.ColName or
(ColName = t.ColName And KeyCol < t.KeyCol)) Rank
From MyTable t

Categories