MySQL Join two tables and combine multiple rows into one - mysql

I have 2 tables:
sms_recipients
campaign_id | message | user_id | contact_number | status
.........................................................
1 something 12334 078237812719 1
1 something 12123 071231231232 1
2 other 12124 078123123126 0
2 other 12334 078234234212 0
2 other 42124 078124124415 1
sms_campaign
campaign_id | shop_id| campaign_type
.....................................
1 1123 marketing
2 2123 awareness
3 3231 something else
4 4432 bla bla
5 5244 last
campaign_id's are unique for the sms_campaign table, there are multiple user_id's related to the same campaign_id the message is the same for all unique champagne_id
I want to combine them so that every contact_number of the same campaign_id with a status = 0 appears in a single row and column like this:
campaign_id | shop_id| campaign_type | users_mobile_numbers | message
..........................................................................
1 1123 marketing something
2 2123 awareness 078123123126,078234234212 other
3 3231 something else
4 4432 078234234212
5 5244 078124124415
Here is my query so far:
SELECT c.campaign_id,
shop_id,
campaign_type,
contact_number AS users_mobile_numbers,
message FROM sms_campaign c
LEFT JOIN sms_recipients r
ON u.campaign_id = c.campaign_id
WHERE status = 0

In the LEFT JOIN, you will need to either move the filter status = 0 into a join condition, OR if you leave the filter in the WHERE clause, then status = 0 OR status IS NULL to avoid filtering out campaigns with no messages at all - I've done the first option.
As per the comment, you will need to GROUP the data by the campaign columns, and apply aggregate functions to all non-grouped columns, in order to guarantee just one row per group - GROUP_CONCAT will concatenate all text values in each GROUP. I've arbitrarily used MIN to resolve a value for shop and message, but you may need to adjust otherwise. (You can also do a DISTINCT in a GROUP CONCAT, if required).
SELECT
c.campaign_id,
MIN(shop_id) AS shop_id,
campaign_type,
GROUP_CONCAT(contact_number) AS users_mobile_numbers,
MIN(message) AS message
FROM sms_campaign c
LEFT JOIN sms_recipients r
ON u.campaign_id = c.campaign_id AND status = 0
GROUP BY c.campaign_id, campaign_type;

Move the condition on r.status to the ON clause of the outer join. (With the condition in the WHERE clause requiring r.status to be non-NULL, that will negate the outerness of the LEFT JOIN, making it equivalent to an INNER JOIN.)
Add a GROUP BY clause to collapse the rows.
Use a GROUP_CONCAT function to combine the values of contact_number. Column references which appear in the SELECT list but are not included in the GROUP BY clause should also be enclosed in aggregate expressions.)
SELECT c.campaign_id
, ...
, GROUP_CONCAT(DISTINCT r.contact_number ORDER BY r.contact_number) AS `c_numbers`
, MIN(r.message) AS `message`
FROM campaign c
LEFT
JOIN sms_recipients r
ON r.campaign_id = c.campaign_id
AND r.status = 0
GROUP BY c.campaign_id
The value returned by GROUP_CONCAT aggregate function is limited by max_group_concat_len variable. Longer values will be silently truncated to the maximum length.

Related

Mysql, left join and count conditions

I have fallowing SQL query (pseudo query):
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions
FROM
campaign
LEFT JOIN
clicks ON clicks.key = campaign.key
LEFT JOIN (
SELECT
id, key
FROM
transactions
GROUP BY
userkey
) transactions ON clicks.key = transactions.key;
GROUP BY
campaign.id
Query return good results. On example:
column | columns [..] | 34 | 10
column | columns [..] | 22 | 1
column | columns [..] | 34 | 17
So, records in table clicks they have transactions or a few transactions or they do not.
Haw Can I retun COUNT() clicks who have COUNT(transactions.id) = 0 and COUNT(transactions.id) > 0 ? On example:
column | columns [..] | 34 | 10 | 4 (count data from clicks table which have related transactions) | 30 (count data from clicks table which not have related transactions)
column | columns [..] | 22 | 1 | 6 | 16
column | columns [..] | 34 | 17 | 10 | 24
Tahnks for help.
#UPDATE:
I solved my problem adding second table. Now my SQL query looks like:
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions,
COUNT(clicks_count.id) as witchout_transactions,
(COUNT(clicks.id) - COUNT(clicks_count.id)) as witch_transactions
FROM
campaign
LEFT JOIN
clicks ON clicks.key = campaign.key
LEFT JOIN (
SELECT
id, key
FROM
transactions
GROUP BY
userkey
) transactions ON clicks.key = transactions.key
LEFT JOIN (
SELECT
clicks.id,
COUNT(transactions.id) AS transactions
FROM
clicks
LEFT JOIN transactions ON clicks.key = transactions.key
GROUP BY clicks.id
HAVING transactions = 0
) clicks_count ON clicks_count.id = clicks.id
GROUP BY
campaign.id
If I understand correctly, you can try to use CASE WHEN expression and COUNT
Because you didn' provide any sample data and expected result, so I can only provide pseudo-query.
SELECT...,
COUNT(CASE WHEN [have transactions condition] then 1 end),
COUNT(CASE WHEN [not have related transactions condition] then 1 end)
If that didn't help you, you can provide some data and expect result, I will edit my answer.
What about adding a second join with the clicks table
JOIN (
SELECT id
FROM clicks
LEFT JOIN transactions ON clicks.key = transactions.key AND transactions.id != 0) clicks2 ON clicks.id = clicks2.id
And in the select clause use this to for the two columns
SELECT
some columns [...]
COUNT(clicks.id) AS clicks,
COUNT(transactions.id) AS transactions
COUNT(clicks2.id) as clicks_with
clicks - clicks_with AS clicks_without

Join two subqueries and have a field: division of the results of two subqueries

I have a table like this:
userid | trackid | path
123 70000 ad
123 NULL abc.com
123 NULL Apply
345 70001 Apply
345 70001 Apply
345 NULL Direct
345 NULL abc.com
345 NULL cdf.com
And I want a query like this. When path='abc.com', num_website +1; when path='Apply', num_apply +1
userid | num_website | num_Apply | num_website/num_Apply
123 1 1 1
345 1 2 0.5
My syntax looks like this:
select * from
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num from
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
My question is
1. how to have the field num_website/num_apply in term of my syntax above?
2. is there any other easier way to get the result I want?
Any spots shared will appreciate.
The simplest way to do it would be to change the select line:
SELECT a1.userid, a1.is_CWS, a2.Apply_num, a1.is_CWS/a2.Apply_num FROM
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num
from TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
and then continue with the rest of your query as you have it. The star means "select everything." If you wanted to select only a few things, you would just list those things in place of the star, and if you wanted to select some other values based on those things, you would put those in the stars as well. In this case a1.is_CWS/a2.Apply_num is an expression, and MySql knows how to evaluate it based on the values of a1.is_CWS and a2.Apply_num.
In the same vein, you can do a lot of what those subqueries are doing in a single expression instead of a subquery. objectNotFound has the right idea. Instead of doing a subquery to retrieve the number of rows with a certain attribute, you can select SUM(path="abc.com") as Apply_num and you don't have to join anymore. Making that change gives us:
SELECT a1.userid,
SUM(path="abc.com") as is_CWS,
a2.Apply_num,
is_CWS/a2.Apply_num FROM
TABLE
JOIN
(select userid,count(userid) as Apply_num
FROM TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
GROUP BY userid
Notice I moved the GROUP BY to the end of the query. Also notice instead of referencing a1.is_CWS I now reference just is_CWS (it's no longer inside the a1 subtable so we can just reference it)
You can do the same thing to the other subquery then they can share the GROUP BY clause and you won't need the join anymore.
to get you started ... you can build on top of this :
select
userid,
SUM(CASE WHEN path='abc.com'then 1 else 0 end ) as num_website,
SUM(CASE WHEN path='Apply' and trackid is not NULL then 1 else 0 end ) as Apply_Num
from TABLE
WHERE path='abc.com' or path='Apply' -- may not need this ... play with it
group by userid

Counting all the wins in two columns using two columns?

Let's take this table for an example...
m_tid | m_tid2 | m_hteam_score | m_ateam_score
2 5 69 30
5 2 0 5
I'm bad at custom making tables, sorry...
So let's take this data, now m_tid and m_tid2 are columns for TID's that are in a separate table of their own.
Now what I want to do, is collect the score for team id2 (or team id1) of all the scores... How would I count two columns for whether or not the team is on m_tid and m_tid2
I don't have a query made, but I wouldn't know how I would go about making a query for this anyways. :(
The expected results would be something like this
m_tid | m_tid_score | m_tid2 | m_tidscore2
5 35 2 69
If you want to get the total score for each team, here is one method using correlated subqueries:
select t.*,
(coalesce((select sum(s.m_hteam_score) from scores s where s.m_tid = t.tid), 0) +
coalesce((select sum(s.m_ateam_score) from scores s where s.m_tid2 = t.tid), 0)
) as totalscore
from teams t;
Here's another option using conditional aggregation:
select o.id, sum(case when y.m_tid = o.id then y.m_hteam_score
when y.m_tid2 = o.id then y.m_ateam_score
else 0 end) score
from othertable o
join yourtable y on o.id in (y.m_tid, y.m_tid2)
group by o.id

MySQL left join COUNT() and SUM() with CASE from same table

I have the following tables in a succession of 1-to-many relationships:
company_company, company_portfolio, building_site and statistics_meter. The area of difficulty I am having is the final table, statistics_meter.
For the benefit of this exercise, it's structure is as follows:
Records are related within the same table, with some being parent meters, and some being child meters. Where a record is a child, it will have parent_meter_id set, and building_id, which crucially, is how the table is LEFT JOIN'ed set to NULL.
id | parent_meter_id | site_ref | building_id
1 | NULL | some building | 45
2 | NULL | some other building | 45
3 | 1 | and another | NULL
4 | 1 | one another one | NULL
5 | 2 | final one | NULL
I have two requirements:
1 - count the number of parent meters where the building_id is set (which I am doing successfully)
2 - count the number of meters where the parent_meter_id matches the meter_id of those counted in (1)
Thus I would expect a result whereby (1) = 2 and (2) = 3.
Here is the SQL I've got so far...I tried fiddling around with a SUM case when but I think it's totally wrong. Is this even possible within one query?
Thanks for the help.
SELECT
building_site.id as site_id,
building_site.site_ref as building_name,
COUNT(statistics_meter.id) AS meter_count,
SUM(CASE WHEN statistics_meter.parent_meter_id = [???] THEN 1 ELSE 0 END) AS check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
WHERE company_company.id=41
GROUP BY building_site.id
Well if I understand you, you'll need to use a subquery to get the parent meters with a building id, and then join that to your main table.
SQL Fiddle
select
sm.id,
sm.parent_meter_id,
sm2.id as ID2,
sm.site_ref,
sm.building_id
from
statistics_meter sm
inner join (
select
id,
parent_meter_id
from
statistics_meter
where
building_id is not null) sm2
on sm.parent_meter_id = sm2.id
Not sure if this is the most efficient way to do it, but in the end I performed a left join and subquery as below and performed two counts, one COUNT() for total number to answer my requirement (2) and a COUNT(distinct) to answer my requirement (1)
SELECT
count(distinct statistics_meter.id) as meter_count,
count(statistics_meter.id) as check_meter_count
FROM company_company
LEFT JOIN company_portfolio ON company_portfolio.company_id=company_company.id
LEFT JOIN building_site ON building_site.portfolio_id=company_portfolio.id
LEFT JOIN statistics_meter ON statistics_meter.building_id=building_site.id
LEFT JOIN (select * from statistics_meter where parent_meter_id is not NULL) sm2 on sm2.parent_meter_id = statistics_meter.id

Join Table and Select Highest Date Value

Here is the query that I run
SELECT cl.cl_id, cc_rego, cc_model, cl_dateIn, cl_dateOut
FROM courtesycar cc LEFT JOIN courtesyloan cl
ON cc.cc_id = cl.cc_id
Results:
1 NXI955 Prado 2013-10-24 11:48:38 NULL
2 RJI603 Avalon 2013-10-24 11:48:42 2013-10-24 11:54:18
3 RJI603 Avalon 2013-10-24 12:01:40 NULL
The results that I wanted are to group by the cc_rego values and print the most recent cl_dateIn value. (Only Display Rows 1,3)
I've tried to use MAX on the date and group by clause, but it combines rows, 2 & 3 together showing both the highest value of dateIn and dateOut.
I resolved the problem.
Instead of using left join, I added a condition in the where clause which embeds to MAX of the dateIn
SELECT cll.cl_id, cc.cc_id, cc_rego, cc_model, cll.cl_dateIn, cll.cl_dateOut
FROM courtesycar cc, courtesyloan cll
WHERE cl_dateIn = (
SELECT MAX( cl.cl_dateIn )
FROM courtesyloan cl
WHERE cl.cc_id = cc.cc_id )
AND cc.cc_id = cll.cc_id