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

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.

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;

Stored Procedure with Pivot Does Not Return Result Structure but Zero Value

MyActualresultI am trying to get count of data which have particular status on different dates. Something like tracking shipment and getting today's report.
I have used Pivot with stored procedure. I get result as column and rows but return value null on executing the Sp.
Can any one please suggests how can I rectify the issue?
I tried to put the query in some variable and then execute but its not done correctly.
Create PROCEDURE [dbo].[GetCountOfShipmentWithStatus]
#DateToStart Date,
#DateToEnd Date,
#LabName nvarchar(30)
AS
BEGIN
SET NOCOUNT ON;
WITH TrackShipment AS
(
SELECT exData.ID, CAST(ExpectedDeliveryDt AS DATE) AS Deliverydate,A.AccountCode,L.Name, DATENAME(dw, ExpectedDeliveryDt) AS DayOfWeek,exData.MileStoneTypeId AS TrackingStatus,exData.AccountNo
FROM [Tracking].[TrackingExternalData] exData
Left Join [systemManagement].[SystemMetaData] sysmetadat on exdata.MileStoneTypeId=sysmetadat.systemMetaDataId
Left Join [Data_Replication_EZSHIP].[dbo].[AccountNumber] AS A on exData.AccountNo=A.AccountCode
Left JOIN [Data_Replication_EZSHIP].[dbo].[Location] AS L ON A.LocationId = L.Id
WHERE L.Name=#LabName
AND sysmetadat.systemMetaDataId IN ('E770CE7C-E0E6-40C6-AC51-5D2129F2DEB7','D6A011C8-C39C-45B5-9127-52D20C68E1C3','C7657AE1-9354-E911-BB4A-005056B00B08','559ECEC2-969A-4F8C-9A95-21C613D82F3A')
AND exData.ExpectedDeliveryDt <= #DateToStart AND exData.ExpectedDeliveryDt >= #DateToEnd
)
SELECT Deliverydate, DayOfWeek,
-- List of Pivoted Columns
[E770CE7C-E0E6-40C6-AC51-5D2129F2DEB7],[D6A011C8-C39C-45B5-9127-52D20C68E1C3], [C7657AE1-9354-E911-BB4A-005056B00B08], [559ECEC2-969A-4F8C-9A95-21C613D82F3A]
FROM TrackShipment
PIVOT
(
COUNT(Id)
-- List of Pivoted columns
FOR TrackingStatus IN([E770CE7C-E0E6-40C6-AC51-5D2129F2DEB7],[D6A011C8-C39C-45B5-9127-52D20C68E1C3], [C7657AE1-9354-E911-BB4A-005056B00B08], [559ECEC2-969A-4F8C-9A95-21C613D82F3A])
) as pvt
ORDER BY Deliverydate DESC
END
GO
I expect return value same as result of pivot.
I am guessing that this condition is incorrect:
exData.ExpectedDeliveryDt <= #DateToStart AND
exData.ExpectedDeliveryDt >= #DateToEnd
I don't know what you intend, because you haven't provided sample data, desired results, or an explanation of the logic you want.
But I am guessing that the comparison are backwards:
exData.ExpectedDeliveryDt >= #DateToStart AND
exData.ExpectedDeliveryDt <= #DateToEnd

Update MySQL table any time another table changes

I am trying to reduce the number of queries my application uses to build the dashboard and so am trying to gather all the info I will need in advance into one table. Most of the dashboard can be built in javascript using the JSON which will reduce server load doing tons of PHP foreach, which was resulting in excess queries.
With that in mind, I have a query that pulls together user information from 3 other tables, concatenates the results in JSON group by family. I need to update the JSON object any time anything changes in any of the 3 tables, but not sure what the "right " way to do this is.
I could set up a regular job to do an UPDATE statement where date is newer than the last update, but that would miss new records, and if I do inserts it misses updates. I could drop and rebuild the table, but it takes about 16 seconds to run the query as a whole, so that doesn't seem like the right answer.
Here is my initial query:
SET group_concat_max_len = 100000;
SELECT family_id, REPLACE(REPLACE(REPLACE(CONCAT("[", GROUP_CONCAT(family), "]"), "\\", ""), '"[', '['), ']"', ']') as family_members
FROM (
SELECT family_id,
JSON_OBJECT(
"customer_id", c.id,
"family_id", c.family_id,
"first_name", first_name,
"last_name", last_name,
"balance_0_30", pa.balance_0_30,
"balance_31_60", pa.balance_31_60,
"balance_61_90", pa.balance_61_90,
"balance_over_90", pa.balance_over_90,
"account_balance", pa.account_balance,
"lifetime_value", pa.lifetime_value,
"orders", CONCAT("[", past_orders, "]")
) AS family
FROM
customers AS c
LEFT JOIN accounting AS pa ON c.id = pa.customer_id
LEFT JOIN (
SELECT patient_id,
GROUP_CONCAT(
JSON_OBJECT(
"id", id,
"item", item,
"price", price,
"date_ordered", date_ordered
)
) as past_orders
FROM orders
WHERE date_ordered < NOW()
GROUP BY customer_id
) AS r ON r.customer_id = c.id
where c.user_id = 1
) AS results
GROUP BY family_id
I briefly looked into triggers, but what I was hoping for was something like:
create TRIGGER UPDATE_FROM_ORDERS
AFTER INSERT OR UPDATE
ON orders
(EXECUTE QUERY FROM ABOVE WHERE family_id = orders.family_id)
I was hoping to create something like that for each table, but at first glance it doesn't look like you can run complex queries such as that where we are creating nested JSON.
Am I wrong? Are triggers the right way to do this, or is there a better way?
As a demonstration:
DELIMITER $$
CREATE TRIGGER orders_au
ON orders
AFTER UPDATE
FOR EACH ROW
BEGIN
SET group_concat_max_len = 100000
;
UPDATE target_table t
SET t.somecol = ( SELECT expr
FROM ...
WHERE somecol = NEW.family_id
ORDER BY ...
LIMIT 1
)
WHERE t.family_id = NEW.family_id
;
END$$
DELIMITER ;
Notes:
MySQL triggers are row level triggers; a trigger is fired for "for each row" that is affected by the triggering statement. MySQL does not support statement level triggers.
The reference to NEW.family_id is a reference to the value of the family_id column of the row that was just updated, the row that the trigger was fired for.
MySQL trigger prohibits the SQL statements in the trigger from modifying any rows in the orders table. But it can modify other tables.
SQL statements in a trigger body can be arbitrarily complex, as long as its not a bare SELECT returning a resultset, or DML INSERT/UPDATE/DELETE statements. DDL statements (most if not all) are disallowed in a MySQL trigger.

Update Query using SELECT results

As my application is expanding, I now am changing the structure of my database; I now want to control file types within the database. I wanted to start with the current file types already in the database. My Database now has a [simplified] 2 table structure like:
tbFiles: pkFileID, fileType, fileName
tblFileType: pkFileType, typeName, typeDesc
I am trying to have the output of a SELECT query update into the newly created tblFileType table. I have tried among other things:
UPDATE tblFileType
INNER JOIN
(SELECT DISTINCT fileType FROM tblFiles) as X
SET typeName = fileType
but I always seem to get 0 row(s) affected.
When I run
SELECT DISTINCT fileType
FROM `tblFiles`
I get Showing rows 0 - 22 (~23 total, Query took 0.0074 sec)
I know this must be simple, but why is the UPDATE query not affecting 23 rows?
You need to add a JOIN condition like ON t1.fileType = x.fileType as follows:
UPDATE tblFileType t1
INNER JOIN
(
SELECT DISTINCT fileType
FROM tblFiles
)as X ON t1.fileType = x.fileType
SET t1.typeName = X.fileType
Update: Since the table tblFileType is blank, you will need to use INSERT something like:
INSERT INTO tblFileType(typeName )
SELECT DISTINCT fileType
FROM tblFiles
WHERE -- a condition here
you just want to populate the table - not update anything in there (especially since nothing exists yet)
INSERT INTO tblFileType(typeName )
SELECT DISTINCT fileType FROM tblFiles

Count Events/year in SQL

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.