Monthly sales in product list query - php

Can anyone help me solve my complicated sql request?
I have an html page with a product list and to every item i want to join loading and sales information for last 4 months.
My desired result is:
Item1 - Month0 (12 in, 0 out), Month-1 (33 in, 36 out)......
Item2 - Month0 (10 in, 30 out), Month-1 (0 in, 66 out)......
My SQL Queries:
Products(simplified) in method "getProducts":
Select item_id, item_name
From Products
Loading
SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS YearMonth,SUM(load_qty) AS total,
FROM loading
GROUP BY YearMonth
WHERE item_id = ?
ORDER BY YearMonth DESC ', array($productId));
Sales
SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS YearMonth,SUM(load_qty) AS total,
FROM sales
GROUP BY YearMonth
WHERE item_id = ?
ORDER BY YearMonth DESC ', array($productId));
Then I got PHP file to call SQL method:
$products = $productManager->getProducts();
$this->data['portalProducts'] = $portalProducts;
And final HTML file with product list:
<?php foreach ($products as $product): ?>
<table>
<tr>
<td><?php echo date("Y - m", strtotime("now"))?></td> //current month
<td>//here goes current month load</td>
<td>//here goes current month sale</td>
</tr>
<tr>
<td><?php echo date("Y - m", strtotime("-1 month"))?></td> //last month
<td>//here goes last month load</td>
<td>//here goes last month sale</td>
</tr>
..............
</table>
Is it possible to combine all of the results from the above queries into one request and then display it as I mentioned in the beginning...
Thanks in advance.

You can combine your SQL queries as follows:
SELECT p.item_id, p.item_name, cal.year_month, COALESCE( q_in.total, 0 ) AS qty_in, COALESCE( q_out.total, 0 ) AS qty_out
FROM Products AS p
INNER JOIN
( SELECT CONCAT(YEAR(NOW()), MONTH(NOW())) AS `year_month`
UNION
SELECT CONCAT(YEAR(NOW() - INTERVAL 1 MONTH), MONTH(NOW() - INTERVAL 1 MONTH)) AS `year_month` ) AS `cal`
ON 1 = 1
LEFT JOIN
( SELECT `item_id`, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS `year_month`, SUM(`load_qty`) AS `total`,
FROM loading
WHERE (`load_date` BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
GROUP BY `item_id`, YEAR(`load_date`), MONTH(`load_date`)) AS q_in
ON p.item_id = q_in.item_id AND cal.year_month = q_in.year_month
LEFT JOIN
( SELECT item_id, CONCAT(YEAR(`load_date`), MONTH(`load_date`)) AS `year_month`, SUM(`load_qty`) AS `total`,
FROM sales
WHERE (`load_date` BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
GROUP BY `item_id`, YEAR(`load_date`), MONTH(`load_date`)) AS q_out
ON p.item_id = q_out.item_id AND cal.year_month = q_out.year_month
ORDER BY p.item_id, p.item_name, q_in.year_month
Output example:
-------------------------------------------------------
| item_id | item_name | year_month | qty_in | qty_out |
-------------------------------------------------------
| 1 | blah | 201604 | 0 | 16 |
-------------------------------------------------------
| 1 | blah | 201605 | 12 | 16 |
-------------------------------------------------------
Explanation:
I have used "Calendar" sub-query to generate a list of months so that your output will always have a row for the current and the previous months even when total quantity for a given month is 0 in and 0 out:
( SELECT CONCAT(YEAR(NOW()), MONTH(NOW())) AS YearMonth
UNION
SELECT CONCAT(YEAR(NOW() - INTERVAL 1 MONTH), MONTH(NOW() - INTERVAL 1 MONTH)) AS YearMonth ) AS Cal
Afterwards I use a left join to join the queries for "loading" and "sales".
I have added a
WHERE (load_date BETWEEN LAST_DAY(NOW() - INTERVAL 2 MONTH) + INTERVAL 1 DAY AND NOW())
to each of the sub-queries to restrict the calculation to current and last month only. This is to improve performance.
Note: I have not tested the query in MySQL

Related

MySQL + PHP: Display items per week?

This has me a stummped...
If I have a this MySQL table:
UserId | Commission | Date Of Commission
1 | 200.00 | 2014-02-12
1 | 50.00 | 2014-04-01
2 | 10.00 | 2014-04-05
and I would like to display the Total Commission for a specific user per week starting from his/her first record, and display 0 for that range if there's no record.
how would I go about it?
Sample Output
UserId | Date Range | Total Commission
1 | 02/10/14 - 02/16/14 | 200.00
1 | 02/17/14 - 02/23/14 | 0.00
...
1 | 03/31/14 - 04/06/14 | 50.00
I'm not a seasoned coder so any help will be much appreciated.
Thanks!
Edit:
I have tried this:
SELECT IFNULL(SUM(Commisssion),0) Total ,DATE_SUB(`DateOfCommission`,INTERVAL 7 DAY)
AS RangStart,DATE_SUB(`DateOfCommission`,INTERVAL 1 DAY) AS RangeEnd
FROM `comms` WHERE `UserId` = '$UserID' GROUP BY DATE(`DateOfCommission`) DESC
but it starts the week with whatever date the first record was entered..
This is very tricky to accomplish. Here is what I managed to do with small modifications it should work they way it needs to be. I have done it for userid = 1 and this could be done for other users as well.
In the query I have 2 lines
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
and
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
The query will try to generate the list of dates using the min() date of transaction for the user till today. Instead of now() this could be used as max() date of transaction for the user as well.
select
t1.date_range,
coalesce(SUM(t1.Commission+t2.Commission), 0) AS Commission
from
(
select
a.Date as date,
concat(
DATE_ADD(a.Date, INTERVAL(1-DAYOFWEEK(a.Date)) +1 DAY),
' - ',
DATE_ADD(a.Date, INTERVAL(7- DAYOFWEEK(a.Date)) +1 DAY)
) as date_range,
'0' as Commission
from (
select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as date
from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) a
where a.Date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
)t1
left join
(
SELECT date ,
coalesce(SUM(Commission), 0) AS Commission
FROM transactions
WHERE date BETWEEN (select min(date) from transactions where UserId = 1) AND NOW()
AND UserId = 1
GROUP BY date
)t2
on t2.date = t1.date
group by t1.date_range
order by t1.date_range asc
DEMO
So, this is sort of an algorithm you could use:
$Result = select distinct userid from table(this will fetch all userids from table)
while(There are rows in $Result)
{
$Userid = $Result['userid']
$StartDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date asc limit 1)
$StartDateRow = mysql_fetch_assoc($StartDateRes)
$StartDate = $StartDateRes['Date']
$StartWeekNumber = $StartDateRes['week']
$EndDateRes = mysql_query(select Date, WEEKOFYEAR(Date) as week from table where userid = Userid order by date desc limit 1)
$EndDateRow = mysql_fetch_assoc($EndDateRes)
$EndDate = $EndDateRes['Date']
$EndWeekNumber = $EndWeekRes['week']
for($i=$StartWeekNumber; $i<=$EndWeekNumber; $i++)
{
$StartDateOfWeek = FuncToFindStartDateOfWeek($i)
$EndDateOfWeek = FuncToFindEndDateOfWeek($i)
$Result2 = mysql_query(select sum(commission) as sum from table where date between StartDateOfWeek and EndDateOfWeek group by userid)
$Row2= mysql_fetch_assoc($Result2)
$Sum = $Row2['sum']
mysql_query("insert into OutputTable values($UserId, $StartDateOfWeek. '-'. $EndDateOfWeek ,$Sum");
}
}
SELECT UserId, COALESCE(SUM(Commission),0), YEARWEEK(DateOfCommission) AS TheWeek
GROUP BY UserId, TheWeek
ORDER BY UserId, TheWeek;
This will not print the nice date range, but should get you started in a SQL-only direction where the sum is broken down by the week of the year. I think you could take it from this point to add the nicer formatting of the Year/Week column. YEARWEEK() should give you pretty fast results.
The easiest way I can think of doing this as follows
Step 1: Get the date of the first record
"SELECT dateofcommission FROM comissionstable WHERE id='userid' ORDER BY dateofcommission ASC LIMIT 1"
The above query will return the first date of commission only
Step 2: Create a loop which starts from the date you got in Step 1 and continue the loop till the date is greater than or equal to today's date. Increment this date using PHP date function.
date('Y-m-d', strtotime($dateofcommission. ' + 7 days'));
Step 3: In this loop you can get the commission with-in the starting date and ending date. Starting date will be the date before adding 7 days and ending date will be the one after you have added 7 days.
SELECT SUM(commission) FROM commissiontable WHERE dateofcommission>= startingdate AND dateofcomission < endingdate AND id='userid'
The above logic should work. If you end up having some issues with this logic feel free to post in comments. I would be happy to help
The following is another solution
function getStartAndEndDate($week, $year) {
$time = strtotime("1 January $year", time());
$day = date('w', $time);
$time += ((7*$week)+1-$day)*24*3600;
$return[0] = date('Y-n-j', $time);
$time += 6*24*3600;
$return[1] = date('Y-n-j', $time);
return $return;
}
$query = mysqli_query($con, "SELECT userid, COALESCE( SUM( commission ) , 0 ) AS thecommission , YEARWEEK( doc ) AS TheWeek FROM commission GROUP BY userid, TheWeek ORDER BY userid, TheWeek");
while ($array = mysqli_fetch_array($query)) {
$test = $array['TheWeek'];
$store_array = getStartAndEndDate(substr($test,4,2), substr($test,0,4));
echo $array['userid']."-".$array['thecommission']."-".$store_array[0]."/".$store_array[1]."<br>";
}

make visitor count for the day equal to 0 which is not present in MySQL database

I have a table
___________________________________________
id | user | Visitor | timestamp
___________________________________________
13 |username |abc | 2014-01-15 15:01:44
14 |username |abc | 2014-01-15 15:01:44
15 |username |abc | 2014-01-18 15:01:44
16 |username |abc | 2014-01-18 15:01:44
___________________________________________
I used QUERY to COUNT no of visitor of USER abc for last 7 days from TODAY.
SELECT DATE(`timestamp`) as `date`, COUNT(*) as `count`
FROM `table` WHERE (`timestamp` >= (NOW() - INTERVAL 7 DAY)) AND (`user` = 'username')
GROUP BY `date`;
It get following output:
______________________________
date | count
______________________________
2014-01-15 | 2
2014-01-18 | 2
But I need:
______________________________
date | count
______________________________
2014-01-15 | 2
2014-01-16 | 0 // Make 0 for the day which is not present
2014-01-17 | 0 // Make 0 for the day which is not present
2014-01-18 | 2
What will be query for this?
Use a subquer to create a table with all the days in the past week, then join that with your table:
SELECT `date`, IFNULL(COUNT(*), 0) as `count`
FROM (SELECT DATE(NOW()) AS `date`
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 1 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 2 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 3 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 4 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 5 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 6 DAY))
UNION
SELECT DATE(DATE_SUB(NOW(), INTERVAL 7 DAY))) AS days
LEFT JOIN `table` ON DATE(`timestamp`) = `date`
WHERE (`timestamp` >= (NOW() - INTERVAL 7 DAY)) AND (`user` = 'username')
GROUP BY `date`;
It's a well-known problem about gap - and it has many answers on SO.
First, most obvious way - is to use table that will hold all dates consecutive (for current year as a sample) For example, let it be dates table with field record_date (it holds date) then your query will look like:
SELECT
DATE(`timestamp`) as `date`,
COUNT(`dates`.`id`) as `count`
FROM
`dates`
LEFT JOIN
`table`
ON `dates`.`record_date` = DATE(`table`.`timestamp`)
WHERE
(`timestamp` >= (NOW() - INTERVAL 7 DAY))
AND
(`user` = 'username')
GROUP BY
`dates`.`record_date`
-so you'll force returning zeros via LEFT JOIN.
There is another, more complex way, to achieve this with sequence generator query. Actually, there are no sequences in MySQL, but you can generate consecutive values from CROSS JOIN and then apply them to date selection. For example:
SELECT
DATE_ADD(CURDATE() - INTERVAL 1 WEEK, INTERVAL sequence.id DAY)
FROM
(SELECT
(two_1.id + two_2.id + two_4.id +
two_8.id + two_16.id) AS id
FROM
(SELECT 0 AS id UNION ALL SELECT 1 AS id) AS two_1
CROSS JOIN (SELECT 0 AS id UNION ALL SELECT 2 AS id) AS two_2
CROSS JOIN (SELECT 0 AS id UNION ALL SELECT 4 AS id) AS two_4
CROSS JOIN (SELECT 0 AS id UNION ALL SELECT 8 AS id) AS two_8
CROSS JOIN (SELECT 0 AS id UNION ALL SELECT 16 AS id) AS two_16
) AS sequence
WHERE
sequence.id<7
-will will produce dates for previous week, so you'll be able to use this instead of creating and filling temporary table like in first way. Good thing about this query is - that it's static, thus you'll not have to add another UNION part if you'll want to increase selection interval.

MySQL - count users WHERE (last 30 days, last one year, all time)

I am trying to make a query that checks the clients table and returns a count of users with a specific value for the last 30 days, 365 days and All Time.
The all time count is easy:
$stmt = $conn->prepare("SELECT count(id) AS count FROM clients WHERE referred = :refid");
$stmt->bindParam(':refid', $refid);
$stmt->execute();
$totalreferrals = $stmt->fetchAll();
Table:
id | signup | reffered |
----------------------------
2 | 2012-08-24 | 14 |
----------------------------
3 | 2011-10-13 | 14 |
I am not sure if I can combine the query using a UNION or if I should just make three different queries. Any help?
i think you want this in columns and not in rows if so here it is
SELECT COUNT(CASE WHEN DATEDIFF(CURDATE(),signup) <= 30 THEN id
ELSE NULL
END) AS Last30days ,
COUNT(CASE WHEN DATEDIFF(CURDATE(), signup) <= 365 THEN id
ELSE NULL
END) AS Last365Days ,
COUNT(*) AS Alltime
FROM Table1
WHERE reffered = 14
SQLFiddle http://sqlfiddle.com/#!2/6e6ce/2
Maybe this could solve the problem:
SELECT count(id) AS count FROM clients WHERE referred = :refid AND BETWEEN ADDDATE(NOW(), INTERVAL -1 MONTH) AND NOW();
SELECT count(id) AS count FROM clients WHERE referred = :refid AND BETWEEN ADDDATE(NOW(), INTERVAL -1 YEAR) AND NOW();

calculate price between given dates

Hotel_id Room_id Room_type Start_date End_date Price
----------------------------------------------------------------
13 2 standard 2012-08-01 2012-08-15 7000
13 2 standard 2012-08-16 2012-08-31 7500
13 2 standard 2012-09-01 2012-09-30 6000
13 3 luxury 2012-08-01 2012-08-15 9000
13 3 luxury 2012-08-16 2012-08-31 10000
13 3 luxury 2012-09-01 2012-09-30 9500
Hi this is the structure and data of my table.
I need to create a mysql query for hotel booking, that would match in database user entered data:
Date when they want to checkin and checkout
Room type
For Ex:
If user selects Hotel with luxury room based on these dates (2012-08-30 to 2012-09-04)
the total cost would be (10000*2) for 30th and 31st Aug + (9500*3) for 1st,2nd and 3rd Sep(4th checkout day don't include)
that means total price will be 20000+28500=48500
So query should filter total price based on the Hotel_id,Room_id,Start_date,End_date and Price
Thanks
Use this solution:
SELECT SUM(
CASE WHEN a.Start_date = b.min_sd AND a.Start_date <> b.max_sd THEN
(DATEDIFF(a.End_date, '2012-08-30')+1) * a.Price
WHEN a.Start_date = b.max_sd AND a.Start_date <> b.min_sd THEN
DATEDIFF('2012-09-04', a.Start_date) * a.Price
WHEN (a.Start_date,a.Start_date) IN ((b.min_sd,b.max_sd)) THEN
(DATEDIFF('2012-09-04', '2012-08-30')+1) * a.Price
WHEN a.Start_date NOT IN (b.min_sd, b.max_sd) THEN
(DATEDIFF(a.End_date, a.Start_date)+1) * a.Price
END
) AS totalprice
FROM rooms a
CROSS JOIN (
SELECT MIN(Start_date) AS min_sd,
MAX(Start_date) AS max_sd
FROM rooms
WHERE Room_type = 'luxury' AND
End_date >= '2012-08-30' AND
Start_date <= '2012-09-04'
) b
WHERE a.Room_type = 'luxury' AND
a.End_date >= '2012-08-30' AND
a.Start_date <= '2012-09-04'
Replace occurances of 2012-08-30 and 2012-09-04 with your input start and end dates respectively.
This will account for start and end dates being in the same month as well as spanning across multiple months.
SQLFiddle Demo
You can use MySQL's BETWEEN ... AND ...
operator to find the date ranges in which the desired booking falls (remember to take one day off of the given checkout
date as, like you say, there is no night's stay), then group the results by room and take the
SUM() of price times number of nights (which can
be calculated using MySQL's LEAST() and
GREATEST() functions):
SELECT Room_id,
SUM(Price * (1 + DATEDIFF(
LEAST(End_date, '2012-09-04' - INTERVAL 1 DAY),
GREATEST(Start_date, '2012-08-30')
))) AS Total
FROM mytable
WHERE Room_type = 'luxury' AND (
'2012-09-04' - INTERVAL 1 DAY
BETWEEN Start_date AND End_date
OR '2012-08-30' BETWEEN Start_date AND End_date
)
GROUP BY Room_id
See it on sqlfidde.
try this:
set #Hotel_id :=13;
set #Room_id :=3;
set #Start_date :='2012-08-30' ;
set #End_date :='2012-09-04';
select sum(b.TotalPrice-b.deductions) as total_cost from
( select a.Price,a.StartDate,a.EndDate,price*(DATEDIFF(a.EndDate,a.StartDate)+1) as TotalPrice
,case when a.EndDate=#End_date then a.Price else 0 end as deductions
from
(select price,case when #Start_date>=Start_date then #Start_date else Start_date end as StartDate
,case when #End_date<=End_date then #End_date else End_date end as EndDate
from h_booking h1
where Hotel_id=#Hotel_id
and Room_id=#Room_id
and (#Start_date between Start_date and End_date or #End_date between Start_date and End_date ))a )b

Find first free date in agenda

in my component I have created an agenda, where user can save their appointments.
Agenda table is quite simple: there is a title, description and start/end datetime fields.
When a user adds a new event, I'd wish to hint him with the first empty spot.
How can I achieve that?
Is that possible with a single/bunch queries, or I have to create a loop until I find the first empty spot?
For example this is my table:
| ID | Start date | End date |
| 1 | 2012-06-14 09:00:00 | 2012-06-14 09:32:00 |
| 2 | 2012-06-14 15:00:00 | 2012-06-14 15:45:00 |
| 3 | 2012-06-14 18:20:00 | 2012-06-14 18:55:00 |
The first free datetime should be 2012-06-14 09:33:00, how can I fetch this date?
Interesting challenge to be done in one query :) Took me a while but I came with solution. With these assumptions:
minimum appointment duration is 30 minutes
appointment starts and ends in one day
day starts at 9:00 and ends at 17:00
minimum interval between ending and starting times is 1 minute
There are 4 cases to take into consideration:
There is some free time in the morning
There is some free time during the day
First free slot is next day to the last appointment in db
You have all free slots from now on
So yo have to select minimum form these dates.
And the query:
SELECT
MIN(next_start_date) AS next_start_date
FROM
(
(
SELECT
DATE_FORMAT(t1.start_date,'%Y-%m-%d 09:00:00') AS next_start_date
FROM
agenda t1
WHERE
t1.start_date > NOW()
AND
TIME(t1.start_date) > '09:30:00'
AND
NOT EXISTS(
SELECT 1 FROM agenda t2 WHERE DATE(t2.start_date) = DATE(t1.start_date) AND TIME(t2.start_date) <= '09:30:00'
)
LIMIT 1
)
UNION
(
SELECT
t3.end_date + INTERVAL 1 MINUTE AS next_start_date
FROM
agenda t3
WHERE
t3.start_date > NOW()
AND
TIME(t3.start_date) >= '09:00:00'
AND
TIME(t3.end_date) < '16:30:00'
AND NOT EXISTS
(SELECT 1 FROM agenda t4 WHERE TIMESTAMPDIFF(MINUTE,t3.end_date,t4.start_date) BETWEEN 0 AND 30 )
ORDER BY
t3.start_date ASC
LIMIT 1
)
UNION
(
SELECT CONCAT(CAST(DATE((SELECT MAX(t5.start_date) + INTERVAL 1 DAY FROM agenda t5 WHERE t5.start_date > NOW())) AS CHAR), ' 09:00:00') AS next_start_date
)
UNION
(
SELECT
IF(
TIME(NOW()) < '09:00:00',
DATE_FORMAT(NOW(),'%Y-%m-%d 09:00:00'),
IF(
TIME(NOW()) < '16:30',
NOW(),
DATE_FORMAT(NOW() + INTERVAL 1 DAY,'%Y-%m-%d 09:00:00' )
)
) AS next_start_date
FROM
(SELECT 1) t6
WHERE NOT EXISTS(
SELECT 1 FROM agenda t7 WHERE t7.start_date > NOW()
)
LIMIT 1
)
) t
Of course it is not perfect - when there next free slot occurs in the next day, there's a chance that it is Saturday or other day off from work. Taking it into consideration, the best way will be to check if returned is valid work day, if not then repeat the query with NOW() replaced by datestring of next valid work day.

Categories