Creating procedure to validate data to be inserted - mysql

I'm trying to create a procedure to prevent the insertion of an incorrect date. The table accepts an integer 8 digits long so April 28 2015 would be inserted as 4282015.
My logic here was to create some temp variables to store month, date, and year and then assign them values by taking sub strings from the original 8 digit value. I would then convert them to strings and concatenate together (I am not sure if there's a way to concatenate int, if there is that would probably be better) then convert that back to and int to be inserted. This is what I have tried so far.
UPDATE: The "sample" table is just an example, I will be running this on a different table in a poorly set up database (my job to run analysis and fix it up a little). The way they have it set up, date is an integer.
CREATE TABLE sample (
id INT not null,
date INT not null
);
CREATE PROCEDURE InsertDate ( date int(8))
BEGIN
DECLARE month INT;
DECLARE day INT;
DECLARE year INT;
SET month = SUBSTRING(new.date, 1, 2);
SET day = SUBSTRING(new.date, 3, 2);
SET year = SUBSTRING(new.date, 5, 4);
IF ( month IN(1, 12) AND day IN(1, 31) AND year IN(2012, 2013) )
Declare temp as int;
#Cast all variables as VARCHARS to concatenate together
#Convert back to INT to be inserted
Set temp = CAST( (CAST(month AS VARCHAR(2)) +
CAST(day AS VARCHAR(2)) +
CAST(year as VARCHAR(4))) as INT );
insert into sample (id, date) values (1 ,temp);
END IF;
END;
If anyone wants to take a look and give me some pointers or explain some stuff, it would be much appreciated!

If you do the sensible thing, you will create the column as a Date or DateTime type. Since you have both MySQL and SQL-Server tags I don't actually know which DBMS you are using, but both of them support Date types.
Don't make it more complicated than you need to.

Related

Function that automatically converts the date format for every date column in a query result?

Date information in my database is formatted in unixtime to the millisecond. Currently, in order to convert a result set into MST I use this function string:
date_format(convert_tz(from_unixtime(table.column/1000),'utc','us/mountain')'%m/%d/%Y')
I have a routine which I can apply to individual columns in my query that looks like this:
create function datefmt(convert_tz(TEDATE bigint, TEFMT text),'gmt','us/mountain')
returns varchar(50);
This works fine when I'm specifically calling date columns, but I can't apply it to all date columns in a select * statement. This can make running general queries quite tedious, especially with joins (as most of the tables I use have between 3-6 date columns)!
I am trying to figure out how to create something that will recognize every date column in the result set and then apply the date formatting to all rows in the applicable columns. I've considered using triggers, user defined functions, and routines. But I'm having a hard time figuring out exactly how I can accomplish this feat, or if it can even be accomplished.
An example table would be "Task" with these date columns: rowAddedDate (bigint not null), rowUpdatedDate (bigint not null), createdDate (bigint not null), orderedDate (bigint not null), serviceEndDate (int null), serviceStartDate (int null), expectedServiceDate (int null).
I use a clone, and the database software is MariaDB v 10.2.12.
Any help regarding this matter is greatly appreciated!
Looping with dynamic sql demonstration in sql server:
create table #temp (rowid int identity, test varchar(max))
insert #temp
values
('a'),('b'),('c'),('d')
declare #iterator int = 1
declare #maxrows int = (select max(rowid) from #temp)
while #iterator<=#maxrows
begin
exec('select test from #temp where rowid='+#iterator+'')
set #iterator=#Iterator+1
end
Sounds like the columns should have been TIMESTAMP(3), not DATETIME(3) or some hack with BIGINT. (Looks like you have the BIGINT.)
That way, you could SET the timezone once, and all times would be automatically converted.

how to show legend name if no data is there for the particular series in the legend ?

In my chart, I am displaying months on x axis and percentage of touchpoint qsns answered in y axis . based on the percentage , the legend series are :
full day complete,half day complete, full day incomplete and half day incomplete .
now where there is no data then in the legend, SSRS is not displaying the series name and only other series are displayed .
My requirement is that i want all series name to display even if no data was there in the table . i need to know if i can use a case statement in the stored procedure.
You could create a temp table and fill the table with all the dates within the date range that you want data for. Then update the temp table with the values for each date that exists in the data. Then selected from the temp table at the end of the stored procedure, and that will ensure you have a value for each date. See below for an example:
DECLARE #StartDate datetime = '6/1/2015',
#EndDate datetime = '6/30/2015'
DECLARE #DayRange int = (select DateDiff(day,#StartDate,#EndDate))
--This can be done differently, I made this negative
SET #DayRange = - #DayRange
--This is your temp table that will have all your dates in the range
DECLARE #Temp Table
(
ID int IDENTITY(1,1),
DataDate DateTime,
DataValue int
)
--Fill the temp table with all the dates
WHILE(#DayRange < 1)
BEGIN
INSERT INTO #Temp
SELECT DateAdd(day,#DayRange,#EndDate),0
SET #DayRange = #DayRange + 1
END
--Check out your temp table
SELECT * FROM #Temp

Multi-row action for Instead of trigger SQL Server

Scenario/background:
I am trying to create a table of "Tests". For purposes of this question my table will only have 5 columns defined as follows:
CREATE TABLE TestTable
(
Id UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL,
Name VARCHAR(75) NOT NULL,
DateRequested DATETIME NOT NULL DEFAULT GETDATE(),
TestYear AS YEAR(DateRequested) PERSISTED NOT NULL, -- computed column that shows the year the test was requested. I want to persist this column so I can index on it if need be.
TestNumber CHAR(4) NOT NULL DEFAULT '0000', -- need this to auto increment but also needs to reset for the first test of the year.
CONSTRAINT TestTablePK PRIMARY KEY(Id)
);
GO
My requirement is that I want the 'TestNumber' to "auto-increment" based on the year. For example:
GUID, Test 1 in Old Yr, 2013-01-01 05:00:00.000, 2013, 0001
GUID, Test 2 in Old Yr, 2013-12-25 11:00:00.000, 2013, 0002
GUID, Test 3 in Old Yr, 2013-12-26 09:00:00.000, 2013, 0003
...., ................, ......................., ...., N
GUID, Test N in Old Yr, 2013-12-31 09:00:00.000, 2013, N+1
GUID, Test 1 in New Yr, 2014-01-01 11:00:00.000, 2014, 0001 <-- reset to 1
I was thinking that it would be an auto-increment column but how would I reset it based on this being the first test of a new year? So my incorrect solution thus far has been an "instead of insert" trigger defined as follows:
CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
INSTEAD OF INSERT AS
BEGIN
-- Get the year of the test request being inserted from the pseudo-insert table.
DECLARE #TestYear INT;
SET #TestYear = (SELECT YEAR(DateRequested) FROM inserted);
-- Grab the maximum TestNumber from TestTable based on the year
-- that we are inserting a record for.
DECLARE #MaxTestNumber INT;
SET #MaxTestNumber = (SELECT MAX(TestNumber) FROM dbo.TestTable WHERE TestYear = #TestYear);
-- If this is the first test of the year being inserted it is a special case
IF #MaxTestNumber IS NULL
BEGIN
SET #MaxTestNumber = 0;
END;
-- Here we take the MaxTestNumber, add 1 to it, and then pad it with
-- the appropriate number of zero's in front of it
DECLARE #TestNumber VARCHAR(4);
SET #TestNumber = (SELECT RIGHT('0000' + CAST((#MaxTestNumber + 1) AS VARCHAR(4)), 4));
INSERT INTO dbo.TestTable(Name, DateRequested, TestNumber)
SELECT Name, DateRequested, #TestNumber FROM inserted;
END;
GO
Now here is some DML showing the trigger in action:
INSERT INTO TestTable(Name, DateRequested)
VALUES('Some Test', '05-05-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Some Other Test', '12-25-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Blah Blah', '12-31-2013');
INSERT INTO TestTable(Name, DateRequested)
VALUES('Foo', '01-01-2014');
SELECT * FROM TestTable ORDER BY TestYear ASC, TestNumber ASC;
So as you can see my trigger works for single row inserts but a keen eye will be able to tell it will not work for multi-row inserts.
CREATE TABLE TempTestTable
(
Name VARCHAR(75) NOT NULL,
DateRequested DATETIME NOT NULL DEFAULT GETDATE()
);
GO
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test1', '01-01-2012');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test2', '12-25-2012');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test3', '01-01-2013');
INSERT INTO TempTestTable(Name, DateRequested)
VALUES('Test4', '01-01-2014');
-- This doesnt work because it is a multi-row insert.
INSERT INTO TestTable(Name, DateRequested)
SELECT Name, DateRequested FROM TempTestTable;
My Question
I realize that I can probably handle this with stored procedures and force users to use stored procedures when updating the tables but I want to be extra careful and prevent sysadmins from being able to do direct inserts to the table with an incorrect 'TestNumber'.
So StackOverflow, my question is how can I achieve this? Would I use a cursor inside of my InsteadOfInsertTrigger? I am looking for alternatives.
Not the neatest thing I'll ever write, but seems to do the job:
CREATE TRIGGER InsteadOfInsertTrigger ON dbo.TestTable
INSTEAD OF INSERT AS
BEGIN
;With Years as (
select i.TestYear,
COALESCE(MAX(tt.TestNumber),0) as YMax
from inserted i left join TestTable tt
on i.TestYear = tt.TestYear
group by i.TestYear
), Numbered as (
select i.ID,i.Name,i.DateRequested,
RIGHT('000' + CONVERT(varchar(4),
ROW_NUMBER() OVER (PARTITION BY i.TestYear
ORDER BY i.DateRequested,i.Id) + YMax)
,4) as TestNumber
from inserted i
inner join
Years y
on
i.TestYear = y.TestYear
)
insert into TestTable (Id,Name,DateRequested,TestNumber)
select Id,Name,DateRequested,TestNumber from Numbered;
END;
The first CTE (Years) finds the highest used number for each year that's of interest. The second CTE (Numbered) then uses those values to offset a ROW_NUMBER() that's being assessed over all rows in inserted. I picked the ORDER BY columns for the ROW_NUMBER() so that it's as deterministic as possible.
(I was confused about one thing for a while, but it turns out that I can use TestYear from inserted rather than having to repeat the YEAR(DateRequested) formula)

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;

help with mysql triggers (checking values before insert)

hi I'm quite new to mysql and I'm trying to figure out how to use triggers.
what I'm trying to do:
I have 2 tables, max and sub_max, when I insert a new row to sub_max I want to check if the SUM of the values with the same foreign_key as the new row are less than the value in the max table. I think this sounds confusing so here are my tables:
CREATE TABLE max(
number INT ,
MaxAmount integer NOT NULL)
CREATE TABLE sub_max(
sub_number INT ,
sub_MaxAmount integer NOT NULL,
number INT,
FOREIGN KEY ( number ) REFERENCES max( number ))
and here is my code for the trigger, I know the syntax is off but this is the best I could do from looking up tutorials.
CREATE TRIGGER maxallowed
after insert on sub_max
FOR EACH ROW
BEGIN
DECLARE submax integer;
DECLARE maxmax integer;
submax = select sum(sub_MaxAmount) from sub_max where sub_number = new.sub_number;
submax = submax + new. sub_MaxAmount;
maxmax = select MaxAmount from max where number = new.number ;
if max>maxmax
rollback?
END
I wanted to know if I'm doing this remotely correctly. Thanks in advance.
Caveat - I am also learning triggers.
For the section:
if max>maxmax
rollback?
Would the syntax be something like?:
IF max > maxmax THEN
DELETE the id of the new record?
ELSE
do nothing?
END IF;