Is it possible to modify data from UNION query in this way? - mysql

The data that came out of the UNION query I wrote came out like this.
COLUMNS1 COLUMNS2 DATE1 DATE2
--------------------------------------------------------------------
A B 2022-01-10 05:40:12 NULL
A B NULL 2022-01-25 12:40:00
C D 2022-01-15 05:40:12 NULL
C D NULL 2022-01-17 12:40:00
...
How can I get the above data to come out as a query?
COLUMNS1 COLUMNS2 DATE1 DATE2
--------------------------------------------------------------------
A B 2022-01-10 05:40:12 2022-01-25 12:40:00
C D 2022-01-15 05:40:12 2022-01-17 12:40:00
....
Similarly, you can use a LEFT OUTER JOIN , but I don't want to use it.
It seems very simple, but I can't find an answer at all. Maybe it's impossible, but am I trying?

You can group the results of your query and aggregate the date columns, such as:
select COLUMNS1, COLUMNS2,
max(DATE1) DATE1,
max(DATE2) DATE2
from (...)t
group by COLUMNS1, COLUMNS2;

Related

SQL left join two times

The user table looks like this:
user_id
name
surname
1
a
aa
2
b
bb
3
c
cc
The book's table looks like this:
user_id
book_name
1
book1
1
book2
1
book3
2
book1
The expenses table looks like this:
user_id
amount_spent
date
1
10
2020-02-03
1
30
2020-02-02
1
10
2020-02-01
1
15
2020-01-31
1
13
2020-01-15
2
15
2020-02-01
3
20
2020-02-01
The result which I want:
CountUsers
amount_spent
2
65
Explanation: I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
Now how the query should look like?
I am using MySQL version 8.
I have tried:
SELECT
count(*), sum(amount_spend) as total_amount_spend
FROM
(select sum(amount_spend) as amount_spend
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id WHERE books.book_name ='book1 GROUP BY expanses.user_id) src'
And the result is wrong because I am getting a higher amount_spend than in my table result above. I think while joining the table there are some duplicates but I do not know how to fix them.
I want to count how many users have book1 and how much total they spend on a date between 2020-02-01 - 2020-02-03.
I am thinking:
select count(*), sum(e.amount_spent)
from user_books ub join
expenses e
on ub.user_id = e.user_id
where book_name = 'book1';
Note: This assumes that user_books doesn't have duplicate rows.
FIDDLE
You miss the date part in your code.
SELECT
count(*), sum(amount_spent) as total_amount_spend
FROM
(select sum(amount_spent) as amount_spent
FROM expanses
LEFT JOIN books ON books.user_id = expanses.user_id
WHERE books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03'
GROUP BY expanses.user_id) src;
will do a job.
Please note that you don't need to have left join here (unless you're sure that it may happen that no expenses at all for given user will be), and you don't need to have grouping in subquery. So your query could look like:
select count(distinct expanses.user_id), sum(amount_spent) as amount_spent
from expanses
inner join books on books.user_id = expanses.user_id
where books.book_name ='book1'
and expanses.date between '2020-02-01' and '2020-02-03';

MySQL Finding the next row value by ID

id month status
1 1997-11-01 A
1 2015-08-01 B
2 2010-01-01 A
2 2010-02-01 B
2 2012-10-01 C
That I would like to format to be:
id month lead_month status
1 1997-11-01 2015-08-01 A
1 2015-08-01 NOW() B
2 2010-01-01 2010-02-01 A
2 2010-02-01 2012-10-01 B
2 2012-10-01 NOW() C
MySQL is new to me, and I have trouble wrapping my head around variables. I would prefer to use a simple LEAD() with a PARTITION but unfortunately, I can't.
Here's my attempt, that doesn't work:
SET #lead = '1995-01-01'; --abitrary floor
select id, month, status, #lead, #lead:=month from table
The output looks like this, which also doesn't check if the id's from row to row are the same:
id month lead_month status
1 1997-11-01 1995-01-01 A
1 2015-08-01 1997-11-01 B
2 2010-01-01 2015-08-01 A
2 2010-02-01 2010-01-01 B
2 2012-10-01 2010-02-01 C
Don't muck around with variables in MySQL. That sort of logic would better reside in whatever language you are using for your application. This, however can be done in SQL.
My first instinct is simply to save that data in an extra column. Don't worry about the size of the db–there aren't enough months in the universe to become a problem.
There is also something wrong with your ids: these should almost always be primary keys, i. e. unique.
If you insist on your scheme, you can use a join. Assuming consecutive unique ids:
SELECT a.id, a.month, b.month AS lead_month, status FROM table AS a LEFT JOIN table AS b WHERE a.id - 1 = b.id;
You can use a correlated subquery:
select t.*,
(select t2.month
from t t2
where t.id = t2.id
order by t2.month desc
limit 1
) as next_month
from t;
If you want to replace the value for the last month for each id, then you can use coalesce():
select t.*,
coalesce((select t2.month
from t t2
where t.id = t2.id
order by t2.month desc
limit 1
), now()) as next_month
from t;

one mysql query getting, per each row, the average of the previous three rows

i have something like this:
id | value
---------------
201311 | 10
201312 | 15
201401 | 20
201402 | 5
201403 | 17
and i need a result like this:
201311 | NULL or 0
201312 | 3.3 // 10/3
201401 | 8.3 // (15+10)/3
201402 | 15 // (20+15+10)/3
201403 | 13.3 // (5+20+15)/3
So far, i got to the point where i can get the AVG of the last three previous rows like this:
select AVG(c.value) FROM (select b.value from table as b where b.id < 201401 order by b.id DESC LIMIT 3) as c
passing the id manually. I'm not able to do it for each id.
Any ideas would be much appreciated!
thanks a lot.
regards
I think you'll have to write a stored procedure, use a cursor, iterate through the table and populate a new table using the values calculated in your cursor loop. If you need help with writing out the cursor loop, just drop a comment and I can get you an example.
i got to this now:
SELECT a.id, (select AVG(b.value) FROM table as b where b.id < a.id AND str_to_date(CONCAT(b.id,'01'), '%Y%m%d') >= DATE_SUB(str_to_date(CONCAT(a.id,'01'), '%Y%m%d'), INTERVAL 3 MONTH)) FROM `table` as a WHERE 1
But i'm quite sure there should be a better/cleaner solution
select a.id,coalesce(b.value,0) from test a left outer join
(select a.id, sum(b.value)/3 as value from
(select #row:=#row+1 as rownum,id,value from test,(select #row:=0)r) a,
(select #row1:=#row1+1 as rownum,id,value from test,(select #row1:=0)r) b
where b.rownum in (a.rownum-1,a.rownum-2,a.rownum-3)
group by a.rownum) b
on a.id=b.id;

MYSQL select 2 diffrent Fields with same value from different lines

I have a table:
ID APARTMENT_ID CHECK_IN CEHCK_OUT
1 2 2014-18-07 2014-18-09
2 2 2015-01-01 2014-03-02
3 3 2014-08-01 2014-09-01
4 3 2014-09-01 2014-12-09
5 4 2014-18-07 2014-18-09
I want to get the ID, APARTMENT_ID and CHECK_IN , CEHCK_OUT . where there is on the same date in different ID.
on this table the query will bring the result of
APARTMENT_ID => 3
ID => 3, 4
CHECK_IN CEHCK_OUT => 2014-09-01.
i tried something like
SELECT * FROM `orders`
WHERE `ID` != `ID` and `CHECK_IN`=`CEHCK_OUT`
but its not the right way , 0 results.
thanks to anyone that could help me.
You have to use a self-join, otherwise you're just comparing fields in the same row:
SELECT *
FROM orders o1
JOIN orders o2 ON o1.id != o2.id AND o1.check_in = o2.check_out

MySQL result count from multiple table getting wrong result?

I have three tables: attendance, cv_target, and candidate. I need to find the candidate count for a specific user.
I am not an expert in MySQL. I have tried the query below, but I'm unable to find the exact value.
SELECT
attendance_date,
cv_target_date_for,
cv_requirement,
job_id,
cv_target,
achi,
recruiter_comment,
recruiter_rating
FROM
attendance f
RIGHT JOIN
(
SELECT
cv_requirement,
cv_target,
cv_target_date_for,
achi,
recruiter_comment,
recruiter_rating
FROM
cv_target a
LEFT JOIN
(
SELECT
COUNT(candidate_id) AS achi,
cv_target_date,
fk_job_id
FROM
candidate
GROUP BY
fk_job_id,
cv_target_date
) b
ON a.cv_requirement = b.fk_job_id
AND a.cv_target_date_for = b.cv_target_date
WHERE
cv_target_date_for BETWEEN '2014-02-01' AND '2014-03-01'
AND cv_recruiter = '36'
) c
ON f.attendance_date=c.cv_target_date_for
GROUP BY
cv_requirement,
cv_target_date_for
ORDER BY
c`.`cv_target_date_for` ASC
attendance
id fk_user_id attendance_date
1 44 2014-02-24
2 44 2014-02-25
3 44 2014-02-26
4 44 2014-02-27
5 36 2014-02-24
6 44 2014-02-28
cv_target
id cv_recruiter cv_requirement cv_target cv_target_date_for
1 44 1 3 2014-02-24
2 44 2 2 2014-02-24
3 44 3 2 2014-02-25
4 44 4 3 2014-02-25
4 44 4 3 2014-02-26
candidate
candidate_id fk_posted_user_id fk_job_id cv_target_date
1 44 1 2014-02-24
2 44 3 2014-02-25
3 44 3 2014-02-25
3 44 4 2014-02-25
4 44 4 2014-02-26
5 44 5 2014-02-28
5 44 5 2014-02-28
Desired result
attendance_date cv_target_date_for job_id cv_target achi(count)
2014-02-24 2014-02-24 1 3 1
2014-02-24 2014-02-24 2 2 null
2014-02-25 2014-02-25 3 2 2
2014-02-25 2014-02-25 4 3 1
2014-02-26 2014-02-26 4 3 1
2014-02-27 2014-02-27 null null null
2014-02-28 null 5 null 2
Output that I am getting
attendance_date cv_target_date_for job_id cv_target achi(count)
2014-02-24 2014-02-24 1 3 1
2014-02-24 2014-02-24 2 2 null
2014-02-25 2014-02-25 3 2 2
2014-02-25 2014-02-25 4 3 1
2014-02-26 2014-02-26 4 3 1
Date 27 and 28 are not showing. I want those values also.
Original Answer
I think I understand what you want. The following assumes you want all attendance dates within a specific range for a specific user. And for each of those attendance dates, you want all cv_target records, if any. And for each of those, you want a count of the candidates.
Use a subquery to get the count. That's the only part that needs to go in the subquery. Only use a GROUP BY expression in the subquery, not the outer query. Only select the fields you need.
Use LEFT JOIN to get all the records from the table on the left side of the expression and only matching records from the table on the right side. So all records from attendance (that match the WHERE expression), and matching records from cv_target (regardless of whether they have a match in the candidate subquery), and then matching records from the candidate subquery.
Try this:
SELECT
DATE_FORMAT(a.attendance_date, '%Y-%m-%d') AS attendance_date,
DATE_FORMAT(t.cv_target_date_for, '%Y-%m-%d') AS cv_target_date_for,
t.cv_requirement AS job_id,
t.cv_target,
c.achi AS `achi(count)`
FROM
attendance AS a
LEFT JOIN
cv_target AS t
ON a.fk_user_id = t.cv_recruiter
AND a.attendance_date = t.cv_target_date_for
LEFT JOIN
(
SELECT
COUNT(candidate_id) AS achi,
fk_job_id,
cv_target_date
FROM
candidate
WHERE
fk_posted_user_id = 44
AND cv_target_date BETWEEN '2014-02-01' AND '2014-03-01'
GROUP BY
fk_job_id,
cv_target_date
) AS c
ON t.cv_requirement = c.fk_job_id
AND t.cv_target_date_for = c.cv_target_date
WHERE
a.fk_user_id = 44
AND a.attendance_date BETWEEN '2014-02-01' AND '2014-03-01'
ORDER BY
ISNULL(t.cv_target_date_for), t.cv_target_date_for, t.cv_requirement
Note that the following line is not necessary for the correct result. However, depending on the database structure and amount of data, it may improve performance.
AND cv_target_date BETWEEN '2014-02-01' AND '2014-03-01'
The ISNULL function is being used to sort NULL to the bottom.
I've created an SQL Fiddle showing the output you request, except for cv_target_date_for. It's not possible to output values that do not exist in the data.
UPDATE
With the new data and new requirement of retrieving data where either cv_target or candidate has data for a particular attendance date, you need to add another table to get the job IDs. In your original question you had a table with ID numbers and job titles, but it had no dates.
You might want to rethink your database design. I'm not sure I understand how your tables relate to one another, but those two new records for the candidate table appear to be orphaned. All your joins are based on date, but you don't appear to have a table that links job ID numbers to dates.
You could create a derived table by doing a UNION of cv_target and candidate. Then use the derived table as the left side of the join.
Updated query:
SELECT
DATE_FORMAT(a.attendance_date, '%Y-%m-%d') AS attendance_date,
DATE_FORMAT(t.cv_target_date_for, '%Y-%m-%d') AS cv_target_date_for,
j.job_id,
t.cv_target,
c.achi AS `achi(count)`
FROM
attendance AS a
LEFT JOIN
(
SELECT
cv_requirement AS job_id,
cv_target_date_for AS job_date
FROM
cv_target
WHERE
cv_recruiter = 44
AND cv_target_date_for BETWEEN '2014-02-01' AND '2014-03-01'
UNION
SELECT
fk_job_id AS job_id,
cv_target_date AS job_date
FROM
candidate
WHERE
fk_posted_user_id = 44
AND cv_target_date BETWEEN '2014-02-01' AND '2014-03-01'
) AS j
ON a.attendance_date = j.job_date
LEFT JOIN
cv_target AS t
ON a.fk_user_id = t.cv_recruiter
AND j.job_id = t.cv_requirement
AND j.job_date = t.cv_target_date_for
LEFT JOIN
(
SELECT
COUNT(candidate_id) AS achi,
fk_job_id,
cv_target_date
FROM
candidate
WHERE
fk_posted_user_id = 44
AND cv_target_date BETWEEN '2014-02-01' AND '2014-03-01'
GROUP BY
fk_job_id,
cv_target_date
) AS c
ON j.job_id = c.fk_job_id
AND j.job_date = c.cv_target_date
WHERE
a.fk_user_id = 44
AND a.attendance_date BETWEEN '2014-02-01' AND '2014-03-01'
ORDER BY
ISNULL(t.cv_target_date_for), t.cv_target_date_for, j.job_id
I've created an updated SQL Fiddle showing the output you request, except for cv_target_date_for. It's not possible to output values that do not exist in the data (i.e. 2014-02-27).
If that's a typo and you meant 2014-02-28, then you'll need to select the date from the derived table instead of the cv_target table. And you should probably change the column heading in the result because it's no longer the cv_target_date_for date.
To get the date from either cv_target or candidate, change this line:
DATE_FORMAT(t.cv_target_date_for, '%Y-%m-%d') AS cv_target_date_for,
to this:
DATE_FORMAT(j.job_date, '%Y-%m-%d') AS job_date,
And you may need to tweak the order by expression to suit your needs.