Self Join on MySql table - php

So I a have some users in my database and each one of them is able to submit 'referrals' to our company. I am creating a table which shows how many referrals each user has submitted from the current month and previous month so we can track if they are an active sales rep. While I was successful in displaying the current month numbers for a given user, I have been having a hard time displaying the information from the previous month correctly. I get the number of referrals from the previous month, but it gives me the same number for each of the users.
|=========|========|==========|=============|
|user_id refer_id referals date |
|-------------------------------------------|
|1 1 mcdonalds 2017-12-19 |
|1 2 Burger King 2017-12-19 |
|1 3 Wendys 2017-12-21 |
|1 4 Arby's 2017-12-22 |
|1 5 In n' out 2018-01-02 |
|2 6 Chipotle 2018-01-03
|2 7 Carl's jr. 2018-01-04 |
|===========================================|
User 1 should have 1 referral and user 2 should have 2 referrals for the current month, which indeed works, and User 1 should have 4 referrals for the previous month, however I am getting 4 returned for each user. I would also like to condense this down into one query, but I'm not sure what type of JOIN to use, or do I even need one? I have found information on SELF JOINS and INNER JOIN and previous questions about it, but I'm not sure which to use in my case.
<table class="table table-hover">
<thead>
<tr>
<th>Sales Name</th>
<th>Current Month</th>
<th>Previous Month</th>
</tr>
</thead>
<?php
//CURRENT MONTH QUERY
$query = "SELECT user_id, COUNT(refer_id) AS refer_total
FROM table
WHERE MONTH(date) = MONTH(CURDATE()) GROUP BY user_id";
$result = mysqli_query($connection, $query);
//PREVIOUS MONTH QUERY
$query2 = "SELECT user_id, COUNT(refer_id) AS previous_total
FROM table
WHERE
YEAR(date) = YEAR(CURRENT_DATE - INTERVAL 1 MONTH) AND
MONTH(date) = MONTH(CURRENT_DATE - INTERVAL 1 MONTH) GROUP BY user_id";
$total = mysqli_query($connection, $query2);
while($values = mysqli_fetch_assoc($total)) {
$previous = $values['previous_total'];
}
while($values = mysqli_fetch_assoc($result)) {
$user_id = $values['user_id'];
$num_refer = $values['refer_total'];
echo "<tr><td>$user_id</td><td>$num_refer</td><td>$previous</td></tr>";
}
if(mysqli_num_rows < 5) {
echo "Inactive!";
}
?>
</table> <!--END TABLE-->

You need to group by the date as well using a date function. The only reason it appears as though the current month is correct in your results is because each user has 1 for the current month.
mysql> select user_id, date_format(date,'%Y-%m'), count(*) from test group by user_id, date_format(date,'%Y-%m');
+---------+---------------------------+----------+
| user_id | date_format(date,'%Y-%m') | count(*) |
+---------+---------------------------+----------+
| 1 | 2017-12 | 4 |
| 1 | 2018-01 | 1 |
| 2 | 2018-01 | 1 |
+---------+---------------------------+----------+
3 rows in set (0.00 sec)
actually i just realized that wasn't the format you were looking for. Something like the following should work:
mysql> select user_id,
sum(case date_format(date,'%Y-%m') when date_format(curdate(),'%Y-%m') then 1 else 0 end) as current_month,
sum(case date_format(date,'%Y-%m') when date_format(DATE_SUB(curdate(), INTERVAL 1 MONTH),'%Y-%m') then 1 else 0 end) as previous_month
from test group by user_id;
+---------+---------------+----------------+
| user_id | current_month | previous_month |
+---------+---------------+----------------+
| 1 | 1 | 4 |
| 2 | 1 | 0 |
+---------+---------------+----------------+
2 rows in set (0.00 sec)

Related

Show data of today and one week earlier in one row. Is it possible in mysql?

Table example
id | name | value | date
--------------------------------------------
1 | abc | 20 | 2018-01-26
1 | abc | 24 | 2018-01-27
1 | abc | 25 | 2018-01-28
1 | abc | 30 | 2018-01-29
I know how to fetch data from 28th Jan or today. But I need some way to show values of two dates in two columns. Is it possible in one mysql query?
Like this
name | value_today | value_pre
---------------------------------------
abc | 30 | 25
You can achieve this using the case. This query returns the today's value and previous date value:
SELECT
id, name,
Sum(Case When date = CURDATE()
Then value Else 0 End) TodaySum,
Sum(Case When (date = CURDATE()-1)
Then value Else 0 End) PreviousSum
FROM tbl1
group by id, name
Here's a hypothetical query that would do it.
SELECT t1.value AS value_today,(SELECT t2.value FROM table AS t2 WHERE t2.date=2018-01-29 ) AS value_pre
FROM table AS t1 WHERE t1.date=2018-01-28

Mysql Running Total off one table

I have looked up a few different answers to this question and can't seem to get the query to work properly.
Here is my table has the columns user, weekNo, salesTotalYTD.
I am currently pulling these out and grouping them by week like so:
+------+--------+---------------+
| user | weekNo | salesTotalYTD |
+------+--------+---------------+
|Jared | 1 | 200 |
+------+--------+---------------+
| Jim | 1 | 50 |
+------+--------+---------------+
|Jared | 2 | 30 |
+------+--------+---------------+
| Jim | 2 | 100 |
+------+--------+---------------+
What I am trying to do but cannot accomplish is the following:
+------+--------+---------------+
| user | weekNo | salesTotalYTD |
+------+--------+---------------+
|Jared | 1 | 200 |
+------+--------+---------------+
| Jim | 1 | 50 |
+------+--------+---------------+
|Jared | 2 | 230 |
+------+--------+---------------+
| Jim | 2 | 150 |
+------+--------+---------------+
This is the query that I have working for the first pass but every pass after that is wrong:
SET #runtot:=0
SELECT
salesTotalYTD,
user,
(#runtot := #runtot + salesTotalYTD) AS rt
FROM weeksAndSalesmantbl
GROUP BY user, weekNo
ORDER BY (CASE WHEN weekNo = 52 THEN 0 ELSE 1 END) ASC, weekNo, user ASC
Updated
Updated code courtesy of Tim but returning error:
$assignments = "
SELECT
t1.user,
t1.weekNo,
(SELECT SUM(t2.salesTotalYTD) FROM weeksAndSalesmantbl t2
WHERE t2.user = t1.user AND t2.weekNo <= t1.weekNo) AS salesTotalYTD
FROM weeksAndSalesmantbl t1
ORDER BY
t1.weekNo,
t1.user";
$salesTotalSalesManCumulative = [];
$assignmentsqry = mysqli_query($db,$assignments);
if (!$assignmentsqry) {
printf("Error: %s\n", mysqli_error($db));
exit();
}
while ($row = mysqli_fetch_array($assignmentsqry)) {
$float = floatval($row['salesTotalYTD']);
$float = round($float,2);
array_push($salesTotalSalesManCumulative,$float);
}
You can approach this using the standard running total query. However, in this case, we also restrict the sum to a particular user.
SELECT
t1.user,
t1.weekNo,
(SELECT SUM(t2.salesTotalYTD) FROM weeksAndSalesmantbl t2
WHERE t2.user = t1.user AND t2.weekNo <= t1.weekNo) AS salesTotalYTD
FROM weeksAndSalesmantbl t1
ORDER BY
t1.weekNo,
t1.user
Output:
Demo here:
Rextester
Update:
Since late in the game you told us that weeksAndSalesmantbl is a temporary table, and MySQL does not like the query I gave above, we can consider using a single pass over your table with session variables.
SET #rt = NULL;
SET #user = NULL;
SELECT
t.user,
t.weekNo,
t.rt AS salesTotalYTD
FROM
(
SELECT
#rt:=CASE WHEN #user=user THEN #rt+salesTotalYTD ELSE salesTotalYTD END AS rt,
#user:=user AS user,
weekNo
FROM weeksAndSalesmantbl
ORDER BY
user,
weekNo
) t
ORDER BY
t.weekNo,
t.user;
Demo
If this still gives you the error, then you might want to think about getting rid of that temporary table. Anyway, you probably would not want to be using a temporary table in production.

PHP MYSQL - select high score winner

Assuming we have a table like below
id | name | userid | score | date |
------------------------------------------------------------
1 | john | 1 | 44 | 2013-03-2
2 | mary | 2 | 59 | 2013-03-5
3 | john | 1 | 38 | 2013-03-8
4 | elvis | 3 | 19 | 2013-03-10
5 | john | 1 | 100 | 2013-03-11
I want to select four winner of last week.week start Sunday to Sunday.
also i want to select one winner of a month.
winner have highest score.
i use this code for weekly winner
SELECT winner.id,winner.score FROM winner WHERE winner.date >= curdate() - INTERVAL DAYOFWEEK(curdate())+6 DAY
AND winner.date < curdate() - INTERVAL DAYOFWEEK(curdate())-1 DAY ORDER BY winner.score DESC
LIMIT 4
Also monthly winner
SELECT winner.id,winner.score FROM winner WHERE MONTH(CURDATE())= MONTH(winner.date) ORDER BY winner.score DESC
LIMIT 1
both are give wrong results
Try this
select id ,score
from winner
where DATE_FORMAT(date, '%Y-%m-%d') between date_sub(DATE_FORMAT(now(), '%Y-%m-%d'),INTERVAL 1 WEEK) and DATE_FORMAT(now(), '%Y-%m-%d')
ORDER BY score DESC
LIMIT 4
-------------------
SELECT id,score FROM winner
WHERE MONTH(CURDATE())= MONTH(date)
ORDER BY score DESC
LIMIT 1
Make sure that your example data will not work because they are 2013 !!
DEMO HERE

dynamic columns using pivot table query

I have this attendance table
---------------------------------------------------------------------------
attendance_id | stud_id | week | sy |sem |present
---------------------------------------------------------------------------
1 | 1 | 02/18/2012 | 2010-2011 |1st semester|1
2 | 2 | 02/18/2012 | 2010-2011 |1st semester|1
3 | 3 | 02/18/2012 | 2010-2011 |1st semester|1
4 | 1 | 02/25/2012 | 2010-2011 |1st semester|1
5 | 2 | 02/25/2012 | 2010-2011 |1st semester|1
6 | 1 | 03/03/2012 | 2010-2011 |1st semester|1
7 | 2 | 03/03/2012 | 2010-2011 |1st semester|1
8 | 3 | 03/03/2012 | 2010-2011 |1st semester|1
my query is this
Select cadet_record.fname,cadet_record.lname,cadet_record.mname, student_id,
MAX(case WHEN week = '02/18/2012' then present end) as 'week1',
MAX(case WHEN week = '02/25/2012' then present end) as 'week2'
From attendance
LEFT JOIN cadet_record ON cadet_record.stud_no = attendance.student_id WHERE section = '$section' AND schoolyear = '$year' AND component = '$component' AND semester = '$semester'
GROUP BY student_id
how can I dynamically call all the week without inserting the dates
for e.g. 02/28/2012, 02/29/2012 so on and so forth.
any ideas? =(
As far as I know, you can't dynamically add columns to a SELECT statement. What you're asking for is a way of presenting data and that is not something MySQL cares about. You should handle that in the front end.
However, you can cheat by creating your queries in your model and dynamically adding those new columns, by dynamically inserting more MAX(case... to the query string. That's not a nice solution, though.
Edit:
Im using php, how can I accomplish that?
So, I guess you're talking about the ugly solution. Well, basically you should dynamically create your query string (pseudocode):
$initialDay = 02/28/2012;
$lastDay = 03/28/2012;
$dayNumber = 1;
$sql = 'Select cadet_record.fname,cadet_record.lname,cadet_record.mname, student_id';
while ($initialDay <= $lastDay) {
$sql .= ', MAX(case WHEN week = $initialDay then present end) as day' . $dayNumber;
$initialDay = $initialDay + 1 day;
$dayNumber++;
}
$sql .= ' From attendance blah blah...';
Then your query should look like this for dates from 02/18/2012 to 03/18/2012:
Select cadet_record.fname,cadet_record.lname,cadet_record.mname, student_id,
,MAX(case WHEN week = '02/18/2012' then present end) as day1
,MAX(case WHEN week = '02/19/2012' then present end) as day2
From attendance
LEFT JOIN cadet_record ON cadet_record.stud_no = attendance.student_id WHERE section = '$section' AND schoolyear = '$year' AND component = '$component' AND semester = '$semester'
GROUP BY student_id
Notice I added days instead of weeks because your example showed increasing days, although the column name was weeks
Without pivoting the table, you can get the weekly attendance of each student with this query:
SELECT
cadet_record.fname,
cadet_record.lname,
cadet_record.mname,
student_id,
week,
SUM(present) AS att
FROM attendance
LEFT JOIN cadet_record
ON cadet_record.stud_no = attendance.student_id
WHERE section = '$section'
AND schoolyear = '$year'
AND component = '$component'
AND semester = '$semester'
GROUP BY week, student_id
ORDER BY week, cadet_record.fname, student_id
(EDIT Sorry, you should use SUM, not COUNT).

Group and Rank Rows in Mysql

I'm trying to build a ranking system in a mysql database.
I've found several tutorials on ranking and items here on StackOverflow about ranking individual rows against each other.
However, my issue is that I need to group rows by a user id column, add up the values to a second column grouped by user id, then rank them against other groups of a different user id.
Here's an example of the table I'm using:
user_id km_skied date_entered
1 34 2010-08-19
3 2 2010-08-23
1 3 2010-08-13
4 23 2010-08-01
3 5 2010-08-02
The result printout would be by rank:
Skier Rank:
Rank User ID Total KM
1 1 37
2 4 23
3 3 7
Also, I was wondering how I find the rank for a specific user. Meaning, if I know what the user id is, can I give them just their rank? Like say
"Your Rank: 2 of 345"
That is the second part of this.
Anyone know how to do that?
Thanks!
Troy
Your query should look something like this. Add the ranking logic to the outer loop.
select * from
(select user_id, sum(km_skied) as km from ski group by user_id) x
order by x.km desc;
Don't know if it's an option, but you can use a temporary table for rankings as follows:
create temporary table ranks (rank int primary key auto_increment, user_id int, km int);
insert into ranks (user_id, km)
select user_id, km from (
select user_id, sum(km_skied) as km from ski group by user_id
) x order by x.km desc;
This gives you what you want:
mysql> select * from ranks;
+------+---------+------+
| rank | user_id | km |
+------+---------+------+
| 1 | 1 | 37 |
| 2 | 4 | 23 |
| 3 | 3 | 7 |
+------+---------+------+
3 rows in set (0.00 sec)
One downside to this approach is that skiers who are tied won't get the same rank.
Do the grouping in subquery and ranking of the results (using any of the methods you've found before) in outer query.
Thanks for your help guys.
I was able to come up with an answer based on the following Query:
$totalQuery = "SELECT SUM(track_length) as usertracklength, username, MAX(track_create_time) as lasttrack, count(DISTINCT track_create_time) as totaldays FROM user_tracks GROUP BY username ORDER BY usertracklength DESC";
$totalResult = mysql_query($totalQuery);
$rankResult = mysql_query($totalQuery);
$totalNumEntries = mysql_num_rows($totalResult);
Then Ouputting that to an array
// rank position array
$rankArray = array();
while ($row1 = mysql_fetch_array($rankResult)) {
$rankArray[] = $row1['username'];
}
Then finding position of that username in the array by using a foreach in php
foreach ($rankArray as $rank => $user) {
if ($user == $username) {
$yourRank = $rank+1;
}
}
It's the long way around, but I suppose it works for what I'm going for.
Was kind of hoping to get it done within the mysql query for efficiency.
Thanks!
You could try grouping to sum the Km as a first query, then follow it by a correlated subquery to find the ranks. For instance, if your values are stored in a table called "test", sum the Km values into a table called testtbl and then do the ranking.
mysql> select * from test;
+------+--------+------+
| Id | km_run | name |
+------+--------+------+
| 1 | 34 | a |
| 3 | 2 | c |
| 1 | 3 | a |
| 4 | 23 | d |
| 3 | 5 | c |
+------+--------+------+
5 rows in set (0.00 sec)
mysql> create table testtbl as
(select Id, sum(km_run) as tot
from test
group by Id);
Query OK, 3 rows affected (0.04 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> select * from testtbl;
+------+------+
| Id | tot |
+------+------+
| 1 | 37 |
| 3 | 7 |
| 4 | 23 |
+------+------+
3 rows in set (0.00 sec)
mysql> select t1.Id,t1.tot,
((select count(distinct t2.tot) from testtbl t2 where t1.tot < t2.tot)+1) as Rk from testtbl t1
order by Rk;
+------+------+------+
| Id | tot | Rk |
+------+------+------+
| 1 | 37 | 1 |
| 4 | 23 | 2 |
| 3 | 7 | 3 |
+------+------+------+
3 rows in set (0.00 sec)

Categories