Complex MySQL query between two tables - mysql

I've a real complex query here, at least for me.
Here's a table with car dates releases (where model_key = 320D):
+------------+-----------+
| date_key | model_key |
+------------+-----------+
| 2003-08-13 | 320D |
| 2005-11-12 | 320D |
| 2007-02-11 | 320D |
+------------+----------+
Then I have a table with daily purchases (where model_key = 320D):
+------------+-----------+-----------+
| date_key | model_key | sal_quant |
+------------+-----------+ ----------+
| 2003-08-13 | 320D | 0 |
| 2003-08-14 | 320D | 1 |
| 2003-08-15 | 320D | 2 |
| 2003-08-16 | 320D | 0 |
...
| 2005-11-12 | 320D | 2 |
| 2005-11-13 | 320D | 0 |
| 2005-11-14 | 320D | 4 |
| 2005-11-15 | 320D | 3 |
...
| 2007-02-11 | 320D | 1 |
| 2007-02-12 | 320D | 0 |
| 2007-02-13 | 320D | 0 |
| 2007-02-14 | 320D | 0 |
...
+------------+-----------+-----------|
I want to know the sum of car sales by day after each release during N days.
I want a table like (assuming 4 days analysis):
+-----------------+
| sum(sal_quant) |
+-----------------+
| 3 |
| 1 |
| 6 |
| 3 |
+-----------------+
Which means, in the first day of car release, 3 BMW 320D were sold, in the second day, just one,... an so on.
Now I have the following query:
SELECT SUM(sal_quant)
FROM daily_sales
WHERE model_key='320D'
AND date_key IN (
SELECT date_key FROM car_release_dates WHERE model_key='320D')
But this only gives me the sum for the first release day. How can I get the next days?
Thanks.

I suppose there's no way a car could be sold before its release date. So you could simply group on the date_key:
SELECT date_key, SUM(sal_quant)
FROM daily_sales
WHERE model_key='320D'
GROUP BY date_key
ORDER BY date_key DESC
LIMIT 4
If it is possible to sell cars before the release date, add a WHERE clause:
AND date_key >= (
SELECT date_key FROM car_release_dates WHERE model_key='320D')
EDIT: Per your comment, you can constrain the query to within four days of multiple release dates with an INNER JOIN. For example:
SELECT ds.date_key, SUM(ds.sal_quant)
FROM daily_sales ds
INNER JOIN car_release_dates crd
ON crd.model_key = ds.model_key
AND ds.date_key BETWEEN crd.date_key
AND DATE_ADD(crd.date_key, INTERVAL 4 DAY)
WHERE ds.model_key='320D'
GROUP BY ds.date_key
ORDER BY ds.date_key DESC

Related

How to get Total Overdraft amounts from a particular Date in SQL

I'm trying to get the total amount of overdraft accounts from an old Date, the goal is to get the total amount it was on the 31st of January.
I have the following tables Users and Transactions.
USERS (currently)
| user_id | name | account_balance |
|---------|---------|------------------|
| 1 | Wells | 1.00 |
| 2 | John | -10.00 |
| 3 | Sahar | -5.00 |
| 4 | Peter | 1.00 |
TRANSACTIONS (daily transition can go back in time)
| trans_id | user_id | amount_tendered | trans_datetime |
|------------|---------|-------------------|---------------------|
| 1 | 1 | 2 | 2021-02-16 |
| 2 | 2 | 3 | 2021-02-16 |
| 3 | 3 | 5 | 2021-02-16 |
| 4 | 4 | 2 | 2021-02-16 |
| 5 | 1 | 10 | 2021-02-15 |
so the current total overdraft amount is
SELECT sum(account_balance) AS O_D_Amount
FROM users
WHERE account_balance < 0;
| O_D_Amount |
|------------|
| -15 |
I need Help to reverse this amount to a date in history.
Assuming overdrafts are based on the sum of transactions up to a point, you can use a subquery:
select sum(total) as total_overdraft
from (select user_id, sum(amount_tendered) as total
from transactions t
where t.trans_datetime <= ?
group by user_id
) t
where total < 0;
The ? is a parameter placeholder for the date/time you care about.

How to sum values of two tables and group by date

I am building a trading system where users need to know their running account balance by date for a specific user (uid) including how much they made from trading (results table) and how much they deposited or withdrew from their accounts (adjustments table).
Here is the sqlfiddle and tables: http://sqlfiddle.com/#!9/6bc9e4/1
Adjustments table:
+-------+-----+-----+--------+------------+
| adjid | aid | uid | amount | date |
+-------+-----+-----+--------+------------+
| 1 | 1 | 1 | 20 | 2019-08-18 |
| 2 | 1 | 1 | 50 | 2019-08-21 |
| 3 | 1 | 1 | 40 | 2019-08-21 |
| 4 | 1 | 1 | 10 | 2019-08-19 |
+-------+-----+-----+--------+------------+
Results table:
+-----+-----+-----+--------+-------+------------+
| tid | uid | aid | amount | taxes | date |
+-----+-----+-----+--------+-------+------------+
| 1 | 1 | 1 | 100 | 3 | 2019-08-19 |
| 2 | 1 | 1 | -50 | 1 | 2019-08-20 |
| 3 | 1 | 1 | 100 | 2 | 2019-08-21 |
| 4 | 1 | 1 | 100 | 2 | 2019-08-21 |
+-----+-----+-----+--------+-------+------------+
How do I get the below results for uid (1)
+--------------+------------+------------------+----------------+------------+
| ResultsTotal | TaxesTotal | AdjustmentsTotal | RunningBalance | Date |
+--------------+------------+------------------+----------------+------------+
| - | - | 20 | 20 | 2019-08-18 |
| 100 | 3 | 10 | 133 | 2019-08-19 |
| -50 | 1 | - | 84 | 2019-08-20 |
| 200 | 4 | 90 | 378 | 2019-08-21 |
+--------------+------------+------------------+----------------+------------+
Where RunningBalance is the current account balance for the particular user (uid).
Based on #Gabriel's answer, I came up with something like, but it gives me empty balance and duplicate records
SELECT SUM(ResultsTotal), SUM(TaxesTotal), SUM(AdjustmentsTotal), #runningtotal:= #runningtotal+SUM(ResultsTotal)+SUM(TaxesTotal)+SUM(AdjustmentsTotal) as Balance, date
FROM (
SELECT 0 AS ResultsTotal, 0 AS TaxesTotal, adjustments.amount AS AdjustmentsTotal, adjustments.date
FROM adjustments LEFT JOIN results ON (results.uid=adjustments.uid) WHERE adjustments.uid='1'
UNION ALL
SELECT results.amount AS ResultsTotal, taxes AS TaxesTotal, 0 as AdjustmentsTotal, results.date
FROM results LEFT JOIN adjustments ON (results.uid=adjustments.uid) WHERE results.uid='1'
) unionTable
GROUP BY DATE ORDER BY date
For what you are asking you would want to union then group the results from both tables, this should give the results you want. However, I recommend calculating the running balance outside of MySQL since this adds some complexity to our query.
Weird things could start to happen, for example, if someone already defined the #runningBalance variable as part of the queries scope.
SELECT aggregateTable.*, #runningBalance := ifNULL(#runningBalance, 0) + TOTAL
FROM (
SELECT SUM(ResultsTotal), SUM(TaxesTotal), SUM(AdjustmentsTotal)
, SUM(ResultsTotal) + SUM(TaxesTotal) + SUM(AdjustmentsTotal) as TOTAL
, date
FROM (
SELECT 0 AS ResultsTotal, 0 AS TaxesTotal, amount AS AdjustmentsTotal, date
FROM adjustments
UNION ALL
SELECT amount AS ResultsTotal, taxes AS TaxesTotal, 0 as AdjustmentsTotal, date
FROM results
) unionTable
GROUP BY date
) aggregateTable

MySQL - select average of column A for first N entries from column B

I have a ratings table, where each user can add one rating a day. But each user might miss several days between ratings.
I'd like to get the average rating for each user_id's first 7 entries of created_at.
My table:
mysql> desc entries;
+------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| rating | tinyint(4) | NO | | NULL | |
| user_id | int(10) unsigned | NO | MUL | NULL | |
| created_at | timestamp | YES | | NULL | |
+------------+------------------+------+-----+---------+----------------+
Ideally I'd just get something like:
+------------+------------------+
| day | average_rating |
+------------+------------------+
| 1 | 2.53 |
+------------+------------------+
| 2 | 4.30 |
+------------+------------------+
| 3 | 3.67 |
+------------+------------------+
| 4 | 5.50 |
+------------+------------------+
| 5 | 7.23 |
+------------+------------------+
| 6 | 6.98 |
+------------+------------------+
| 7 | 7.22 |
+------------+------------------+
The closest I've been able to get is:
SELECT rating, user_id, created_at FROM entries ORDER BY user_id asc, created at desc
Which isn't very close at all...
Is it even possible? Will the performance be terrible? It's something that would need to run every time a web page is loaded, so would it be better to just run this once a day and save the results? (to another table!?)
edit - second attempt
Working towards a solution, I think this would get the rating for each user's first day:
select rating from entries where user_id in
(select user_id from entries order by created_at limit 1);
But I get:
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
So now I'm going to play around with JOIN to see if that helps.
edit - third attempt, getting closer
I found this stackoverflow post, which is closer to what I want.
select e1.* from entries e1 left join entries e2
on (e1.user_id = e2.user_id and e1.created_at > e2.created_at)
where e2.id is null;
It gets the rating for the first day for each user.
Next step is to work out how to get days 2 to 7. I can't use 1.created_at > e2.created_at for that, so I'm really confused now.
edit - fourth attempt
Okay, I think it's not possible. Once I worked out how to turn off 'full group by' mode, I realised I'll probably need to use a subquery with limit <user_id>, <day_num>, for which I get:
ERROR 1235 (42000): This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
My current method is to just get the entire table, and use PHP to calculate the average for each day.
If I understand correctly you want to take the last 7 ratings the user gave, ordered by the date they gave the rating. The last 7 ratings of one user may fall on different days to another user, however they will be averaged together regardless of date.
First we need to order the data by user and date and give each user their own incrementing row count. I do this by adding two variables, one for the last user id and one for the row number:
select e.created_at,
e.rating,
if(#lastUser=user_id,#row := #row+1, #row:=1) as row,
#lastUser:= e.user_id as user_id
from entries e,
( select #row := 0, #lastUser := 0 ) vars
order by e.user_id asc,
e.created_at desc;
If the previous user_id is different we reset the row counter to 1. The result from this is:
+---------------------+--------+------+---------+
| created_at | rating | row | user_id |
+---------------------+--------+------+---------+
| 2017-01-10 00:00:00 | 1 | 1 | 1 |
| 2017-01-09 00:00:00 | 1 | 2 | 1 |
| 2017-01-08 00:00:00 | 1 | 3 | 1 |
| 2017-01-07 00:00:00 | 1 | 4 | 1 |
| 2017-01-06 00:00:00 | 1 | 5 | 1 |
| 2017-01-05 00:00:00 | 1 | 6 | 1 |
| 2017-01-04 00:00:00 | 1 | 7 | 1 |
| 2017-01-03 00:00:00 | 1 | 8 | 1 |
| 2017-01-02 00:00:00 | 1 | 9 | 1 |
| 2017-01-01 00:00:00 | 1 | 10 | 1 |
| 2017-01-13 00:00:00 | 1 | 1 | 2 |
| 2017-01-11 00:00:00 | 1 | 2 | 2 |
| 2017-01-09 00:00:00 | 1 | 3 | 2 |
| 2017-01-07 00:00:00 | 1 | 4 | 2 |
| 2017-01-05 00:00:00 | 1 | 5 | 2 |
| 2017-01-03 00:00:00 | 1 | 6 | 2 |
| 2017-01-01 00:00:00 | 1 | 7 | 2 |
| 2017-01-13 00:00:00 | 1 | 1 | 3 |
| 2017-01-01 00:00:00 | 1 | 2 | 3 |
| 2017-01-03 00:00:00 | 1 | 1 | 4 |
| 2017-01-01 00:00:00 | 1 | 2 | 4 |
| 2017-01-02 00:00:00 | 1 | 1 | 5 |
+---------------------+--------+------+---------+
We now simply wrap this in another statement to select the avg where the row number is less than or equal to seven.
select e1.row day, avg(e1.rating) avg
from (
select e.created_at,
e.rating,
if(#lastUser=user_id,#row := #row+1, #row:=1) as row,
#lastUser:= e.user_id as user_id
from entries e,
( select #row := 0, #lastUser := 0 ) vars
order by e.user_id asc,
e.created_at desc) e1
where e1.row <=7
group by e1.row;
This outputs:
+------+--------+
| day | avg |
+------+--------+
| 1 | 1.0000 |
| 2 | 1.0000 |
| 3 | 1.0000 |
| 4 | 1.0000 |
| 5 | 1.0000 |
| 6 | 1.0000 |
| 7 | 1.0000 |
+------+--------+

MySQL select a row from a daterange excluding the year

I'm trying to create a MySQL query to select the daily price from a table that is between a date range from another. I only want to use 'starting-ending' months and days from the table "seasons" and I want to pass the year dynamically to the query.
This is my query: (I'm giving it the Year to exclude the one on the table)
SELECT a.season, b.base_price
FROM seasons a
JOIN pricebyseason b ON a.id=b.season_id
WHERE b.prop_id='6' AND '2015-11-29' BETWEEN DATE_FORMAT(a.starting,'2015-%m-%d') AND DATE_FORMAT(a.ending,'2016-%m-%d')
ORDER BY b.base_price DESC
It works but not with all dates.
These are the tables:
seasons (these are static date values)
+----+--------------+------------+------------+
| id | season | starting | ending |
+----+--------------+------------+------------+
| 1 | Peak Season | 2015-12-11 | 2016-01-09 |
| 2 | High Season | 2015-11-27 | 2016-04-15 |
| 3 | Mid Season | 2015-04-16 | 2015-09-01 |
| 4 | Low Season | 2015-09-02 | 2015-11-26 |
| 5 | Spring Break | 2015-03-05 | 2015-03-21 |
+----+--------------+------------+------------+
pricebyseason
+----+---------+-----------+------------+
| id | prop_id | season_id | base_price |
+----+---------+-----------+------------+
| 1 | 6 | 1 | 950 |
| 2 | 6 | 2 | 750 |
| 3 | 6 | 3 | 450 |
| 4 | 6 | 4 | 400 |
| 5 | 6 | 5 | 760 |
+----+---------+-----------+------------+
What I want to achive is query the dialy price between checkin, checkout selection
I create this sqlfiddle: http://sqlfiddle.com/#!9/4a6f4
This is a previuos query that is not working either:
SELECT a.base_price,b.season,b.starting,b.ending
FROM pricebyseason a JOIN seasons b ON a.season_id=b.id
WHERE a.prop_id='6' AND
(DATE_FORMAT(b.starting,'%m-%d') <= '12-27' OR DATE_FORMAT(b.starting,'2016-%m-%d') >= '2015-12-27')
AND
(DATE_FORMAT(b.ending,'%m-%d') >= '12-27' OR DATE_FORMAT(b.ending,'2016-%m-%d') <= '2015-12-27')
ORDER BY base_price DESC
And here are some sample dates for each season: '2016-01-08','2015-12-27','2016-04-14','2015-11-29','2016-04-15','2015-09-01','2016-09-02','2015-11-26','2016-10-10','2016-03-18','2016-06-22','2015-06-15'
Thank a lot

Get distinct records of two columns in a join with 6 columns

I have two MySQL tables SPONSORSHIPS and EVENTS. I want to display a list of SPONSORSHIPS sorted by the category of the events they sponsor, but to only show a sponsorship once under each event. Sample join table:
SPONSORSHIPS
sponsorhipid | sponsorid | eventid | date |
-------------|-----------|---------|------------|
1 | 3 | 20 | 06/01/2013 |
2 | 2 | 20 | 06/02/2013 |
3 | 3 | 20 | 06/03/2013 |
4 | 2 | 21 | 06/04/2013 |
EVENTS
eventid | name | premium |
--------|-----------|------------|
20 | Lunch | 0 |
21 | Dinner | 1 |
What I'd like to have as a result of the JOIN is:
sponsorhipid | sponsorid | eventid | date | name | premium |
-------------|-----------|---------|------------|---------| ---------|
1 | 3 | 20 | 06/01/2013 | Lunch | 0 |
2 | 2 | 20 | 06/02/2013 | Lunch | 0 |
4 | 2 | 21 | 06/04/2013 | Dinner | 1 |
I tried DISTINCT and GROUP BY but these collapse the events so if sponsor #2 sponsors two different events they'd still be shown only once. How can I achieve this? Here is my last SQL query:
SELECT DISTINCT (sponsorships.sponsorshipid), sponsorships.*, events.*
FROM events
INNER JOIN sponsorships
ON events.eventid = sponsorships.eventid
Thanks so much for any pointers!
You need to use nested sub-queries like this:
SELECT s.sponsorhipid, s.sponsorid, s.eventid, s.date
,e.name, e.premium
FROM EVENTS e
JOIN
(
SELECT s1.* FROM SPONSORSHIPS s1
JOIN
(
SELECT sponsorid, MIN(Date) As minDate
FROM SPONSORSHIPS
GROUP BY eventid,sponsorid
) s2
ON s1.sponsorid = s2.sponsorid
AND s1.date = s2.minDate
) s
ON e.eventid = s.eventid;
Output:
| SPONSORHIPID | SPONSORID | EVENTID | DATE | NAME | PREMIUM |
|--------------|-----------|---------|------------|--------|---------|
| 1 | 3 | 20 | 06/01/2013 | Lunch | 0 |
| 2 | 2 | 20 | 06/02/2013 | Lunch | 0 |
| 4 | 2 | 21 | 06/04/2013 | Dinner | 1 |
See this SQLFiddle