How to stored a SUM result in a column in MySQL? - mysql

I have 2 tables:
Medical_Appointment
ID INT AUTO_INCREMENT NOT NULL,
Day DATE NOT NULL,
Start_Time TIME NOT NULL,
Patient_ID INT NOT NULL,
Service_ID INT NOT NULL,
PRIMARY KEY(ID)
And
Service
ID INT NOT NULL AUTO_INCREMENT,
Name VARCHAR(30) NOT NULL,
Duration VARCHAR(6) NOT NULL,
PRIMARY KEY(ID)
I want to get the end time of every appointment, and for that I have the next query:
SELECT DATE_FORMAT(m.Day, '%d-%m-%Y') AS `Appointment day:`,
TIME_FORMAT(m.Start_Time, '%H:%i') AS `Appointment start time:`,
ADDTIME(m.Start_Time, TIME_FORMAT(s.Duration, '%H:%i')) AS `Appointment end time:`
FROM Medical_Appointment m
JOIN Service s
ON m.Service_ID = s.ID
WHERE m.Day = '2017-03-14'
ORDER BY m.Start_Time;
This works for me, but it is just a temporary view, I want to stored the result in a column for the Medical_Appointment table because I want to use that value in a Java application and just take it from the database. This is possible?

Alter your table to add the new column.
After that, just insert the result of your select

you would have had end time when updating duration; so you should just also include end time as a field.
P.S. i was unable to comment so writing it as answer

You can check if database 'trigger' can be useful here. Triggers should do the purpose.

You could use a MySQL Table View for that:
CREATE VIEW appointment_info
AS SELECT DATE_FORMAT(m.Day, '%d-%m-%Y') AS `day`,
TIME_FORMAT(m.Start_Time, '%H:%i') AS `start`,
ADDTIME(m.Start_Time, TIME_FORMAT(s.Duration, '%H:%i')) AS `end`
FROM Medical_Appointment m
JOIN Service s
ON m.Service_ID = s.ID;
You could then query the view 'appointment_info' like a usual table e.g.:
SELECT `day` AS `Appointment day:`, `start` AS `Appointment start time:`, `end` AS `Appointment end time:`
FROM appointment_info
WHERE day = '2017-03-14'
ORDER BY `start`;
Of course you could also use Appointment day:, Appointment start time: and Appointment end time: directly as column names in your table view.

Related

Woes using UPDATE with a CTE in MySQL (MariaDB)

I'm going crazy trying to get UPDATE to work with a CTE in MySQL.
Here's a simplified schema of sa_general_journal:
CREATE TABLE `sa_general_journal` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Transaction_ID` int(10) unsigned DEFAULT NULL COMMENT 'NULL if not split, same as ID for split records',
`Date` timestamp NOT NULL DEFAULT current_timestamp(),
…
`Statement_s` int(10) unsigned DEFAULT NULL,
…
`Name` varchar(255) DEFAULT NULL,
…
PRIMARY KEY (`ID`),
…
) ENGINE=InnoDB AUTO_INCREMENT=25929 DEFAULT CHARSET=utf8;
Some records are "split," for example, a credit card statement amount might have a sales tax amount that is split out. In such cases, both parts of the split record have the same ID in the Transaction_ID field.
When records are imported in bulk, they can't refer to last_insert_ID in order to fill in the Transaction_ID field, thus the need to go clean these up afterward.
This was my first, naive attempt, which said I had an error near UPDATE. Well duh.
WITH cte AS (
SELECT
ID,
MIN(ID) OVER(PARTITION BY `Date`, `Name`, Statement_s) AS Trans,
Transaction_ID
FROM sa_general_journal
WHERE Transaction_ID = 0)
UPDATE cte
SET Transaction_ID = Trans
The CTE itself seems to work, as I can follow it with SELECT * FROM cte and get what I expected.
So I started searching StackOverflow, and discovered that CTEs are not updatable, but that you need to join them to what you want to update. "No problem!" I think, as I code this up:
WITH cte AS (
SELECT
ID,
MIN(ID) OVER(PARTITION BY `Date`, `Name`, Statement_s) AS Trans,
Transaction_ID
FROM sa_general_journal
WHERE Transaction_ID = 0)
UPDATE sa_general_journal gj, cte
SET gj.Transaction_ID = cte.Trans
WHERE gj.ID = cte.ID
No joy. Same error message.
My understanding is that in MySQL, you don't need a column list, but I did also try this using the column list (a, b, c), with the proper columns referenced in the UPDATE statement, but it still said I had a problem near UPDATE.
There are incredibly few examples of using UPDATE with WITH on the Internet! I found one, from Percona, which I used to create my attempt above, and then found another very similar example from MySQL itself.
Thanks in advance for any help offered!
CTE is a part of subquery definition, not a part of the whole query. The query must be specified after CTE. CTE cannot be used itself. So
UPDATE sa_general_journal gj
JOIN (WITH cte AS ( SELECT
ID,
MIN(ID) OVER(PARTITION BY `Date`, `Name`, Statement_s) AS Trans,
Transaction_ID
FROM sa_general_journal
WHERE Transaction_ID = 0)
SELECT * FROM cte) subquery ON gj.ID = subquery.ID
SET gj.Transaction_ID = subquery.Trans
CTEs work with UPDATE in MySQL 8, but not MariaDB 10.x.

If using sum>100, sums under 100 will still show

I have a table with some data. Many of these data have the name ICA Supermarket with different sums for every data. If I use the following SQL query, it will also show data with the sum under 100. This applies also if I change >= '100' to a higher digit, for an example 200.
SELECT *
FROM transactions
WHERE data_name LIKE '%ica%'
AND REPLACE(data_sum, '-', '') >= '100'
If I change >= to <= no data will show at all. Here's how the table looks like:
CREATE TABLE IF NOT EXISTS `transactions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`data_name` tinytext NOT NULL,
`data_sum` decimal(10,2) NOT NULL
UNIQUE KEY `id` (`id`)
)
Is it because data_sum is a DECIMAL? How can I prevent this from happening? I want to use DECIMAL for sums :)
Note: data_sum will also contain sums that are above minus.
REPLACE(data_sum, '-', '') returns a string. Also '100' is a string. So a string compare will be used. You should use ABS function:
SELECT *
FROM transactions
WHERE data_name LIKE '%ica%'
AND ABS(data_sum) >= 100
Are you looking for values >= 100 and <= -100? Or just values <= -100.
If the latter, then
... AND data_sum <= -100
This applies to DECIMAL, INT, FLOAT, etc.
Every table 'needs' a PRIMARY KEY. Promote that UNIQUE to PRIMARY.

How to select the most popular rows from today

I've been looking around but couldn't find my answer anywhere.
I have a MySQL table which looks something like this:
CREATE TABLE IF NOT EXISTS `g_video_watch` (
`vwatch_id` int(11) NOT NULL,
`vwatch_user` int(11) NOT NULL,
`vwatch_track` int(11) NOT NULL,
`vwatch_ip` varchar(255) NOT NULL,
`vwatch_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=36 ;
A page where the user can watch a video inserts everytime a page opens a row with the information from who the user is (vwatch_user) what track/video he or she opens (vwatch_track) and what the IP is. Also the current timestamp is inserted for the goal I'm trying to achieve.
And that goal is to show the tracks/videos which are most watched today. So in other words, select TODAY, GROUP all tracks and check how many rows per group is returned to order in COUNT where views is the highest...
Anybody an idea?
To get 100 most viewed tracks try this:
SELECT vwatch_track, COUNT(*)
FROM g_video_watch
WHERE DATE(vwatch_date) = DATE(NOW())
GROUP BY vwatch_track
ORDER BY COUNT(*) DESC
LIMIT 100
SELECT vwatch_track , count(*)
FROM g_video_watch
WHERE vwatch_date >= CURRENT_DATE
GROUP BY vwatch_track

adding '1' returns "BLOB"

A JobID goes as follows: ALC-YYYYMMDD-001. The first three are a companies initials, the last three are an incrementing number that resets daily and increments throughout the day as jobs are added for a maximum of 999 jobs in a day; it is these last three that I am trying to work with.
I am trying to get a before-insert trigger to look for the max JobID of the day, and add one so I can have the trigger derive the proper JobID. For the first job, it will of course return null. So here is what I have so far.
Through the following I can get a result of '000'.
set #maxjobID =
(select SUBSTRING(
(Select MAX(
SUBSTRING((Select JobID FROM jobs WHERE SUBSTRING(JobID,5,8)=date_format(curdate(), '%Y%m%d')),4,12)
)
),14,3)
);
select lpad((select ifnull(#maxjobID,0)),3,'0')
But I really need to add one to this keeping the leading zeros to increment the first and subsequent jobs of the day. My problem is as soon as try to add '1' I get a return of 'BLOB'. That is:
select lpad((select ifnull(#maxjobID,0)+1),3,'0')
returns 'BLOB'
I need it to return '001' so I can concatenate that result with the CO initials and the current date.
try casting VARCHAR back to INTEGER
SELECT lpad(SELECT (COALESCE(#maxjobID,0, CAST(#maxjobID AS SIGNED)) + 1),3,'0')
If you're using the MyISAM storage engine, you can implement exactly this with AUTO_INCREMENT, without denormalising your data into a delimited string:
For MyISAM tables, you can specify AUTO_INCREMENT on a secondary column in a multiple-column index. In this case, the generated value for the AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1 WHERE prefix=given-prefix. This is useful when you want to put data into ordered groups.
In your case:
Normalise your schema:
ALTER TABLE jobs
ADD initials CHAR(3) NOT NULL FIRST,
ADD date DATE NOT NULL AFTER initials,
ADD seq SMALLINT(3) UNSIGNED NOT NULL AFTER date,
;
Normalise your existing data:
UPDATE jobs SET
initials = SUBSTRING_INDEX(JobID, '-', 1),
date = STR_TO_DATE(SUBSTRING(JobID, 5, 8), '%Y%m%d'),
seq = SUBSTRING_INDEX(JobID, '-', -1)
;
Set up the AUTO_INCREMENT:
ALTER TABLE jobs
DROP PRIMARY KEY,
DROP JobID,
MODIFY seq SMALLINT(3) UNSIGNED NOT NULL AUTO_INCREMENT,
ADD PRIMARY KEY(initials, date, seq)
;
You can then recreate your JobID as required on SELECT (or even create a view from such a query):
SELECT CONCAT_WS(
'-',
initials,
DATE_FORMAT(date, '%Y%m%d'),
LPAD(seq, 3, '0')
) AS JobID,
-- etc.
If you're using InnoDB, whilst you can't generate sequence numbers in this fashion I'd still recommend normalising your data as above.
So, I found a query that works (thus far).
Declare maxjobID VARCHAR(16);
Declare jobincrement SMALLINT;
SET maxjobID =
(Select MAX(
ifnull(SUBSTRING(
(Select JobID FROM jobs WHERE SUBSTRING(JobID,5,8)=date_format(curdate(), '%Y%m%d')),
5,
12),0)
)
);
if maxjobID=0
then set jobincrement=1;
else set jobincrement=(select substring(maxjobID,10,3))+1;
end if;
Set NEW.JobID=concat
(New.AssignedCompany,'-',date_format(curdate(), '%Y%m%d'),'-',(select lpad(jobincrement,3,'0')));
Thanks for the responses! Especially eggyal for pointing out the auto_increment capabilities in MyISAM.

Populating a time dimension table automatically

I am currently working on a reporting project. In my datawarehouse I need a dimension table "Time" containing all dates (since 01-01-2011 maybe?) and which increments automatically everyday having this format yyyy-mm-dd.
I'm using MySQL on Debian by the way.
thanks
JT
You can add DATE field and use a query like this -
INSERT INTO table(date_column, column1, column2)
VALUES(DATE(NOW()), 'value1', 'value2');
Also, you can add TIMESTAMP column with ON UPDATE CURRENT_TIMESTAMP, in this case date-time value will be updated automatically.
Automatic Initialization and Updating for TIMESTAMP
See this answer
Or This one
There are a number of suggestions there. If your date range is going to be moderate, perhaps a year or two, and assuming your report uses a stored procedure to return the results, you could just create a temporary table on the fly using a rownum technique with limit to get you all of the dates in the range. Then join with your data as required.
Failing that the Union trick in the second answer seems to perform well according to the comments and can be extended to whatever maximum range you will need. It's very messy though!
This article seems to cover what you want. See also this question for another example of the columns you might want to have on your table. You should definitely generate a large amount of dates in advance instead of updating the table daily; it saves a lot of work and complications. 100 years are only ~36500 rows, which is a small table.
Temporary tables or procedural code are not good solutions for a data warehouse, because you want your reporting tool to be able to access the dimension tables. And if your RDBMS has optimizations for star schema queries (I don't know if MySQL does or not) then it would need to see the dimension too.
Here is what I am using to create and populate time dimension table:
DROP TABLE IF EXISTS time_dimension;
CREATE TABLE time_dimension (
id INTEGER PRIMARY KEY, -- year*10000+month*100+day
db_date DATE NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL, -- 1 to 12
day INTEGER NOT NULL, -- 1 to 31
quarter INTEGER NOT NULL, -- 1 to 4
week INTEGER NOT NULL, -- 1 to 52/53
day_name VARCHAR(9) NOT NULL, -- 'Monday', 'Tuesday'...
month_name VARCHAR(9) NOT NULL, -- 'January', 'February'...
holiday_flag CHAR(1) DEFAULT 'f' CHECK (holiday_flag in ('t', 'f')),
weekend_flag CHAR(1) DEFAULT 'f' CHECK (weekday_flag in ('t', 'f')),
UNIQUE td_ymd_idx (year,month,day),
UNIQUE td_dbdate_idx (db_date)
) Engine=MyISAM;
DROP PROCEDURE IF EXISTS fill_date_dimension;
DELIMITER //
CREATE PROCEDURE fill_date_dimension(IN startdate DATE,IN stopdate DATE)
BEGIN
DECLARE currentdate DATE;
SET currentdate = startdate;
WHILE currentdate <= stopdate DO
INSERT INTO time_dimension VALUES (
YEAR(currentdate)*10000+MONTH(currentdate)*100 + DAY(currentdate),
currentdate,
YEAR(currentdate),
MONTH(currentdate),
DAY(currentdate),
QUARTER(currentdate),
WEEKOFYEAR(currentdate),
DATE_FORMAT(currentdate,'%W'),
DATE_FORMAT(currentdate,'%M'),
'f',
CASE DAYOFWEEK(currentdate) WHEN 1 THEN 't' WHEN 7 then 't' ELSE 'f' END
);
SET currentdate = ADDDATE(currentdate,INTERVAL 1 DAY);
END WHILE;
END
//
DELIMITER ;
TRUNCATE TABLE time_dimension;
CALL fill_date_dimension('1800-01-01','2050-01-01');
OPTIMIZE TABLE time_dimension;