MySQL - Join a table twice with a main table - mysql

I'm not sure if this can be done. But I just wanted to check with the experts out here.
My case is:
I have a table tbl_campaign which basically stores a campaigns which has a one to many relation with a table called tbl_campaign_user where the users that were selected during the campaign are stored along with the campaign id (tbl_campagin_user.cu_campaign_id = tbl_campaign.campaign_id ).
The second table (tbl_campaign_user) has a status field which is either 0 / 1 denoting unsent/sent. I wanted to write a single sql query which would read the campaign data as well as the number of sent and unsent campaign users (which is why I'm joining twice on the second table).
I tried this below, but I get the same number of count as sent and unsent.
SELECT `tbl_campaign`.*,
COUNT(sent.cu_id) as numsent,
COUNT(unsent.cu_id) as num_unsent FROM (`tbl_campaign`)
LEFT JOIN tbl_campaign_user as sent on (sent.cu_campaign_id = tbl_campaign.campaign_id and sent.cu_status='1')
LEFT JOIN tbl_campaign_user as unsent on (unsent.cu_campaign_id = tbl_campaign.campaign_id and unsent.cu_status='0')
WHERE `tbl_campaign`.`campaign_id` = '19'
I tried debugging by breaking the query into two parts:
=>
SELECT `tbl_campaign`.*,
COUNT(unsent.cu_id) as num_unsent FROM (`tbl_campaign`)
Left join tbl_campaign_user as unsent on (unsent.cu_campaign_id = tbl_campaign.campaign_id and unsent.cu_status='0')
WHERE `tbl_campaign`.`campaign_id` = '19'
The above works exactly as wanted. And so does the one below:
=>
SELECT `tbl_campaign`.*,
COUNT(sent.cu_id) as numsent FROM (`tbl_campaign`)
Left join tbl_campaign_user as sent on (sent.cu_campaign_id = tbl_campaign.campaign_id and sent.cu_status='1')
WHERE `tbl_campaign`.`campaign_id` = '19'
I am not sure what I've been doing wrong while merging the two. I know I don't know much about joins so possibly a conceptual error? Please could anyone help me?
Thx in advance!

You only need to join tbl_campaign_user once and
count (sum, whatever) how many times cu_status was zero/one.
SELECT `tbl_campaign`.id,
count(u.id) as num_all_campaign_users
sum(u.cu_status) as num_sentcampaign_users,
count(u.id) - sum(u.cu_status) as num_unsent_campaign_users
FROM `tbl_campaign` c
LEFT JOIN tbl_campaign_user as u on (u.cu_campaign_id = c.campaign_id)
WHERE `tbl_campaign`.`campaign_id` = '19'
group by `tbl_campaign`.id
Note that this is sort of pseudo code, you may have to elaborate
the sum/count in the select clause and the group by clause as well.

Related

Joining the same table twice in MYSQL

I am trying to create a MYSQL query that pulls in data from a range of tables. I have a master bookings table and an invoice table where I am recording invoice id's etc from Stripe.
I am storing two invoices per booking; one a deposit, the second the final balance.
In my admin backend I then display to the admin info on whether the invoice is paid etc so need to pull in data from SQL to show this.
I'm following some previous guidance here What's the best way to join on the same table twice?.
My query is returning data, however when the invoices table is included twice (to give me the deposit and balance invoices) however the column names are identical.
Could someone point me in the right direction? I think I need to somehow rename the columns on the second returned invoice??? Sorry new to anything but basic SQL queries.
This is my SQL
SELECT * FROM bookings
INNER JOIN voyages ON bookings.booking_voyageID = voyages.voyage_id
LEFT JOIN emailautomations ON bookings.booking_reference = emailautomations.automation_bookingRef AND emailautomations.automation_sent != 1
LEFT JOIN invoices ON bookings.booking_stripeDepositInvoice = invoices.invoice_id
LEFT JOIN invoices inv2 ON bookings.booking_stripeBalanceInvoice = inv2.invoice_id
Thanks to #Algef Almocera's answer I have amended my SQL (and stopped being lazy by using SELECT *, was able to trim loads of columns down to not many!)
SELECT
bookings.booking_status,
bookings.booking_reference,
bookings.booking_stripeCustomerReference,
bookings.booking_stripeDepositInvoice,
bookings.booking_stripeBalanceInvoice,
bookings.booking_totalPaid,
bookings.booking_voyageID,
bookings.booking_firstName,
bookings.booking_lastName,
bookings.booking_contractName,
bookings.booking_contractEmail,
voyages.voyage_id,
voyages.voyage_name,
voyages.voyage_startDate,
depositInvoice.invoice_id AS depositInvoice_id,
depositInvoice.invoice_status AS depositInvoice_status,
balanceInvoice.invoice_id AS balanceInvoice_id,
balanceInvoice.invoice_status AS balanceInvoice_status
FROM bookings
INNER JOIN voyages ON bookings.booking_voyageID = voyages.voyage_id
LEFT JOIN emailautomations ON bookings.booking_reference = emailautomations.automation_bookingRef AND emailautomations.automation_sent != 1
LEFT JOIN invoices depositInvoice ON bookings.booking_stripeDepositInvoice = depositInvoice.invoice_id
LEFT JOIN invoices balanceInvoice ON bookings.booking_stripeBalanceInvoice = balanceInvoice.invoice_id
This, sometimes, couldn't be avoided as keywords might actually often be the same but of different purpose per table. To help with that, you can use aliases. for example:
SELECT
invoices.column_name AS invoices_column_name,
transactions.column_name AS transactions_column_name
FROM invoices ...
LEFT JOIN transactions ...

How can I filter out results based on another table. (A reverse join I guess?)

Basically, I have a table which contains two fields: [id, other] which have user tokens stored in them. The goal of my query is to select a random user that has not been selected before. Once the user is selected it is stored in the table shown above. So if Jack selects Jim randomly, Jack cannot select Jim again, and on the flip side, Jim cannot select Jack.
Something like this is what comes to mind:
SELECT * FROM users
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?));
Well, first of all I've read that uses sub-queries like this is extremely inneficient, and I'm not even sure if I used the correct syntax, the problem is however, that I have numerous tables in my scenario which I need to filter by, so it would look more like this.
SELECT * FROM users u
WHERE (SELECT * FROM selected WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM other_table WHERE (id=? AND other=?) OR (id=? AND other=?))
AND (SELECT * FROM diff_table WHERE (id=? AND value=?))
AND u.type = 'BASIC'
LIMIT = 1
I feel like there's a much, much more efficient way of handling this.
Please note: I don't want a row returned at all if the users id is present in any of the nested queries. Returning "null" is not sufficient. The reason I have the OR clause is because the user's id can be stored in either the id or the other field, so we need to check both.
I am using Postgre 9.5.3, but I added the MySQL tag as the code is mostly backwards comptable, Fancy Postgre only solutions are accepted(if any)
You can left join to another table, which produces nulls where no record is found:
Select u.* from users u
left selected s on s.id = u.id or s.other = u.other
where s.id is null
The or in a join is different, but should work. Example is kinda silly...but as long as you understand the logic. Left join first table to second table, where second table column is not null means there was atleast one record found that matched the join conditions. Where second table column is null means no record was found.
And you are right...avoid the where field = (select statement) logic when you can, poor performer there.
Use an outer join filtered on missed joins:
SELECT * FROM users u
LEFT JOIN selected s on u.id in (s.id, s.other) and ? in (s.id, s.other)
WHERE u.id != ?
AND s.id IN NULL
LIMIT 1

SQL - How would I select data involving multiple tables?

RDMS: MySQL
I'm designing a private messaging thread system and have the following schema laid out for the messaging system (simplified). I need to select every conversation that an account with a known ID (we'll say '1') is in, the last message that was sent in the conversation, and the username of the other account that account '1' is chatting with. Grouped by the conversation id. (imagine an inbox page like Facebook has)
My SQL knowledge is limited (I'm going to spend the weekend advancing it) but it the meantime does anyone have a query that would get the job done?
Thanks in advance. Let me know if you need more info.
Schema
EDIT: I believe I may have found a solution (still needs proper testing).
I'm still accepting answers as I'm not sure this is the best route to go yet. I've reviewed the current answers posted and they are getting me in the right direction but so far aren't returning the right results.
Here's what I've come up with.
SELECT account_has_conversations.account_id, messages.*, account.username FROM messages, account, account_has_conversations WHERE account_has_conversations.conversation_id IN
(
SELECT account_has_conversations.conversation_id FROM account_has_conversations WHERE account_has_conversations.account_id = '1'
)
AND account_has_conversations.account_id != '1' AND account_has_conversations.account_id = account.account_id
AND account_has_conversations.conversation_id = messages.conversation_id
GROUP BY account_has_conversations.conversation_id
(I probably should have created some aliases haha)
First, you don't have any text fields in any of the tables in your pictured schema - so I don't know where you're storing the actual message content or how to retrieve it. That said, this query should work for you and you can just add the field that you need.
This will get you the username, the last message received, and the sending username for all of the selected user's conversations. I will ASSUME the message content is in the messages table and is stored in a field called "message_text":
DECLARE #AccountID int = 1 //For testing, but this should be parameterized
SELECT A.username as [User],
A2.username as [SendingUser]
M.message_text as [LastMessage]
FROM account A
JOIN account_has_conversations AHC
ON A.account_id = AHC.account_id
JOIN conversation C
ON AHC.conversation_id = C.conversation_id
JOIN
(
Select
sender_account_id,
conversation_id,
message_text,
row_number() over(partition by conversation_id order by time_sent desc) as rn
FROM messages
) as M
ON C.conversation_id = M.conversation_id
AND M.sender_account_id <> #AccountID //This prevents a circular join in case the last message was sent by the user. You may not need this, but it's impossible to tell based on the information you provided.
JOIN account A2
ON M.sender_account_id = A2.account_ID
WHERE rn = 1
AND A.account_id = #AccountID
I have tested this, but maybe this will get you in the right direction.
Select m.*
from account a
inner join account_has_conversation ahc
on (a.account_id = account_id)
inner join messages m
on (ahc.conversation_id = m.conversation_id)
where a.account_id = 1
I would add:
Your conversation table doesn't really seem to have any value.
Group By is great, but you're going to need to squeeze all your results down as well. By that I mean that if you want to squish ten rows into one (grouping by one of the columns where all the rows have the same value in that column), you'll need to have all the other columns in an aggregate function.
Based on the new schema as below.
messages(msg_id, con_id, message, time_sent, status)
acc(acc_id, username)
con(con_id)
conrel(con_id, acc_id)
(typing from phone so ignore any smaller mistakes).
SELECT distinct msg.message, conrel.con_id, acc.username FROM messages AS msg INNER JOIN conrel ON conrel.con_id = msg.con_id INNER JOIN acc ON conrel.acc_id=acc.acc_id where conrel.con_id IN (SELECT DISTINCT con_id FROM connrel WHERE acc_id=1) ORDER BY msg.time_sent DESC
The solution can further be improved or changed according to needs. Plus remember to use coma separated result of INNER query above instead as INNER queries are slower but when they are passed with IDs like 1,4,5 they become fast.
SELECT a.username, m.*, c.* FROM account a, messages m, conversation c
WHERE a.account_id = m.sender_account_id AND m.conversation_id = c.conversation_id

Inefficient Query

SELECT submissions.created_at, guests.accepted
FROM submission
INNER JOIN guests
ON guests.event_id = 1
ORDER BY submissions.created_at ASC;
Currently this query is taking a long time to run (may not even return everything).
What I am trying to accomplish:
Grab the field submissions.created at and guests.accepted (the two are linked by a submission_id)
given a certain event_id (event 1 for example).
What is wrong with my query?
You forgot to give the JOIN condition in your query. Try this:
SELECT submissions.created_at, guests.accepted
FROM submission s
INNER JOIN guests g on g.event_id = s.submissions_id
where guests.event_id = 1
ORDER BY submissions.created_at ASC;
SELECT submissions.created_at, guests.accepted
FROM submission
INNER JOIN guests
ON guests.(column to match against) = submissions.(column to match on)
where guests.event_id=1
ORDER BY submissions.created_at ASC;
As many others here have already said, your join is a little goofed. It's attempting to join the one row that matches in the guests table against every single row in the submission column.
You would need to do your join like this :
Inner join guest on guest.event_id = submissions_id
where guest.event_id = 1
When you join you need to join on two columns from different table (most of the time id columns)
You can read the MySQL example for more explanation. And here is the link to the MySQL reference manual

mysql query finding results using joined status table that needs EXIST and NOT EXIST results

I have been looking around for ages for a solution to my problem.
I have something that works but i am not sure it is the most efficient way of doing things and can't find anyone trying to do this when googling around.
I have a table with customers and a table with statuses that that customer has had.
If I want to find results where a customer has had a status happen I have managed to get the required results using a join, but sometimes I want to be able to find clients where not only has a status been reached but also where a few other statuses haven't been.
Currently I am doing this with a NOT EXISTS Sub query but it seem a bit slow and thinking about it if I have to check after finding a result that matches the first status through all the results again to see if it doesn't match another it could explain the slowness.
for instance a client could have a status of invoiced and a status of paid.
If I wanted to see which clients have been invoiced thats fine, If I want to see which clients have been invoiced and paid thats fine, but if I wanted to see which clients have been invoiced but NOT paid thats where I start having to use a NOT EXIST subquery
Is there another more efficient way around this? or is this the best way to proceed but I need to sort out how mysql uses indxes with these tables to be more efficient?
I can provide more detail of the actual sql if that helps?
Thanks
Matt
If this is over multiple clients then the usual solution would be to have a subselect for the status per client and then use LEFT OUTER JOIN to connect this.
Something like
SELECT *
FROM Clients a
LEFT OUTER JOIN (SELECT ClientId, COUNT(*) FROM ClientsStatus WHERE Status IN (1,2) GROUP BY ClientId) b
ON a.ClientId = b.ClientId
WHERE b.ClientId IS NULL
This (very rough) example is to give you a list of clients who do not have a status of 1 or 2.
You should be able to expand this basic idea to cover the scenarios / data you are dealing with
Edited for below
I have had a play with your SQL. I think you can use a JOIN onto the subselect fairly easily, but this doesn't seem to be checking anything other than whether a claim has had a status of 3 or 95.
SELECT claims.ID, claims.vat_rate, claims.com_rate,
claims.offer_val, claims.claim_value, claims.claim_ppi_amount, claims.claim_amount, claims.approx_loan_val, claims.salutationsa, claims.first_namesa, claims.last_namesa,
clients.salutation, clients.first_name,clients.last_name, clients.phone, clients.phone2, clients.mobile, clients.dob,clients.postcode, clients.address1, clients.address2, clients.town, client_claim_status.person,clients.ID
AS client_id,claims.ID AS claim_id, claims.date_added AS status_date_added,client_claim_status.date_added AS last_client_claim_status_date_added,work_suppliers.name AS refname, financial_institution.name AS lendname, clients.date_added AS client_date_added,ppi_claim_type_2.claim_type AS ppi_claim_type_name
FROM claims
RIGHT JOIN clients ON claims.client_id = clients.ID
RIGHT JOIN client_claim_status
ON claims.ID = client_claim_status.claim_id
AND client_claim_status.deleted != 'yes'
AND ((client_claim_status.status_id IN (1, 170))
AND client_claim_status.date_added < '2012-12-02 00:00:00' )
LEFT OUTER JOIN (SELECT claim_id FROM client_claim_status WHERE status_id IN (3, 95 )) Sub1
ON claims.ID = Sub1.claim_id
LEFT JOIN financial_institution ON claims.claim_against = financial_institution.ID
LEFT JOIN work_suppliers ON clients.work_supplier_id = work_suppliers.ID
LEFT JOIN ppi_claim_type_2 ON claims.ppi_claim_type_id = ppi_claim_type_2.ID
WHERE claims.deleted != 'yes'
AND Sub1.claim_id IS NULL
ORDER BY last_client_claim_status_date_added DESC
I would suggest that you rearrange the code to remove the RIGHT OUTER JOINs though to be honest. Mixing left and right joins up tend to be very confusing.