Count Events/year in SQL - mysql

I have a list of events that have a date. I'm trying to count how many events take place in the current year, and 5 years on either side (regardless of whether any events took place) in mySQL using simple joins, selects, etc (no subqueries) in a single statement.
I have a table that produces the years and the number of events in that year, but am having problems when the year has no events taking place

Look into date functions on mysql http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff
You can use datediff which will give you difference in days. Ex;
WHERE abs(datediff(now(), event_date)) < 365*5
or dateadd(), if your event dates are timestamps, use timestampdiff()
Sample query
SELECT count(*) FROM mytable
WHERE abs(datediff(now(), event_date)) < 365*5
UPDATE
based on some of the comments I've read here, here's a query for you
SELECT year(event_date) as event_year, count(event_date)
FROM mytable
WHERE
abs(datediff(now(), event_date)) < 365*5
GROUP by year(event_date)
Feel free to adjust 5 in (365 * 5) for different range
UPDATE 2
This is NOT very pretty but you can try this with pure mysql. You can also modify this to be a stored proc if necessary:
SET #y6 = year(now());
SET #y5 = #y6-1;
SET #y4 = #y5-1;
SET #y3 = #y4-1;
SET #y2 = #y3-1;
SET #y1 = #y2-1;
SET #y7 = #y6+1;
SET #y8 = #y7+1;
SET #y9 = #y8+1;
SET #y10 = #y9+1;
SET #y11 = #y10+1;
CREATE TEMPORARY TABLE event_years (event_year int not null);
INSERT INTO event_years SELECT #y1;
INSERT INTO event_years SELECT #y2;
INSERT INTO event_years SELECT #y3;
INSERT INTO event_years SELECT #y4;
INSERT INTO event_years SELECT #y5;
INSERT INTO event_years SELECT #y6;
INSERT INTO event_years SELECT #y7;
INSERT INTO event_years SELECT #y8;
INSERT INTO event_years SELECT #y9;
INSERT INTO event_years SELECT #y10;
INSERT INTO event_years SELECT #y11;
SELECT ey.event_year , (SELECT count(event_date) from mytable where year(event_date) = ey.event_year)
from event_years ey;
temporary table will get dropped by itself after your connection is closed. If you add DROP TABLE after SELECT, you might not get your results back.

did you try to use join left?
MODIFIED:
SELECT tleft.YEARS, COUNT(tright.EVENTS)
FROM ONLY_YEARS tleft LEFT JOIN TABLE1 tright
ON (tleft.YEARS = tright.YEARS)
GROUP BY tleft.YEARS;
With that modification, you need to point to a table that holds all the years (ONLY_YEARS), maybe a dummy table with one column that goes from 1990 to 2020...
Left join optimization for MySQL link

To select a count of events that happened between two years, grouped by years, the following sql should suffice:
select year(event.date), count(*) from event where event.date >= '2006' and event.date <= '2016' group by year(event.date);
However, if no events occurred in a year, no result will be returned for it.
Databases are not really designed for such dynamic things and I'd suggest such logic should be put in a business (or possibly data-access) layer.

Related

MySQL: Conditional Trigger

Is it possible to create a trigger that conditionally updates a column with a random value from another tables column.
Previously I received help to create a trigger that updates a column with a random value from another tables column: MySQL: Trigger Update with random value from another tables column. I’m trying now to make it conditionally based on another columns value.
If the users.selection column = ‘fruits’ then random select from fruits.
If the users.selection column = ‘animals’ then random from animals.
If neither ‘fruits’ nor ‘animals’ don’t update.
Here is a db-fiddle: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=6bc76ed2c104dad0e27dd35b1da112a7
Major thanks to #Akina for getting me this far! Lots to learn.
Update (May 29th):
I still can’t figure it out. I thought maybe I would need a SELECT with IF statement first to return the selection column value but that didn’t seem to work. Basically I have tried a lot of different combinations using these examples below as templates. None of them seem to bring my closer.
Anyone have any ideas?
Examples:
SELECT T1.ID, IFNULL(T1.name, T2.name) AS name
FROM firsttable T1
LEFT JOIN secondtable T2
ON T1.T2_id = T2.id
SET final_price= CASE
WHEN currency=1 THEN 0.81*final_price
ELSE final_price
END
SET col = (
SELECT other_col
FROM other_table
WHERE other_table.table_id = table.id
);
SELECT book_name,isbn_no,
IF((SELECT COUNT(*) FROM book_mast WHERE pub_lang='English')>
(SELECT COUNT(*) FROM book_mast WHERE pub_lang<>'English'),
(CONCAT("Pages: ",no_page)),(CONCAT("Price: ",book_price)))
AS "Page / Price"
FROM book_mast;
I think you need to conditionally define what does what, if selection is fruit, then do something. else if selection is animals, then do another thing.
e.g:
CREATE TRIGGER trigger_test
BEFORE UPDATE
ON users
FOR EACH ROW
BEGIN
IF (NEW.selection = 'fruits') THEN
SET NEW.random = ( SELECT fruits
FROM list
ORDER BY RAND() LIMIT 1 );
ELSEIF (NEW.selection = 'animals') THEN
SET NEW.random = ( SELECT animals
FROM list
ORDER BY RAND() LIMIT 1 );
END IF;
END;

MySQL - make "Update" a permanent change to a table?

So I have two tables - "Horses" and "Results". "Horses" lists a bunch of information about each horse, including a spot called "LTE", which totals from an "Earnings" field from the "Results" table. "Results" listed all the results of recent horse shows. I use the following code to calculate LTE -
UPDATE horses
SET horses.LTE = ( SELECT SUM(results.earnings)
FROM results WHERE horses.hname=results.hname )
Which works wonderfully - it updates the LTE column. However...I have to run this code EVERY time I add new data to the "Results" table. I will be adding data month for...well, pretty much ever.
I don't want to have to run this code every time. Is there a way to make the code "permanent," in a sense that the LTE field KNOWS it just calculates whenever new information is added? Or does MySQL not work this way?
Here's a peek at my tables with some data in them.
MySQL doesn't work that way. But you can achieve such functionality by using triggers. For example, you can update your sum each time there's insert in respective table (and/or update), this way you will always have your sum 'cached' and you can recalculate if needed.
If you want a bit more analytics on sum changes, you can follow this pattern, I think it will be helpful
You can use a TRIGGER on INSERT, UPDATE and DELETE to update your table:
-- trigger for INSERT (new rows on table result).
DELIMITER |
CREATE TRIGGER ins_result AFTER INSERT ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
-- trigger for UPDATE (changed rows on table result).
CREATE TRIGGER upd_result AFTER UPDATE ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
-- trigger for DELETE (removed rows on table result).
CREATE TRIGGER del_result AFTER DELETE ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
Another solution could be a VIEW:
CREATE VIEW v_horses AS
SELECT h.*, SUM(r.earnings) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname;
With the created VIEW you can get the information about the horses with the following query:
SELECT * FROM v_horses;
In your case you doesn't use a DECIMAL column. So you have to convert the VARCHAR column to SUM the earnings. So in your case you have to use the following VIEW:
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname;
-- for a specific year (like 2017)
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
WHERE DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y') = 2017
GROUP BY h.hname;
-- grouped by year (so you can use WHERE on the VIEW):
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE',
DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y') AS 'year'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname, DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y');
Note: In case of using this VIEW you have to remove the column LTE from table horses.

Update column with Datediff between current and next row

The table contains the columns id, timestamp (eg. 2013-09-23 12:10:53), activity and I want to add another column duration which would contain the duration of each activity (ie. the difference between the next row time stamp and the current one).
I've tried this query:
UPDATE `MyTable` this SET `duration`=
(SELECT DATEDIFF(next.`timestamp`, curr.`timestamp`)
FROM `MyTable` curr
JOIN `MyTable` next
ON next.`id` = curr.`id`+1
WHERE this.`id` = curr.`id`)
And got this error:
#1093 - You can't specify target table 'this' for update in FROM clause
How can I go about doing that?
Instead, use a join:
UPDATE MyTable this left join
MyTable next
ON next.id = this.id + 1
SET this.duration = DATEDIFF(next.timestamp, this.timestamp) ;
I think the error is self-explanatory. The usual solution in MySQL is to convert the update to use joins instead of correlated subqueries.
Some reference for you
You can't specify target table for update in FROM clause
http://dev.mysql.com/doc/refman/5.0/en/update.html
UPDATE `MyTable` this SET `duration`=
(SELECT DATEDIFF(next.`timestamp`, curr.`timestamp`)
FROM (SELECT * from MyTable) curr
JOIN (SELECT * from MyTable) next
ON next.`id` = curr.`id`+1
WHERE this.`id` = curr.`id`);

MySQL - INSERT multiple values conditionally

Ok, so I have a table which holds bets on games.
The table holds the columns: user_id, event_id, bet.
A user can send his/her (multiple) bets to the server in one request.
I need to insert multiple bets using one query, while checking that none of the bets
are on an event that already started/finished.
In case of at least 1 started/finished event, I don't really care if the whole query cancels, or just ignores the 'unqualified' bets.
Question
How can I insert multiple bets (rows) with one query, while conditioning the insert on a select statement (which checks for each of the events' statuses)?
Here is the query I would've used if it worked (and it doesn't of course):
INSERT INTO bet_on_event (user_id, event_id, bet)
VALUES (1,5,1), (1,6,2)
IF (SELECT COUNT(*) FROM events WHERE _id IN(5,6) AND status=0) = ?;
Explanation
1. As mentioned, the values are pre-made - requested by the user.
2. Games/events have status. 0 means a game hasn't started, so it's ok to bet.
3. The select statement just counts how many of the requested events have status 0.
4. The 'IF' should check if the count from (3) equals the number of events the user requested to bet on, thus confirming that all the events are ok to bet on.
The 'IF' should be replaced with something that work, and the whole statement can be replaced if you have a better idea for what I'm trying to achieve.
A simpler query (which isn't enough for my case, but works with 1 row) is:
INSERT INTO bet_on_event (user_id, event_id, bet)
SELECT 1,5,1 FROM dual
WHERE (SELECT COUNT(*) FROM events WHERE _id IN(5,6) AND status=0) = ?;
Any idea? Is this even possible? Betting is gonna be used a lot, so I want to do it as quick as possible - with 1 query.
Thank you so much.
EDIT
That is what I ended up doing, taken from Thorsten's answer (I changed it to a dynamically built query, as that is what I need):
var query='INSERT INTO bet_on_event (user_id, event_id, bet)';
for(var i=0; i<eventIds.length; i++){
query+= ' SELECT ' + userId + ',' + eventIds[i] + ',' + bets[i]
+ ' FROM dual WHERE EXISTS (SELECT * FROM events WHERE id = ' + eventIds[i]
+ ' AND Status = 0)';
if(i < eventIds.length - 1){
query += ' UNION ALL';
}
}
Where eventIds and bets are in a corresponding order (like a map)
EDIT 2
I also wanted to UPDATE the bets which already exist (in case the user wanted to...).
So there's a need to update each row with the relevant bet in the array. This is the solution:
ON DUPLICATE KEY UPDATE bet=VALUES(bet)
Just added (concatenated) to the end of the query...
Does this work for you? It inserts 1,5,1 if there is no event for id 5 that has started. Same for 1,6,1 and id 6.
INSERT INTO bet_on_event (user_id, event_id, bet)
SELECT 1,5,1 FROM dual WHERE NOT EXISTS
(SELECT * FROM events WHERE _id = 5 AND Status <> 0)
UNION ALL
SELECT 1,6,1 FROM dual WHERE NOT EXISTS
(SELECT * FROM events WHERE _id = 6 AND Status <> 0);
EDIT: If you don't want to insert anything in case one or more of the games have started, you can simply replace WHERE _id = 5 and WHERE _id = 6 with WHERE _id IN (5,6). Or have just one exists clause:
INSERT INTO bet_on_event (user_id, event_id, bet)
SELECT *
FROM
(
SELECT 1,5,1 FROM dual
UNION ALL
SELECT 1,6,1 FROM dual
) tmp
WHERE NOT EXISTS (SELECT * FROM events WHERE _id IN (5,6) AND Status <> 0);
have you tried with UNION ?
INSERT INTO bet_on_event (user_id, event_id, bet)
(SELECT 1,5,1 FROM dual
WHERE (SELECT COUNT(*) FROM events WHERE _id IN(5,6) AND status=0) = ?
UNION
SELECT 1,6,2 FROM dual
WHERE (SELECT COUNT(*) FROM events WHERE _id IN(5,6) AND status=0) = ? );

how to set an array as a mysql user variable

I didn't expect to find this so difficult, but I'm trying to set a user variable in MySQL to contain an array of values. I have no clue how to do this so tried doing some research and was quite suprised to find no answer. I have tried:
SET #billable_types = ['client1','client2','client3'];
The reason being I would like to use the variable in the following statement later on:
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (#billable_types)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
Would be very grateful for help.
Fast forward a while, I ended up with the following quick and dirty solution which doesn't give me the flexibility of re-using the array elsewhere in the code but hey it's an unchargeable admin task so I don't want to spend any more time on it.
SELECT WEEKDAY(tlg.time_start) AS day_of_week, date(tlg.time_start) as start_date,
sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN ('c1','c2','c3')
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
joostschouten seems to have found the most elegant solution (not tested it myself yet) but next time I'm writing something which calls for this I will remember to test it!
Just found the answer here: How to cycle with an array in MySQL?
set #billable_types = 'client1,client2,client3';
select * from mttl where find_in_set(mttl.type, #billable_types);
As Marc B mentioned, there is no array variable in MYSQL.
The alternative to find_in_set solution is to use SELECT with UNION to simulate the array:
SELECT billable_type FROM (
SELECT 'client1' AS billable_type UNION
SELECT 'client2' AS billable_type UNION
SELECT 'client3' AS billable_type) AS t
So your query will looks like that:
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (
SELECT billable_type FROM (
SELECT 'client1' AS billable_type UNION
SELECT 'client2' AS billable_type UNION
SELECT 'client3' AS billable_type) AS t
)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
If the user has the CREATE TABLE privilege, an array can be simulated by creating a temporary, single-column table. A value or values in the table can be retrieved with a SELECT statement. Temporary tables are dropped at the end of the session, but it's a good idea to explicitly drop them once they're no longer needed.
CREATE TEMPORARY TABLE billable_types (c VARCHAR(16));
INSERT INTO billable_types VALUES ('client1'), ('client2'), ('client3');
SELECT sum((time_to_sec(timediff(tlg.time_stop, tlg.time_start))/3600)) as billable_hours
from mod_tmlog_time_log tlg, mod_tmlog_task_list mttl
where date(tlg.time_start) >= #time_start
and date(tlg.time_stop) <= #time_stop
and mttl.type IN (SELECT * FROM billable_types)
and tlg.task_id = mttl.id
group by start_date
order by start_date desc;
DROP TABLE billable_types;