MYSQL select query with multiple tables - php

I have 2 tables,
prices (pcode, date, priceperweek)
1 record covers 1 week
booked (pcode, date)
1 record = 1 day, because shortbreaks are available
And a form consists of 3 fields
startdate
flexibility (+/- 1/2/3 weeks)
duration (3 / 4 / 5 / 6 / 7 / 14)).
The below query should be selecting prices & dates and checking to see if the selected start date appears in the prices table and that each day from the startdate doesn't appear in the bookings table.
SELECT SUM(priceperweek) AS `ppw`, prices.date AS `startdate`
FROM `prices` LEFT JOIN `booked` ON prices.pcode=booked.pcode
WHERE prices.pcode='A2CD59GH'
AND (prices.date IN ('20131221', '20131228')
AND booked.date NOT IN ('20131221', '20131222', '20131223',
'20131224', '20131225', '20131226', '20131227', '20131228',
'20131229', '20131230', '20131231', '20140101', '20140102',
'20140103')
)
OR (prices.date IN ('20131214', '20131221')
AND booked.date NOT IN ('20131214', '20131215', '20131216',
'20131217',
'20131218', '20131219', '20131220', '20131221', '20131222',
'20131223', '20131224', '20131225', '20131226', '20131227')
)
OR (prices.date IN ('20131228', '20140104') AND booked.date NOT IN
('20131228', '20131229', '20131230', '20131231', '20140101',
'20140102', '20140103', '20140104', '20140105', '20140106',
'20140107', '20140108', '20140109', '20140110')
)
GROUP BY prices.date
ORDER BY prices.date ASC
VALUES GIVEN TO QUERY...
startdate = 20131221
duration = 14
property = A2CD59GH
plusminus = 1
My problem is that this query returns records even if some of the dates in a range appear in the "bookings" table AND the ppw value is alot more than i would have expected.
The reason for using SUM(ppw) is when a duration of 14 is specified the price will sum both weeks together.
Thanks for any help on this

The problem with your approach is that the startdate will only be filtered from the results if every single record in the booked table for the given pcode falls within the booking period. Obviously this won't be the case if the property has been booked on some other date.
I'd suggest performing an anti-join along the following lines:
SELECT t.date, SUM(prices.priceperweek) FROM prices JOIN (
SELECT prices.date
FROM prices LEFT JOIN booked
ON booked.pcode = prices.pcode
AND booked.date BETWEEN prices.date
AND prices.date + INTERVAL 14 DAY
WHERE booked.pcode IS NULL
AND prices.pcode = 'A2CD59GH'
AND prices.date BETWEEN '20131221' - INTERVAL 1 WEEK
AND '20131221' + INTERVAL 2 WEEK
) t ON prices.date BETWEEN t.date AND t.date + INTERVAL 13 DAY
GROUP BY t.date

Related

How to edit this query to get last 7 days visits even if there are no visits on specific days? [duplicate]

This question already has answers here:
MySQL how to fill missing dates in range?
(6 answers)
MySQL: Select All Dates In a Range Even If No Records Present
(6 answers)
Closed 2 years ago.
I have the following query that returns the dates first_visit starting from today and 7 days back, as well as the visitors hash per day:
SET time_zone= '{$company_timezone}';
SELECT DATE( first_visit ) AS day , COUNT( DISTINCT hash ) AS total
FROM table
WHERE company = 1 and first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day
The flaw with this is that if company = 1 have visitors only today and three days ago, I will get this:
day --------- total
2020-03-08 ----- 30
2020-03-05 ----- 40
leaving out all other dates inbetween.
What I want is to get all the past 7 days, even there are no visitors at all. If there are no visitors, then it should just show 0.
How to edit my query in order to achieve this?
Thank you
Perform an outer join with a derived table that contains desired dates:
select b.date as day, count(distinct hash) as total
from table
right join (select #now := #now - interval 1 day as date from (select #now := curdate()) a, table limit 7) b
on b.date = date(first_visit) and company = 1
group by b.date
This assumes that table has at least 7 rows.
Note: there are two occurrences of table.
If you have data for each day -- but not for that company -- then conditional aggregation is a pretty simply approach:
SELECT DATE( first_visit ) AS day ,
COUNT( DISTINCT CASE WHEN company = 1 THEN hash END ) AS total
FROM table
WHERE first_visit > SUBDATE( CURDATE( ) , 7 )
GROUP BY day;
This only works if all days are represented in your table for some company.
Some solutions involve a table of numbers.
Here is one way with a recursive query, available in MySQL 8.0:
with d as (select 0 n union all select n + 1 where n < 6)
select
current_date - interval n day myday,
count(distinct t.hash) total
from d
left join mytable t
on t.company = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n
In earlier version, you can enumerate the numbers as a derived table:
select
current_date - interval n day myday,
count(distinct t.hash) total
from (
select 0 n 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
) d
left join mytable t
on t.compatny = 1
and t.first_visit >= current_date - inteval n day
and t.first_visit < current_date - interval (n - 1) day
group by d.n

Manipulate Date, new day starts at specific time SQL

I'm developping a dashboard for a restaurant/bar.
I want to manipulate the date , so for example:
If they sell something at 02 january ,01h30 .
This amount should be added to the amount of the 1st January and not to the amount of the 2nd January.
So basically on the next day from 00h00 until 03h59, the amount that they sell, should be added to the previous date.
At the moment my SQL query just displays both dates but I want it grouped in 1 date. If this isn't possible with SQL, I have my dashboard in PHP, so I can eventually manipulate it in PHP if anyone could provide me that info.
My query
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
So the current output is like this (shortened):
Date Amount
1 01/01/2018 100
2 02/01/2018 20
But I want it like this
Date Amount
1 01/01/2018 120
You can use this query and work with dates as you want
here is you first query that gives you that result
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
you can use this result as a first select with this :
With CTE as
(
select CONVERT(CHAR(10), receiptdatetime, 120), datename(DW,receiptdatetime),
sum(rld.NetAmount), count(rl.ReceiptId)
from receipt r, receiptline rl, vw_ReceiptLineDetail rld
where rl.ReceiptId = r.ReceiptId and
rl.ModifiedKind != 300
and rld.ReceiptLineId = rl.ReceiptLineId and
receiptdatetime <= DATEADD(HOUR,4,DATEADD(DAY,1, '01/01/2018'))
and receiptdatetime >= DATEADD(HOUR,4,'01/01/2018')
group by CONVERT(CHAR(10), receiptdatetime, 120), datename(DW, receiptdatetime)
order by 1
)
so like this you have the result stored on a table called CTE
NOw i don't have the data so i will create my owne Variable table to store the result that you got from first query you can use your CTE tabale as a source instade of #Table
Declare #Table table (
id int,
dates date,
amout int
)
insert into #Table
select 1 , '2018-01-01' , 100 union
select 2 , '2018-01-02' , 20 union
select 2 , '2018-02-02' , 200 union
select 2 , '2018-01-03' , 20 union
select 2 , '2018-01-04' , 20
now to get the Amout with the result that you want here is the query to use
you do the select from CTE :
select sum(amout) as Amout from #Table
where dates between '2018-01-01' and '2018-01-04'
Result :
Amout
160
now you will use that result and union it with you table to get the ID that you want and the date that you want and i thing you should convert the last table date into nvarchar(50) so you will have this result
1- when you do the whole month
ID Date Amout
1 2018-01 160
2- when you do by timeframe
ID Date Amout
1 '2018-01-01 2018-01-14' 160
you can start by hardcoding the ID and Date as you want and union is to the result Amout that you get from the query
or you can do variables to configure the ID and date that you want to show with the Amout
thanks if you ahve any questions i'm here i have done the test on my local and it works and i hope that this is what you need :)
use the :
;With CTE as ( select (RowCount() Over (Partition by Date order by id) row_count) , date, amout
from tables and where conditions
Then on the select Add the amount that have the same Date into each others
selecting from CTE table
if you want to Add the Amounts of all day on the same month then select Only Year and Month on Date column then do the partition by over this Date

Display payment made by mark every 3 month for the last 3 years

I have a table called payment it has date field, i have a customer called Mark who has been making payment every day for 3 years
Table: Payment
Fields: Name , Amountpaid, date
I want to display payment record made by mark every 3 month and also the total Amountpaid for 3 years
How i want the result to look like
First 3 months payment record table
total Amountpaid at the bottom of the table
second 3 months payment record table
total Amountpaid at the bottom of the table
Third 3 months payment record table
total Amountpaid at the bottom of the table
and so on for 3 years
Please do help out
It seems like you're looking for a SQL solution for this, but databases are for holding data, they aren't for formatting it into a report. To this end my advice would be: Don't try and do this in the database, do it in the front end code instead
It will be very simple to run a query like
SELECT * FROM payment WHERE
name = 'mark' and
`date` between date_sub(now(), interval 3 year) and now()
ORDER BY date
And then put the results into an HTML table usig a loop, and a variable that keeps track of the amount paid total. Every 3 months reset the variable. If you want MySQL to do a bit more data processing to help out you can do this:
SELECT * FROM
payment
INNER JOIN
(SELECT YEAR(`date`) + (QUARTER(`date`)/10) as qd, SUM(amountpaid) as qp FROM payment WHERE name = 'mark' GROUP BY YEAR(`date`), QUARTER(`date`)) qpt
ON
qpt.qd = YEAR(`date`) + (QUARTER(`date`)/10)
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
ORDER BY `date`
This will give all mark's data row by row and an extra two columns (that mostly repeat themselves over and over) showing the year and quarter (3 months) of the year like 2017.1, 2017.2, together with a sum of all payments made in that quarter. Formatting it in the front end now won't need a variable to keep a running total of the amount paid
This is about the limit of what you should do with formatting the data in the database (personal opinion). If, however, you're determined to have MySQL do pretty much all this, read on..
Ysth mentioned rollup, which is intended for summarising data.. such a solution would look like this:
SELECT
Name, `date`, SUM(amountpaid) as amountpaid
FROM
payment
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
GROUP BY
name,
YEAR(`date`) + (QUARTER(`date`)/10),
`date`
WITH ROLLUP
The only downside with this approach is you also get a totals row for all payments by mark. To suppress that, use grouping sets instead:
SELECT
Name, `date`, SUM(amountpaid) as amountpaid
FROM
payment
WHERE
name = 'mark' AND
`date` between date_sub(now(), interval 3 year) and now()
GROUP BY GROUPING SETS
(
(
name,
YEAR(`date`) + (QUARTER(`date`)/10),
`date`
),
(
name,
YEAR(`date`) + (QUARTER(`date`)/10)
)
)
You can use a group by on the year and month divided by 3 and truncated using floor
SELECT
EXTRACT(YEAR_MONTH FROM `date`),
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
GROUP BY
EXTRACT(YEAR FROM `date`),
FLOOR(EXTRACT(MONTH FROM `date`) / 3)
For the total you will need to iterate the result set and sum up the amounts paid, or if you want it as the final record you could do a UNION SELECT but this would be ineffecient, but for completeness it is below:
SELECT
EXTRACT(YEAR_MONTH FROM `date`),
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
GROUP BY
EXTRACT(YEAR FROM `date`),
FLOOR(EXTRACT(MONTH FROM `date`) / 3)
UNION SELECT
NULL,
SUM(`Amountpaid`)
FROM
`Payment`
WHERE
`Name` = 'Mark'
AND `date` >= DATE_SUB(NOW(), INTERVAL 3 YEAR)
This is for get summary per 3 months :
select year(date)*100+floor(month(date)/3) as period, sum(amountpaid)
from payment
where name = 'mark' and (date between '2014-01-01' and '2017-01-01')
group by year(date)*100+floor(month(date)/3)
order by period
And this is how to get summary 3 year :
select sum(amountpaid) from payment where name = 'mark' and (date between '2014-01-01' and '2017-01-01')
You can change the date between for your need

3 Months previous data by given date mysql

I have fee records in my database table. I want to fetch 3 months back records of the fees in database. I am using:
SELECT * FROM fee_challans
WHERE student_id = 630
AND STATUS = 'un-paid'
AND DATE_FORMAT( fee_date, '%Y-%m-%d' ) - INTERVAL 2 MONTH
This query that I searched and found on google.
You forgot to compare your column to something...
SELECT * FROM fee_challans
WHERE student_id = 630
AND STATUS = 'un-paid'
AND fee_date >= CURDATE() - INTERVAL 3 MONTH;
And if your fee_date column is of type date, datetime or timestamp, date_format() is not necessary.

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

Categories