SQL right join between tables - mysql

I have three tables where I want to perform a join between them.
1st table looks like (named users)
id column1
1 1
2 2
3 3
2nd table looks like (named transactions) here we have some transactions of users
user_id transaction_date transaction_expire
1 2017-03-31 2017-05-16
1 2017-02-28 2017-04-16
3rd table looks like (named user_logs) we have logs of the users based on days
user_id date some_log_data
1 2017-03-07 1505
1 2017-03-03 1201
1 2017-03-22 942
1 2017-03-31 1490
1 2017-04-05 1490
I want to know the sum of every user based on transactions something like:
user_id transaction_date transaction_expire log
1 2017-03-31 2017-05-16 2980
1 2017-02-28 2017-04-16 6628
So this is the result which I want to achieve for every use get the SUM of their log in all transactions.
By doing this query between transaction_date and transaction_expire I get some result but when I try to do the summation the result are for all of them:
SELECT t.transaction_date, t.transaction_expire, ul.log
FROM user_logs as ul
RIGHT JOIN transactions as t ON ul.user_id= t.user_id
WHERE ul.date BETWEEN t.transaction_date AND t.transaction_expire
This query gives me 7 rows which is correct but now I want to find only the sum of the logs in these two different transactions.

Your query is basically correct, but you need aggregation:
SELECT t.transaction_date, t.transaction_expire, SUM(ul.log)
FROM transactions t LEFT JOIN
user_logs ul
ON ul.user_id = t.user_id AND
ul.date BETWEEN t.transaction_date AND t.transaction_expire
GROUP BY t.transaction_date, t.transaction_expire;
Also note that the condition in the WHERE clause is moved to the ON clause. I switched the JOIN to a LEFT JOIN. I find LEFT JOIN much more intuitive than RIGHT JOIN, because it keeps all rows in the first table.

How about this
SELECT t.user_id, t.transaction_date, t.transaction_expire, SUM(IFNULL(ul.log, 0))
FROM user_logs as ul
LEFT JOIN transactions as t ON (ul.user_id= t.user_id AND
ul.date BETWEEN t.transaction_date AND t.transaction_expire)
GROUP BY t.user_id, t.transaction_date, t.transaction_expire;
Your expected result contains user_id. So, I included that. If a user doesnt have a log, this will return a 0 for the SUM. I assume this is what you wanted

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';

how to select data from multiple table with variable condition | MySQL

I have two tables in the datbase to store client basic info (name, location, phone number) and another table to store client related transactions (date_sub, profile_sub,isPaid,date_exp,client_id) and i have an html table to view the client basic info and transaction if are available, my problem that i can't get a query to select the client info from table internetClient and from internetclientDetails at the same time, because query is only resulting when client have trans in the detail table. the two table fields are as follow:
internetClient
--------------------------------------------------------
id full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 03556133
and
internetclientdetails
--------------------------------------------------------------------------
incdid icid date_sub date_exp isPaid sub_price
----------------------------------------------------------------------------
6 4 2018-01-01 2018-01-30 0 2000
7 5 2017-01-01 2017-01-30 0 1000
8 4 2018-03-01 2018-03-30 1 50000
9 5 2018-05-01 2019-05-30 1 90000
// incdid > internetClientDetailsId
// icid> internetClientId
if client have trans in orderdetails, the query should return value like that:
client_id full_name date_sub date_exp isPaid sub_price
-------------------------------------------------------------------------------------
4 Joe Amine 2018-03-01 2018-03-30 1 50000
5 Mariam zoue 2018-05-01 2019-05-30 1 90000
else if the client has no id in internetOrederDetails
--------------------------------------------------------
icid full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 0355613
Thanks in advance
try with left join. It will display all records from internetClient and related record from internetclientdetails
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.incdid, internetclientdetails.icid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid group by internetclientdetails.icid order by internetclientdetails.incdid desc
if you want to get records of, only paid clients then you can try the following
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.icid, internetclientdetails.incdid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid
and internetclientdetails.isPaid=1 group by internetclientdetails.icid
order by internetclientdetails.incdid desc
SUMMARY
We generate a dataset containing just the ICID and max(date_sub) (alias:ICDi) We join this to the InternetClientDetails (ICD) to obtain just the max date record per client. Then left join this to the IC record; ensuring we keep all InternetClient(IC) records; and only show the related max Detail Record.
The below approach should work in most mySQL versions. It does not use an analytic which we could use to get the max date instead of the derived table provided the MySQL version you use supported it.
FINAL ANSWER:
SELECT IC.id
, IC.full_name
, IC.location
, IC.phone_number
, ICD.icid
, ICD.incdid
, ICD.date_sub
, ICD.date_exp
, ICD.isPaid
, ICD.sub_price
FROM internetClient IC
LEFT JOIN (SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
on IC.id=ICD.icid
ORDER BY ICD.incdid desc
BREAKDOWN / EXPLANATION
The below gives us a subset of max(date_Sub) for each ICID in clientDetails. We need to so we can filter out all the records which are not the max date per clientID.
(SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
Using that set we join to the details on the Client_ID's and the max date to eliminate all but the most recent detail for each client. We do this because we need the other detail attributes. This could be done using a join or exists. I prefer the join approach as it seems more explicit to me.
(SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
Finally the full query joins the client to the detail keeping client even if there is no detail using a left join.
COMPONENTS:
You wanted all records from InternetClient (FROM internetClient IC)
You wanted related records from InternetClientDetail (LEFT Join InternetClientDetail ICD) while retaining teh records from InternetClient.
You ONLY wanted the most current record from InternetClientDetail (INNER JOIN InternetClientDetail mICD as a derived table getting ICID and max(date))
Total record count should = total record count in InternetClient which means all relationships must be a 1:1o on the table joins -- one-to-one Optional.

Postgres LEFT JOIN with WHERE condition

I need to left join two tables with a where condition:
Table time_table
id rid start_date end_date
1 2 2017-07-01 00:00:00 2018-11-01 00:00:00
2 5 2017-01-01 00:00:00 2017-06-01 00:00:00
3 2 2018-07-01 00:00:00 2020-11-01 00:00:00
Table record_table
id name date
1 record1 2017-10-01 00:00:00
2 record2 2017-02-01 00:00:00
3 record3 2017-10-01 00:00:00
I need to get all those records which are present under given date range. In the above example, I need those records that lie under range for rid = 2 only. Hence the output for the above query needs to be:
1 record1 2017-10-01 00:00:00
3 record3 2017-10-01 00:00:00
left join two tables with a where condition
It's typically wrong to use a LEFT [OUTER] JOIN and then filter with a WHERE condition, thereby voiding the special feature of a LEFT JOIN to include all rows from the left table unconditionally. Detailed explanation:
Explain JOIN vs. LEFT JOIN and WHERE condition performance suggestion in more detail
Put conditions supposed to filter all rows into the WHERE clause (rid = 2), but move conditions on record_table to the join clause:
SELECT t.start_date, t.end_date -- adding those
, r.id, r.name, r.date
FROM time_table t
LEFT JOIN record_table r ON r.date >= t.start_date
AND r.date < t.end_date
WHERE t.rid = 2;
As commented, it makes sense to include columns from time_table in the result, but that's my optional addition.
You also need to be clear about lower and upper bounds. The general convention is to include the lower and exclude the upper bound in time (timestamp) ranges. Hence my use of >= and < above.
Related:
SQL query on a time series to calculate the average
Selecting an average of records grouped by 5 minute periods
Performance should be no problem at all with the right indexes.
You need an index (or PK) on time_table(rid) and another on record_table(date).
I'm not exactly sure if this is what you want, but if you are saying you want the dates where the record_table date is between the dates in the time_table, then this would do the job:
select
rt.id, rt.name, rt.date
from
time_table tt
join record_table rt on
rt.date between tt.start_date and tt.end_date
where
tt.rid = 2
That said, this will be horribly inefficient for large datasets. If your data is relatively small (< 10k records in each table, post-filters), then it probably won't matter much, but if you would need to scale this concept, it would warrant knowing more about your data -- for example, do the dates, always round to the first of each month?
Again, from your example, I wasn't sure if this is what you meant by "get all those records which are present under given date range."
SELECT time_tbl.name,record_tbl.date
FROM dbo.time_table AS time_tbl
INNER JOIN record_table AS record_tbl
ON time_tbl.id=record_tbl.id
WHERE(time_tbl.rid=2)

How to consolidate several transaction tables a query with each table representing a status?

I have transaction tables that are related to a main table Order. I would like to consolidate all these transactions into an order history query, such that each transaction and its date is presented as a status of the order at a point in time.
What query would provide the following output?
Order Table
Order ID
1
2
Order Confirmation Table
Order Confirmation ID Date
1 2015-08-01
2 2015-08-01
Order Cancellation Table
Order Cancellation ID Date
1 2015-08-02
Order Completion Table
Order Completion ID Date
2 2015-08-02
Output:
Order ID Date Status
1 2015-08-01 Confirmed
2 2015-08-01 Confirmed
1 2015-08-02 Cancelled
2 2015-08-02 Completed
select o.orderid,
oc.date,
'Cancelled' as status
from order o
join order_cancellation oc
on o.orderid = oc.orderid
union
select o.orderid,
ol.date,
'Completed' as status
from order o
join order_completed ol
on o.orderid = ol.orderid
You can use a union query to achieve this.

MySQL select values from Multiple Tables dependent on latest value in one

I have the following three tables to look after support tickets in a small web application, but I need some help getting the data I need.
Table 1 (ticket):
user_ID site_ID support_ID timestamp priority title
12 25 3 2014-09-26 14:09:25 0 A Test Row
12 26 4 2014-09-27 09:41:18 0 A 2nd Test Row
Table 2 (ticket_reply):
reply_ID support_ID user_ID support_reply reply_timestamp
3 3 12 some really boring text 2014-09-26 14:09:25
4 3 25 some really boring reply 2014-09-26 15:35:18
5 4 12 some really boring text 2014-09-27 09:41:18
Table 3 (ticket_status):
ticket_status_ID support_ID status_ID status_timestamp
3 3 40 2014-09-26 14:09:25
4 3 41 2014-09-26 15:35:18
5 4 40 2014-09-27 09:41:18
The 1st table holds the key ticket information, the 2nd, any replies made to the corresponding ticket, and the third tracks the change in status (statuses are held in another table, but don't need anything from there).
What I need to do is get the number of tickets where the latest status is == 40, and if this is greater than 0, get the latest reply along with the data from the first table.
I've tried multiple ways of doing this, but I am stuck. Don't really want to paste them here as they will likely confuse people, and I doubt they are even close.
This one was rather tricky, however here is a working solution for you.
This query will get the most recent support_reply value for all tickets where the most recent status_ID is 40.
SELECT
ticket_status_ID,
support_ID,
status_ID,
status_timestamp,
reply_ID,
support_reply,
reply_timestamp,
`timestamp` ticket_timestamp,
`priority` ticket_priority,
title
FROM (
SELECT * FROM (
SELECT * FROM (
SELECT
ticket_status.ticket_status_ID,
ticket_status.support_ID,
ticket_status.status_ID,
ticket_status.status_timestamp,
ts1.reply_ID,
ts1.user_ID,
ts1.support_reply,
ts1.reply_timestamp
FROM
ticket_status
INNER JOIN (SELECT * FROM ticket_reply ORDER BY reply_timestamp DESC) ts1 ON ts1.support_ID = ticket_status.support_ID
GROUP BY support_ID, status_ID
ORDER BY status_timestamp DESC
) ts2
GROUP BY ts2.support_ID
) ts3
INNER JOIN (SELECT support_ID as `ticket_support_ID`, site_ID, `timestamp`, priority, title FROM ticket) ts4 ON ts4.ticket_support_ID = ts3.support_ID
WHERE ts3.status_ID = 40
) ts5
From the example given, it looks that all timestamp are equivalent, so a query like this should be enough:
SELECT
ticket.*,
ticket_reply.*
FROM
(SELECT support_ID, MAX(status_timestamp) as max_timestamp
FROM ticket_status
GROUP BY support_ID) m
INNER JOIN ticket
ON m.support_ID=ticket.support_ID
AND m.max_timestamp=ticket.`timestamp`
INNER JOIN ticket_reply
ON m.support_ID=ticket_reply.support_ID
AND m.max_timestamp=ticket_reply.reply_timestamp
INNER JOIN ticket_status
ON m.support_ID=ticket_status.support_ID
AND m.max_timestamp=ticket_status.status_timestamp
WHERE
status_ID=40;
but depending on the logic of your application, it might happen that the last row in a table has a timestamp of 2014-09-27 09:41:18 and the last in another has for example 2014-09-27 09:41:19.
In this case, you should use a query like this one:
SELECT
ticket.*,
ticket_reply.*
FROM
(SELECT support_ID, MAX(status_timestamp) AS max_status_timestamp
FROM ticket_status
GROUP BY support_ID) m_status
INNER JOIN
(SELECT support_ID, MAX(reply_timestamp) AS max_reply_timestamp
FROM ticket_reply
GROUP BY support_ID) m_reply
ON m_status.support_ID=m_reply.support_ID
INNER JOIN
(SELECT support_ID, MAX(`timestamp`) AS max_ticket_timestamp
FROM ticket
GROUP BY support_ID) m_ticket
ON m_status.support_ID=m_ticket.support_ID
INNER JOIN ticket_status
ON ticket_status.support_ID=m_status.support_ID
AND ticket_status.status_timestamp=m_status.max_status_timestamp
INNER JOIN ticket_reply
ON ticket_reply.support_ID=m_reply.support_ID
AND ticket_reply.reply_timestamp=m_reply.max_reply_timestamp
INNER JOIN ticket
ON ticket.support_ID=m_ticket.support_ID
AND ticket.`timestamp`=m_ticket.max_ticket_timestamp
WHERE
ticket_status.status_ID=40;
Please see fiddle here.
You can try this one:
SELECT t.*, tr.support_reply, ts.status_timestamp
FROM ticket_status as ts
left join ticket_reply as tr on(ts.support_ID=tr.support_ID)
left join ticket as t on(t.support_ID=tr.support_ID)
where status_ID=40
order by status_timestamp desc
limit 1;