MySQL Left Join throwing off my count numbers - mysql

I'm doing a left join on a table to get the number of leads we've generated today and how many times we've called those leads. I figured a left join would be the best thing to do, so I wrote the following query:
SELECT
COUNT(rad.phone_number) as lead_number, rals.lead_source_name as source, COUNT(racl.phone_number) as calls, SUM(case when racl.contacted = 1 then 1 else 0 end) as contacted
FROM reporting_app_data rad
LEFT JOIN reporting_app_call_logs racl ON rad.phone_number = racl.phone_number, reporting_app_lead_sources rals
WHERE DATE(rad.created_at) = CURDATE() AND rals.list_id = rad.lead_source
GROUP BY rad.lead_source;
But the problem with that, is that if in the reporting_app_call_logs table, there are multiple entries for the same phone number (so a phone number has been called multiple times that day), the lead_number (which I want to count how many leads were generated on the current day grouped by lead_source) equals how many calls there are. So the count from the LEFT table equals the count from the RIGHT table.
How do I write a SQL query that gets the number of leads and the total number of calls per lead source?

Try COUNT(DISTINCT expression)
In other words, change COUNT(rad.phone_number) to COUNT(DISTINCT rad.phone_number)

Related

How to combine rows and take highs and lows from those rows

I am trying to generate a 5M OHLC chart (Open, High, Low, Close). I'm currently reading in data by the minute, but I want to make a 5 minute data chart as well which I can do pretty easy by simply doing
SELECT * FROM intraday_data.intraday WHERE intraday.id mod 5 = 0;
However, this doesn't accurately represent the data because for the OHLC chart to be accurate it would need to also have the open from the very first row and close from the very last row, and it would have to have the highest high from all 5 and the lower low from all 5 if that makes sense, I would also like to implement where it is able to add up all of the volumes.
Here is the current schema:
As you can see the open in highlighted would need to be pulled out in the final row, the highlighted value in the 5th row which is the highest high as well as the remaining rows and the total volume, so essentially after running the function the row presented should be:
id 5: open 4402.75: high 4403: low 4402.5: volume : 12+24+37+32+29
Obviously, I would need to iterate over all of the rows and return every 5th row with the data combined from the last 5 rows,
Current Updated Query:
select open_close_t.cross_id,open_close_t.open_val,open_close_t.close_val,high_low_t.high,high_low_t.low,high_low_t.total_volume from (
select open_close.max_id as cross_id,open_t.open as open_val,close_t.close as close_val from
(select max(id) as max_id,min(id) as min_id from intraday group by FLOOR(id/5)) as open_close
inner join intraday as open_t on (open_t.id=open_close.min_id)
inner join intraday as close_t on (close_t.id=open_close.min_id)
) as open_close_t
left join (
select max(id) as cross_id,max(high_val) as high,min(low_val) as low,sum(volume) as total_volume
from (select id,GREATEST(open,high,low,close) as high_val,GREATEST(open,high,low,close) as low_val,volume from intraday_data.intraday) as _t
group by FLOOR(id/5)
) as high_low_t on (open_close_t.cross_id=high_low_t.cross_id)
Current Updated Results:
we can seperate this question with two main query.
get the the open and close value
get max and min id of every 5th row
use that max_id and min id to get close and open value
get highest and lowest value
first we need get max and min cross open,high,low,close per row
group by 5th rows the previous generated and get highest and lowest values
join two previous generated table by max_id(cross_id in the sql) of each 5th rows
select open_close_t.cross_id,open_close_t.open_val,open_close_t.close_val,high_low_t.high,high_low_t.low,high_low_t.total_volume from (
select open_close.max_id as cross_id,open_t.open as open_val,close_t.close as close_val from
(select max(id) as max_id,min(id) as min_id from intraday group by CEILING(id/5)) as open_close
inner join intraday as open_t on (open_t.id=open_close.min_id)
inner join intraday as close_t on (close_t.id=open_close.max_id)
) as open_close_t
left join (
select max(id) as cross_id,max(high_val) as high,min(low_val) as low,sum(volume) as total_volume
from (select id,greatest(open,high,low,close) as high_val,least(open,high,low,close) as low_val,volume from intraday) as _t
group by CEILING(id/5)
) as high_low_t on (open_close_t.cross_id=high_low_t.cross_id)
fix every 4 instead every 5 bug,because I should use ceiling not floor
the open_close temp table join problem from close_t.id=open_close.min_id to close_t.id=open_close.max_id
the lowest value using least not greatest
I made a db-fiddle example,if has further problem we can test on db-fiddle

mySQL can't get results with zero occurrences to show with left outer join

Im breaking my head with this little query so hopefully someone can tell me what is wrong with it. I need to group orders by same month and year as well as order status. So I have different table with which matches a number let's say 4 to an order status in text (let's say DELIVERED). I am trying get mySQL to tell me out of each Month/Year how many orders it had in every given status (even if that number is zero). Up to this point I receive the right answer except for the months/year that have zero occurences. So I only see for example: 05/205 | DELIVERED | 45, but if there is for example zero "PENDING" orders for 05/25 I do not see 05/2015 | PENDING | 0 and that is what i'm looking for. Thanks a bunch guys
select order_statuses.status, order_statuses.status_text,
Month(str_to_date(orders.order_date, '%d/%c/%y')) as Good_Month,
Year(str_to_date(orders.order_date, '%d/%c/%y')) as Good_Year,
CONCAT(Month(str_to_date(orders.order_date, '%d/%c/%y')), "/",Year(str_to_date(orders.order_date, '%d/%c/%y'))) as The_Date
from order_statuses
left outer join orders
on order_statuses.`status` = orders.order_status
where orders.admin_comments like '%moveit.ca%'
group by Good_Year, Good_Month, order_statuses.`status`

Print all rows from a nested Left Join + Union MySQL query

I have a query that calculates information for a revolving monthly retainer. The Project has a certain number of hours assigned to it each monthly period, with periods starting at different times of the month (e.g., February 5th to March 4th). The columns of the query result include:
Project Name
Total Hours Logged
Monthly Hours Remaining
Last Day of Period
Days Remaining
For example, Project A has 15 hours logged to it, with 5 hours remaining in the monthly period. The last day of the period is November 17th, with 3 days remaining from today.
The current Query takes 4 tables that are joined using a left join to print all of the Clients even if there are no hours logged, then it uses a nested subquery (lines 8-86) to calculate columns 2, 3, 4, and 5. However, column 4 and 5 do not print, they just show as always NULL. (Those are Last Day of Period and Days Remaining).
You can see the schema + query code at the SQL fiddle link here: http://sqlfiddle.com/#!2/fc830/12
How can I get column 4 and 5 to print the data and not be null when there are no Hours Logged? I think I may need an additional left join but I am not able to get a solution. If you have any suggestions it would be appreciated. Thanks!
When there has been no hours logged, the left join to the nested query returns nulls for the columns you're having trouble with.
The answer is to provide values for them when they are null and us ifnull() to use those value when the left join returns nulls:
select
...
ifnull(<left joined value>, <value when there's no join>),
...
See the working solution in sqlfiddle.
Incidentally, some values you are returning from the inner query, particularly the "last day" value can be derived directly from client (as seen in my solution). It's best to keep your queries as simple as possible - don't get data in a complicated way when there's an simpler or more direct way.
I think you are not thorough with the idea of LEFT JOIN. LEFT JOIN results in all rows from the outer table. Its otherwise called LEFT Outer JOIN (contrary to RIGHT OUTER JOIN). In your case the inner query results in only record with client id = 4. See the last clause X on X.id=client.id. Now what do you expect the results to be?
OUTER TABLE (client table)
id = 1, 2, 3, 4
INNER TABLE
client id = 4
ON X.id=client.id
An INNER JOIN would result in just one record since there is only matching record - for id = 4.
But a LEFT JOIN would result in all 4 records from outer table but the values for invalid fields will be NULL. Here except for client id 4, there is no valid records from inner table, hence they will be null.
To get more clarity you will have to see the id field along the records. Try this fiddle
You can see the other answer as to how to fill those NULL fields..

Multiple LEFT JOINs to self with criteria to produce distribution

Although several . questions . come . close . to what I want (and as I write this stackoverflow has suggested several more, none of which quite capture my problem), I just don't seem to be able to find my way out of the SQL thicket.
I have a single table (let's call it the user_classification_fct) that has three fields: user, week, and class (e.g. user #1 in week #1 had a class of 'Regular User', while user #2 in week #1 has a class of 'Infrequent User'). (As an aside, I have implemented classes as INTs, but wanted to work with something legible in the form of VARCHAR while I sorted out the SQL.)
What I want to do is produce a summary report of how user behaviour is changing in aggregate along the lines of:
There were 50 users who were regular users in both week 1 and week 2 and ...
There were 10 users who were regular users in week 1, but fell to infrequent users in week 2
There were 5 users who went from infrequent in week 1 to regular in week 2
... and so on ...
What makes this slightly more tricky is that user #5000 might only have started using the service in week 2 and so have no record in the table for week 1. In that case, I'd want to see a NULL FOR week 1 and a 'Regular User' (or whatever is appropriate) for week 2. The size of the table is not strictly relevant, but with 5 weeks' worth of data I'm looking at 42 million rows, so I do not want to insert 4 'fake' rows of 'Non-User' for someone who only starts using the service in week 5 or something.
To me this seems rather obviously like a case for using a LEFT or RIGHT JOIN in MySQL because the NULL should come through on the 'missing' record.
I have tried using both WHERE and AND conditions on the LEFT JOINs and am just not getting the 'right' answers (i.e. I either get no NULL values at all in the case of trailing WHERE conditions, or my counts are far, far too high for the number of distinct users (which is ca. 10 million) in the case of the AND constraints used below). Here's was my last attempt to get this working:
SELECT
ucf1.class_nm AS 'Class in 2012/15',
ucf2.class_nm AS 'Class in 2012/16',
ucf3.class_nm AS 'Class in 2012/17',
ucf4.class_nm AS 'Class in 2012/18',
ucf5.class_nm AS 'Class in 2012/19',
count(*) AS 'Count'
FROM
user_classification_fct ucf5
LEFT JOIN user_classification_fct ucf4
ON ucf5.user_id=ucf4.user_id
AND ucf5.week_key=201219 AND ucf4.week_key=201218
LEFT JOIN user_classification_fct ucf3
ON ucf4.user_id=ucf3.user_id
AND ucf4.week_key=201218 AND ucf3.week_key=201217
LEFT JOIN user_classification_fct ucf2
ON ucf3.user_id=ucf2.user_id
AND ucf3.week_key=201217 AND ucf2.week_key=201216
LEFT JOIN user_classification_fct ucf1
ON ucf2.user_id=ucf1.user_id
AND ucf2.week_key=201216 AND ucf1.week_key=201215
GROUP BY 1,2,3,4,5;
In looking at the various other questions on stackoverflow.com, it may well be that I need to perform the queries one-at-a-time and UNION the result sets together or use parentheses to chain them one-to-another, but those approaches are not ones that I'm familiar with (yet) and I can't even get a single LEFT JOIN (i.e. week 5 to week 1, dropping all the other weeks of data) to return something useful.
Any tips would be much, much appreciated and I would really appreciate suggestions that work in MySQL as switching database products is not an option.
You can do this with a group by. I would start by summarizing all the possible combinations for the five weeks as:
select c_201215, c_201216, c_201217, c_201218, c_201219,
count(*) as cnt
from (select user_id,
max(case when week_key=201215 then class_nm end) as c_201215,
max(case when week_key=201216 then class_nm end) as c_201216,
max(case when week_key=201217 then class_nm end) as c_201217,
max(case when week_key=201218 then class_nm end) as c_201218,
max(case when week_key=201219 then class_nm end) as c_201219
from user_classification_fct ucf
group by user_id
) t
group by c_201215, c_201216, c_201217, c_201218, c_201219
This may solve your problem. If you have 5 classes (including NULL), then this will return at most 5^5 or 3,125 rows.
This fits into Excel, so you can do the final processing there. Alternatively, you can still use the database.
If you want to extract pairs of weeks, then I would suggest putting the above into a temporary table, say "t". And doing a series of extracts with unions:
select *
from ((select '201215' as weekstart, c_201215, c_201216, sum(cnt) as cnt
from t
group by c_201215, c_201216
) union all
(select '201216', c_201216, c_201217, sum(cnt) as cnt
from t
group by c_201216, c_201217
) union all
(select '201217', c_201217, c_201218, sum(cnt) as cnt
from t
group by c_201217, c_201218
) union all
(select '201218', c_201218, c_201219, sum(cnt) as cnt
from t
group by c_201218, c_201219
)
) tg
order by 1, cnt desc
I suggest putting it in a subquery because you don't want to message around with common-subquery optimizations on such a large table. You'll get to your final answer by summarizing first, and then bringing the data together.

MySQL multiple left join group by

There is something wrong with this MySQL code.
it seems to be returning more stock then there should be.
table positions holds the stock available (multiple positions one product)
table orderbody holds the orders ordered products (1 orderheader to many orderbody)
SELECT PRO.ProductID,
PRO.ProductCode,
SUM( POS.Qty ) AS instock,
SUM( OB.Qty ) AS onorder
FROM products AS PRO
LEFT JOIN position AS POS ON POS.ProductID = PRO.ProductID
LEFT JOIN orderbody AS OB ON OB.ProductID = PRO.ProductID
WHERE POS.ProductID = OB.ProductID
GROUP BY PRO.ProductID, POS.ProductID, OB.ProductID
i'm getting instock 320
actual stock quantity = 40
number of positions = 2 (qty 20 each)
onorder = 16 qty
actual number of orderbody = 8 rows
actually on order = 8 (each with qty = 1)
this is on one of the products
i know it has something to do with the group by but i cant work it out.
Appreciate any help received.
I had the same problem a few days ago. Try it by SELECTing from a separate query: SELECT ... FROM products, (SELECT...)..." where you have the two tables to be left joined. try to test the sub-query by itself first, and try to put it together once ot works. (once you have the data you want, and not duplicates, because that is you problem.
you are selecting this field PRO.ProductCode, but not grouping by it, at a guess it might be the problem.