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).
Related
I currently have a database table which records the bookings of jobs
and there are 8 timeslots available
+-----------+
|tbl_booking|
+-----------+
|room_id |
|date |
|timeslot |
|booking |
+-----------+
sample data
+-----------+----------+-----------+
|room_id | date | timeslot |
+-----------+----------+-----------+
|1 |2018-01-01| 1 |
|1 |2018-01-01| 2 |
|1 |2018-01-01| 4 |
|2 |2018-01-01| 1 |
+-----------+----------+-----------+
intended outcome - when statement filters for bookings on 2018-01-01
+-----------+----------+-----------+----------+-----------+
|room |timeslot1 | timeslot2 |timeslot3 | timeslot4 |
+-----------+----------+-----------+----------+-----------+
|1 | X | X | | X |
|2 | X | | | |
+-----------+----------+-----------+----------+-----------+
i started off with this statement:
SELECT * from tbl_booking WHERE date = '2018-01-01' GROUP BY room_id
and this would return results to see the results grouped by rooms.
I would like to know where i should go from here to also have the results display it's timeslots that are shown in a table displaying the booking status of eacah room's timeslot in the day?
Should there be an SQL statement that i should be using or am I on the wrong track completely?
Any help would be appreciated.
Thank you!
What you want to do with the data isn't nice to do and if you have a fixed number of time slots then you can hardcode the columns like this:
SELECT room_id,
SUM(CASE WHEN timeslot = 1 then 1 else 0 END) AS Timeslot1,
SUM(CASE WHEN timeslot = 2 then 1 else 0 END) AS Timeslot2,
SUM(CASE WHEN timeslot = 3 then 1 else 0 END) AS Timeslot3,
SUM(CASE WHEN timeslot = 4 then 1 else 0 END) AS Timeslot4
FROM tbl_booking
GROUP BY room_id
(see SQL Fiddle)
You could use MAX if you just want to see if at least 1 booking exist
SQL to include remark, you can trick it to select the remark through a group by with MAX
SELECT room_id,
SUM(CASE WHEN timeslot = 1 then 1 else 0 END) AS Timeslot1,
MAX(CASE WHEN timeslot = 1 THEN remark ELSE '' END) AS Timeslot1Remark,
SUM(CASE WHEN timeslot = 2 then 1 else 0 END) AS Timeslot2,
MAX(CASE WHEN timeslot = 2 THEN remark ELSE '' END) AS Timeslot2Remark,
SUM(CASE WHEN timeslot = 3 then 1 else 0 END) AS Timeslot3,
MAX(CASE WHEN timeslot = 3 THEN remark ELSE '' END) AS Timeslot3Remark,
SUM(CASE WHEN timeslot = 4 then 1 else 0 END) AS Timeslot4,
MAX(CASE WHEN timeslot = 4 THEN remark ELSE '' END) AS Timeslot4Remark
FROM tbl_booking
GROUP BY room_id
your extended SQL Fiddle. This won't work for multiple dates only 1 selected date
If you are looking for dynamic solutions then you must need to use pivot table.
Select * from
(select * from yourtable) as
Temptable
Pivot (
Count(room)
For timeslot
In (list of timeslot))
As tempSlot
For more information check this link MySQL pivot table
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)
I am trying to select data from mysql by a date field in the database. (Users can enter start date and end date)
For each selected row between user selected dates, I need to select from the same table to produce a result.
Example:
$query = "SELECT * FROM table WHERE date BETWEEN $begindate AND $enddate"; //Select by date
$result = mysqli_query($dbc,$query);
while($row = mysqli_fetch_array($result)){
vardump($row); //user needs to see all data between date selection
$query = "SELECT * FROM table WHERE field = $row['field']";
// and then do calculations with the data
}
This runs very slowly and I can see why. How can I improve the run speed?
Edit:
The original purpose was to generate a sales report between dates. Now the user wants the report to produce another result. This result could only be produced by searching against the same table, and the rows that I need is not within the date selection.
Edit 2:
I do need to output the entire table between date selection. Each row will need to find ALL other rows where field = field, within or out side of the date selection.
Edit 3: Solved the problem. All the answers are helpful, though I think the chosen answer was most related to my question. However, I believe using join when working with two tables is the right way to go. For my problem, I actually just solved it by duplicating the table and run my search against the duplicated table. The chosen answer did not work for me because the second query selection is not a part of the first query selection. Hope this would help anyone looking at this post. Again, thanks for all the help!
Well, so if you are really looking for such a conditions in same table, I suggest you should use IN selector like following:
$query = "SELECT * FROM table
WHERE field IN
(SELECT DISTINCT field FROM table
WHERE
date BETWEEN $begindate AND $enddate)";
So final code will look some like following:
$query = "SELECT * FROM table
WHERE field IN
(SELECT DISTINCT field FROM table
WHERE
date BETWEEN $begindate AND $enddate)";
$result = mysqli_query($dbc,$query);
while($row = mysqli_fetch_array($result)){
// do calculations with the $row
}
I guess your table names arent TABLE:
just user inner join
$query = "SELECT *
FROM table1
JOIN table2
ON table1.field = table2.field
WHERE date BETWEEN $begindate AND $enddate
ORDER BY table1.field;"
Stop writing pseudo-SQL
SELECT * FROM is technically pseudo-SQL (a sql command which the interpreter has to modify before the command can be executed. It is best to get in a habit of specifying columns in the SELECT statement.
Use SQL joins
Joins are what makes relational databases so useful, and powerful. Learn them. Love them.
Your set of SQL queries, combined into a single query:
SELECT
table1.id as Aid, table1.name as Aname, table1.field as Afield,
table2.id as Bid, table2.name as Bname, table2.field
FROM table table1
LEFT JOIN table table2
ON table1.field = table2.field
WHERE table1.date BETWEEN $begindate AND $enddate
ORDER BY table1.id, table2.id
Your resulting print of the data should result in something which access each set of data akin to:
$previous_table1_id = 0;
while($row = mysqli_fetch_array($result)){
if ($row['Aid'] != $previous_table1_id) {
echo 'Table1: ' . $row['Aid'] . ' - ' . $row['Aname'] . ' - '. $row['Afield'] . "\n";
$previous_table1_id = $row['Aid'];
}
echo 'Table2: ' . $row['Bid'] . ' - ' . $row['Bname'];
}
Dealing with aggregated data
Data-aggregation (multiple matches for table1/table2 on field), is a complex subject, but important to get to know. For now, I'll leave you with this:
What follows is a simplified example of one of what aggregated data is, and one of the myriad approaches to working with it.
Contents of Table
id | name | field
--------------------
1 | foos | whoag
2 | doh | whoag
3 | rah | whoag
4 | fun | wat
5 | ish | wat
Result of query I gave you
Aid | Aname | Afield | Bid | Bname
----------------------------------
1 | foos | whoag | 1 | foos
1 | foos | whoag | 2 | doh
1 | foos | whoag | 3 | rah
2 | doh | whoag | 1 | foos
2 | doh | whoag | 2 | doh
2 | doh | whoag | 3 | rah
3 | rah | whoag | 1 | foos
3 | rah | whoag | 2 | doh
3 | rah | whoag | 3 | rah
4 | fun | wat | 4 | fun
4 | fun | wat | 5 | ish
5 | ish | wat | 4 | fun
5 | ish | wat | 5 | ish
GROUP BY example of shrinking result set
SELECT table1.id as Aid, table1.name as Aname
group_concat(table2.name) as field
FROM table table1
LEFT JOIN table table2
ON table1.field = table2.field
WHERE table1.date BETWEEN $begindate AND $enddate
ORDER BY table1.id, table2.id
GROUP BY Aid
Aid | Aname | field
----------------------------------
1 | foos | foos,doh,rah
2 | doh | foos,doh,rah
3 | rah | foos,doh,rah
4 | fun | fun, ish
5 | ish | fun, ish
I've the following tables (example):
users:
id | user | photo | joined | country
1 | Igor | abc.jpg | 2015 | Brazil
2 | John | cga.png | 2014 | USA
3 | Lucas| hes.jpg | 2016 | Japan
posts (see that there are two lines with author = Igor and ft = 2 and one line with author = Igor and ft = 3 and Igor have three posts):
id | author | content | date | ft (2 = photos and 3 = videos)
1 | Igor | hi | 2016 | 2
2 | Igor | hello | 2016 | 3
3 | John | hehehe | 2016 | 2
4 | Igor | huhuhuh | 2016 | 2
5 | Lucas | lol | 2016 | 3
friendship (when status = 2 means that they are friends):
id | friend1 | friend2 | status
1 | Igor | Lucas | 2
2 | Lucas | John | 2
3 | John | Igor | 2
And I want to do a COUNT of posts with ft = 2 and a COUNT of friends (status = 2) according to the currently logged user (Igor, in this case).
So, I do (assuming that the current user logged in is Igor):
SELECT photo, joined, country, sum(CASE WHEN ft = 2 THEN 1 ELSE 0 END) AS numPhotos, sum(CASE WHEN ft = 3 THEN 1 ELSE 0 END) AS numVideos
FROM users
LEFT JOIN posts
ON users.user = posts.author
WHERE users.user = 'Igor'
GROUP BY users.user
LIMIT 1
And when I check on a foreach, the data is correct:
numPhotos = 2 and numVideos = 1.
But, I want to select too the number of friends, so, I do:
SELECT photo, joined, country, sum(CASE WHEN ft = 2 THEN 1 ELSE 0 END) AS numPhotos, sum(CASE WHEN ft = 3 THEN 1 ELSE 0 END) AS numVideos, count(friendship.status) AS numFriends
FROM users
LEFT JOIN posts
ON users.user = posts.author
LEFT JOIN friendship
ON (users.user = friend1 OR users.user = friend2) AND friendship.status = 2
WHERE users.user = 'Igor'
GROUP BY users.user
LIMIT 1
But, the output is:
numPhotos = 4, numVideos = 2 and numFriends = 6.
In other words, he is duplicating all results but in numFriends he's taking the total of posts of Igor (3) and duplicating the value too. And if I change count(friendship.status) to sum(friendship.status) the output is:
numPhotos = 4, numVideos = 2 and numFriends = 18 (triples the numFriends).
I tried too with count(distinct friendship.status) and the result is:
numPhotos = 4, numVideos = 2 and numFriends = 1 (duplicates the values again as well as return the wrong value 1 for numFriends that should be 2 knowing he has two friends).
So, how I can do this? (I'm using MySQL)
EDIT:
I changed the count(distinct friendship.status) to count(distinct friendship.id) and it worked to select the number of friends. But the rest of values (numPhotos and numVideos) continue duplicated.
I discovered that the problem is in ON (users.user = friend1 OR users.user = friend2), because if I leave only ON (users.user = friend1) or ON (users.user = friend2) the output isn't duplicated. I tried too with ON 'Igor' IN (friend1, friend2) but the result is the same (numPhotosandnumVideos` continue duplicated).
I think the left join may be joining on a one-to-many relationship, which is causing inflated counts.
Since you are only retrieving the counts for 1 user, I suggest using a subquery to retrieve the friendship counts (for retrieving the counts for multiple users, a derived table may be faster than a subquery):
SELECT
sum(ft = 2) AS numPhotos,
sum(ft = 3) AS numVideos,
(select count(*) from friendships f
where (friend1 = users.user
or friend2 = users.user)
and status = 2) as friendship_count
FROM users
LEFT JOIN posts
ON users.user = posts.author
WHERE users.user = 'Igor'
Note that I removed the group by because users.user is already in the where clause, which means there is only 1 group.
Instead of count(distinct friendship.status), try using count(distinct friendship.id). That should give you the number of unique friends. Counting distinct statuses doesn't work because all the statuses will be 2 by definition, so there is only one distinct value.
i have three tables( runners, stages and time)
Runners table:
+--+----+
|id|name|
+--+----+
|1 |Karl|
+--+----+
|2 |Lou |
+--+----+
Stage Table:
+--+-----+-----+---+
|id|name |order|end|
+--+-----+-----+---+
|1 |start| 1 | 0 |
+--+-----+-----+---+
|2 |bike | 2 | 0 |
+--+-----+-----+---+
|3 |run | 3 | 0 |
+--+-----+-----+---+
|4 |end | 4 | 1 |
+--+-----+-----+---+
Runners data(time) Table:
+------+-----+-----+
|runner|stage|time |
+------+-----+-----+
| 1 | 1 |10:00|
+------+-----+-----+
| 1 | 2 |10:30|
+------+-----+-----+
| 1 | 3 |11:00|
+------+-----+-----+
| 2 | 1 |10:00|
+------+-----+-----+
| 2 | 2 |10:43|
+------+-----+-----+
| 2 | 3 |11:56|
+------+-----+-----+
| 1 | 4 |12:14|
+------+-----+-----+
| 2 | 4 |12:42|
+------+-----+-----+
Well ... then what I want now is to get the results as follows( order by total time ):
+------+-----+-----+-----+-----+----------+
|runner|start|bike |run | end | Total |
+------+-----+-----+-----+-----+----------+
| Karl |10:00|10:30|11:00|12:14| 01:44:00 | <--- FIRST( one hour)
+------+-----+-----+-----+-----+----------+
| Lou |10:30|10:30|11:56|12:42| 02:12:00 | <--- SECONDS( two hours )
+------+-----+-----+-----+-----+----------+
Have any idea how I can accomplish this?
Greetings!
the following should work (times are in seconds, not in HH:MM:SS)
select r.name, rd_start.time as start, rd_bike.time as bike, rd_run.time as run, rd_end.time as end, from runner as r, rd_start.time+rd_bike.time+rd_run.time+rd_end.time as total
inner join runnerdata as rd_start on r.id=rd_start.runner and rd_start.stage=1
inner join runnerdata as rd_bike on r.id=rd_bike.runner and rd_start.stage=2
inner join runnerdata as rd_run on r.id=rd_run.runner and rd_start.stage=3
inner join runnerdata as rd_end on r.id=rd_end.runner and rd_start.stage=4
order by (rd_start.time+rd_bike.time+rd_run.time+rd_end.time)
(If you post the create tables or even better use this tool: http://sqlfiddle.com/ it would make it easier for us to test our statements)
The query would look something like this but the method for calculating the total depends on the data type of the time.
select runners.name as runner, starttime.time as start, biketime.time as bike, runtime.time as run, endtime.time as end, endtime.time - starttime.time as Total
from runners
inner join time as starttime on runners.id = starttime.runner
inner join stages as startstages on starttime.stage = startstages.id and startstages.name = 'start'
inner join time as biketime on runners.id = biketime.runner
inner join stages as bikestages on biketime.stage = bikestages.id and bikestages.name = 'bike'
inner join time as runtime on runners.id = runtime.runner
inner join stages as runstages on runtime.stage = runstages.id and runstages.name = 'run'
inner join time as endtime on runners.id = endtime.runner
inner join stages as endstages on endtime.stage = endstages.id and endstages.name = 'end'
order by endtime.time - starttime.time
This requires a join and then conditional aggregation. The final column uses timediff() to subtract the two times:
select r.name,
max(case when rt.stage = 1 then rt.time end) as start,
max(case when rt.stage = 2 then rt.time end) as walk,
max(case when rt.stage = 3 then rt.time end) as bike,
max(case when rt.stage = 4 then rt.time end) as end,
timediff(max(case when rt.stage = 4 then rt.time end),
max(case when rt.stage = 1 then rt.time end)
) as TotalTime
from RunnersTime rt join
Runners r
on rt.runner = r.id
group by r.id
order by TotalTime;
Note that the column names are fixed, so the stages table is not used. Making them dynamic would make the query much more complicated.
You would probably need to do a lot of inner joining, subquerying, and comparing this time vs. that time if you want to go with that schema, and it really won't be pretty. Alternatively if your stages are fixed you could simplify to one table with each column as a stage. If the number and names of stages need to vary (for whatever reason) then I'd suggest storing a start time and end time in your runners date/time table.
If your stages are fixed then getting the result you are looking for straight out of the database will be easy. If the stages can vary (depending on your site users configuring stages for example) then you'll want to cross-tab your data in PHP or look at this SO question if you insist on doing it in the database (which I'd discourage).