We store the customer data like name, userId and Total Amount they spent on different orders they place, now I want to group a customer into ranks 1 to 4 based on Total Amount he spent till now. Below is the script I am using but it takes a lot of time, is there any better way to do this?? There is no index on dateCreate Field.
public function getBiggestSpenders($customerUserId){
global $db, $database;
$sql = "SELECT userId, SUM(total) AS Total, ORD.dateCreate
FROM $database.`order` ORD
WHERE year(ORD.dateCreate) >= '2013'
group by ORD.userId
order by Total DESC";
$result = $db->getTable($sql);
$numRows = count($result);
$flag=0;
for($i=0;$i<$numRows && $flag==0;$i++){
$userId = $result[$i]['userId'];
if($userId==$customerUserId){
$position = $i;
$Total = $result[$i]['Total'];
$flag=1;
}
}
$quartile = $this->getQuartiles($numRows, $position);
if($quartile==1)
return $quartile;
else
return 0;
}
public function getQuartiles($numRows, $position){
$total = $numRows;
$segment = round($total / 4);
$Quartile = floor($position / $segment) + 1;
return $Quartile;
}
Thanks!
To improve the speed, you can create an index on dateCreate column and use the following condition to make MySQL use it:
WHERE ORD.dateCreate >= '2013-01-01'
As far as grouping is concerned, you can use CASE statement to define groups based on spending, e.g.:
SELECT userId, SUM(total) AS Total,
CASE
WHEN Total >= 2000 then 1
WHEN Total >= 1000 AND Total <2000 THEN 2
WHEN Total >=500 AND Total < 1000 THEN 3
ELSE 4
END as `rank`,
ORD.dateCreate
FROM $database.`order` ORD
WHERE ORD.dateCreate >= '2013-01-01'
group by ORD.userId
order by Total DESC
Related
I have a small system that accepts votes, on my query I'm selecting the vote(name of user) and the total of votes for this user.
My Idea is to only display the user with more votes, with the query that i have I know that i can add ORDER BY and get the first value which will be the one with the highest umber of votes.
But i'm trying to identifie if two persons have the number of votes. any ideas how can i do that?
I'm using cgi.
my $connection = $MY_CONNECTION->prepare("SELECT voto, COUNT(*) total FROM votos WHERE mes = 12 GROUP BY vote HAVING COUNT(*) > 0 ORDER BY COUNT(*)");
$connection->execute || print "ERROR 1";
my #resultados;
my $info = '';
while ($info = $connection->fetchrow_hashref()) {
my $nombre = $info->{voto};
my $totalVotos = $info->{total};
my $winner = "";
my $temp2 = "";
my $temp = $totalVotos ;
if ($temp2 => $temp) {
$temp2 = $totalVotos ;
$winner = $nombre ;
}
print "<p><strong>Winner: </strong> $winner </p> ";
print "<hr>";
}
You can return everyone who has the highest number of votes (and how many votes) with:
select voto, count(*) as total from votos
where mes = 12
group by voto -- I assume "vote" in your question was typo
having total = (
select max(ct) from (
select voto,count(*) as ct from votos group by voto
)
)
and total > 0 -- from your original but not sure it can fail
;
You may also be able to use a rank() function.
Without modifying your SQL, you could change the perl to something like:
# ...
# initialisation shouldn't be inside while loop
my $winner = "";
my $temp2 = "";
while ($info = $connection->fetchrow_hashref()) {
my $nombre = $info->{voto};
my $totalVotos = $info->{total};
if ($temp2 < $totalVotos) { # your original "=>" seems wrong
$temp2 = $totalVotos;
#winner = $nombre;
elsif ($temp2 == $totalVotos) {
push #winner, $nombre;
}
}
# postprocessing shouldn't be inside while loop
$winner = join(", ", #winner); # or whatever
print "<p><strong>Winner: </strong> $winner </p> ";
print "<hr>";
use subquery then apply order by, to limit the result by 2 add limit 2.
select t1.voto, t1.ct as total from (
SELECT voto, count(1) ct FROM votos
WHERE mes = 12
GROUP BY voto
HAVING COUNT(1) > 1) t1
order by t1.total desc limit 2
I would recommend rank():
SELECT voto, total
FROM (SELECT voto, COUNT(*) as total,
RANK() OVER (ORDER BY COUNT(*) DESC) as seqnum
FROM votos v
WHERE mes = 12
GROUP BY voto
) v
WHERE seqnum = 1;
Note that HAVING COUNT(*) > 0 does nothing. In your query, it is not possible for COUNT(*) to be equal to or less than 0.
I'm stuck at the query where I need to concat IDs of the table. And from that group of IDs, I need to fetch that rows in sub query. But when I try to do so, MySQL consider group_concat() as a string. So that condition becomes false.
select count(*)
from rides r
where r.ride_status = 'cancelled'
and r.id IN (group_concat(rides.id))
*************** Original Query Below **************
-- Daily Earnings for 7 days [Final]
select
group_concat(rides.id) as ids,
group_concat(ride_category.name) as rideType,
group_concat(ride_cars.amount + ride_cars.commission) as rideAmount ,
group_concat(ride_types.name) as carType,
count(*) as numberOfRides,
(
select count(*) from rides r where r.ride_status = 'cancelled' and r.id IN (group_concat(rides.id) )
) as cancelledRides,
(
select count(*) from rides r where r.`ride_status` = 'completed' and r.id IN (group_concat(rides.id))
) as completedRides,
group_concat(ride_cars.status) as status,
sum(ride_cars.commission) + sum(ride_cars.amount) as amount,
date_format(from_unixtime(rides.requested_at/1000 + rides.offset*60), '%Y-%m-%d') as requestedDate,
date_format(from_unixtime(rides.requested_at/1000 + rides.offset*60), '%V') as week
from
ride_cars,
rides,
ride_category,
ride_type_cars,
ride_types
where
ride_cars.user_id = 166
AND (rides.ride_status = 'completed' or. rides.ride_status = 'cancelled')
AND ride_cars.ride_id = rides.id
AND (rides.requested_at >= 1559347200000 AND requested_at < 1561852800000)
AND rides.ride_category = ride_category.id
AND ride_cars.car_model_id = ride_type_cars.car_model_id
AND ride_cars.ride_type_id = ride_types.id
group by
requestedDate;
Any solutions will be appreciated.
Try to replace the sub-query
(select count(*) from rides r where r.ride_status = 'cancelled' and r.id IN (group_concat(rides.id) )) as cancelledRides,
with below to count using SUM and CASE, it will make use of the GROUP BY
SUM(CASE WHEN rides.ride_status = 'cancelled' THEN 1 ELSE 0 END) as cancelledRides
and the same for completedRides
And move to using JOIN instead of implicit joins
I am trying to extract a report that shows the last two full calendar months sales as per the following format:
Customer August 2013 September 2014
Company A 1,250 2,543
Company B 4,245 1,423
Company C 1,432 1,642
I have a table with the customer and individual transaction with a cost. I am fine getting this out but have no idea how to get two columns next to each other.
select customers.customer as Customer,
sum(job.price) as August_2014
from job,customers
where customers.cust_id=job.customer
and
job.collect_date between '2014-08-01' and '2014-08-31'
group by customer
order by August_2014 desc
limit 0,10
Thanks for any help.
John
Use case expressions inside the SUM() function, like this:
SELECT
customers.customer AS Customer
, SUM(case when job.collect_date >= '2014-08-01' and job.collect_date < '2014-09-01' THEN job.price ELSE 0 END) AS August_2014
, SUM(case when job.collect_date >= '2014-09-01' and job.collect_date < '2014-10-01' THEN job.price ELSE 0 END) AS September_2014
FROM job
INNER JOIN customers ON customers.cust_id = job.customer
WHERE ( job.collect_date >= '2014-08-01' AND job.collect_date < '2014-10-01' )
GROUP BY
customers.customer
ORDER BY
August_2014 DESC
limit 0,10
;
I changed the join syntax, explicit joins like this are strongly recommended.
Also note I have changed the method for "date ranges". Just do NOT trust "between" for this. Always use the method shown above and you will not have any gaps or overlaps in the data (i.e. your numbers will be accurate).
( date_field >= lower_date_here AND date_field < higher_date_here )
As you can see, the "higher date" is one day more than your original, that's because we use LESS THAN on the higher date
Try this:
SELECT
customers.customer AS Customer,
MONTH(job.collect_date) AS Month,
sum(job.price) AS SumPrice
FROM
job,
customers
WHERE
customers.cust_id = job.customer
GROUP BY
customer,
MONTH(job.collect_date)
You cannot do the order by price anymore. I'm sure you can make the table yourself?
No, people cannot write PHP themselves, and want an example:
// the sql command
$sql = "SELECT
customers.customer AS Customer,
MONTH(job.collect_date) AS Month,
sum(job.price) AS SumPrice
FROM
job,
customers
WHERE
customers.cust_id = job.customer
GROUP BY
customer,
MONTH(job.collect_date)";
// fetching data
if ($result = $mysqli->query($sql))
{
while($row = $result->fetch_assoc())
{
extract($row);
$data[$Customer][$Month] = $SumPrice;
}
$result->close();
}
// render
foreach($data as $customer => $prices)
{
echo $customer;
foreach($prices as $month => $price)
{
echo ' '.$price;
}
echo '<br>';
}
Note that the table rendering is the very basic, but it given you the idea, you need to work a bit on it to make it nicer. This will work regardless of which months are in the database. The main advantage is the simplicity of the SQL query.
I've got a column of DATETIME values in MySQL and I'm looking to return the smallest difference between any two of those values in the column; I don't need to know what values the difference was between or when it was, just the difference between these two values alone.
My table looks similar to this.
id | launch_date_time
----------------------------
1 | 2012-01-02 18:42:00
2 | 2012-04-05 07:23:50
...
x | 2014-08-07 22:19:11
Would anyone be able to point me in the correct direction for constructing such a query?
My first idea is, if your table name is table do this
select min( abs( datediff(t1.launch_date_time, t2.launch_date_time) ) )
from table t1
, table t2
where t1.id <> t2.id
it depends how big those tables are, the above is O(N^2) solution, in O(N * log N) you can do this by sorting and result is min of consecutive elements
// pseudo code
list = fromDb();
sort(list);
min = list[1] - list[0];
for i in 2 to list.size()
min = min( min, list[i] - list[i-1] )
select id,FROM_UNIXTIME(launch_date_time) as uTime,max(FROM_UNIXTIME(launch_date_time)) as max from table;
//do db stuff
//set the lowest to highest possible number so first comparison it will become the lowest.
$lowest = $result['max'];
// iterate through array to get each time
foreach ($result['uTime'] as $v)
{
//iterate again to compare each time u got from first loop to each in this loop
foreach ($result['uTime as $x)
{
//subtract your first value from the current
$diff = $v - $x;
//if its lower then any previous, and its positive set the new lowest.
if ($diff < $lowest and $diff > 0)
{
$lowest = $diff;
}
}
}
echo $lowest;
havent tested this....
SQL method of doing it in a single statement (difference here in days):-
SELECT MIN(DATEDIFF(Sub1.launch_date_time, Sub2.launch_date_time))
FROM
(
SELECT id, launch_date_time, #aCnt1 := #aCnt1 + 1 AS SeqCnt
FROM SomeTable
CROSS JOIN
(
SELECT #aCnt1:=0
) Deriv1
ORDER BY launch_date_time
) Sub1
INNER JOIN
(
SELECT id, launch_date_time, #aCnt2 := #aCnt2 + 1 AS SeqCnt
FROM SomeTable
CROSS JOIN
(
SELECT #aCnt2:=1
) Deriv2
ORDER BY launch_date_time
) Sub2
ON Sub1.SeqCnt = Sub2.SeqCnt
SQL Fiddle here:-
http://www.sqlfiddle.com/#!2/6dc399/1
Try this query -
SELECT
t1.launch_date_time, MIN(t2.launch_date_time) launch_date_time2
FROM launch t1
JOIN launch t2
ON t2.launch_date_time > t1.launch_date_time
GROUP BY
t1.launch_date_time
ORDER BY
DATE(MIN(t2.launch_date_time)) * 86400 + TIME_TO_SEC(TIME(MIN(t2.launch_date_time))) - DATE(t1.launch_date_time) * 86400 + TIME_TO_SEC(TIME(t1.launch_date_time))
LIMIT 1
My query:
SELECT *
FROM ranks
WHERE
(price = 25.00 AND accumulate = 0)
OR
(price <= (SELECT SUM(amount) FROM donations WHERE username = 'username' AND amount IN (SELECT price FROM ranks WHERE accumulate = 1)))
ORDER BY price
DESC LIMIT 1
Basically I want to return the first where clause if it finds a match, otherwise return the second clause.
This query will work
SELECT *
FROM ranks
WHERE price = 25.00 AND accumulate = 0
UNION ALL
SELECT *
FROM ranks
WHERE NOT EXISTS(SELECT *
FROM ranks
WHERE price = 25.00 AND accumulate = 0)
AND (price <= (SELECT SUM(amount) FROM donations WHERE username = 'username' AND amount IN (SELECT price FROM ranks WHERE accumulate = 1)))
ORDER BY price
DESC LIMIT 1
Try this query with IF
SELECT *
FROM ranks
WHERE if(price = 25.00,accumulate = 0, price <= (SELECT
SUM(amount)
FROM donations
WHERE username = 'username'
AND amount IN(SELECT
price
FROM ranks
WHERE accumulate = 1)))
ORDER BY price DESC
LIMIT 1