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.
Related
please help me, I want to display data with eloquent, before that I have to create a Raw Query to make it easier for me when I want to implement it to Eloquent.
I want to get a result like this :
What I want
I've tried using this query, but the result is not what I want.
SELECT COUNT(t.pengaduan_id) jml, t.kanal_id, mpk.nama_kanal, DATE_FORMAT(t.created_at, '%M %Y') tgl
FROM tr_pengaduan AS t
LEFT JOIN ms_pengaduan_kanal mpk ON t.kanal_id = mpk.kanal_id
GROUP BY YEAR(t.created_at),MONTH(t.created_at) ORDER BY kanal_id
Result of the above query :
Result
Here are the details from the table :
1. Table Tr_pengaduan : Tr_pengaduan
2. Table ms_pengaduan_kanal : ms_pengaduan_kanal
Is there a way to get the result I want with/without creating a procedure or function? I really appreciate it if you reply with Raw Query or Eloquent or both.
UPDATE :
I tried #ProGu 's suggestion, but the result of the join is partially NULL.
SELECT COUNT(t.pengaduan_id) jml, t.kanal_id, mpk.nama_kanal, DATE_FORMAT(t.created_at, '%M %Y') tgl, mont.MONTH
FROM tr_pengaduan AS t
LEFT JOIN ms_pengaduan_kanal mpk ON t.kanal_id = mpk.kanal_id
LEFT JOIN (SELECT MONTH(CURRENT_DATE()) AS
MONTH
UNION SELECT MONTH(CURRENT_DATE())-1 AS
MONTH
UNION SELECT MONTH(CURRENT_DATE())-2 AS
MONTH
UNION SELECT MONTH(CURRENT_DATE())-3 AS
MONTH
UNION SELECT MONTH(CURRENT_DATE())-4 AS
MONTH) AS mont ON MONTH(t.created_at) = mont.MONTH
WHERE t.kanal_id = mpk.kanal_id
GROUP BY mpk.kanal_id ORDER BY kanal_id
Result : Update Result
try something like this:
three parts in the query
month5 - represent current month and previous 4 months
t - id and names for result set
counts - counting results for last 5 months
SELECT COALESCE(counts.jml, 0) counts, t.kanal_id, t.nama_kanal, DATE_FORMAT(CURDATE() - INTERVAL month5.months MONTH, '%M %Y') tgl
FROM (
SELECT 0 AS months
UNION
SELECT 1
UNION
SELECT 2
UNION
SELECT 3
UNION
SELECT 4
) month5
CROSS JOIN (
SELECT t.kanal_id, MAX(mpk.nama_kanal) nama_kanal
FROM tr_pengaduan t
LEFT JOIN ms_pengaduan_kanal mpk ON t.kanal_id = mpk.kanal_id
WHERE t.created_at BETWEEN CURDATE() - INTERVAL DAY(CURDATE()) - 1 DAY - INTERVAL 4 MONTH AND NOW()
GROUP BY t.kanal_id
) t
LEFT JOIN (
SELECT COUNT(t.pengaduan_id) jml, t.kanal_id, DATE(MAX(t.created_at)) tgl
FROM tr_pengaduan AS t
WHERE t.created_at BETWEEN CURDATE() - INTERVAL DAY(CURDATE()) - 1 DAY - INTERVAL 4 MONTH AND NOW()
GROUP BY t.kanal_id, YEAR(t.created_at), MONTH(t.created_at)
) counts ON LAST_DAY(counts.tgl) = LAST_DAY(CURDATE() - INTERVAL month5.months MONTH) AND t.kanal_id = counts.kanal_id
ORDER BY kanal_id, tgl
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
Could you help me to calculate percent of users, which made payments?
I've got two tables:
activity
user_id login_time
201 01.01.2017
202 01.01.2017
255 04.01.2017
255 05.01.2017
256 05.01.2017
260 15.03.2017
2
payments
user_id payment_date
200 01.01.2017
202 01.01.2017
255 05.01.2017
I try to use this query, but it calculates wrong percent:
SELECT activity.login_time, (select COUNT(distinct payments.user_id)
from payments where payments.payment_time between '2017-01-01' and
'2017-01-05') / COUNT(distinct activity.user_id) * 100
AS percent
FROM payments INNER JOIN activity ON
activity.user_id = payments.user_id and activity.login_time between
'2017-01-01' and '2017-01-05'
GROUP BY activity.login_time;
I need a result
01.01.2017 100 %
02.01.2017 0%
03.01.2017 0%
04.01.2017 0%
05.01.2017 - 50%
If you want the ratio of users who have made payments to those with activity, just summarize each table individually:
select p.cnt / a.cnt
from (select count(distinct user_id) as cnt from activity a) a cross join
(select count(distinct user_id) as cnt from payment) p;
EDIT:
You need a table with all dates in the range. That is the biggest problem.
Then I would recommend:
SELECT d.dte,
( ( SELECT COUNT(DISTINCT p.user_id)
FROM payments p
WHERE p.payment_date >= d.dte and p.payment_date < d.dte + INTERVAL 1 DAY
) /
NULLIF( (SELECT COUNT(DISTINCT a.user_id)
FROM activity a
WHERE a.login_time >= d.dte and p.login_time < d.dte + INTERVAL 1 DAY
), 0
) as ratio
FROM (SELECT date('2017-01-01') dte UNION ALL
SELECT date('2017-01-02') dte UNION ALL
SELECT date('2017-01-03') dte UNION ALL
SELECT date('2017-01-04') dte UNION ALL
SELECT date('2017-01-05') dte
) d;
Notes:
This returns NULL on days where there is no activity. That makes more sense to me than 0.
This uses logic on the dates that works for both dates and date/time values.
The logic for dates can make use of an index, which can be important for this type of query.
I don't recommend using LEFT JOINs. That will multiply the data which can make the query expensive.
First you need a table with all days in the range. Since the range is small you can build an ad hoc derived table using UNION ALL. Then left join the payments and activities. Group by the day and calculate the percentage using the count()s.
SELECT x.day,
concat(CASE count(DISTINCT a.user_id)
WHEN 0 THEN
1
ELSE
count(DISTINCT p.user_id)
/
count(DISTINCT a.user_id)
END
*
100,
'%')
FROM (SELECT cast('2017-01-01' AS date) day
UNION ALL
SELECT cast('2017-01-02' AS date) day
UNION ALL
SELECT cast('2017-01-03' AS date) day
UNION ALL
SELECT cast('2017-01-04' AS date) day
UNION ALL
SELECT cast('2017-01-05' AS date) day) x
LEFT JOIN payments p
ON p.payment_date = x.day
LEFT JOIN activity a
ON a.login_time = x.day
GROUP BY x.day;
I have MySQL queries both of which work fine independantly which I would like to combine together so I get three values returned.
Query 1 checks how many accounts have been deleted:
SELECT
COUNT(1) AS deleted_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date
FROM
exit_reasons e
WHERE
e.timestamp>='$sixmonths'
GROUP BY
WEEKOFYEAR(e.timestamp)
ORDER BY
display_date ASC
LIMIT 26
This returns a date and the number who deleted in that week
Query 2 checks how many of these have subsequently signed up again:
SELECT
COUNT(1) AS date_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date
FROM
exit_reasons e
LEFT JOIN
companies c on e.email=c.email
WHERE
e.timestamp>='$sixmonths' AND c.email IS NOT NULL
GROUP BY
WEEKOFYEAR(e.timestamp)
ORDER BY
display_date ASC
LIMIT 26
This returns a date and the number of that weeks deleted who now have a new account
I would like it to return a date and then the number deleted and number rejoined in one query so I tried:
SELECT
COUNT(1) AS date_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date,
date_count as rejoined_count from
(SELECT
COUNT(1) AS date_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date
FROM
exit_reasons e2
LEFT JOIN
companies c on e.email=c.email
LEFT JOIN
companies_users cu on e.email=cu.email
WHERE
e2.timestamp>='$sixmonths' AND c.email IS NOT NULL
GROUP BY
WEEKOFYEAR(e.timestamp)
ORDER BY
display_date ASC
LIMIT 26)
FROM
exit_reasons e
WHERE
e.timestamp>='$sixmonths'
GROUP BY
WEEKOFYEAR(e.timestamp)
ORDER BY
display_date ASC
LIMIT 26
but I am getting a syntax error - how can I combine these queries together into one query?
You should be able to combine the two queries into a single query by using an aggregate function along with some conditional logic like a CASE expression:
SELECT
COUNT(1) AS deleted_count,
SUM(CASE WHEN c.email IS NOT NULL THEN 1 ELSE 0 END) as date_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date
FROM exit_reasons e
LEFT JOIN companies c
on e.email=c.email
WHERE e.timestamp>='$sixmonths'
GROUP BY WEEKOFYEAR(e.timestamp)
ORDER BY display_date ASC
LIMIT 26;
See Demo. Your check on the second query if the c.email IS NOT NULL is moved into the SUM(CASE.. which allows you to get a total of the rows that are not null.
I think the following will do what you want:
SELECT COUNT(*) AS deleted_count,
COUNT(c.email) as date_count,
SUBDATE(e.timestamp, INTERVAL WEEKDAY(e.timestamp) DAY) AS display_date
FROM exit_reasons e LEFT JOIN
companies c
on e.email = c.email
WHERE e.timestamp >= '$sixmonths'
GROUP BY WEEKOFYEAR(e.timestamp)
ORDER BY display_date ASC
LIMIT 26;
In the event that someone can sign up more than once with the same email, you should change the count() to use distinct:
COUNT(DISTINCT e.email) as deleted_count,
COUNT(DISTINCT c.email) as date_count
I have these two tables:
DateRanges
some_id start_date end_date
---------------------------------
1 2012-12-01 2012-12-15
1 2013-01-01 2013-01-15
3 2013-01-03 2013-01-10
Items
id name
----------------
1 Some name
2 Other name
3 So on...
What I try to achieve is to get, for each element in Items table, the biggest start_date (ignoring the smaller dates/date ranges for that Item) and check if the current date is in that range, like in the next table (let's say today's 02 January 2013):
id name TodayIsInTheRange
---------------------------------------------
1 Some name true
2 Other name false
3 So on... false
I have tried to obtain the 3rd table with the next query:
SELECT A.*, (B.`start_date` <= CURRENT_DATE AND CURRENT_DATE <= B.`end_date`) AS `TodayIsInTheRange`
FROM `Items` as A
LEFT JOIN `DateRanges` as B ON
A.id = B.some_id
SORT BY B.`end_date` DESC
But with this query my items repeat themselves because I have two records in DateRanges for the same item.
I use SQL Server, but I think something like this should be pretty close:
SELECT
I.Id,
I.Name,
(DR.start_date <= CURRENT_DATE AND CURRENT_DATE <= DR.end_date) AS `TodayIsInTheRange`
FROM `Items` AS I
LEFT JOIN
(SELECT Some_Id, MAX(Start_Date) as MaxStartDate
FROM `DateRanges`
GROUP BY Some_ID) AS HDR ON I.Id = HDR.Some_Id
LEFT JOIN `DateRanges` AS DR ON HDR.Some_Id = DR.Some_Id AND HDR.MaxStartDate = DR.Start_Date
select * from items join date_ranges dr0 on items.id = dr0.some_id
where start_date =
(select max(start_date) from date_ranges dr1 where dr0.some_id = dr1.some_id);
SELECT
a.*,
( b.start_date <= CURRENT_DATE
AND CURRENT_DATE <= b.end_date ) AS TodayIsInTheRange
FROM
Items AS a
LEFT JOIN
( SELECT some_id, MAX(start_date) AS start_date
FROM DateRanges
GROUP BY some_id
) AS m
JOIN
DateRanges AS b
ON b.some_id = m.some_id
ON a.id = m.some_id
ORDER BY b.end_date DESC ;
try to use GROUP BY and MAX function. The first provide you only one row for each item.id, the second tell you if there is at least one date in your range
SELECT A.*, MAX(B.`start_date` <= CURRENT_DATE AND CURRENT_DATE <= B.`end_date`) AS `TodayIsInTheRange`
FROM `Items` as A
LEFT JOIN `DateRanges` as B ON
A.id = B.some_id
GROUP BY A.id
ORDER BY B.`end_date` DESC