Laravel MySql get attendance for players for match - mysql

Currently I am designing an app for Football Club which organises friendly match every weekend.
The system opens entry for players on Wednesday morning till Thursday 6PM. So players give their availability if they are available for match on that weekend or not and club organises accordingly.
I have 2 tables matches which saves match information like following.
id | date | entry_open_time | entry_close_time | active | deleted | created | modified
Another table is availabilities table which saves players availability
id | match_id | player_id | is_available | deleted | created | modified
How can I calculate who is top of the leaderboard by attendance percentage for the year?
Currently I do the following.
Run 2 queries, one to get total number of matches organised in current year and second get total attendance count per user
Then I calculate percentage of attendance for each user looping through collection.
Then sort the collection.
Is there a better way to do this? May be get the results directly using mysql query?
Total Match count query
Match::where('active', true)->count();
Attendance by user
User::active()->withCount([
'availabilities' => function ($query) {
$query->available();
},
])->orderBy('availabilities_count')->get()
Then I calculate the percentage for each user in collection
$availabilities->map(function ($item) use ($matchCount) {
if ($item['availabilities_count'] && $matchCount) {
$percentage = ($item['availabilities_count'] * 100) / $matchCount;
$item['attendance'] = round($percentage,2);
} else {
$item['attendance'] = 0;
}
return $item;
});
After that I sort the collection by attendance
$availability->sortBy('attendance');
Here is the sample SqlFiddle
How can I get same result with mysql query?
Thank you

I believe in Mysql you can achieve your expected results as
select `u`.*,
ac.availabilities_count,
mc.match_count,
round((ac.availabilities_count/ mc.match_count) * 100,2) attendance
from `users` u
join (
select user_id, count(*) availabilities_count
from `availabilities`
where `is_available` = 1
group by user_id
) ac on `u`.`id` = `ac`.`user_id`
join (
select count(*) match_count
from matches
-- where active = 1
) mc
order by `first_name`, `last_name`
DEMO

Related

Get max price date for a product within a given period (mysql)

My mysql table with prices
+----------+-----+----------+
|product_id|price|date_at |
+----------+-----+----------+
| 1 |41.50|2020-09-01|
| 1 |99.50|2020-09-02|
| 1 |41.50|2020-09-03|
| 1 |41.50|2020-09-04|
| 1 |41.50|2020-09-05|
| 1 |9.90 |2020-09-06|
+----------+-----+----------+
I can get today and yesterday prices:
select today.price,
yesterday.price as yesterday_price
from prices today
left join prices yesterday on today.product_id = yesterday.product_id
where yesterday.date_at = '2020-09-05' <-- I need to have it dynamic
and today.date_at = '2020-09-06'
I need to have yesterday date as any date from period N days with MAX(price).
Example:
yesterday.date_at = last 20 days max price for the same product_id
Maybe I should use more code in left join instead of where.
Please tell me how to change my query.
Apparently what you want to do is something like this:
SELECT MAX(price) FROM prices
WHERE date_at BETWEEN _from_date AND _to_date
AND product_id = _product_id;
You have to hard type the parameters (_from_date; to_date; and _product_id). Should you want to make it dynamic, you will have to think about writing an equivalent stored procedure

Mixing HAVING with CASE OR Analytic functions in MySQL (PartitionQualify(?

I have a SELECT query that returns some fields like this:
Date | Campaign_Name | Type | Count_People
Oct | Cats | 1 | 500
Oct | Cats | 2 | 50
Oct | Dogs | 1 | 80
Oct | Dogs | 2 | 50
The query uses aggregation and I only want to include results where when Type = 1 then ensure that the corresponding Count_People is greater than 99.
Using the example table, I'd like to have two rows returned: Cats. Where Dogs is type 1 it's excluded because it's below 100, in this case where Dogs = 2 should be excluded also.
Put another way, if type = 1 is less than 100 then remove all records of the corresponding campaign name.
I started out trying this:
HAVING CASE WHEN type = 1 THEN COUNT(DISTINCT Count_People) > 99 END
I used Teradata earlier int he year and remember working on a query that used an analytic function "Qualify PartitionBy". I suspect something along those lines is what I need? I need to base the exclusion on aggregation before the query is run?
How would I do this in MySQL? Am I making sense?
Now that I understand the question, I think your best bet will be a subquery to determine which date/campaign combinations of a type=1 have a count_people greater than 99.
SELECT
<table>.date,
<table>.campaign_name,
<table>.type,
count(distinct count_people) as count_people
FROM
(
SELECT
date,
campaign_name
FROM
<table>
WHERE type=1
HAVING count(distinct count_people) > 99
GROUP BY 1,2
) type1
LEFT OUTER JOIN <table> ON
type1.campaign_name = <table>.campaign_name AND
type1.date = <table>.date
WHERE <table>.type IN (1,2)
GROUP BY 1,2,3
The subquery here only returns campaign/date combinations when both the type=1 AND it has greater than 99 count_people. It uses a LEFT JOIN back to the to insure that only those campaign/date combinations make it into the result set.
The WHERE on the main query keeps the results to only Types 1 and 2, which you stated was already a filter in place (though not mentioned in the question, it was stated in a comment to a previous answer).
Based on your comments to answer by #JNevill I think you will have no option but to use subselects to pre-filter the record set you are dealing with, as working with HAVING is going to limit you only to the current record being evaluated - there is no way to compare against previous or subsequent records in the set in this manner.
So have a look at something like this:
SELECT
full_data.date AS date,
full_data.campaign_name AS campaign_name,
full_data.type AS type,
COUNT(full_data.people) AS people_count
FROM
(
SELECT
date,
campaign_name,
type,
COUNT(people) AS people_count
FROM table
WHERE type IN (1,2)
GROUP BY date, campaign_name, type
) AS full_data
LEFT JOIN
(
SELECT
date,
campaign_name,
COUNT(people) AS people_count
FROM table
WHERE type = 1
GROUP BY date, campaign_name
HAVING people_count < 100
) AS filter
ON
full_data.date = filter.date
AND full_data.campaign_name = filter.campaign_name
WHERE
filter.date IS NULL
AND filter.campaign_name IS NULL
The first subselect is basically your current query without any attempt at using HAVING to filter out results. The second subselect is used to find all date/campaign name combos which have people_count > 100 and use those as a filter for against the full data set.

create view or table or create an entry to auto generate a row in another table?

I have a table store all blocktrade ( say over $500,000 per transaction, it could either be bid transaction(direct buy), ask transactino(direct sell), as shown above. I am going to make summary of the results each day. But due to very large amount of data in BlockTrade(hundreds thousand rows of data and even more), I will only make use of the summary data as expected to be done below.
The problem is it is very slow when I select * from [View], shall I create a table storing the data output as historical data is not needed to be query again from BlockTrade Table.
Table:BlockTrade
Code | DateTime | Price | Order(Buy=1, Sell=-1) | Vol | Amount
Part of SQL to sum the relevant transaction of same day same stock:
... -> sum(case when Order = 1 then Amount else 0 end) ... -> convert to results
View: Summary1
Code | Date | Price | TotalBuyVol| TotalBuyAmount | TotalSellVol| TotalSellAmount
By each price of each stock per day, it will have a summary.
View: Summary2
Code | Date | TotalBuyVol| TotalBuyAmount | TotalSellVol| TotalSellAmount | NetAmount
By each stock per day, not by each price, summary on total buy and sell volume and amount.
If you will be accessing the data frequently. You can create views and access the data with a better performance & easily just by a simple select statement.
create view summary1 as (
select CODE, date_format(DateTime,'%d/%m/%Y'), PRICE, sum(buyvol), sum(buyamount),sum(sellvol),sum(sellamount)
from BlockTrade group by CODE, date_format(DateTime,'%d/%m/%Y'), PRICE);
create view summary2 as (
select Code, date_format(DateTime,'%d/%m/%Y'), sum(buyvol), sum(buyamount),sum(sellvol),sum(sellamount), sum(price)
from BlockTrade group by CODE, date_format(DateTime,'%d/%m/%Y')
);
You can access the summary1 & summary2 just as follows:
select * from summary1;
select * from summary2;
Here you go with the sqlfiddle
link

Codeigniter mysql query joining 2 values across 3 tables

Although I can get the joins to work, it doesn't return the lowest deal possible. The result should be lowest handset price possible with the lowest monthly cost. Currently I can get the lowest handset price, but not with the lowest monthly cost and similarly lowest monthly cost but not with the lowest priced handset.
I have 3 tables DEALS, TARIFFS and HANDSETS. When a customer comes to a manufacturer product listing page, it loads all products related to that manufacturer (in most instances around 40 products). What I would like is it to load the cheapest deal available for each product. Tariff table has over 4000 records. Handset table has over 3000 records. Deals table has over 1 million records.
There is a representation of the the 3 tables below with relevant columns:
Handset Table
==================================
id | manufacturer_id
-----------------------------------
100 1
Deals Table
==================================================
tariff_id | handset_id | handset_cost
--------------------------------------------------
15 100 44.99
20 100 114.99
Tariffs Table
==============================
id | monthly_cost
------------------------------
15 12.50
20 7.50
This is the query
$this->db->select('h.name, t.monthly_cost, d.handset_cost');
$this->db->from('aff_deals d');
$this->db->join('aff_tariffs t', 't.id = d.tariff_id', 'inner');
$this->db->join('aff_handsets h', 'h.id = d.handset_id', 'inner');
if (isset($manuid)) {
$this->db->where('h.manufacturer_id', $manuid);
}
$this->db->order_by('d.handset_cost ASC');
$this->db->order_by('t.monthly_cost ASC');
$this->db->group_by('h.name');
$query = $this->db->get();
$result = $query->result();
Unfortunately this returns handset cost of £114.99 and monthly cost of £7.50, when I need £44.99 and £12.50. The example above is a simple snap shot. I have tried MIN(d.handset_cost), sub-queries but cannot get the desired results. Somehow I need to be able to get the lowest price handsets, even if it's 0.00 (FREE), with its equivalent monthly cost. Any help would be most welcome.
According to your query you are misusing Mysql's GROUP BY extension without having any aggregate function this will lead your query to return indeterminate results for columns which are absent in group by taking your query as an example,columns t.monthly_cost, d.handset_cost values are indeterminate if you are specific to pick minimum row from deals table per handset then you can use below query
SELECT h.name,
t.monthly_cost,
d.handset_cost
FROM aff_deals d
INNER JOIN (SELECT handset_id,MIN(handset_cost) handset_cost
FROM aff_deals
GROUP BY handset_id) dd
ON d.handset_id = dd.handset_id AND d.handset_cost = dd.handset_cost
INNER JOIN aff_tariffs t ON t.id = d.tariff_id
INNER JOIN aff_handsets h ON h.id = d.handset_id
WHERE h.manufacturer_id =1
ORDER BY d.handset_cost ASC,t.monthly_cost ASC
See Demo
For active record query it will difficult to replicate above (subselect) query bu you can directly run this query through $this->db->query('your query')

Months calculations inside stored procedure

I need to get a list of months that are not located inside the database.
Example:
Table members
ID | Member's code | Member since
1 | 555-12 | 2012-11-22
Table membership
ID | Code | Paid
1 | 555-12 | 2013-1-1
2 | 555-12 | 2013-3-12
3 | 555-12 | 2013-5-1
Let's say that today is : 2013-11-17
I need to get output like this:
Member's code | Debt ( Months )
555-12 | 11-2012
555-12 | 12-2012
555-12 | 2-2013
555-12 | 4-2013
Is this possible to do with a SQL? Do I need to have a stored procedure where I will pass Member's code?
My idea is to use a number table, that contains just numbers from 0 to 100 or more:
CREATE TABLE numbers (
n INT
);
INSERT INTO numbers (n)
VALUES
(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15)...;
Then you can use a query like this:
SELECT
m.ID,
m.Code,
DATE_FORMAT(m.Member_since + INTERVAL num.n MONTH, '%m-%Y') As Debt_Month_Year
FROM
members m INNER JOIN numbers num
ON TIMESTAMPDIFF(MONTH, m.Member_since, LAST_DAY(CURDATE()))>=num.n
LEFT JOIN membership ms
ON
m.Code = ms.Code
AND
LAST_DAY(ms.Paid)=LAST_DAY(m.Member_since + INTERVAL num.n MONTH)
WHERE
ms.id IS NULL
-- and if you wish, add the following line:
AND m.Code = '555-12'
Please see fiddle here.
select code,
left(since,7) as debt
from user where code='555-12'
union all
select code,
left(date_add(paid, interval -1 MONTH),7) as debt
from paid where code='555-12'
Fiddle
http://sqlfiddle.com/#!2/3d988/1
If you can guarantee that you will only have a consistent one month gap between the entries for each user, then I think timus's solution should work.
If not, I think you'll need to have some sort of date table to determine what values you don't have in the database.
Here's a SQL Fiddle demonstrating what I'm talking about. I intentionally removed one of the rows from your paid table to test skipping more than one month. Basically it gets the month from the member table (the first entry, I guess), and I union that to a second query getting the months that are not in the paid table.
SQL Fiddle
select
left (mbr_since,7) as MNTH
from
user
union
select
mnth
from
months
left outer join paid
on months.mnth = left(paid,7)
where
paid.code is null
The months table is just a row for each month (2013-01, 2013-02, etc). The second query joins to that, and then filters out rows where there is a match in the paid table. Hopefully my explanation is making sense...