After trawling through the web and still not finding any simple answers I'm hoping someone can advise on this.
All I want to do is insert a new row into a table if a value from that table is not null. In this case, I have a table called "Events".
Events Table
Event_ID | Event_Name | Event_Interval | Event_Date | Event_Interval | Event_Repeat
-----------------------------------------------------------------------------------
1 My Event 14 2017-04-04 14 Completion
There are two conditions on which I'm working towards; best described using a bit of pseudocode:
IF EVENT_INTERVAL != NULL THEN
IF EVENT_REPEAT = "DUE" THEN
DATE_ADD(EVENT_DATE, INTERVAL EVENT_INTERVAL DAY)
ELSE
DATE_ADD(NOW(), INTERVAL EVENT_INTERVAL DAY)
END IF
END IF
So: when someone completes an event, if the interval value is not null, it inserts a new row to the table where it adds the interval value to either the event date or the current date, depending on the value in "Event_Repeat".
So far I've got to :
SELECT
(IF(ISNULL((SELECT e.Event_Interval from events e where e.Event_ID = 1)) = 1, "NULL", "NOT NULL"))
This works absolutely fine and can distinguish whether the interval value is NULL or not. I've tried to incorporate/substitute an insert statement into here but as of yet no luck.
If anyone can help to structure this properly (or better!) please feel free as I'm still finding my way around queries etc.
Thanks in advance.
you can create SP as
CREATE PROCEDURE update_event
#event_id int
AS
BEGIN
DECLARE #event_repeat_temp nvarchar(200)
SELECT #event_repeat_temp = event_repeat FROM event_table WHERE event_id = #event_id
PRINT #event_repeat_temp
IF #event_repeat_temp = 'Due'
BEGIN
INSERT INTO event_table (event_id , event_name, event_interval, event_date, interval,event_repeat) VALUES (2,'My Event',14,'completion')
END
ELSE
BEGIN
//whatever you want
END
END
and call this SP as below
EXEC update_event #event_id =1
Related
I've written a stored procedure to iterate over every week for three years. It doesn't work though and returns a vague error message.
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 18
DELIMITER $$
CREATE PROCEDURE loop_three_years()
BEGIN
declare y INT default 2016;
declare m int default 4;
declare d int default 20;
WHILE y <= 2019 DO
WHILE YEARWEEK(concat(y, '-', m, '-', d)) <= 53 DO
WHILE m < 12 DO
WHILE (m = 2 and d <= 29) OR (d <=30 and m in(4, 6,9,11)) OR ( m in(1,3,5,7,8,10,12) AND d <= 31) DO
set d = d + 7;
SELECT YEARWEEK(concat(y, '-', m, '-', d));
END WHILE;
set d=1;
END WHILE;
set m = 1;
SET y = y + 1;
END WHILE;
END
$$
When I used this as minimal parts they work so I'm not sure what the issue is with my reassembly. Also not sure if there's a better way to do this. (The select is just for testing, it will be an insert when I use the real code.
Slightly Altered from a previous solution
You can build your own dynamic calendar / list using ANY other table in your system that has at least as many records as you need to fake row numbers. The query below will use MySQL # variables which work like an inline program and declaration. I can start the list with a given date... such as your 2016-04-20 and then each iteration through, add 1 week using date-based functions. No need for me to know or care about how many days have a 28, 29(leap-year), 30 or 31 days.
The table reference below of "AnyTableThatHasAtLeast156Records" is just that.. Any table in your database that has at least 156 records (52 weeks per year, 3 years)
select
YEARWEEK( #startDate ) WeekNum,
#startDate as StartOfWeek,
#startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from
( select #startDate := '2016-04-20') sqlv,
AnyTableThatHasAtLeast156Records
limit
156
This will give you a list of 156 records (provided your "anyTable…" has 156 records all at once. If you need to join this to some other transaction table, you could do so by making the above a JOIN table. Benefit here, Since I included the begin date and end of week, those can be part of your joining to table.
Example, on
record WeekNum StartOfWeek EndOfWeek
1 ?? 2016-04-20 2016-04-27
2 ?? 2016-04-27 2016-05-04
3 ?? 2016-05-04 2016-05-11
4 ?? 2016-04-11 2016-05-18... etc
By adding 1 week to the starting point, you can see that it would do Ex: Monday to Monday. And the JOIN Condition below I have LESS THAN the EndOfWeek. This would account for any transactions UP TO but not including the ending date... such as transactions on 2016-04-26 11:59:59PM (hence LESS than 2016-04-27, as 04/27 is the beginning of the next week's cycle of transactions)
select
Cal.WeekNum,
YT.YourColumns
from
YourTransactionTable YT
JOIN ( aboveCalendarQuery ) Cal
on YT.TransactionDate >= Cal.StartOfWeek
AND YT.TransactionDate < Cal.EndOfWeek
where
whatever else
You could even do sum() with group by such as by WeekNum if that is what you intend.
Hopefully this is a much more accurate and efficient way to build out your calendar to run with and linking to transactions if you so needed to.
Response from comment.
You could by doing a join to a ( select 1 union select 2 union … select 156 ), but your choice. The ONLY reason for the "AnyTable…" is I am sure with any reasonable database with transactions you would have 156 records or more easily. It's sole purpose is to just allow a row for cycling through the iterations to dynamically create the rows.
Also much more sound than the looping mechanism you have run into to begin with. Nothing wrong with that, especially learning purposes, but if more efficient ways, doesn't that make more sense?
Per feedback from comment
I dont exactly know your other table you are trying to insert into, but yes, you can use this for all 3000 things. Provide more of what you are trying to do and I can adjust... In the mean-time, something like this...
insert into YourOtherTable
( someField,
AnotherField,
WeekNum
)
select
x.someField,
x.AnotherField,
z.WeekNum
from
Your3000ThingTable x
JOIN (select
YEARWEEK( #startDate ) WeekNum,
#startDate as StartOfWeek,
#startDate := date_add( #startDate, interval 1 week ) EndOfWeek
from
( select #startDate := '2016-04-20') sqlv,
AnyTableThatHasAtLeast156Records
limit
156 ) z
on 1=1
where
x.SomeCodition...
By joining the the select of 156 records on 1=1 (which is always true), it will return 156 entries for whatever record is in the Your3000ThingTable. So, if you have an inventory item table with
Item Name
1 Thing1
2 Thing2
3 Thing3
Your final insert would be
Item Name WeekNum
1 Thing1 1
1 Thing1 2
1 Thing1 ...
1 Thing1 156
2 Thing2 1
2 Thing2 2
2 Thing2 ...
2 Thing2 156
3 Thing3 1
3 Thing3 2
3 Thing3 ...
3 Thing3 156
And to pre-confirm what you THINK would happen, just try the select/join on 1=1 and you'll see all the records the query WOULD be inserting into your destination table.
I have a table tblMatch :
+---------+---------------------+------------------+-----------+
| ID | start_date | end_date | status |
+---------+---------------------+------------------+-----------+
| 1 | 2017-12-09 03:23 | 2017-12-10 03:23 | 1 |
+---------+---------------------+------------------+-----------+
| ... | ... | ... | 1 |
+---------+---------------------+------------------+-----------+
| 1000000 | 2017-12-22 15:12 | 2017-12-30 15:12 | 1 |
+---------+---------------------+------------------+-----------+
When I insert a row, I create one event too.
Event will change status to 0 if the match is ended.
CREATE EVENT test_event_increment_number
ON SCHEDULE AT end_date
ON COMPLETION NOT PRESERVE
DO
UPDATE tblMatch SET status = 0 WHERE ID = increment_number;
If tblMatch has 100 million matches :
Does it effect server performance?
Is it bad or good idea to create a lot of events?
Create just 1 event that runs daily and closes the matches expiring that day.
CREATE EVENT test_event
ON SCHEDULE AT 1 every day
STARTS (TIMESTAMP(CURRENT_DATE) + INTERVAL 1 DAY)
DO
UPDATE tblMatch SET status = 0 WHERE end_date = CURRENT_DATE;
UPDATE
If you want to time your events at a minute level, then either change the frequency of your event to minute level and use minute level when determining if a match needs closing, or completely drop the status field and just use the end_date field's value compared to now() to determine if the event is closed. The latter is a better way.
I Create 25 events is different time. After a while, the database takes all the resources, and reboots.
CREATE EVENT `auction_event_46709`
ON SCHEDULE EVERY 5 MINUTE STARTS '2017-12-07 10:23:03'
ON COMPLETION NOT PRESERVE ENABLE
DO CALL auction_update_price(46709)
Procedure:
CREATE PROCEDURE `auction_update_price`( IN p_id INT )
BEGIN
DECLARE cur_price INT;
DECLARE stp_price INT;
DECLARE st_price INT;
DECLARE str_price INT;
SELECT current_price, step_price, stop_price, start_price INTO cur_price, stp_price, st_price, str_price FROM product_to_auction WHERE product_id = p_id;
IF( cur_price - stp_price > st_price ) THEN
UPDATE product_to_auction SET current_price = current_price - step_price WHERE current_price > stop_price AND product_id = p_id;
ELSE
UPDATE product_to_auction SET current_price = str_price WHERE product_id = p_id;
END IF;
END
How to Fix?
Don't use events at all for this sort of operation. Certainly don't use many events for it. Instead, use a query (or view) that takes your end_date into account and determines your status value dynamically based on date. For example, to retrieve a particular item by id, do this.
SELECT id, start_date, end_date
CASE WHEN end_date <= CURDATE() THEN 0 ELSE status END AS status
FROM tblMatch
WHERE id = something
This query returns the row from the table, along with the status value based on the moment you run the query. (I set it up so items with status = 0 are always marked as expired never mind the current time.)
If you want all the items with status 1 (meaning non expired) do this:
SELECT id, start_date, end_date, 1 AS status
FROM tblMatch
WHERE end_date < CURDATE ()
AND status = 1
If you MUST use an event, you can run it once a day, sometime after midnight to reset the status columns of all expiring rows to 0, with a query like this.
UPDATE tblMatch SET status = 0 WHERE status = 1 AND end_date < CURDATE();
(I prefer to run daily update queries shortly after 03:00 local time. Why? I'm located in the USA, and our daylight saving time switchover is done, twice a year, at 02:00 local time. Doing daily updates after 03:00 ensures they'll still work properly on switchover days. )
For these queries to be efficient, you need a compound index on (status, end_date)
I'd like to retrieve the activity of a user from the last 7 days. The table I'm querying is a many-to-many, linking users, events and dates.
If today is the 7th of May, but the user only has activity recorded for the 4th and 2nd of May, can I build a query that will return a NULL corresponding with the dates of no activity recorded?
Source
May 2 42
May 4 88
Desired Output
May 1 NULL
May 2 42
May 3 NULL
May 4 88
May 5 NULL
May 6 NULL
May 7 NULL
How can this be done in MySQL?
SELECT DISTINCT users, events, dates
FROM table
WHERE dates between date_format(date1, '%Y-%m-%d')
and date_format(date2, '%Y-%m-%d') or NOW() or CURDATE()....
and user = 'username'
This is pretty general, but if you replace the names and the dates (probably don't need to format for your purposes), this will return all results regardless of null place holders
You could populate a date_reference_table...here is the stored proc for this:
I use a date format to only recognize the year and month, but you can alter this...
You would also need an inner join then... so you would do an inner join on the date_reference table and it will spit out the results as mentioned in the comment above.
delimiter $$
CREATE PROCEDURE `generate_date_reference_table`(d1 date, d2 date)
BEGIN
declare d datetime;
create table BLMBP.date_reference (d char(7) not null);
set d = d1;
while d <= d2 do
insert into BLMBP.date_reference (d) values (date_format(d, '%Y-%m'));
set d = date_add(d, interval 1 month);
end while;
END$$
I have a log table with a date field called logTime. I need to show the number of rows within a date range and the number of records per day. The issue is that i still want to show days that do not have records.
Is it possible to do this only with SQL?
Example:
SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);
It returns something like this:
+---------------------+----------+
| logTime | COUNT(*) |
+---------------------+----------+
| 2011-02-01 | 2 |
| 2011-02-02 | 1 |
| 2011-02-04 | 5 |
+---------------------+----------+
3 rows in set (0,00 sec)
I would like to show the day 2011-02-03 too.
MySQL will not invent rows for you, so if the data is not there, they will naturally not be shown.
You can create a calendar table, and join in that,
create table calendar (
day date primary key,
);
Fill this table with dates (easy with a stored procedure, or just some general scripting), up till around 2038 and something else will likely break unitl that becomes a problem.
Your query then becomes e.g.
SELECT logTime, COUNT(*)
FROM calendar cal left join logs l on cal.day = l.logTime
WHERE day >= '2011-02-01' AND day <= '2011-02-04' GROUP BY day;
Now, you could extend the calendar table with other columns that tells you the month,year, week etc. so you can easily produce statistics for other time units. (and purists might argue the calendar table would have an id integer primary key that the logs table references instead of a date)
In order to accomplish this, you need to have a table (or derived table) which contains the dates that you can then join from, using a LEFT JOIN.
SQL operates on the concept of mathematical sets, and if you don't have a set of data, there is nothing to SELECT.
If you want more details, please comment accordingly.
I'm not sure if this is a problem that should be solved by SQL. As others have shown, this requires maintaining a second table that contains the all of the individual dates of a given time span, which must be updated every time that time span grows (which presumably is "always" if that time span is the current time.
Instead, you should use to inspect the results of the query and inject dates as necessary. It's completely dynamic and requires no intermediate table. Since you specified no language, here's pseudo code:
EXECUTE QUERY `SELECT logTime, COUNT(*) FROM logs WHERE logTime >= '2011-02-01' AND logTime <= '2011-02-04' GROUP BY DATE(logTime);`
FOREACH row IN query result
WHILE (date in next row) - (date in this row) > 1 day THEN
CREATE new row with date = `date in this row + 1 day`, count = `0`
INSERT new row IN query result AFTER this row
ADVANCE LOOP INDEX TO new row (`this row` is now the `new row`)
END WHILE
END FOREACH
Or something like that
DECLARE #TOTALCount INT
DECLARE #FromDate DateTime = GetDate() - 5
DECLARE #ToDate DateTime = GetDate()
SET #FromDate = DATEADD(DAY,-1,#FromDate)
Select #TOTALCount= DATEDIFF(DD,#FromDate,#ToDate);
WITH d AS
(
SELECT top (#TOTALCount) AllDays = DATEADD(DAY, ROW_NUMBER()
OVER (ORDER BY object_id), REPLACE(#FromDate,'-',''))
FROM sys.all_objects
)
SELECT AllDays From d
I want to get the number of Registrations back from a time period (say a week), which isn't that hard to do, but I was wondering if it is in anyway possible to in MySQL to return a zero for days that have no registrations.
An example:
DATA:
ID_Profile datCreate
1 2009-02-25 16:45:58
2 2009-02-25 16:45:58
3 2009-02-25 16:45:58
4 2009-02-26 10:23:39
5 2009-02-27 15:07:56
6 2009-03-05 11:57:30
SQL:
SELECT
DAY(datCreate) as RegistrationDate,
COUNT(ID_Profile) as NumberOfRegistrations
FROM tbl_profile
WHERE DATE(datCreate) > DATE_SUB(CURDATE(),INTERVAL 9 DAY)
GROUP BY RegistrationDate
ORDER BY datCreate ASC;
In this case the result would be:
RegistrationDate NumberOfRegistrations
25 3
26 1
27 1
5 1
Obviously I'm missing a couple of days in between. Currently I'm solving this in my php code, but I was wondering if MySQL has any way to automatically return 0 for the missing days/rows. This would be the desired result:
RegistrationDate NumberOfRegistrations
25 3
26 1
27 1
28 0
1 0
2 0
3 0
4 0
5 1
This way we can use MySQL to solve any problems concerning the number of days in a month instead of relying on php code to calculate for each month how many days there are, since MySQL has this functionality build in.
Thanks in advance
No, but one workaround would be to create a single-column table with a date primary key, preloaded with dates for each day. You'd have dates from your earliest starting point right through to some far off future.
Now, you can LEFT JOIN your statistical data against it - then you'll get nulls for those days with no data. If you really want a zero rather than null, use IFNULL(colname, 0)
Thanks to Paul Dixon I found the solution. Anyone interested in how I solved this read on:
First create a stored procedure I found somewhere to populate a table with all dates from this year.
CREATE Table calendar(dt date not null);
CREATE PROCEDURE sp_calendar(IN start_date DATE, IN end_date DATE, OUT result_text TEXT)
BEGIN
SET #begin = 'INSERT INTO calendar(dt) VALUES ';
SET #date = start_date;
SET #max = SUBDATE(end_date, INTERVAL 1 DAY);
SET #temp = '';
REPEAT
SET #temp = concat(#temp, '(''', #date, '''), ');
SET #date = ADDDATE(#date, INTERVAL 1 DAY);
UNTIL #date > #max
END REPEAT;
SET #temp = concat(#temp, '(''', #date, ''')');
SET result_text = concat(#begin, #temp);
END
call sp_calendar('2009-01-01', '2010-01-01', #z);
select #z;
Then change the query to add the left join:
SELECT
DAY(dt) as RegistrationDate,
COUNT(ID_Profile) as NumberOfRegistrations
FROM calendar
LEFT JOIN
tbl_profile ON calendar.dt = tbl_profile.datCreate
WHERE dt BETWEEN DATE_SUB(CURDATE(),INTERVAL 6 DAY) AND CURDATE()
GROUP BY RegistrationDate
ORDER BY dt ASC
And we're done.
Thanks all for the quick replies and solution.