JOIN, WHERE and the QUERY - mysql

Here is the code. It returns table with 0 values in months where there are no data in database. Perfect!
SELECT YEAR(k.miesiace) as year,
MONTH(k.miesiace) AS month,
IFNULL(YEAR(data_zlozenia), YEAR(k.miesiace)) order_year,
IFNULL(MONTH(data_zlozenia), MONTH(k.miesiace)) order_month,
IFNULL(MONTHNAME(data_zlozenia), MONTHNAME(k.miesiace)) monthname,
IFNULL(SUM(zp.cena_netto * ilosc), 0) sum
FROM kalendarz k
LEFT OUTER JOIN zamowienia z
on MONTH(z.data_zlozenia) = MONTH(k.miesiace) AND YEAR(z.data_zlozenia) = YEAR(k.miesiace)
LEFT OUTER JOIN zamowienia_pozycje zp on z.id_ezamowienia = zp.id_ezamowienia
WHERE k.miesiace >= DATE_SUB(now(), INTERVAL 12 MONTH)
AND k.miesiace <= now()
GROUP BY MONTH(k.miesiace), YEAR(k.miesiace)
ORDER BY YEAR(k.miesiace), MONTH(k.miesiace);
But when I add to my WHERE clause this:
AND zp.id_artykulu = 9593
it ruins my code and limits output to only months where data existed instead of all 12 months. What should I change brothers?

Move your zp in a subquery.
SELECT YEAR(k.miesiace) as year,
MONTH(k.miesiace) AS month,
IFNULL(YEAR(data_zlozenia), YEAR(k.miesiace)) order_year,
IFNULL(MONTH(data_zlozenia), MONTH(k.miesiace)) order_month,
IFNULL(MONTHNAME(data_zlozenia), MONTHNAME(k.miesiace)) monthname,
IFNULL(SUM(zp.cena_netto * zp.ilosc), 0) sum
FROM kalendarz k
LEFT OUTER JOIN zamowienia z
on MONTH(z.data_zlozenia) = MONTH(k.miesiace) AND YEAR(z.data_zlozenia) = YEAR(k.miesiace)
LEFT OUTER JOIN
(select ilosc, cena_netto, id_ezamowienia from zamowienia_pozycje
where id_artykulu = 9593) zp on z.id_ezamowienia = zp.id_ezamowienia
WHERE k.miesiace >= DATE_SUB(now(), INTERVAL 12 MONTH)
AND k.miesiace <= now()
GROUP BY MONTH(k.miesiace), YEAR(k.miesiace)
ORDER BY YEAR(k.miesiace), MONTH(k.miesiace);

I am not sure to understand your exact need correctly but I think your WHERE clause you try to add is misplaced. It might work well like this :
SELECT YEAR(k.miesiace) as year,
MONTH(k.miesiace) AS month,
IFNULL(YEAR(data_zlozenia), YEAR(k.miesiace)) order_year,
IFNULL(MONTH(data_zlozenia), MONTH(k.miesiace)) order_month,
IFNULL(MONTHNAME(data_zlozenia), MONTHNAME(k.miesiace)) monthname,
IFNULL(SUM(zp.cena_netto * ilosc), 0) sum
FROM kalendarz k
LEFT OUTER JOIN zamowienia z
on (MONTH(z.data_zlozenia) = MONTH(k.miesiace) AND YEAR(z.data_zlozenia) = YEAR(k.miesiace))
LEFT OUTER JOIN zamowienia_pozycje zp on (z.id_ezamowienia = zp.id_ezamowienia AND zp.id_artykulu = 9593)
WHERE k.miesiace >= DATE_SUB(now(), INTERVAL 12 MONTH)
AND k.miesiace <= now()
GROUP BY MONTH(k.miesiace), YEAR(k.miesiace)
ORDER BY YEAR(k.miesiace), MONTH(k.miesiace);

zp is left joined so adding this where clause:
AND zp.id_artykulu = 9593
will turn the query into kind of an inner join... rows from kalendarz table will be suppressed if there is no match in the right table. Move the condition to the on clause:
LEFT OUTER JOIN zamowienia_pozycje zp on z.id_ezamowienia = zp.id_ezamowienia and zp.id_artykulu = 9593

Related

How to set default value from mysql join interval yearmonth

I have problem with my query. I have two tables and I want join them to get the results based on primary key on first table, but I missing 1 data from first table.
this my fiddle
as you can see, I missing "xx3" from month 1
I have tried to change left and right join but, the results stil same.
So as you can see I have to set coalesce(sum(b.sd_qty),0) as total, if no qty, set 0 as default.
You should cross join the table to the distinct dates also:
SELECT a.item_code,
COALESCE(SUM(b.sd_qty), 0) total,
DATE_FORMAT(d.sd_date, '%m-%Y') month_year
FROM item a
CROSS JOIN (
SELECT DISTINCT sd_date
FROM sales_details
WHERE sd_date >= '2020-04-01' - INTERVAL 3 MONTH AND sd_date < '2020-05-01'
) d
LEFT JOIN sales_details b
ON a.item_code = b.item_code AND b.sd_date = d.sd_date
GROUP BY month_year, a.item_code
ORDER BY month_year, a.item_code;
Or, for MySql 8.0+, with a recursive CTE that returns the starting dates of all the months that you want the results, which can be cross joined to the table:
WITH RECURSIVE dates AS (
SELECT '2020-04-01' - INTERVAL 3 MONTH AS sd_date
UNION ALL
SELECT sd_date + INTERVAL 1 MONTH
FROM dates
WHERE sd_date + INTERVAL 1 MONTH < '2020-05-01'
)
SELECT a.item_code,
COALESCE(SUM(b.sd_qty), 0) total,
DATE_FORMAT(d.sd_date, '%m-%Y') month_year
FROM item a CROSS JOIN dates d
LEFT JOIN sales_details b
ON a.item_code = b.item_code AND DATE_FORMAT(b.sd_date, '%m-%Y') = DATE_FORMAT(d.sd_date, '%m-%Y')
GROUP BY month_year, a.item_code
ORDER BY month_year, a.item_code;
See the demo.

Getting the results of a query for each day in the past 30 days

Below is a query I run to extract some data in the past 24 hours.
SELECT
s.symbol,
count(cs.symbol_id) AS mentions
FROM symbols s
LEFT JOIN comments_symbols cs ON cs.symbol_id = s.id
LEFT JOIN comments c ON c.id = cs.comment_id
WHERE c.`date` > DATE_SUB(NOW(), INTERVAL 1 DAY)
GROUP BY (s.symbol)
ORDER BY mentions
DESC LIMIT 15
However, I need 24 hour intervals of data for the past 30 days in order to show a 30-day chart.
Instead of executing this query 30 times for the each day in the past 30 days, is there an approach I can take to do it with just one query execution?
It seems executing this query 30 times per page load may not be the best way to do this, no?
I hope I explained clearly, please let me know if any details are fuzzy.
Let me assume you have a list of dates. If you don't want to list them out, you can generate them:
with recursive dates as (
select curdate() - interval 30 day as dte
union all
select dte + interval 1 day
from dates
where dte < curdate()
)
Second, the LEFT JOIN seems superfluous, because you are filtering the results using LIMIT. However, I'll leave it in. Use a cross join to generate a row for each day and symbol . . . then aggregate:
SELECT s.symbol, COUNT(cs.symbol_id) AS mentions
FROM dates d CROSS JOIN
symbols s LEFT JOIN
comments_symbols cs
ON cs.symbol_id = s.id LEFT JOIN
comments c
ON c.id = cs.comment_id AND
c.date >= d.dte AND
c.date < d.date + interval 1 day
GROUP BY d.dte, s.symbol
ORDER BY d.dte, mentions DESC
Finally, to get 15 per day, let's put that into a CTE and use window functions:
WITH sm as (
SELECT d.dte, s.symbol, COUNT(cs.symbol_id) AS mentions
FROM dates d CROSS JOIN
symbols s LEFT JOIN
comments_symbols cs
ON cs.symbol_id = s.id LEFT JOIN
comments c
ON c.id = cs.comment_id AND
c.date >= d.dte AND
c.date < d.date + interval 1 day
GROUP BY d.dte, s.symbol
)
SELECT cs.*
FROM (SELECT cs.*,
ROW_NUMBER() OVER (PARTITION BY dte ORDER BY mentions DESC) as seqnum
FROM cs
) cs
WHERE seqnum <= 15;
ORDER BY dte, mentions DESC;

Join two queries mysql

I would like to join two queries :
This first query get "pgm_posts.post_title" and "pgm_post_views.count" (WHERE pgm_post_views.type="4")
SELECT pgm_posts.post_title, pgm_post_views.count
FROM pgm_posts, pgm_post_views
WHERE pgm_post_views.type="4" AND pgm_post_views.id = pgm_posts.id
This second query get all the ids from "pgm_post_views" with condition.
pgm_post_views.type="0" : its the condition for get "period". (look img)
SELECT pgm_posts.id
FROM pgm_posts
INNER JOIN pgm_post_views
ON pgm_posts.id = pgm_post_views.id AND pgm_post_views.type="0" AND DATE_FORMAT(pgm_post_views.period, "%Y-%m-%d") <= DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y-%m-%d') AND DATE_FORMAT(pgm_posts.post_date, "%Y-%m-%d") <= DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y-%m-%d')
Resume : First query get post_title and total count based on all ids of second query.
How i can do this ?
A simple way is a pure join based on selected table
SELECT p
gm_posts.post_title
, pgm_post_views.count
FROM pgm_posts
INNER JOIN pgm_post_views ON pgm_post_views.id = pgm_posts.id
AND pgm_post_views.type="4"
INNER JOIN (
SELECT
pgm_posts.id
FROM pgm_posts
INNER JOIN pgm_post_views ON pgm_posts.id = pgm_post_views.id AND pgm_post_views.type="0"
AND DATE_FORMAT(pgm_post_views.period, "%Y-%m-%d") <=
DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y-%m-%d')
AND DATE_FORMAT(pgm_posts.post_date, "%Y-%m-%d") <=
DATE_FORMAT(CURRENT_DATE - INTERVAL 1 MONTH, '%Y-%m-%d')
) T on T.id = pgm_posts

MySql graph query multiple series aligned to same time x-axis

I have queries that I'm using to make a graph of earnings. But now people are able to earn from two different sources, so I want to separate this out into two lines on the same chart
This one for standard earnings:
SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day, SUM(earning_amount) AS earning_standard
FROM earnings
WHERE earning_account_id = ? AND earning_referral_id = 0 AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
GROUP BY DATE(earning_created)
ORDER BY earning_created
And this one for referral earnings:
SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day, SUM(e.earning_amount) AS earning_referral
FROM earnings AS e
INNER JOIN referrals AS r
ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ? AND e.earning_referral_id > 0 AND (e.earning_created > DATE_SUB(now(), INTERVAL 90 DAY)) AND r.referral_type = 0
GROUP BY DATE(e.earning_created)
ORDER BY e.earning_created
How do I get it to run the queries together, so that it outputs two columns/series for the y-axis: earning_standard and earning_referral.
But with them both aligned to the same day column/scale for the x-axis - substituting zero when there are no earnings for a specific series.
You'll need to set both of those queries as subqueries
SELECT DATE_FORMAT(earnings.earning_created, '%c/%e/%Y') AS day,
COALESCE(es.earning_standard, 0) AS earning_standard,
COALESCE(er.earning_referral, 0) AS earning_referral
FROM earnings
LEFT JOIN (SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day,
SUM(earning_amount) AS earning_standard
FROM earnings
WHERE earning_account_id = ?
AND earning_referral_id = 0
AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
GROUP BY DATE(earning_created)) AS es
ON (day = es.day)
LEFT JOIN (SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day,
SUM(e.earning_amount) AS earning_referral
FROM earnings AS e
INNER JOIN referrals AS r
ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ?
AND e.earning_referral_id > 0
AND (e.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND r.referral_type = 0
GROUP BY DATE(e.earning_created)) AS er
ON (day = er.day)
WHERE earnings.earning_account_id = ?
ORDER BY day
where I'm assuming earning_account_id = ? is intended to be with a question mark because the language you're using to run the query is replacing it with the actual id before running the query.
SELECT
COALESCE(t1.amount,0) AS link_earnings,
COALESCE(t2.amount,0) AS publisher_referral_earnings,
COALESCE(t3.amount,0) AS advertiser_referral_earnings,
t1.day AS day
FROM
(
SELECT DATE_FORMAT(earning_created, '%c/%e/%Y') AS day, SUM(earning_amount) AS amount
FROM earnings
WHERE earning_referral_id = 0
AND (earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND earning_account_id = ?
GROUP BY DATE(earning_created)
) t1
LEFT JOIN
(
SELECT DATE_FORMAT(ep.earning_created, '%c/%e/%Y') AS day, (SUM(ep.earning_amount) * rp.referral_share) AS amount
FROM earnings AS ep
INNER JOIN referrals AS rp
ON ep.earning_referral_id = rp.referral_id
WHERE ep.earning_referral_id > 0
AND (ep.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND ep.earning_account_id = ?
AND rp.referral_type = 0
GROUP BY DATE(ep.earning_created)
) t2
ON t1.day = t2.day
LEFT JOIN
(
SELECT DATE_FORMAT(ea.earning_created, '%c/%e/%Y') AS day, (SUM(ea.earning_amount) * ra.referral_share) AS amount
FROM earnings AS ea
INNER JOIN referrals AS ra
ON ea.earning_referral_id = ra.referral_id
WHERE ea.earning_referral_id > 0
AND (ea.earning_created > DATE_SUB(now(), INTERVAL 90 DAY))
AND ea.earning_account_id = ?
AND ra.referral_type = 1
GROUP BY DATE(ea.earning_created)
) t3
ON t1.day = t3.day
ORDER BY day
Seems to run ok....
You can simply use an outer join to retain earnings even when there is no matching referral, and then conditionally sum depending on whether a referral exists or not:
SELECT DATE_FORMAT(e.earning_created, '%c/%e/%Y') AS day,
SUM(IF(r.referral_id IS NULL, e.earning_amount, 0)) earning_standard,
SUM(IF(r.referral_id IS NULL, 0, e.earning_amount)) earning_referral
FROM earnings e LEFT JOIN referrals r ON r.referral_id = e.earning_referral_id
WHERE e.earning_account_id = ?
AND e.earning_created > CURRENT_DATE - INTERVAL 90 DAY
AND (r.referral_id IS NULL OR r.referral_type = 0)
GROUP BY 1
ORDER BY 1
I've assumed here that earnings.earning_referral_id is never negative, though you can add an explicit test to filter such records if so desired.
I've also changed the filter on earnings.earning_created to base from CURRENT_DATE rather than NOW() to ensure that any earnings created earlier than the current time on the first day of the series are still included—this would typically be what one actually wants, but feel free to change back if not.

Filling empty MySQL Result

I want to have a continuos date set with the sales.
SELECT *,
UNIX_TIMESTAMP(calendar.datefield) * 1000 AS time,
IFNULL(Sum(mos + mosnk), 0) AS mosfinal,
IFNULL(Sum(neukunden), 0) AS neukunden
FROM sms_stats
RIGHT JOIN calendar
ON ( DATE(sms_stats.date) = calendar.datefield )
WHERE calendar.datefield BETWEEN Curdate() - INTERVAL 90 day AND Now()
GROUP BY Date_format(calendar.datefield, '%Y%m%d')
this returns me a list of the last 90 days. Now I want to filter it, but if I do
WHERE owner = 2 AND calendar.datefield BETWEEN Curdate() - INTERVAL 90 day AND Now()
it just returns one result and not the list of dates.
The condition in the where clause "undoes" the right outer join. The solution is to move the condition to the where clause. I have a preference for left outer join over right outer join, so I'll swap the tables:
SELECT *,
UNIX_TIMESTAMP(c.datefield) * 1000 AS time,
IFNULL(Sum(mos + mosnk), 0) AS mosfinal,
IFNULL(Sum(neukunden), 0) AS neukunden
FROM calendar c LEFT JOIN
sms_stats ss
ON DATE(ss.date) = c.datefield and
ss.owner = 2
WHERE c.datefield BETWEEN Curdate() - INTERVAL 90 day AND Now()
GROUP BY Date_format(c.datefield, '%Y%m%d') ;
I added in table aliases to make the query a bit more readable.