How to get data status wise after grouping? - mysql

I want to get count of different statuses for bookings for each event and some other data. Each row should represent an event.
So I have an events table and a bookings table.
Events table has id, name, max_allowed
Bookings table has id,event_id,status
Status can be booked, canceled, waitlisted.
I want to get data for all events with the count for each status.
So I need these columns -
event_id
booked_count
canceled_count
waitlisted_count
remaining_slots - (max_allowed - booked_count)
occupancy_rate - booked_count/max_allowed
Sample data:
Events
| id | name | max_allowed |
|---- |--------- |------------- |
| 1 | Yoga | 5 |
| 2 | Boxing | 2 |
| 3 | Pilates | 5 |
Bookings
| id | event_id | status |
|---- |---------- |------------ |
| 1 | 1 | booked |
| 2 | 1 | booked |
| 3 | 2 | booked |
| 4 | 2 | canceled |
| 5 | 2 | booked |
| 6 | 2 | waitlisted |
| 7 | 3 | booked |
| 8 | 3 | booked |
| 9 | 3 | booked |
Output:
| event_id | booked_count | canceled_count | waitlisted_count | remaining_slots | occupancy_rate |
|---------- |-------------- |---------------- |------------------ |----------------- |---------------- |
| 1 | 2 | 0 | 0 | 3 | 0.4 |
| 2 | 2 | 1 | 1 | 0 | 1 |
| 3 | 3 | 0 | 0 | 2 | 0.6 |

Use conditional aggregation:
select t.*,
greatest(0, t.max_allowed - t.booked_count + t.canceled_count - t.waitlisted_count) remaining_slots,
least(t.max_allowed, t.booked_count - t.canceled_count + t.waitlisted_count) / t.max_allowed occupancy_rate
from (
select e.id, e.name, e.max_allowed,
sum(status = 'booked') booked_count,
sum(status = 'canceled') canceled_count,
sum(status = 'waitlisted') waitlisted_count
from Events e left join Bookings b
on b.event_id = e.id
group by e.id, e.name, e.max_allowed
) t

Try below query.
with src_data as
(select
event_id,
sum(case when status='booked' then 1 else 0 end ) as booked_count,
sum(case when status='canceled' then 1 else 0 end ) as canceled_count,
sum(case when status='waitlisted' then 1 else 0 end ) as waitlisted_count
from bookings group by event_id
)
select
s.event_id,
s.booked_count,
s.canceled_count,
s.waitlisted_count,
e.max_allowed-s.booked_count,
s.booked_count/e.max_allowed
from events e inner join src_data s
on e.id=s.event_id;

Related

How to select values from the previous row based on two columns where the value columns can alternate

From the following game table of sports matches:
+-----+-----------+---------------+----------+-------+-------+---------+---------+
| id_ | date_time | tournament_id | round_id | p1_id | p2_id | p1_stat | p2_stat |
+-----+-----------+---------------+----------+-------+-------+---------+---------+
| 1 | NULL | 1 | 4 | 1 | 3 | 2 | 3 |
| 2 | NULL | 1 | 5 | 1 | 4 | 4 | 6 |
| 3 | NULL | 1 | 9 | 1 | 5 | 6 | 9 |
| 4 | NULL | 1 | 10 | 2 | 1 | 8 | 12 |
| 5 | NULL | 2 | 4 | 1 | 2 | 10 | 15 |
| 6 | NULL | 2 | 5 | 4 | 1 | 12 | 18 |
+-----+-----------+---------------+----------+-------+-------+---------+---------+
I'm trying to get the stats for each player for their previous match. The output should look like this:
+-----+--------------+--------------+
| id_ | prev_p1_stat | prev_p2_stat |
+-----+--------------+--------------+
| 1 | NULL | NULL |
| 2 | 2 | NULL |
| 3 | 4 | NULL |
| 4 | NULL | 6 |
| 5 | 12 | 8 |
| 6 | 6 | 10 |
+-----+--------------+--------------+
However, the date_time column is quite often blank and id_ is not date sequential. The tournament table does have a date_time column which is always populated:
+-----+------------+
| id_ | date_time |
+-----+------------+
| 1 | 1997-01-01 |
| 2 | 1997-01-06 |
+-----+------------+
This means the tournament date_time can be used in conjunction with game round_id to determine the previous match.
I've found the following answers here and here but they both focus on a single table and don't have the added complexity of having to determine whether the p1_stat or the p2_stat should be selected.
I've got as far as this query:
SELECT
g.id_ AS game_id,
CASE
WHEN g.p1_id = sq_p1.p1_id THEN sq_p1.p1_stat
ELSE sq_p1.p2_stat
END AS prev_p1_stat,
CASE
WHEN g.p1_id = sq_p2.p1_id THEN sq_p2.p1_stat
ELSE sq_p2.p2_stat
END AS prev_p2_stat
FROM
test.game AS g
JOIN
test.tournament AS t ON t.id_ = g.tournament_id
LEFT OUTER JOIN
(SELECT
g.id_ AS match_id,
t.date_time AS tournament_date,
g.round_id,
g.p1_id,
g.p2_id,
g.p1_stat,
g.p2_stat
FROM
test.game AS g
JOIN test.tournament AS t ON t.id_ = g.tournament_id) AS sq_p1 ON (sq_p1.p1_id = g.p1_id
OR sq_p1.p2_id = g.p1_id)
AND (sq_p1.tournament_date = t.date_time
AND sq_p1.round_id < g.round_id
OR sq_p1.tournament_date < t.date_time)
LEFT OUTER JOIN
(SELECT
g.id_ AS match_id,
t.date_time AS tournament_date,
g.round_id,
g.p1_id,
g.p2_id,
g.p1_stat,
g.p2_stat
FROM
test.game AS g
JOIN test.tournament AS t ON t.id_ = g.tournament_id) AS sq_p2 ON (sq_p2.p1_id = g.p1_id
OR sq_p2.p2_id = g.p1_id)
AND (sq_p2.tournament_date = t.date_time
AND sq_p2.round_id < g.round_id
OR sq_p2.tournament_date < t.date_time)
ORDER BY t.date_time , g.round_id
But this isn't even close to what I'm looking for :(
I've created a dbfiddle.
One other thing that's perhaps worth mentioning... I intend to use a couple of versions of this query in a union query such that the final result (including all columns for reference) will look like this:
+-----+------------+-----------+-------------+-------------+---------------+------------------+--------------------+
| id_ | player_num | player_id | opponent_id | player_stat | opponent_stat | player_prev_stat | opponent_prev_stat |
+-----+------------+-----------+-------------+-------------+---------------+------------------+--------------------+
| 1 | 1 | 1 | 3 | 2 | 3 | NULL | NULL |
| 1 | 2 | 3 | 1 | 3 | 2 | NULL | NULL |
| 2 | 1 | 1 | 4 | 4 | 6 | 2 | NULL |
| 2 | 2 | 4 | 1 | 6 | 4 | NULL | 2 |
| 3 | 1 | 1 | 5 | 6 | 9 | 4 | NULL |
| 3 | 2 | 5 | 1 | 9 | 6 | NULL | 4 |
| 4 | 1 | 2 | 1 | 8 | 12 | NULL | 6 |
| 4 | 2 | 1 | 2 | 12 | 8 | 6 | NULL |
| 5 | 1 | 1 | 2 | 10 | 15 | 12 | 8 |
| 5 | 2 | 2 | 1 | 15 | 10 | 8 | 12 |
| 6 | 1 | 4 | 1 | 12 | 18 | 6 | 10 |
| 6 | 2 | 1 | 4 | 18 | 12 | 10 | 6 |
+-----+------------+-----------+-------------+-------------+---------------+------------------+--------------------+
Perhaps it makes more sense to do a union and then engineer the previous stats?
For some final info, the actual game table has about 1.5m rows and the actual tournament table has about 30k rows. I'm using MySQL 8.0.26.
Kudos to #Barmer for the direction - here's the query I created using LAG():
WITH union_matches AS (
SELECT
g.id_ AS match_id,
t.date_time AS tournament_date,
g.round_id AS round_id,
1 AS player_num,
g.p1_id AS player_id,
g.p2_id AS opponent_id,
g.p1_stat AS player_stat,
g.p2_stat AS opponent_stat
FROM
game AS g
JOIN
tournament AS t ON t.id_ = g.tournament_id
UNION SELECT
g.id_ AS match_id,
t.date_time AS tournament_date,
g.round_id AS round_id,
2 AS player_num,
g.p2_id AS player_id,
g.p1_id AS opponent_id,
g.p2_stat AS player_stat,
g.p1_stat AS opponent_stat
FROM
game AS g
JOIN
tournament AS t ON t.id_ = g.tournament_id
)
SELECT
match_id,
player_num,
player_id,
opponent_id,
player_stat,
opponent_stat,
LAG(player_stat, 1) OVER (PARTITION BY player_id ORDER BY tournament_date, round_id) AS wrong_player_prev_stat,
LAG(opponent_stat, 1) OVER (PARTITION BY opponent_id ORDER BY tournament_date, round_id) AS wrong_opponent_prev_stat
FROM
union_matches
ORDER BY
tournament_date, round_id, player_num
And a link to the dbfiddle.

Group data by foreign key and date with total by date

I need help to select daily payments made and group by the organization and date.
Group by date, then the total number of payments and the sum total amount of payments for each day
Tables are as follows,
organizations
-----------------------------
| id | name |
+-------------+-------------+
| 1 | org_1 |
+-------------+-------------+
| 2 | org_2 |
+-------------+-------------+
| 3 | org_2 |
-----------------------------
payments
------------------------------------------------------------
| id | org_id | amount | date_created |
+-----------+------------+-------------+-------------------+
| 1 | 2 | 20 | 2020-11-06 |
+-----------+------------+-------------+-------------------+
| 2 | 2 | 10 | 2020-11-06 |
+-----------+------------+-------------+-------------------+
| 3 | 1 | 50 | 2020-11-05 |
+-----------+------------+-------------+-------------------+
| 4 | 2 | 10 | 2020-11-05 |
------------------------------------------------------------
Expected Result
----------------------------------------------------------------------------------------------
| date_created | total_amount | num_payments | org_1 | org_2 | org_3 |
+----------------+----------------+-------------------+-----------+-------------+------------+
| 2020-11-06 | 30.00 | 2 | 0 | 2 | 0 |
+----------------+----------------+-------------------+-----------+-------------+------------+
| 2020-11-05 | 60.00 | 2 | 1 | 1 | 0 |
+----------------+----------------+-------------------+-----------+-------------+------------+
Use conditional aggregation:
select p.date_created,
sum(p.amount) as total_amount,
count(*) as num_payments,
sum(case when o.name = 'org_1' then p.amount else 0 end) as org_1,
sum(case when o.name = 'org_2' then p.amount else 0 end) as org_2,
sum(case when o.name = 'org_3' then p.amount else 0 end) as org_3
from payments p
inner join organizations o on o.id = p.org_id
group by p.date_created

How to select only first matching value from another table in MySQL?

Here is my database schema:
Payment table:
+------------+--------+--------+---------------------+
| payment_id | tab_id | amount | created |
+------------+--------+--------+---------------------+
| 1 | 1 | 5 | 2017-05-22 12:14:27 |
| 2 | 2 | 10 | 2017-05-22 12:15:21 |
| 3 | 2 | 1 | 2017-05-22 13:11:14 |
+------------+--------+--------+---------------------+
Tab table:
+------------+----------------+
| tab_id | service_charge |
+------------+----------------+
| 1 | 1 |
| 2 | 3 |
+------------+----------------+
I need to calculate total amounts (amount + service_charge) per payment, but service_charge should be included only in first payment matching tab_id.
My current query:
SELECT
payment.payment_id,
(payment.amount + tab.service_charge) as total_amount,
payment.created
FROM payment
INNER JOIN tab
ON payment.tab_id = tab.tab_id;
Actual result:
As you can see below service_charge from tab_id = 2 included twice (payment_id = 2 and payment_id = 3).
+------------+-----------------+---------------------+
| payment_id | total_amount | created |
+------------+-----------------+---------------------+
| 1 | 6 | 2017-05-22 12:14:27 |
| 2 | 13 | 2017-05-22 12:15:21 |
| 3 | 4 | 2017-05-22 13:11:14 |
+------------+-----------------+---------------------+
Expected result:
total_amount should not include service_charge in payment_id = 3 as shown below.
+------------+-----------------+---------------------+
| payment_id | total_amount | created |
+------------+-----------------+---------------------+
| 1 | 6 | 2017-05-22 12:14:27 |
| 2 | 13 | 2017-05-22 12:15:21 |
| 3 | 1 | 2017-05-22 13:11:14 |
+------------+-----------------+---------------------+
You should determine which is the first payment matching the tab_id and then based on that info, decide if you want to use the service_charge or not:
SELECT
payment.payment_id,
payment.amount + if (payment.created=m.mintime, tab.service_charge, 0) as total_amount,
payment.created
FROM payment
INNER JOIN tab
ON payment.tab_id = tab.tab_id
JOIN (
SELECT tab_id, min(created) as 'mintime'
FROM payment
GROUP BY tab_id
) AS m on m.tab_id = payment.tab_id;

How can I order a table from another table's column then run a query?

I'm building a website for our ball team for the fun of it and keeping track of stats using PHP and SQL for the database. I've learned both by reading the manuals and through forums. I'm working on building a query that will display the current longest hitting streak. I stumbled across a page about detecting runs and streaks and am trying to work with that. I'm really new to all this stuff, so maybe I've structured my tables incorrectly.
Table "games"
+--------+------------+------+
| GameID | Date | Time |
+--------+------------+------+
| 1 | 2015/08/19 | 6:30 |
| 2 | 2015/08/20 | 6:30 |
| 3 | 2015/08/22 | 6:30 |
| 4 | 2015/08/24 | 8:00 |
| 5 | 2015/08/24 | 6:30 |
| 6 | 2015/07/15 | 8:00 |
+--------+------------+------+
Table "player"
+--------+----+---+
| GameID | AB | H |
+--------+----+---+
| 1 | 3 | 1 |
| 2 | 4 | 2 |
| 3 | 2 | 0 |
| 4 | 3 | 0 |
| 5 | 2 | 1 |
| 6 | 3 | 0 |
+--------+----+---+
Code
SELECT games.GameID, GR.H,
(SELECT COUNT(*)
FROM player G
WHERE (CASE WHEN G.H > 0 THEN 1 ELSE 0 END) <> (CASE WHEN GR.H > 0 THEN 1 ELSE 0 END)
AND G.GameID <= GR.GameID) as RunGroup
FROM player GR
INNER JOIN games
ON GR.gameID = games.GameID
ORDER BY Date ASC, Time ASC
Basically in order to correctly get the hit streak right, I need to reorder the GameIDs on the "player" table based on the Date (ASC) and Time (ASC) on the "games" table before executing the RunGroup part of the code. Obviously by adding the ORDER BY, everything gets sorted only after the RunGroup has finished querying and results in incorrect data. I've been stuck here for a few days and now need some help.
The Result I currently get is:
+--------+---+----------+
| GameID | H | RunGroup |
+--------+---+----------+
| 6 | 0 | 3 |
| 1 | 1 | 0 |
| 2 | 2 | 0 |
| 3 | 0 | 2 |
| 5 | 1 | 2 |
| 4 | 0 | 2 |
+--------+---+----------+
This is what I'm trying to achieve:
+--------+---+----------+
| GameID | H | RunGroup |
+--------+---+----------+
| 6 | 0 | 0 |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 0 | 2 |
| 5 | 1 | 2 |
| 4 | 0 | 3 |
+--------+---+----------+
Thanks
Consider the following:
DROP TABLE IF EXISTS games;
CREATE TABLE games
(game_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,date_played DATETIME NOT NULL
);
INSERT INTO games VALUES
(1,'2015/08/19 18:30:00'),
(2,'2015/08/20 18:30:00'),
(3,'2015/08/22 18:30:00'),
(4,'2015/08/24 20:00:00'),
(5,'2015/08/24 18:30:00'),
(6,'2015/07/15 20:00:00');
DROP TABLE IF EXISTS stats;
CREATE TABLE stats
(player_id INT NOT NULL
,game_id INT NOT NULL
,at_bat INT NOT NULL
,hits INT NOT NULL
,PRIMARY KEY(player_id,game_id)
);
INSERT INTO stats VALUES
(1,1,3,1),
(1,2,4,2),
(1,3,2,0),
(1,4,3,0),
(1,5,2,1),
(1,6,3,0),
(2,1,2,1),
(2,2,3,2),
(2,3,3,0),
(2,4,3,1),
(2,5,2,1),
(2,6,3,0);
SELECT x.*
, SUM(y.at_bat) runningAB
, SUM(y.hits) runningH
, SUM(y.hits)/SUM(y.at_bat) BA
FROM
(
SELECT s.*, g.date_played FROM stats s JOIN games g ON g.game_id = s.game_id
) x
JOIN
(
SELECT s.*, g.date_played FROM stats s JOIN games g ON g.game_id = s.game_id
) y
ON y.player_id = x.player_id
AND y.date_played <= x.date_played
GROUP
BY x.player_id
, x.date_played;
+-----------+---------+--------+------+---------------------+-----------+----------+--------+
| player_id | game_id | at_bat | hits | date_played | runningAB | runningH | BA |
+-----------+---------+--------+------+---------------------+-----------+----------+--------+
| 1 | 6 | 3 | 0 | 2015-07-15 20:00:00 | 3 | 0 | 0.0000 |
| 1 | 1 | 3 | 1 | 2015-08-19 18:30:00 | 6 | 1 | 0.1667 |
| 1 | 2 | 4 | 2 | 2015-08-20 18:30:00 | 10 | 3 | 0.3000 |
| 1 | 3 | 2 | 0 | 2015-08-22 18:30:00 | 12 | 3 | 0.2500 |
| 1 | 5 | 2 | 1 | 2015-08-24 18:30:00 | 14 | 4 | 0.2857 |
| 1 | 4 | 3 | 0 | 2015-08-24 20:00:00 | 17 | 4 | 0.2353 |
| 2 | 6 | 3 | 0 | 2015-07-15 20:00:00 | 3 | 0 | 0.0000 |
| 2 | 1 | 2 | 1 | 2015-08-19 18:30:00 | 5 | 1 | 0.2000 |
| 2 | 2 | 3 | 2 | 2015-08-20 18:30:00 | 8 | 3 | 0.3750 |
| 2 | 3 | 3 | 0 | 2015-08-22 18:30:00 | 11 | 3 | 0.2727 |
| 2 | 5 | 2 | 1 | 2015-08-24 18:30:00 | 13 | 4 | 0.3077 |
| 2 | 4 | 3 | 1 | 2015-08-24 20:00:00 | 16 | 5 | 0.3125 |
+-----------+---------+--------+------+---------------------+-----------+----------+--------+
I rebuilt my database to have only one table to contain the stats from all players. From there i was able to use this query to find my longest current hitting streak for a certain player.
SELECT *
FROM (SELECT (CASE WHEN h > 0 THEN 1 ELSE 0 END) As H, MIN(date_played) as StartDate,
MAX(date_played) as EndDate, COUNT(*) as Games
FROM (SELECT date_played, (CASE WHEN h > 0 THEN 1 ELSE 0 END) as H, (SELECT COUNT(*)
FROM stats G WHERE ((CASE WHEN G.h > 0 THEN 1 ELSE 0 END) <> (CASE WHEN GR.h > 0 THEN 1 ELSE 0 END))
AND G.date_played <= GR.date_played AND player_id = 13) as RunGroup
FROM stats GR
WHERE player_id = 13) A
GROUP BY H, RunGroup
ORDER BY Min(date_played)) A
WHERE H = 1
ORDER BY Games DESC
LIMIT 1

Select total members and amount paid

I need help generating SQL for MySQL database.
I have three tables:
Organisations
Members
Payments
Organisations table:
+------------+---------+--------+
| id | name |website |
+------------+---------+--------+
| 1 | AAA | a.com |
|-------------------------------+
| 2 | BBB | b.com |
+------------+---------+--------+
Members table:
+------------+-------------------+--------+-----------------+-----------+
| id | organisation_id |name | Payment_confirm | join_date |
+------------+-------------------+--------+-----------------+-----------+
| 1 | 1 | james | 1 | 2013-8-02 |
|-----------------------------------------+-----------------+-----------+
| 2 | 1 | Jimmy | 0 | 2013-6-25 |
+------------+-------------------+--------+-----------------+-----------+
| 3 | 2 | Manny | 1 | 2013-07-02|
|-----------------------------------------+-----------------+-----------+
| 4 | 1 | Kim | 1 | 2013-09-02|
+------------+-------------------+--------+-----------------+-----------+
Payments table:
+------------+-------------------+--------+-----------------+----------------+
| id | member_id |amount | transaction_id | transferred_at |
+------------+-------------------+--------+-----------------+----------------+
| 1 | 1 | 100 | T1001 | 2013-8-03 |
|-----------------------------------------+-----------------+--------------- +
| 2 | 2 | 0 | null | Null |
+------------+-------------------+--------+-----------------+----------------+
| 3 | 3 | 200 | T1002 | Null |
|-----------------------------------------+-----------------+----------------+
| 4 | 4 | 50 | T1005 | 2013-09-05 |
+------------+-------------------+--------+-----------------+----------------+
How can I select the following?
Expecting the following output:
+------------+-------------------+--------+-----------------+---------------+--------------+
| Org name | Revenue |untransferred amount | Total members | last 30 days |
+------------+-------------------+--------------------------+---------------+--------------+
| AAA | 150 | 0 | 3 | 2 |
|-----------------------------------------------------------+---------------+--------------+
| BBB | 200 | 200 | 1 | 0 |
+------------+-------------------+--------------------------+---------------+--------------+
Org name = organisation name
Revenue = Total amount received
untransferred amount = transferred_at is null (payments table)
Total members = total members joined till today
last 30 days = total members joined last 30 days
You need to join your tables, group the results and select the desired logic:
SELECT org.name,
SUM(pmt.amount) AS revenue,
SUM(IF(pmt.transferred_at IS NULL, pmt.amount, 0)) AS untransferred
FROM Organisations org
JOIN Members mem ON mem.organisation_id = org.id
JOIN Payments pmt ON pmt.member_id = mem.id
GROUP BY org.id
See it on sqlfiddle.
select o.name,
sum(amount) as Revenue,
sum(if(transferred_at is null, amount, 0)) as untransfered_ammt,
sum(if(join_date>=curdate() - interval 30 day, 1, 0)) as last_30_d
from organisations o
inner join members m on o.id=m.organisation_id
inner join payments p on p.member_id=m.member_id
group by 1