How to count multiple columns with mysqli? - php

I am facing a problem I am not capable to solve on my own. There a several questions out that regarding counting but I did no see one where the counting is done like I would need it. In my table I have three slots that can be booked by a Person, represented by their ID, like this:
ID | Slot1 | Slot2 | Slot3
1 45 53
2 1 27 6
3 53
4 6 45
5 15 53
It is possible that slots are free but it is not possible that an ID blocks to slots.
Now I would like to count how often each ID used on of the three slots. The result would look like this for the table above:
ID Count
1 1
6 2
15 1
27 1
45 2
53 3
Is that possible with one mysql statement or do I need to GROUP BY for each slot and add the slots up later in my script?
If it is possible to do the counting in mysql over all three slots, would it also be possible to join the result with a second table that holds the names to the IDs?

You want a union all and aggregation:
select slot, count(*)
from ((select slot1 as slot from t
) union all
(select slot2 as slot from t
) union all
(select slot3 as slot from t
)
) s
where slot is not null
group by slot

You can use UNION :
SELECT s.SLOT_ID, COUNT(*)
FROM (
SELECT slot1 as slot_id FROM YourTable
UNION ALL
SELECT slot2 FROM YourTable
UNION ALL
SELECT slot3 FROM YourTable ) s
GROUP BY s.slot_id

Related

SQL lottery query

I have a simple table of 7 columns
Week ¦ 1st ¦ 2nd ¦ 3rd ¦ 4th ¦ 5th ¦ 6th ¦
Each week, my father adds Saturdays UK lottery numbers to a simple PHP script that I created. He has early onset Alzheimers and tries to keep his brain active. Tonight he asked me a question about the database. He asked me if it was possible to see the 6 most popular numbers.
I tried to create a simple SQL query:
SELECT 1st, 2nd, 3rd, 4th, 5th, 6th, COUNT(*) AS 'foo' FROM `dad` GROUP BY 1st, 2nd, 3rd, 4th, 5th, 6th ORDER BY foo DESC
But the results weren't as I expected.
1st 2nd 3rd 4th 5th 6th foo
2 6 8 32 33 35 1
3 6 12 17 35 40 1
3 6 31 43 46 53 1
etc
What I hoped would happen would be for the table to merge into one column, and then count and have a simple result, something like:
Number Count
2 1
3 2
6 3
8 1
And then maybe put it in ascending order. I can then use that SQL query to create a simple table for him to show the most common numbers.
I'm thinking of doing a general SQL query
SELECT 1st FROM `dad`
Then creating an Array with the results, then adding
SELECT 2nd FROM `dad`
To the end of the Array and continuing for all 6 columns, then using PHP to count the numbers individually.
Is there a quicker way?
Your first effort should go into fixing your data model. Each number should be stored on a separate row rather than in a column, like:
week pos num
1 1 6
1 2 8
1 3 32
Then your query would be a simple aggregate query:
select num, count(*) no_picks from dad group by num order by no_picks desc
For your given table structure, you would need to unpivot the columns to rows. In MySQL, you can use union all for this:
select num, count(*) no_picks
from (
select `1st` num from dad
union all select `2nd` from dad
union all select `3rd` from dad
union all select `4th` from dad
union all select `5th` from dad
union all select `6th` from dad
) t
group by num
order by no_picks
You can do:
select
n, cnt
from (
select
n,
count(*) as cnt
from (
select `1st` as n from `dad` union all
select `2nd` from `dad` union all
select `3rd` from `dad` union all
select `4th` from `dad` union all
select `5th` from `dad` union all
select `6th` from `dad`
) x
group by n
) y
order by cnt desc
limit 6

Get total hours with PHP & MySQL

I have the following table
id | user_id | date | status
1 | 53 | 2018-09-18 06:59:54 | 1
2 | 62 | 2018-09-18 07:00:16 | 1
3 | 53 | 2018-09-18 09:34:12 | 2
4 | 53 | 2018-09-18 12:16:27 | 1
5 | 53 | 2018-09-18 18:03:19 | 2
6 | 62 | 2018-09-18 18:17:41 | 2
I would like to get the total working hours (from date range) and group them by user_id
UPDATE
The system does not "require" a check-out so if there is only one value can we set a default check out time lets say 19:00:00? IF not I can check every day at 21:00:00 if there is not a checkout time to manually insert it at 19:00:00
UPDATE 2
I have added a new field in the table "status" so the very first check-in of the date the status = 1 and every 2nd check-in the status = 2
So if a user check-ins for the 3rd time during the day the status will be 1 again etc.
I hope this will make things easier
Thanks
In case of multiple check-in and check-out happening within a day, for a user:
Utilizing Correlated Subquery, we can find corresponding "checkout_time" for every "checkin_time".
Also, note the usage of Ifnull(), Timestamp() functions etc, to consider default "checkout_time" as 19:00:00, in case of no corresponding entry.
Then, considering this enhanced data-set as Derived Table, we group the data-set based on the user_id and date. Date (yyyy-mm-dd) can be determined using Date() function.
Eventually, use Timestampdiff() function with Sum aggregation, to determine the total work seconds for a user_id at a particular date.
You can easily convert these total seconds to hours (either in your application code, or at the query itself (divide seconds by 3600).
The reason I have preferred to compute using seconds, as Timestampdiff() function returns integer only. So there may be truncation errors, in case of multiple checkin/checkout(s).
Use the following query (replace your_table with your actual table name):
SELECT inner_nest.user_id,
DATE(inner_nest.checkin_time) AS work_date,
SUM(TIMESTAMPDIFF(SECOND,
inner_nest.checkin_time,
inner_nest.checkout_time)) AS total_work_seconds
FROM
(
SELECT t1.user_id,
t1.date as checkin_time,
t1.status,
IFNULL( (
SELECT t2.date
FROM your_table AS t2
WHERE t2.user_id = t1.user_id
AND t2.status = 2
AND t2.date > t1.date
AND DATE(t2.date) = DATE(t1.date)
ORDER BY t2.date ASC LIMIT 1
),
TIMESTAMP(DATE(t1.date),'19:00:00')
) AS checkout_time
FROM `your_table` AS t1
WHERE t1.status = 1
) AS inner_nest
GROUP BY inner_nest.user_id, DATE(inner_nest.checkin_time)
Additional: Following solution will work for the case when there is a single check-in, and corresponding check-out on the same date.
You first need to group the dataset based on the user_id and date. Date (yyyy-mm-dd) can be determined using Date() function.
Now use aggregation functions like Min() and Max() to find the starting and closing time for a user_id at a particular date.
Eventually, use Timestampdiff() function to determine the working hours for a user_id at a particular date (difference between the closing and starting time)
Try the following query (replace your_table with your actual table name):
SELECT user_id,
DATE(`date`) AS working_date,
TIMESTAMPDIFF(HOUR, MIN(`date`), MAX(`date`)) AS working_hours
FROM your_table
GROUP BY
user_id,
DATE(`date`)
Use TIMESTAMPDIFF function
the query more like :
SELECT t1.user_id, TIMESTAMPDIFF(HOUR,t1.date,t2.date) as difference
FROM your_table t1
INNER JOIN your_table t2 on t1.user_id = t2.user_id
Group By t1.user_id
You can see this as preference TimeStampDiff

Get each day of the week as columns MySQL and PHP

I am writing a web application in PHP with MySQL.
I have a table called counts and this is how data is stored in that table:
Table: counts
id counts location_id media_id created_at
--------------------------------------------------
1 50 1 1 2017-03-15
2 30 2 1 2017-03-15
3 80 1 2 2017-03-15
4 20 1 1 2017-03-16
5 100 2 2 2017-03-16
For every unique location_id, media_id and created_at, I store count.
I have another table locations which is like this:
Table: locations
id name
----------------
1 Location 1
2 Location 2
3 Location 3
4 Location 4
5 Location 5
This is the SQL Query I have at the moment:
select sum(counts.count) as views, locations.name as locations, DAYNAME(counts.created_at) AS weekday from `counts` inner join `locations` on `locations`.`id` = `counts`.`location_id` where `counts`.`created_at` between '2016-12-04' and '2016-12-10' group by `weekday`, `counts`.`location_id`;
This is how the data is displayed:
locations weekday views
-----------------------------------
Location 1 Mon 50
Location 1 Tue 30
Location 2 Mon 20
Location 2 Tue 70
I'm creating reports and I would like to run a query such that all the days of the week appear as a column with their corresponding values as the view count for that day of the week. I want something like this:
locations mon tue wed thu fri sat sun
-------------------------------------------------
Location 1 40 60 51 20 40 20 30
Location 2 80 60 100 24 30 10 5
Is the above possible in MySQL or I would have to use PHP to achieve that? If so, how do I go about it?
Any help will be appreciated, thanks.
NB: The sample data is not accurate.
It's possible to achieve this result with MySQL, using conditional aggregation.
The trick is to use a conditional test in an expression in the SELECT list to determine whether to return a value of count.
Something like this:
SELECT l.name AS `locations`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Mon',c.count,0)) AS `mon`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Tue',c.count,0)) AS `tue`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Wed',c.count,0)) AS `wed`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Thu',c.count,0)) AS `thu`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Fri',c.count,0)) AS `fri`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Sat',c.count,0)) AS `sat`
, SUM(IF(DATE_FORMAT(c.created_at,'%a')='Sun',c.count,0)) AS `sun`
FROM `locations` l
LEFT
JOIN `counts` c
ON c.location_id = l.id
AND c.created_at >= '2016-12-04'
AND c.created_at < '2016-12-04' + INTERVAL 7 DAY
GROUP BY l.name
ORDER BY l.name
NOTE:
With the sample data, there are two rows for location_id=1 and created_at='2016-03-15', so this query would return a total of 130 for tue (=50+80), not 50 (as shown in the sample output of the existing query).

MySQL: How can I count visitors per page per day?

I have an interesting query here. I have a table that stores visitor's ip and page_id along with a timestamp (date). I would like to count the amount of visitors per day for each page_id so I can then take that output and calculate which page_id is trending (on a daily basis)
Table visitors_counter looks like this:
id|page_id | ip | date
1 | 37 |1.1.1.1| 2017-02-10 14:03:16
2 | 38 |1.2.1.1| 2017-02-10 11:04:16
3 | 39 |1.1.3.1| 2017-02-10 16:05:16
4 | 37 |1.5.1.1| 2017-02-10 17:08:16
5 | 37 |1.1.1.1| 2017-02-10 19:07:16
And what I would like to achieve would be something like:
id|page_id |visitors | date
1 | 37 |3 | 2017-02-10 14:03:16
2 | 38 |1 | 2017-02-10 11:04:16
3 | 39 |1 | 2017-02-10 16:05:16
So far I've been able to count the amount of unique visitors per day with
SELECT DATE(date) Date, COUNT(DISTINCT page_id) uniqueperday
FROM visitors_counter
GROUP BY DATE(date)
i know im close, but its not quite what I want as I don't know which page_id are the most visited ones
Thanks
for your result you should use the group by and count(*) (you have two times the same id for page 37 and want result 3)
SELECT DATE(date) Date, page_id, COUNT(*) visitperday
FROM visitors_counter
GROUP BY DATE(date), page_id
othewise the Giorgios is the right one
Simply add page_id in the GROUP BY clause:
SELECT DATE(date) Date, page_id, COUNT(DISTINCT ip) uniqueperday
FROM visitors_counter
GROUP BY DATE(date), page_id
The above query returns the number of unique ip visits per page per day.
You can use aggregation on page and date to find count of distinct visitors per page per day. Also, use user variables to generate sequence id.
set #id := 0;
select #id = #id + 1 id,
page_id,
count(distinct ip) visitors,
min(date)
from visitors_counter
group by page_id, DATE(date)
order by visitors desc, page_id;

how to query for at least X results with a specific attribute

I am trying to get a certain amount of rows of which another amount of rows satisfy a specific condition.
I'll explain.
table 1:
ID | NAME
1 | Thomas
2 | Jason
3 | Oleg
4 | Matt
5 | Sheldon
6 | Jenny
table 2:
ID | ACTIVE
1 | 1
2 | 0
3 | 1
4 | 1
5 | 0
6 | 1
Query:
SELECT tbl_1.ID, tbl_1.NAME, tbl_2.ACTIVE
FROM tbl_1 JOIN tbl_2 ON
tbl_1.ID = tbl_2.ID
WHERE tbl_2.ACTIVE=1
LIMIT 5
in this example I would like to get a minimum number of 5 users, of which 3 are active.
of course the query above will not do the job right, as it limits the total rows to 5. But 3 of the rows in the result (or less if no more exist) MUST be active.
the other way I can think of getting this done, is a union, but my query is so cumbersome, long and complex.
Any ideas?
Use ORDER BY instead:
SELECT tbl_1.ID, tbl_1.NAME, tbl_2.ACTIVE
FROM tbl_1 JOIN
tbl_2
ON tbl_1.ID = tbl_2.ID
ORDER BY (tbl_2.ACTIVE = 1) DESC
LIMIT 5;
This puts the active users at the top of the list and then fills in the rest with other users.
Note: The ORDER BY clause could simply be ORDER BY tbl_2.ACTIVE DESC. I left the boolean logic so you could see the similarity to the WHERE clause.
The way to at least x results is to use the count aggregate and the keyword having
select f1, count(*) records
from yourTable
where whatever
group by f1
having count(*) > x

Categories