Is there a way in MySQL to select specific columns based on dates entered as variables?
For example, if there is a table containing daily data from Day 1 to Day 31 for various items. Like the table below -
id
Month
Year
Item
Day 1
Day 2
Day3
Day 4
Day 5
1
December
2021
Bag
2
4
2
5
1
2
December
2021
Pencil
3
5
1
8
2
This goes up to Day 31 if needed for any number of items in the month.
I'd like to be able to enter a date range such as 2021-12-01 and 2021-12-03 along with the item name 'Bag' and get the sum of the values in that data range - 2+4+2 = 8.
Is this possible?
And if it isn't possible with this current method I'm attempting, is there a way to get it done?
Thanks.
This may not be the answer you want, but you should seriously fix your data model. Do not store your dates as columns, but rather store each date's data point in a separate record. So the first record from your sample table would become:
id | Item | Date | num_items
1 | Bag | 2021-12-01 | 2
1 | Bag | 2021-12-02 | 4
1 | Bag | 2021-12-03 | 2
1 | Bag | 2021-12-04 | 5
1 | Bag | 2021-12-05 | 1
Now your query becomes much more tractable:
SELECT id, Item, SUM(num_items) AS total
FROM yourTable
WHERE Date BETWEEN '2021-12-01' AND '2021-12-03' AND Item = 'Bag'
GROUP BY id, Item;
a bit verbose, but you can do something like this :
SELECT
IF (DAY('2021-12-01') <= 1 AND DAY('2021-12-03') >= 1, day1, 0)
+ IF (DAY('2021-12-01') <= 2 AND DAY('2021-12-03') >= 2, day2, 0)
+ IF (DAY('2021-12-01') <= 3 AND DAY('2021-12-03') >= 3, day3, 0)
+ ...
Although it's probably better to change your data model to have a date and value as column.
Related
I have a table which contains the following data
userId | name | from
1 | aaa | 2020-09-23
2 | bbb | 2020-09-01
3 | ccc | 2019-05-12
4 | ddd | 2019-06-01
5 | eee | 2018-06-23
6 | fff | 2018-07-23
It is for an educational purpose and therefore the year runs from September to August rather than January to December. How do I output all of the users who were added since the previous September (so if it's October 2020 then 1 month ago, if it's January 2021 then 4 months ago)? The query has to be relative so that it always outputs the previous September to the time that the query is run rather than a specific September.
The result of the query should be
bbb 2020-09-01
aaa 2020-09-23
With NOT EXISTS:
select t.* from tablename t
where t.`from` >= concat(year(current_date), '-09-01')
and not exists (
select 1 from tablename
where name = t.name
and `from` between
concat(year(current_date), '-09-01') - interval 1 year
and
concat(year(current_date), '-09-01') - interval 1 day
)
Or maybe:
select t.* from tablename t
where t.`from` >= concat(year(current_date) - (month(current_date) < 9), '-09-01')
and not exists (
select 1 from tablename
where name = t.name
and `from` between
concat(year(current_date) - (month(current_date) < 9), '-09-01') - interval 1 year
and
concat(year(current_date) - (month(current_date) < 9), '-09-01') - interval 1 day
)
so if you execute the query in say March 2021, you will get the correct results that compare the current educational year with the previous one.
See the demo.
Results:
> userId | name | from
> -----: | :--- | :---------
> 1 | aaa | 2020-09-23
> 2 | bbb | 2020-09-01
You can subtract 9 months and compare to the year:
where year(`from` - interval 9 month) = year(curdate() - interval 9 month)
Actually, you might want year(curdate()) +/- 1 depending on how you are identifying the year.
I have two tables:
Table 1: planA
ID Date Count
3 2017-01-01 10
2 2017-02-03 15
10 2017-01-30 8
Table 2: planB
ID Date Value
3 2017-01-02 11
2 2017-02-04 12
21 2017-01-30 3
3 2017-02-03 33
What I want to do is to join the two tables on (ID and Date) columns.
However, on Date, I want to use the next day to the date on the table 1.
Therefore, the joined table should look like the following:
PlanA.ID PlanA.Date PlanB.Date PlanA.Count PlanB.Value
3 2017-01-01 2017-01-02 10 11
2 2017-02-03 2017-02-04 15 12
Is this even possible?
Any suggestion would be appreciated!
Yes it is possible:
select
PlanA.ID,
PlanA.Date,
PlanB.Date,
PlanA.Count,
PlanB.Value
from
PlanA inner join PlanB
on (
PlanA.ID = PlanB.ID
and
PlanA.Date + INTERVAL 1 DAY = PlanB.Date
)
if Date is a column of type date, + INTERVAL 1 DAY will return the next day of the one given, and then you can perform the join.
I'm trying to return a list of vehicles where I know they are not already booked for a current date range. I have a tblVehicles table:
ID Name
1 BMW
2 Merc
3 Land Rover
4 Hummer
5 Convertable
I also have a table (tblDiary) that holds all the dates that vehicle is being used:
ID VehicleId startDate endDate
1 1 2016-06-20 2016-06-22
2 1 2016-06-24 2016-06-24
3 2 2016-05-01 2016-06-05
So I'm trying to write a query where I want to find vehicles that are available between 2016-06-21 & 2016-06-23. So, in this instance the desired result would be:
ID Name
2 Merc
3 Land Rover
4 Hummer
5 Convertable
What the most efficient way of doing this?
SELECT *
FROM tblVehicles v
WHERE NOT EXISTS
(
SELECT NULL
FROM tblDiary d
WHERE d.vehicleId = v.id
AND endDate >= '2016-06-21'
AND startDate <= '2016-06-23'
)
I have a reoccurring payment day of 14th of each month and want to group a subset of data by month/year and sum the sent column. For example for the given data:-
Table `Counter`
Id Date Sent
1 10/04/2013 2
2 11/04/2013 4
3 15/04/2013 7
4 10/05/2013 3
5 14/05/2013 5
6 15/05/2013 3
7 16/05/2013 4
The output I want is something like:
From Count
14/03/2013 6
14/04/2013 10
14/05/2013 12
I am not worried how the from column is formatted or if its easier to split into month/year as I can recreated a date from multiple columns in the GUI. So the output could easily just be:
FromMth FromYr Count
03 2013 6
04 2013 10
05 2013 12
or even
toMth toYr Count
04 2013 6
05 2013 10
06 2013 12
If the payment date is for example the 31st then the date comparison would need to be the last date of each month. I am also not worried about missing months in the result-set.
I will also turn this into a Stored procedure so that I can push in the the payment date and other filtered criteria. It is also worth mentioning that we can go across years.
Try this query
select
if(day(STR_TO_DATE(date, "%Y-%d-%m")) >= 14,
concat('14/', month(STR_TO_DATE(date, "%Y-%d-%m")), '/', year(STR_TO_DATE(date, "%Y-%d-%m"))) ,
concat('14/', if ((month(STR_TO_DATE(date, "%Y-%d-%m")) - 1) = 0,
concat('12/', year(STR_TO_DATE(date, "%Y-%d-%m")) - 1),
concat(month(STR_TO_DATE(date, "%Y-%d-%m"))-1,'/',year(STR_TO_DATE(date, "%Y-%d-%m")))
)
)
) as fromDate,
sum(sent)
from tbl
group by fromDate
FIDDLE
| FROMDATE | SUM(SENT) |
--------------------------
| 14/10/2013 | 3 |
| 14/12/2012 | 1 |
| 14/3/2013 | 6 |
| 14/4/2013 | 10 |
| 14/5/2013 | 12 |
| 14/9/2013 | 1 |
Pay date could be grouped by months and year separatedly
select Sum(Sent) as "Count",
Extract(Month from Date - 13) as FromMth,
Extract(Year from Date - 13) as FromYr
from Counter
group by Extract(Year from Date - 13),
Extract(Month from Date - 13)
Be careful, since field's name "Date" coninsides with the keyword "date" in ANSISQL
I think the simplest way to do what you want is to just subtract 14 days rom the date and group by that month:
select date_format(date - 14, '%Y-%m'), sum(sent)
from counter
group by date_format(date - 14, '%Y-%m')
I got an alert table for users, in which we have to send alerts to users in user defined intervals like 0 ( only once), 3 months, 6 months, 1 year
So I designed a table like this
id | user_id | alert_date | repeat_int
-----+--------------+-------------------------+-------------
12 | 747 | 2013-04-19 00:00:00 | 0
13 | 746 | 2013-03-19 00:00:00 | 1
14 | 745 | 2012-04-19 00:00:00 | 0
15 | 744 | 2013-04-19 00:00:00 | 0
16 | 743 | 2013-05-19 00:00:00 | 0
We are sending alert just a day before "alert_date"
With the following query I can fetch the data
SELECT al.id,
al.user_id,
al.alert_date,
al.repeat_int AS repunit
FROM alerts AS al
WHERE DATE_ADD(alert_date,INTERVAL repeat_int MONTH)=date_add(CURRENT_DATE,INTERVAL 1 DAY)
OR date(al.alert_date)=date_add(CURRENT_DATE,INTERVAL 1 DAY)
Its working file but my real problem is
The repeat will only works once, we need it repeat every interval
ie. If alert date is 2012-03-14 and repeat_int is 0 - Need to work only once
but if alert date is 2012-03-14 and repeat_int is 1 - Need to work in every 14th from 2012-03-14
and if the alert date is 2012-03-14 and repeat_int is 3 - Need to work in every three month's 14. ie alert on 2012-03-14, 2012-06-14, 2012-09-14 etc...
Is there any way to do that?
Update
The OP has changed his schema in response to comments, so the query is essentially:
SELECT *
FROM alerts
WHERE CURRENT_DATE + INTERVAL 1 DAY = COALESCE(next_alert_date, alert_date);
This handles "next_alert_date" being NULL on the very first run.
Original answer
For the original schema:
SELECT *
FROM alerts
JOIN (SELECT CURRENT_DATE + INTERVAL 1 DAY AS tomorrow) d
WHERE -- We want to alert if
-- 1. Tomorrow is the alert_date
tomorrow = alert_date
OR
--
-- 2. Tomorrow is "repeat_int" months removed from alert_date, falling on
-- the same day of the month or on the end of the month if the original
-- alert_date day of month is later in the month than is possible for us
-- now. E.g., 2013-01-31 repeated monthly is adjusted to 2013-02-28.
(
PERIOD_DIFF(DATE_FORMAT(tomorrow, '%Y%m'), DATE_FORMAT(alert_date, '%Y%m'))
MOD repeat_int = 0
AND
-- Make sure we are at the same day of the month
( (DAYOFMONTH(tomorrow) = DAYOFMONTH(alert_date)
OR
-- Or, if the day of the alert is beyond the last day of our month,
-- that we are at the end of our month.
(LAST_DAY(alert_date) > LAST_DAY(tomorrow)
AND
DAYOFMONTH(tomorrow) = LAST_DAY(tomorrow)) )
);