MySQL Group By Date for NULL values - mysql

I am trying to return the number of orders for every day a commerce site has been live, but for days where there were 0 orders my sql statement returns null, and hence their is a gap in the data. How can I insert a 0 into these days so the data is complete?
$sql = "SELECT COUNT(*) AS orders
FROM `orders`
GROUP BY DATE(`order_datetime`)
ORDER BY DATE(`order_time`) ASC";
$query = $this->db->query($sql);
I have tried using ifnull like so but I get the same result as above:
$sql = "SELECT ifnull(COUNT(*),0) AS orders
FROM `orders`
GROUP BY DATE(`order_datetime`)
ORDER BY DATE(`order_time`) ASC";
$query = $this->db->query($sql);

can you use COALESCE?
eg:
SELECT COALESCE(COUNT(*),0) AS orders
FROM `orders`
GROUP BY DATE(`order_datetime`)
ORDER BY DATE(`order_time`) ASC
it returns first non null value in the list - count if its not null and 0 if it is.
http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce
hang on.. is the issue that there are no rows with those dates so you just get results for those dates?
if so you could return the order date too and be a bit more clever about how you display your result:
SELECT order_datetime,COUNT(*) AS orders
FROM `orders`
GROUP BY DATE(`order_datetime`)
ORDER BY DATE(`order_time`) ASC
then when outputting your results maybe create a loop that iterates through the dates you are interested in and prints a 0 if there is no row for that date:
for (datetime d = startdate; d <= enddate; datetime.adddays(1)){
if (recordset[order_datetime] == d){
output (d,recordset[orders]);
recordset.movenext();
}else
{
output (d,0);
}
}
(note i have no idea what programming language that would be ;-)
if you want to do it all in the DB then look at this SO question for how to create a table of the dates on the fly and then you can left outer join from it to you query on date..
Get a list of dates between two dates

You should select all the dates you want manually in a sub-query and LEFT JOIN it with your table:
SELECT COUNT(*) AS orders FROM
( SELECT '2010-09-01' AS date UNION SELECT '2010-09-02' UNION
SELECT '2010-09-03' UNION SELECT '2010-09-04' UNION SELECT '2010-09-05' UNION
SELECT '2010-09-06' UNION SELECT '2010-09-07' ... ) AS dates
LEFT JOIN orders ON( dates.date = orders.order_datetime )
GROUP BY orders.order_datetime
ORDER BY orders.order_datetime
or use you current query and fill the gaps in your php program after receiving the results.

Related

Using the results of a function multiple times for duplicates - SQL

I am trying to produce a result that shows duplicates in a table. One method I found for getting duplicates and showing them is to run the select statement again through an inner join. However, one of my columns needs to be the result of a function, and the only thing I can think to do is use an alias, however I can't use the alias twice in a SELECT statement.
I am not sure what the best way to run this code for getting the duplicates I need.
My code below
SELECT EXTRACT(YEAR_MONTH FROM date) as 'ndate', a.transponderID
FROM dispondo_prod_disposition.event a
inner JOIN (SELECT EXTRACT(YEAR_MONTH FROM date) as ???,
transponderID, COUNT(*)
FROM dispondo_prod_disposition.event
GROUP BY mdate, transponderID
HAVING count(*) > 1 ) b
ON ndate = ???
AND a.transponderID = b.transponderID
ORDER BY b.transponderID
SELECT b.ndate, transponderID
FROM dispondo_prod_disposition.event a
INNER JOIN ( SELECT EXTRACT(YEAR_MONTH FROM date) as ndate,
transponderID
FROM dispondo_prod_disposition.event
GROUP BY 1, 2
HAVING COUNT(*) > 1 ) b USING (transponderID)
WHERE b.ndate = ??? -- for example, WHERE b.ndate = 202201
ORDER BY transponderID

Getting all value from every month, put zero if no data of that month

i'm trying to get data for each month, if there is no data found for a particular month, I will put zero. I already created a calendar table so I can left join it, but I still can't get zero.
Here's my query
SELECT calendar.month, IFNULL(SUM(transaction_payment.total),0) AS total
FROM `transaction`
JOIN `transaction_payment` ON `transaction_payment`.`trans_id` =
`transaction`.`trans_id`
LEFT JOIN `calendar` ON MONTH(transaction.date_created) = calendar.month
WHERE`date_created` LIKE '2017%' ESCAPE '!'
GROUP BY calendar.month
ORDER BY `date_created` ASC
the value in my calendar tables are 1-12(Jan-Dec) int
Result should be something like this
month total
1 0
2 20
3 0
4 2
..
11 0
12 10
UPDATE
The problem seems to be the SUM function
SELECT c.month, COALESCE(t.trans_id, 0) AS total
FROM calendar c
LEFT JOIN transaction t ON month(t.date_created) = c.month AND year(t.date_created) = '2018'
LEFT JOIN transaction_payment tp ON tp.trans_id = t.trans_id
ORDER BY c.month ASC
I tried displaying the ID only and it's running well. but when I add back this function. I can only get months with values.
COALESCE(SUM(tp.total), 0);
This fixes the issues with your query:
SELECT c.month, COALESCE(SUM(tp.total), 0) AS total
FROM calendar c LEFT JOIN
transaction t
ON month(t.date_created) = month(c.month) AND
year(t.date_created) = '2017' LEFT JOIN
transaction_payment tp
ON tp.trans_id = t.trans_id
GROUP BY c.month
ORDER BY MIN(t.date_created) ASC;
This will only work if the "calendar" table has one row per month -- that seems odd, but that might be your data structure.
Note the changes:
Start with the calendar table, because those are the rows you want to keep.
Do not use LIKE with dates. MySQL has proper date functions. Use them.
The filtering conditions on all but the first table should be in the ON clause rather than the WHERE clause.
I prefer COALESCE() to IFNULL() because COALESCE() is ANSI standard.
You need to use right as per your query because you calendar table is present at right side
SELECT calendar.month, IFNULL(SUM(transaction_payment.total),0) AS total
FROM `transaction`
JOIN `transaction_payment` ON `transaction_payment`.`trans_id` =
`transaction`.`trans_id`
RIGHT JOIN `calendar` ON MONTH(transaction.date_created) = calendar.month
WHERE`date_created` LIKE '2017%' ESCAPE '!'
GROUP BY calendar.month
ORDER BY `date_created` ASC

Select most recent record grouped by 3 columns

I am trying to return the price of the most recent record grouped by ItemNum and FeeSched, Customer can be eliminated. I am having trouble understanding how I can do that reasonably.
The issue is that I am joining about 5 tables containing hundreds of thousands of rows to end up with this result set. The initial query takes about a minute to run, and there has been some trouble with timeout errors in the past. Since this will run on a client's workstation, it may run even slower, and I have no access to modify server settings to increase memory / timeouts.
Here is my data:
Customer Price ItemNum FeeSched Date
5 70.75 01202 12 12-06-2017
5 70.80 01202 12 06-07-2016
5 70.80 01202 12 07-21-2017
5 70.80 01202 12 10-26-2016
5 82.63 02144 61 12-06-2017
5 84.46 02144 61 06-07-2016
5 84.46 02144 61 07-21-2017
5 84.46 02144 61 10-26-2016
I don't have access to create temporary tables, or views and there is no such thing as a #variable in C-tree, but in most ways it acts like MySql. I wanted to use something like GROUP BY ItemNum, FeeSched and select MAX(Date). The issue is that unless I put Price into the GROUP BY I get an error.
I could run the query again only selecting ItemNum, FeeSched, Date and then doing an INNER JOIN, but with the query taking a minute to run each time, it seems there is a better way that maybe I don't know.
Here is my query I am running, it isn't really that complicated of a query other than the amount of data it is processing. Final results are about 50,000 rows. I can't share much about the database structure as it is covered under an NDA.
SELECT DISTINCT
CustomerNum,
paid as Price,
ItemNum,
n.pdate as newest
from admin.fullproclog as f
INNER JOIN (
SELECT
id,
itemId,
MAX(TO_CHAR(pdate, 'MM-DD-YYYY')) as pdate
from admin.fullproclog
WHERE pdate > timestampadd(sql_tsi_year, -3, NOW())
group by id, itemId
) as n ON n.id = f.id AND n.itemId = f.itemId AND n.pdate = f.pdate
LEFT join (SELECT itemId AS linkid, ItemNum FROM admin.itemlist) AS codes ON codes.linkid = f.itemId AND ItemNum >0
INNER join (SELECT DISTINCT parent_id,
MAX(ins1.feesched) as CustomerNum
FROM admin.customers AS p
left join admin.feeschedule AS ins1
ON ins1.feescheduleid = p.primfeescheduleid
left join admin.group AS c1
ON c1.insid = ins1.feesched
WHERE status =1
GROUP BY parent_id)
AS ip ON ip.parent_id = f.parent_id
WHERE CustomerNum >0 AND ItemNum >0
UNION ALL
SELECT DISTINCT
CustomerNum,
secpaid as Price,
ItemNum,
n.pdate as newest
from admin.fullproclog as f
INNER JOIN (
SELECT
id,
itemId,
MAX(TO_CHAR(pdate, 'MM-DD-YYYY')) as pdate
from admin.fullproclog
WHERE pdate > timestampadd(sql_tsi_year, -3, NOW())
group by id, itemId
) as n ON n.id = f.id AND n.itemId = f.itemId AND n.pdate = f.pdate
LEFT join (SELECT itemId AS linkid, ItemNum FROM admin.itemlist) AS codes ON codes.linkid = f.itemId AND ItemNum >0
INNER join (SELECT DISTINCT parent_id,
MAX(ins1.feesched) as CustomerNum
FROM admin.customers AS p
left join admin.feeschedule AS ins1
ON ins1.feescheduleid = p.secfeescheduleid
left join admin.group AS c1
ON c1.insid = ins1.feesched
WHERE status =1
GROUP BY parent_id)
AS ip ON ip.parent_id = f.parent_id
WHERE CustomerNum >0 AND ItemNum >0
I feel it quite simple when I'd read the first three paragraphs, but I get a little confused when I've read the whole question.
Whatever you have done to get the data posted above, once you've got the data like that it's easy to retrive "the most recent record grouped by ItemNum and FeeSched".
How to:
Firstly, sort the whole result set by Date DESC.
Secondly, select fields you need from the sorted result set and group by ItemNum, FeeSched without any aggregation methods.
So, the query might be something like this:
SELECT t.Price, t.ItemNum, t.FeeSched, t.Date
FROM (SELECT * FROM table ORDER BY Date DESC) AS t
GROUP BY t.ItemNum, t.FeeSched;
How it works:
When your data is grouped and you select rows without aggregation methods, it will only return you the first row of each group. As you have sorted all rows before grouping, so the first row would exactly be "the most recent record".
Contact me if you got any problems or errors with this approach.
You can also try like this:
Select Price, ItemNum, FeeSched, Date from table where Date IN (Select MAX(Date) from table group by ItemNum, FeeSched,Customer);
Internal sql query return maximum date group by ItemNum and FeeSched and IN statement fetch only the records with maximum date.

how to mysql group by date multiple left join

I have the following schema:
http://sqlfiddle.com/#!9/bd3a4/1
I would like to
group by date() and add where user_id = ?..
per day and count the results per day.
required result Day|TotalRequests|TotalOrders
Since you could have an order on Day 1, and a request on Day 8, you may have entries on one side but not the other. To qualifify your needs, I would do a UNION of all orders and requests individually by date. Then roll those values up. The inner Pre-Aggregate result query is where the WHERE clause per user would be applied. The pre-aggregate query also has a recSource column to indicate where the record originated from as 'O' from orders and 'R' from requests, so the roll-up knows which column to store the total count respectively.
select
preAgg.recDate,
SUM( case when preAgg.recSource = 'O' then preAgg.recCount else 0 end ) as OrderCount,
SUM( case when preAgg.recSource = 'R' then preAgg.recCount else 0 end ) as RequestCount
from
( select
date(o.created_at) recDate,
'O' as recSource,
count(*) as recCount
from
orders o
where
o.user_id = 3
group by
date(o.created_at)
UNION ALL
select
date(r.created_at) recDate,
'R' as recSource,
count(*) as recCount
from
requests r
where
r.user_id = 3
group by
date(r.created_at) ) preAgg
group by
preAgg.recDate
order by
preAgg.recDate
For query optimization, I would ensure your order and request table both have have an index on ( user_id, created_at ).
SQL Fiddle result
You can use the following query:
SELECT
DATE(o.created_at) AS Day
,COUNT(r.id) AS TotalRequests
,COUNT(o.id) AS TotalOrders
FROM orders o
LEFT JOIN
requests r ON
r.id = o.request_id
WHERE o.user_id = 3
GROUP BY DATE(r.created_at), DATE(o.created_at),o.user_id

MySQL GROUP BY grouping by lowest field value

I'm trying to fetch the lowest price per day per hotel, I get multiple results.
I first try to fetch the lowest amount with the MIN() function, then inner join.
When i later try to group by outside the subquery, it just groups by the lowest id.
The SQL itself:
SELECT mt.id, mt.amount, mt.fk_hotel, mt.start_date
FROM price mt
INNER JOIN
(
SELECT price.id, MIN(price.amount) minAmount
FROM price
WHERE 1=1 AND price.start_date >= '2014-10-08' AND price.start_date <= '2014-10-10' AND price.active = 1 AND price.max_people = 2
GROUP BY id
) t
ON mt.id = t.id AND mt.amount = t.minAmount
ORDER BY mt.fk_hotel, mt.amount;
And the results looks like this:
http://jsfiddle.net/63mg3b2j/
I want to group by the start date and fk_hotel so that it groups by the lowest amount value, can anybody help me? Am I being clear?
Edit: I also need a field fk_room from the corresponding row, so i can inner join
Try this:
SELECT MIN(mt.amount) AS min_amount, mt.fk_hotel, mt.start_date
FROM price mt
WHERE
mt.active = 1 AND
mt.max_people = 2 AND
mt.start_date >= '2014-10-08' AND mt.start_date <= '2014-10-10'
GROUP BY mt.fk_hotel, mt.start_date
ORDER BY mt.fk_hotel, min_amount;
Well first of all get a table with minimum value in top row using ORDER BY and then GROUP BY for your required result
SELECT mt.id, mt.amount, mt.fk_hotel, mt.start_date
FROM
(SELECT id, amount, fk_hotel, start_date
FROM price
WHERE start_date >= '2014-10-08' AND start_date <= '2014-10-10'
AND active = 1 AND max_people = 2
ORDER BY amount DESC) AS mt
GROUP BY mt.id
Well I had to still go with a subquery, cause i needed some additional foreign key fields from the corresponding row to inner join some other stuff. It isn't a great solution, cause it fetches too much stuff, the rest is filtered out programmatically.
The most annoying thing here, when I try to use MIN() or MAX() function and get the appropriate fields to that row, it fetches the first results from the DB, which are incorrect and so i have to use a subquery to inner join to get the other fields, I can use grouping, but I had too many fields to group. Maybe I'm missing something. The amount of data doesn't grow in time, so I guess it works for me. So this is the final SQL i came up with, for future reference..
SELECT mt.*, roomtype.name roomname, hotel.name hotelname
FROM booking.price mt
INNER JOIN roomtype ON roomtype.id = mt.fk_roomtype
INNER JOIN hotel ON hotel.id = mt.fk_hotel
INNER JOIN(
SELECT price.id, MIN(price.amount) minAmount
FROM booking.price WHERE 1=1 AND price.start_date >= '2014-10-22' AND price.start_date <= '2014-10-31' AND price.max_people = 2 AND price.active = 1
GROUP BY id
) t
ON mt.id = t.id AND mt.amount = t.minAmount
ORDER BY mt.start_date, mt.amount