How to select either one row or the other - mysql

I have a table with columns VAT, start and end date.
I have two rows. The standard entry has 0000-00-00 as the start and end date and the other row has the start_date 2020-06-01 and the end_date 2020-12-31
I want VAT of the second row to be selected if today's date is between the start and end date, otherwise the standard VAT with 0000-00-00 should be selected
This is my table:
I tried
SELECT *
FROM taxes
WHERE (CASE WHEN start_date < "2020-06-06"
AND end_date > "2020-06-06" THEN 1
ELSE 0
END) = 1
But i don't know how to formulate the else case or whether it can work at all like this

You can use order by and limit for this:
select t.*
from taxes t
where start_date = '0000-00-00' or
'2020-06-06' between start_date and end_date
order by start_date desc
limit 1;
The idea is that the first condition gets the "default" value. The second condition gets the matching condition. These two rows are then sorted, so the matching condition will be first -- if there is one.

There might be ways o doing it with your suggested "0000-00-00' dates for start and end points, but in my view you run a much cleaner ship if you address the time spans individually, i. e. spell out the date ranges for before and after the "exception period", like:
INSERT INTO vat (startdt,enddt,fullrate,reducedrate)
VALUES ('2000-01-01','2020-06-30',.19,.07), -- before
('2020-07-01','2020-12-31',.16,.05), -- exception period
('2021-01-01','2500-12-31',.19,.07); -- after
select * from vat where now() between startdt and enddt;
This way you document in a very clear way which rates were applicable when. And the query itself becomes trivial, see above and check out my demo here: https://rextester.com/YLYUU53617

SELECT *
FROM taxes
WHERE tax_id=IF(start_date < "2020-06-06" AND end_date > "2020-06-06", 1, 0)

you can find the records for current date, then combine this set with the source table filtered by '0000-00-00' excluding country codes from this set
with
current_taxes as (
select *
from taxes
where current_date between start_date and end_date
)
select *
from current_taxes
union all
select *
from taxes
left join current_taxes
using (country_code)
where taxes.start_date='0000-00-00'
and current_taxes.country_code is null
;

Related

Get earliest date as Start date and latest date as end date

I Have a requirement where I need to get earliest date as start date and If latest date is present then I need to have it as end date, if latest date is blanks which means the person is still active then I need to have it as blanks.
I used Min and Max on date fields but My latest date field is not capturing as Blanks if date is absent.
If you want to get the earliest start_date, by ID. And also bring with whatever is in the End_date field - No matter if it is NULL, or has an date. Then you can first get group by ID(which is not unique in your example given), then use MIN() on start_date. Then you fetch which row these values belong to, and thereby get the End_date. This works, but if you've got several start dates with the same ID, that complicates things - and in that case we need some more example data with a bit mor explanation of how it is supposed to work. But, here goes:
Fiddle: https://www.db-fiddle.com/f/o2NyDpAc76TLYdmGFGHqag/3
CREATE TABLE my_table (
ID int,
Start_Date date,
End_date date null
);
INSERT INTO my_table (ID,Start_Date, End_date)
VALUES
(1,'2021-01-01', '2022-04-05'),
(1,'2022-01-01', '2022-04-02'),
(2,'2022-07-01', '2022-05-07'),
(2,'2022-01-01', null);
SELECT a.*
FROM my_table a
join (SELECT
ID,
MIN(my_table.Start_date) as 'Start_date'
FROM my_table
GROUP BY ID) jn
on a.ID=jn.ID and a.Start_date=jn.Start_date
Source table:
ID
Start_Date
End_date
1
'2021-01-01'
'2022-04-05'
1
'2022-01-01'
2022-04-02
2
'2022-07-01'
'2022-05-07'
2
'2022-01-01'
NULL
Results table:
ID
Start_Date
End_date
1
'2021-01-01'
'2022-04-05'
2
'2022-01-01'
NULL
This might work:
SELECT ID, MIN(start_date) Start_Date,
NULLIF(MAX(COALESCE(end_date,'29991231')), '29991231') End_Date
FROM MyTable
GROUP BY ID
See it work here:
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=5febc25e9c79840fe6aa2e55d77cf5d0
At least it will seem to give the right results based on the sample data available. However, this would still show a null if a record with an earlier start date has a null end date, and a record with a later start date does have an end date. It's likely this should never happen in real data, but then real data tends to be messy even when it shouldn't be.
To really do this properly, you need to find the whole row with the latest start date and then look at the end date value from that row. Fortunately, we have a great way to count rows: the row_number() windowing function:
SELECT ID, Start_Date, End_Date
FROM (
SELECT ID, Start_Date, End_Date,
row_number() over (PARTITION BY ID ORDER BY Start_Date DESC) rn
FROM MyTable
) t0
WHERE rn=1
But this is only part of the solution. This should now always have the right End_Date, but will usually have the wrong Start_Date. We can update it to fix that error like this:
SELECT ID, (SELECT MIN(Start_Date) FROM MyTable t WHERE t.ID=t0.ID) Start_Date, End_Date
FROM (
SELECT ID, Start_Date, End_Date,
row_number() over (PARTITION BY ID ORDER BY Start_Date DESC) rn
FROM MyTable
) t0
WHERE rn=1
And now we will always get the right result.
See it work here:
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=4b7d4cba4849eee9ba3bf978cebfc3bf
Finally, all this assumes you have a reasonable schema using null and DateTime values, and not an unreasonable schema using varchar and empty strings. If the latter really is your situation the schema design really is BROKEN and you should fix it.
This also assumes at least MySql 8.0. If you're using something older than that, condolences. 5.7 and earlier are rooted in basic design from 2006, and don't really qualify as a modern database platform.

How can i include previous row in a selected range if condition is not satisfied

I am trying to find out a maximum number from a given date ranges.
for example, my table contains
date number
---------- --------
01-01-2019 1
05-01-2019 3
07-01-2019 2
10-01-2019 1
11-01-2019 2
and I want to find the max number in date from 06-01-2019 to 11-01-2019
When I use the query,
select max(count) from TABLE where date between startDate and endDate;
the output is 2.
But what I wanted is if the startDate is not in the table, to include the previous row. For example in the previous case, I want to include the row 05-01-2019 and thus the output should be 3.
Is there any query for this process or do I need to write an algorithm?
Assume the dates in table are sorted and I use a MySQL database.
You can do this by using subquery
SELECT MAX(number)
FROM TABLE
WHERE date >= (
SELECT date
FROM TABLE
WHERE date <= startDate
ORDER BY date DESC
LIMIT 1
)
AND date <= endDate
Subquery will return largest nearest date to startDate.
This date can then be used as a minimum value for your outer query.
In MySQL 8+, you can use lead():
select max(number)
from (select t.*, lead(date) over (order by date) as next_date
from t
) t
where next_date > $start_date and
date <= $end_date;

MySQL: Selecting In Between DateTime records

I want to get the count of records between two date-time entries.
I have a column in my table named created_date with the datetime data type.
I want to select rows which were created between 2017-01-10 and 2017-01-30
I have written the following query but it doesn't seem to be inclusive
SELECT* FROM table WHERE created_date BETWEEN '2017-01-10' AND '2017-01-30'
The issue you are having has to do with that the date literal 2017-01-31 represents that date at midnight. To get around this, phrase your query as follows:
SELECT * FROM table WHERE created_date >= '2017-01-10' AND created_date < '2017-01-31';
This says to take any date on or after the very start of 2017-01-10 and before the start of 2017-01-31. This implies including the entire day 2017-01-30.
For get count of records between two date you can try below query
SELECT COUNT(*)
FROM tableName
WHERE date(created_date) >= '2017-01-10' AND date(created_date) <= '2017-01-30'
Try This One
SELECT COUNT(1)
FROM tableName
WHERE Cast(created_date as date) Between Cast('2017-01-10' as date) AND Cast('2017-01-30' as date)

MySQL using count in query to search for availability of multiple rows

I am using one table, mrp to store multi room properties and a second table booking to store the dates the property was booked on.
I thus have the following tables:
mrp(property_id, property_name, num_rooms)
booking(property_id, booking_id, date)
Whenever a property is booked, an entry is made in the bookings table and because each table has multiple rooms, it can have multiple bookings on the same day.
I am using the following query:
SELECT * FROM mrp
WHERE property_id
NOT IN (SELECT property_id FROM booking WHERE `date` >= {$checkin_date} AND `date` <= {$checkout_date}
)
But although this query would work fine for a property with a single room (that is, it only lists properties which have not been booked altogether between the dates you provide), it does not display properties that have been booked but still have vacant rooms. How can we use count and the num_rooms table to show in my results the rooms which are still vacant, even if they already have a booking between the selected dates, and to display in my results the number of rooms that are free.
You need 3 levels of query. The innermost query will list properties and dates where all rooms are fully booked (or overbooked) on any day within your date range. The middle query narrows that down to just a list of property_id's. The outermost query lists all properties that are NOT in that list.
SELECT *
FROM mrp
WHERE property_id NOT IN (
-- List all properties sold-out on any day in range
SELECT DISTINCT Z.property_id
FROM (
-- List sold-out properties by date
SELECT MM.property_id, MM.num_rooms, BB.adate
, COUNT(*) as rooms_booked
FROM mrp MM
INNER JOIN booking BB on MM.property_id = BB.property_id
WHERE BB.adate >= #checkin AND BB.adate <= #checkout
GROUP BY MM.property_id, MM.num_rooms, BB.adate
HAVING MM.num_rooms - COUNT(*) <= 0
) as Z
)
You are close but you need to change the dates condition and add a condition to match the records from the outer and inner queries (all in the inner query's WHERE clause):
SELECT * FROM srp
WHERE NOT EXISTS
(SELECT * FROM bookings_srp
WHERE srp.booking_id = bookings_srp.booking_id
AND `date` >= {$check-in_date} AND `date` <= {$check-out_date})
You have to exclude the properties which are booked between the checkin date and checkout date. This query should do:
SELECT * FROM srp WHERE property_id NOT IN (
SELECT property_id FROM booking WHERE `date` >= {$checkin_date} AND `date` <= {$checkout_date}
)

Running total over date range - fill in the missing dates

I have the following table.
DATE | AMT
10/10 | 300
12/10 | 300
01/11 | 200
03/11 | 100
How do I get the monthly total? A result like -
DATE | TOT
1010 | 300
1110 | 300
1210 | 600
0111 | 800
0211 | 800
0311 | 900
A sql statement like
SELECT SUM(AMT) FROM TABLE1 WHERE DATE BETWEEN '1010' AND '0111'
would result in the 800 for 0111 but...
NOTE There is not a date restriction. which is my dilemma. How do I populate this column without doing a loop for all dates and have the missing months displayed as well?
To cater for missing months, create a template table to join against.
Think of it as caching. Rather than looping through and filling gaps, just have a calendar cached in your database.
You can even combine multiple calendars (start of month, start of week, bank holidays, working day, etc) all into one table, with a bunch of search flags and indexes.
You end up with something like...
SELECT
calendar.date,
SUM(data.amt)
FROM
calendar
LEFT JOIN
data
ON data.date >= calendar.date
AND data.date < calendar.date + INTERVAL 1 MONTH
WHERE
calendar.date >= '20110101'
AND calendar.date < '20120101'
GROUP BY
calendar.date
EDIT
I just noticed that the OP wants a running total.
This -is- possible in SQL but it is extremely inefficient. The reason being that the result from one month isn't used to calculate the following month. Instead the whole running-total has to be calculated again.
For this reason It is normally strongly recommended that you calculate the monthly total as above, then use your application to itterate through and make the running total values.
If you really must do it in SQL, it would be something like...
SELECT
calendar.date,
SUM(data.amt)
FROM
calendar
LEFT JOIN
data
ON data.date >= #yourFirstDate
AND data.date < calendar.date + INTERVAL 1 MONTH
WHERE
calendar.date >= #yourFirstDate
AND calendar.date < #yourLastDate
GROUP BY
calendar.date
the main problem is the and have the missing months displayed as well?
I don't see how to do it with out an aux table containing the combination of month\year to be displayed:
create table table1(
date datetime,
amt int
)
insert into table1 values ('10/10/2010',100)
insert into table1 values ('12/12/2010',200)
insert into table1 values ('01/01/2011',50)
insert into table1 values ('03/03/2011',500)
truncate table #dates
create table #dates(
_month int,
_year int
)
insert into #dates values(10,2010)
insert into #dates values(11,2010) --missing month
insert into #dates values(12,2010)
insert into #dates values(01,2011)
insert into #dates values(02,2011)--missing month
insert into #dates values(03,2011)
select D._month, D._year, sum(amt)
from #dates D left join TABLE1 T on D._month=month(T.date) and D._year=year(T.date)
group by D._month, D._year
You can also generate a range on the fly, pass its value as the interval to DATE_ADD, and basically project a sequence of month values.
As #Dems said, you need to have a correlated subquery calculate the running total, which will be very inefficient, because it will run a nested loop internally.
To see how to generate the sequence, check my post here:
How to generate a range of numbers in Mysql
The end query should look something like this: (Incidentally, you should have a date column, not this varchar mess).
/*NOTE: This assumes a derived table (inline view) containing the sequence of date values and their corresponding TOT value*/
SELECT
DATEVALUES.DateValue,
(
SELECT SUM(TABLE1.AMT) FROM TABLE1 WHERE TABLE1.DateValue <= DATEVALUES.DateValue)
) AS RunningSubTotal
FROM
DATEVALUES
Or something like that.
select sum(AMT) from TABLE1 group by Date