Join tables by filling gaps with previous data - mysql

I have this table:
DATE ENGINEERS
----------------------
2014-03-06 6
2014-03-10 7
In which I register when the number of engineers change. For example, in this case I had 6 engineers, but on the 10th March, I hired one more, so I have 7 from then onwards.
I have another table with the appointments per day
DATE APPOINTMENTS
-------------------------
2014-03-06 4
2014-03-07 5
2014-03-10 5
2014-03-11 6
How can I get a view like this, which combines the appointments and the number of engineers per day?
DATE APPOINTMENTS ENGINEERS
--------------------------------------
2014-03-06 4 6
2014-03-07 5 6
2014-03-10 5 7
2014-03-11 6 7

This is what I could do :
SELECT t2.at, t2.appointments, (#n := IFNULL(t1.engineers, #n)) FROM t2
LEFT JOIN (
SELECT t.at, t1.engineers
FROM t1
JOIN t2 t ON t1.at = t.at
) t1 ON t1.at = t2.at;
I am sure there is something better out there, as the redondant JOIN could not be needed, but I could not find it.
It actually use a sql variable to get the last value if there is no corresponding entry in the engineers table.
Don't forget to run SET #n = 0;.
The corresponding sqlfiddle.

Related

SQL subquery in SELECT clause

I'm trying to find admin activity within the last 30 days.
The accounts table stores the user data (username, password, etc.)
At the end of each day, if a user had logged in, it will create a new entry in the player_history table with their updated data. This is so we can track progress over time.
accounts table:
id
username
admin
1
Michael
4
2
Steve
3
3
Louise
3
4
Joe
0
5
Amy
1
player_history table:
id
user_id
created_at
playtime
0
1
2021-04-03
10
1
2
2021-04-04
10
2
3
2021-04-05
15
3
4
2021-04-10
20
4
5
2021-04-11
20
5
1
2021-05-12
40
6
2
2021-05-13
55
7
3
2021-05-17
65
8
4
2021-05-19
75
9
5
2021-05-23
30
10
1
2021-06-01
60
11
2
2021-06-02
65
12
3
2021-06-02
67
13
4
2021-06-03
90
The following query
SELECT a.`username`, SEC_TO_TIME((MAX(h.`playtime`) - MIN(h.`playtime`))*60) as 'time' FROM `player_history` h, `accounts` a WHERE h.`created_at` > '2021-05-06' AND h.`user_id` = a.`id` AND a.`admin` > 0 GROUP BY h.`user_id`
Outputs this table:
Note that this is just admin activity, so Joe is not included in this data.
from 2021-05-06 to present (yy-mm-dd):
username
time
Michael
00:20:00
Steve
00:10:00
Louise
00:02:00
Amy
00:00:00
As you can see this from data, Amy's time is shown as 0 although she has played for 10 minutes in the last month. This is because she only has 1 entry starting from 2021-05-06 so there is no data to compare to. It is 0 because 10-10 = 0.
Another flaw is that it doesn't include all activity in the last month, basically only subtracts the highest value from the lowest.
So I tried fixing this by comparing the highest value after 2021-05-06 to their most previous login before the date. So I modified the query a bit:
SELECT a.`Username`, SEC_TO_TIME((MAX(h.`playtime`) - (SELECT MAX(`playtime`) FROM `player_history` WHERE a.`id` = `user_id` AND `created_at` < '2021-05-06'))*60) as 'Time' FROM `player_history` h, `accounts` a WHERE h.`created_at` >= '2021-05-06' AND h.`user_id` = a.`id` AND a.`admin` > 0 GROUP BY h.`user_id`
So now it will output:
username
time
Michael
00:50:00
Steve
00:50:00
Louise
00:52:00
Amy
00:10:00
But I feel like this whole query is quite inefficient. Is there a better way to do this?
I think you want lag():
SELECT a.username,
SEC_TO_TIME(SUM(h.playtime - COALESCE(h.prev_playtime, 0))) as time
FROM accounts a JOIN
(SELECT h.*,
LAG(playtime) OVER (PARTITION BY u.user_id ORDER BY h.created_at) as prev_playtime
FROM player_history h
) h
ON h.user_id = a.id
WHERE h.created_at > '2021-05-06' AND
a.admin > 0
GROUP BY a.username;
In addition to the LAG() logic, note the other changes to the query:
The use of proper, explicit, standard, readable JOIN syntax.
The use of consistent columns for the SELECT and GROUP BY.
The removal of single quotes around the column alias.
The removal of backticks; they just clutter the query, making it harder to write and to read.

Select weekly average of user usage, only for some users (mysql)

I have 2 tables, and I want to show a weekly TOTAL average of data usage for users who started using the application 10 weeks ago. (in that week)
Table 1 is called "users"
user_id user_name user_date
1 a 2020-05-01
2 b 2020-05-03
3 c 2020-06-01
4 d 2020-06-06
5 e 2020-06-09
Table 2 is called "data_tbl"
data_id user_id date_used data_used
1 1 2020-05-09 7
2 1 2020-05-09 12
3 2 2020-05-12 100
4 2 2020-05-20 177
5 1 2020-05-21 78
6 2 2020-05-29 33
7 1 2020-06-01 44
8 2 2020-06-01 123
9 1 2020-06-03 62
Consider 10 weeks ago is between 2020-05-01 and 2020-05-08
So the 2 users we are interested in in that case is user_id 1 and 2 (a and b)
We consider first week from 05-01 to 05-08
Second week from 2020-05-08 to 2020-05-15
Third week from 2020-05-15 to 2020-05-22
Forth week from 2020-05-22 to 2020-05-29 and so on
For week 1 we would have average usage = 0
For week 2 we would have average usage (7+12+100)/3=39
For week 3 we would have average usage (177+78)/2=127
For week 4 we would have average usage 33
For week 5 we would have average usage (44+123+62)/3=76
I really don't know how to start, if I should do a join, or a select in select with average.
I tested something like: (but no success)
SELECT AVG(data_used),
FROM data_tbl
LEFT JOIN users ON data_tbl.user_id=users.user_id
WHERE users.user_date>= "2020-05-01" AND users.user_date<="2020-05-08"
GROUP BY date
ORDER BY date;
You can achieve this easily with YEARWEEK() function
However what you want to achieve is not totally clear for me because the results you want don't really match your data.
Example:
SELECT YEARWEEK(SYSDATE()) AS Actual_Week,
YEARWEEK(user_date) User_Date_Week,
YEARWEEK(SYSDATE()) - YEARWEEK(user_date) AS diff_weeks ,
u.*
FROM users u
Returns
Actual_Week User_Date_Week diff_weeks user_id user_name user_date
202029 202017 12 1 a 2020-05-01
202029 202018 11 2 b 2020-05-03
202029 202022 7 3 c 2020-06-01
202029 202022 7 4 d 2020-06-06
202029 202023 6 5 e 2020-06-09
So you can see that user 1 is 12 weeks ago, and user 2 is 11 week ago. And you assume they are 10 weeks ago, which is incorrect. Sames goes with your date_used in data_tbl.
So I'll just put you on the right path, it should then be easy to adapt following your needs...
Do something like this
SELECT YEARWEEK(d.date_used), AVG(data_used)
FROM users u
INNER JOIN data_tbl d ON u.user_id = d.user_id
WHERE (YEARWEEK(SYSDATE()) - YEARWEEK(u.user_date)) BETWEEN 11 AND 12
GROUP BY YEARWEEK(d.date_used)
Returns
YEARWEEK(d.date_used) AVG(data_used)
202018 9.5
202019 100
202020 127.5
202021 33
202022 76.3333
You can see that the numbers you expect are there, but that they are others. And this result seems correct to me, the results in your question were wrong.
Notice that to get the results for user 1 and 2, I specified
WHERE (YEARWEEK(SYSDATE()) - YEARWEEK(u.user_date)) BETWEEN 11 AND 12
If you want the user of 10 weeks ago, just do
WHERE (YEARWEEK(SYSDATE()) - YEARWEEK(u.user_date)) = 10
And to conclude :
you might want to change the mode of YEARWEEK(), if the weeks should start on Monday, Sunday, or other options. Modes are well described here, with plenty of examples
If you also want the weeks without data in your results (so always 0), you have to use a Calendar table. There are plenty of examples on SO.

Mysql select result in one currency

I have to create a reports in one currency. I need to do query in MySQL without using PHP process. but unable to figure it out.
There is a table called currency_exchange_rate table as follows, (exchange rate in LKR to other currency).this table is updating like one record for each currency in LKR in every month
exchange_rates
id currency_id start_date exchange_rate
1 5 2017-01-2 155
2 4 2017-01-3 25
3 6 2017-01-3 53
4 5 2017-02-1 156
5 4 2017-02-1 24
6 6 2017-02-1 54
There is a project table as follows
pro_id name value currency_id status_id owner_id date
1 studio1 500 5 1 44 2017-01-20
2 lotus 120 5 1 42 2017-01-21
3 auro 300 4 2 45 2017-01-21
4 studio2 400 6 1 44 2017-01-22
5 holland 450 4 3 46 2017-02-05
6 studio3 120 4 3 47 2017-02-06
7 studio4 400 6 3 48 2017-02-06
how to generate reports in one currency(DKK but exchange rate in LKR) like status wise,monthly total, total by owner, etc..
and we have to consider currency id,currency to be convert and exchange rate for the month for those currency types to get relevant value for project row.
hope you are clear about my scenario. your help is much appreciated.
I don't need every report. just want a sql for convert values in project table using exchange rates table or status wise report as follows
status_id value_in_one_currency
1 xxxx
2 xxxx
3 xxxx
Try this:
SELECT A.status_id, A.`value` * B.exchange_rate `value_in_one_currency`
FROM project A JOIN exchange_rates B
ON A.currency_id=C.currency_id
AND DATE_FORMAT(A.`date`,'%m-%Y')=DATE_FORMAT(B.`start_date`,'%m-%Y');
See MySQL Join Made Easy for some insight.
This is what I finalize:
I took currency_id=5 as the final currency to be converted
SELECT A.*,C.exchange_rate AS DKK,D.exchange_rate AS LKR, (order_value * D.exchange_rate /C.exchange_rate ) AS `converted_value`
FROM projects A
LEFT JOIN exchange_rates C ON (DATE_FORMAT(C.start_date,'%Y-%m')=DATE_FORMAT(A.`date`,'%Y-%m') AND C.currency_id=5)
LEFT JOIN exchange_rates D ON DATE_FORMAT(D.start_date,'%Y-%m')=DATE_FORMAT(A.`date`,'%Y-%m') AND D.currency_id=A.currency_id

selecting data from two rows into two columns as output mysql Pivot Table

I want to select a collection of pieces of data from four different tables and everything is running smoothly except for one.
ANS_ID REG_ID QST_ID ANS_value
4 1 11 M
5 1 12 12/02/1998
16 15 11 M
17 15 12 11/08/2005
28 16 11 F
29 16 12 05/18/2005
40 17 11 M
41 17 12 11/04/1980
52 18 11 M
I need to pull the gender and birthday from this data as the last two columns of the rows, but when I try to include it all my previous rows are doubled, with the gender and birthday alternating.
ATT_fname ATT_lname sex birthday
Kid1 Kid1Last M NULL
Kid1 Kid1Last NULL 11/8/2005
Kid2 Kid2Last F NULL
Kid2 Kid2Last NULL 5/18/2005
Adult1 Adult1Last M NULL
Adult1 Adult1Last NULL 11/4/1980
What I want is this:
ATT_fname ATT_lname sex birthday
Kid1 Kid1Last M 11/8/2005
Kid2 Kid2Last F 5/18/2005
Adult1 Adult1Last M 11/4/1980
Current Code:
Select ATT_fname, ATT_lname, ATT_email, concat(ATT_address, ' ', ATT_address2), ATT_city,
t4.STA_name, ATT_zip, ATT_phone,
case when QST_ID = 11 then ANS_value end as sex,
case when QST_ID = 12 then ANS_value end as birthday
FROM table1 t1
JOIN table2 t2
ON t1.ATT_ID = t2.ATT_ID
JOIN table3 t3
ON t3.REG_ID=t2.REG_ID
JOIN table4 t4
ON t4.STA_ID=t1.STA_ID
WHERE t2.EVT_ID = 9999;
So instead of spending another 45 minutes trying to format this (paste would be a great option) I decided to spend that time solving it.
Just have to join your table twice with two different alias' one for each field you need to bring in ie:
JOIN Table1 t1sex
ON t1sex.REG_ID=t2.REG_ID AND t1sex.QST_ID=11
JOIN Table1 t1birthday
ON t1birthday.REG_ID=t2.REG_ID AND t1birthday.QST_ID=12

Sub Query or Join MySQL?

table league
team_id name wins losses played recorded created
1 dodgers 10 4 14 1364790000 1353215830
2 angels 9 6 15 1364790000 1353661376
3 pirates 12 3 15 1364790000 1353543466
team_id name wins losses played recorded created
1 dodgers 22 9 31 1367274480 1353215830
2 angels 14 17 31 1367274480 1353661376
3 pirates 19 13 32 1367274480 1353543466
4 yankees 10 9 19 1367274480 1365577298
5 brewers 7 11 18 1367274480 1365394448
Would like Results as:
team_id name wins losses played
1 dodgers 12 5 17
2 angels 5 11 16
3 pirates 7 10 17
4 yankees 10 9 19
5 brewers 7 11 18
I've tried several queries with joins and have had no success. Every day the team, wins, lossed and played are captured and time stamped on the recorded column. The team was created on the created column. (All unix timestamps) There are several rows in between the 2 dates I'm trying for, but I don't need them for this query.
What I wanted to do was to get April's Won/Loss/Played for existing and new teams, I tried several queries, here are a couple that did not give me the desired results:
SELECT a.name as name, a.wins-b.wins as wins, a.losses-b.losses as losses, a.played-b.played as played from league a join league b on a.id=b.id where a.recorded= 1367274480 and b.recorded= 1364790000
and
SELECT new.*, new.wins-old.wins as newwins, new.losses-old.losses as newlosses FROM league new LEFT JOIN league old ON new.id=old.id WHERE (new.recorded=1367274480 and old.recorded=1364790000) or (new.created > 1364790000 and new.recorded=1367274480) GROUP BY new.id
You want every row for the later records and the same number of rows for the earlier, so you need to use LEFT JOIN to get NULL's for teams created between two dates, but WHERE recorded condition for the smaller table should be moved as ON condition for the join.
Also keep in mind that 2-NULL = NULL, so you need to change NULL's into 0's with coalesce().
SELECT a.name AS name,
a.wins - COALESCE( b.wins, 0 ) AS wins,
a.losses - COALESCE( b.losses, 0 ) AS losses,
a.played - COALESCE( b.played, 0 ) AS played
FROM league a LEFT JOIN league b
ON a.team_id = b.team_id AND b.recorded =1364790000
WHERE a.recorded =1367274480
The way the data is set up, it seems that you don't have a recorded value on each day. However, each column would be increasing, so you can take the difference between the max and min vals for the month.
Try this:
SELECT l.name as name,
max(l.wins)-min(l.wins) as wins,
max(l.losses)-min(l.losses) as losses,
max(l.played)-min(l.played) as played
from league l
where l.recorded <= 1367274480 and l.recorded >= 1364790000
group by l.name