Count Distinct is not working in UDF in SQL Server 2008 - sql-server-2008

I am facing problem in creating UDF with count distinct in query. I have created a table amtest which have the following :
CREATE TABLE [dbo].[amtest](
[ztime] [datetime] NULL,
[zutime] [datetime] NULL,
[zid] [int] NOT NULL,
[xamacadyr] [int] NOT NULL,
[xamreg] [varchar](20) NOT NULL,
[xmobile] [varchar](15) NULL,
[xamclasssec] [varchar](30) NULL,
PRIMARY KEY CLUSTERED
(
[zid] ASC,
[xamacadyr] ASC,
[xamreg] ASC
)
Data in the amtest table are as follows:
zid xamacadyr xamreg xmobile xamclasssec
100000 2013 201508001 01713236075 Section-B
100000 2014 201508003 01713236072 Section-A
100000 2015 201508001 01713236071 Section-A
100000 2015 201508003 01713236073 Section-A
100000 2015 201508004 01713236074 Section-A
Now I have created an User-defined-functions with parameter xamreg so that, for any xamreg value, there can not be 2 distinct value in xamclasssec field. For example, here for 201508001 value in xamreg, there should not be 2 distinct values (Section-A, Section-B), it should only allow either Section-A or Section-B. For that I have created the following UDF:
ALTER FUNCTION [dbo].[CheckTest](#xamreg varchar(30))
RETURNS int
AS
BEGIN
DECLARE #retvar int;
select #retvar = COUNT(*) from (select xamclasssec from amtest where xamreg=#xamreg group by xamclasssec) as tbl ;
RETURN #retvar
END
And I have created a constraint for amtest table, which is as follows:
alter table amtest
add constraint chkAmtest
check(dbo.CheckTest(xamreg)<=1)
Where I have made a mistake, as it is not working?

First of all your function can be simplified to:
CREATE FUNCTION [dbo].[CheckTest](#xamreg varchar(30))
RETURNS INT
AS
BEGIN
RETURN (SELECT COUNT(DISTINCT xamclasssec) FROM amtest WHERE xamreg = #xamreg);
END
GO
I run your query with SQL Fiddle it seems to work but only with INSERT:
SqlFiddleDemo
If I try to set DISTINCT values you get:
The INSERT statement conflicted with the CHECK constraint "chkAmtest".
The conflict occurred in database "db_3_5ec42", table "dbo.amtest",
column 'xamreg'.
But there is one drawback with your solution, it won't work when UPDATE occurs.
SqlFiddleDemo2
INSERT INTO [dbo].[amtest]
(zid ,xamacadyr , xamreg , xmobile , xamclasssec)
VALUES (100000 , 2013 , 201508001 , 01713236075 , 'Section-B'),
(100000, 2015, 201508001 , 01713236071 , 'Section-B');
UPDATE [dbo].[amtest]
SET xamclasssec = 'Section-A'
WHERE xmobile = 01713236071;
SELECT *
FROM [dbo].[amtest];
Use AFTER TRIGGER instead:
SqlFiddleDemo3
CREATE TRIGGER trg_amtest
ON [dbo].[amtest]
AFTER INSERT, UPDATE
AS
IF EXISTS (SELECT xamreg, COUNT(DISTINCT xamclasssec)
FROM amtest
GROUP BY xamreg
HAVING COUNT(DISTINCT xamclasssec) > 1)
ROLLBACK;
GO

Related

MYSQL TRIGGER doesn't work sql_extras.sql

Why it doesn't work?
I understand that:
line no.: 9, 21
id int NOT NULL IDENTITY(1, 1), should be AUTO_INCREMENT
id int NOT NULL AUTO_INCREMENT
line no.: 24
wrong column name it can be longs instead of long:
longs decimal(9,6) NOT NULL,
line no.:
out of ; on the end of line 36
out of ; on the end of line 44
line 38, 47 longs instead of long:
INSERT INTO city (city_name, lat, longs, country_id)
but I don't know where is problem below line no.: 91
-- Create new database
CREATE DATABASE lesson7;
USE lesson7;
-- 1. Primary and foreign keys
-- Table: country
CREATE TABLE country (
id int NOT NULL IDENTITY(1, 1),
country_name char(128) NOT NULL,
country_name_eng char(128) NOT NULL,
country_code char(8) NOT NULL,
CONSTRAINT country_ak_1 UNIQUE (country_name),
CONSTRAINT country_ak_2 UNIQUE (country_name_eng),
CONSTRAINT country_ak_3 UNIQUE (country_code),
CONSTRAINT country_pk PRIMARY KEY (id)
);
-- Table: city
CREATE TABLE city (
id int NOT NULL IDENTITY(1, 1),
city_name char(128) NOT NULL,
lat decimal(9,6) NOT NULL,
long decimal(9,6) NOT NULL,
country_id int NOT NULL,
CONSTRAINT city_pk PRIMARY KEY (id),
CONSTRAINT city_country FOREIGN KEY (country_id) REFERENCES country (id)
);
-- Fill the tables
INSERT INTO country (country_name, country_name_eng, country_code)
VALUES ('Deutschland', 'Germany', 'DEU'),
('Srbija', 'Serbia', 'SRB'),
('Hrvatska', 'Croatia', 'HRV'),
('United States of America', 'United States of America', 'USA'),
('Polska', 'Poland', 'POL')
INSERT INTO city (city_name, lat, long, country_id)
VALUES ('Berlin', 52.520008, 13.404954, 1),
('Belgrade', 44.787197, 20.457273, 2),
('Zagreb', 45.815399, 15.966568, 3),
('New York', 40.730610, -73.935242, 4),
('Los Angeles', 34.052235, -118.243683, 4),
('Warsaw', 52.237049, 21.017532, 5)
-- Can we do this?
INSERT INTO city (city_name, lat, long, country_id)
VALUES ('Wien', 48.2084885, 16.3720798, 6);
-- Let's try to delete Poland
DELETE FROM country WHERE id = 5;
-- And check the result
SELECT * FROM city;
SELECT * FROM country;
-- We can remove the constraint using its name
ALTER TABLE city DROP CONSTRAINT city_country;
-- And add it once again with different rescrictions
ALTER TABLE city
ADD CONSTRAINT city_country
FOREIGN KEY (country_id)
REFERENCES country (id)
ON UPDATE CASCADE
ON DELETE CASCADE;
-- Let's try to delete Poland once again
DELETE FROM country WHERE id = 5;
-- And check the results
SELECT * FROM country;
SELECT * FROM city;
-- Triggers
/*
DML (data manipulation language) triggers they react to DML commands.
These are INSERT, UPDATE, and DELETE
DDL (data definition language) triggers they react to DDL commands.
These are CREATE, ALTER, and DROP
*/
/*
DROP <object> IF EXISTS before its creation is helpful in two ways:
1) We could get an arror trying to create a duplicate, we prevent it using DROP
2) If we want to DROP someting non-existent, we can get an error so we check that condidtion first
Combining two above should result in smooth execution
*/
-- Trigger to handle inserts with missing 'country_name' or 'country_name_eng' values
DROP TRIGGER IF EXISTS t_country_insert;
GO
CREATE TRIGGER t_country_insert ON country INSTEAD OF INSERT
AS BEGIN
DECLARE #country_name CHAR(128);
DECLARE #country_name_eng CHAR(128);
DECLARE #country_code CHAR(8);
SELECT #country_name = country_name, #country_name_eng = country_name_eng, #country_code = country_code FROM INSERTED;
IF #country_name IS NULL SET #country_name = #country_name_eng;
IF #country_name_eng IS NULL SET #country_name_eng = #country_name;
INSERT INTO country (country_name, country_name_eng, country_code) VALUES (#country_name, #country_name_eng, #country_code);
END;
-- 'country_name' has NOT NULL constraint, but thanks to the trigger, this insert works fine
SELECT * FROM country;
INSERT INTO country (country_name_eng, country_code) VALUES ('United Kingdom', 'UK');
SELECT * FROM country;
-- Trigger to prevent removal of record from parent table if there are records in child table referencing it
DROP TRIGGER IF EXISTS t_country_delete;
GO
CREATE TRIGGER t_country_delete ON country INSTEAD OF DELETE
AS BEGIN
DECLARE #id INT;
DECLARE #count INT;
SELECT #id = id FROM DELETED;
SELECT #count = COUNT(*) FROM city WHERE country_id = #id;
IF #count = 0
DELETE FROM country WHERE id = #id;
ELSE
THROW 51000, 'can not delete - country is referenced in other tables', 1;
END;
-- Example
SELECT * FROM country;
SELECT * FROM city;
DELETE FROM country WHERE id = 4;
/*
Functions - idea behind them is to avoid writing the same code over and over again
*/
-- Example: function returning if point on the map is located in a western or eastern hemisphere
-- Input: single decimal value (longitude)
-- Output: single char value (position)
-- It's an example of a "scalar-valued function" - returns a single value
CREATE FUNCTION east_or_west (
#long DECIMAL(9,6)
)
RETURNS CHAR(4) AS
BEGIN
DECLARE #return_value CHAR(4);
SET #return_value = 'same';
IF (#long > 0.00) SET #return_value = 'east';
IF (#long < 0.00) SET #return_value = 'west';
RETURN #return_value
END;
-- Examples
SELECT * FROM city;
SELECT city_name, dbo.east_or_west(long) AS 'position'
FROM city;
-- Example: function returning cities from table 'city' located to the east of a given point
-- Input: single decimal value (longitude of a point)
-- Output: filtered table 'city'
-- It's an example of a "table-valued function" - returns a table (multiple values)
CREATE FUNCTION east_from_long (
#long DECIMAL(9,6)
)
RETURNS TABLE AS
RETURN
SELECT *
FROM city
WHERE city.long > #long;
-- Example
SELECT *
FROM east_from_long(0.00);
/*
Stored procedures - idea behind them is to put multiple operations
(inserting, updating, deleting, retrieving data) into one "black box" that can be called multiple times
using various parameters
*/
DROP PROCEDURE IF EXISTS p_cities_all;
GO
CREATE PROCEDURE p_cities_all
-- procedure returns all rows from the customer table
AS BEGIN
SELECT *
FROM city;
END;
-- Execute the procedure with EXEC, procedure uses no parameters
EXEC p_cities_all;
-- Another example, procedure returns the entire row for the given id
DROP PROCEDURE IF EXISTS p_city;
GO
CREATE PROCEDURE p_city (#id INT)
AS BEGIN
SELECT *
FROM city
WHERE id = #id;
END;
-- Execute using EXEC and providing the value for a parameter (id of a row)
EXEC p_city 4;
SELECT * FROM country;
SELECT * FROM city;
-- Example: procedure inserting city from germany (no 'country_id' value is needed while inserting records)
DROP PROCEDURE IF EXISTS p_city_in_germany_insert;
GO
CREATE PROCEDURE p_city_in_germany_insert (#city_name CHAR(128), #lat DECIMAL(9, 6), #long DECIMAL(9, 6))
AS BEGIN
INSERT INTO city(city_name, lat, long, country_id)
VALUES (#city_name, #lat, #long, 1);
END;
-- Example, let's insert Munich to our table
SELECT * FROM city;
EXEC p_city_in_germany_insert 'Munich', 48.13743, 11.57549;
SELECT * FROM city;
-- Example: deleting rows based on 'id'
DROP PROCEDURE IF EXISTS p_city_delete;
GO
CREATE PROCEDURE p_city_delete (#id INT)
AS BEGIN
DELETE
FROM city
WHERE id = #id;
END;
-- Example, delete city with id = 1
EXEC p_city_delete 1;
-- Results
SELECT * FROM city;

MySQL Query (Sub Queries + Composed Functions + JOIN operations) takes too long to run

How can I revise the following query with subqueries composed of functions and join queries in functions. I want to append extra values to my main query that relies on main tables and two primary joins (risks, users) on several occasions.
Creating a MCVRE (Minimal Complete Verifiable Reproduceable Example) proved to be somewhat challenging because of request sent to SQL Fiddle has too many rows (too many text characters) After removing nearly all rows on main two tables ( users, risks ) I ended up with a running query.
The Fiddle (http://www.sqlfiddle.com/#!9/1d52a0/17) create functions and insertion of data commands have reduced rows from actual example on my local pc due to character count of 8000 being exceeded for request payload for SQLFiddle to understand.
Actual table has about 100 rows for risks, and 20 or so rows for users and takes about 3 seconds to run
What can I do to speed up query, via staving desired function results in table, or by revision, index insertion, movement of joins to outer main query, or even using stored procedure, or rewriting query structure, to reduce execution time to possibly half the time or less optimistically. SQL fiddle does not take all rows needed so I pasted a very limited subset, Even SQLFiddle query (see total select query below) does not run, due to Stack Overflow (pun partially intended).
http://www.sqlfiddle.com/#!9/1d52a0/17
Base Queries that do run on the fiddle (see fiddle)
select * from users;
select * from risks;
select * from riskevents;
select * from riskmatrixthresholds;
select * from risklevels;
#significantly minimized result set but still query does not run due to stack overflow issue on sql fiddle - see fiddle result (on bottom most portion of fiddle query output)
SELECT r.RiskID,
r.CreatorID,
r.OwnerID,
r.ApproverID,
r.RiskTitle,
r.RiskStatement,
r.ClosureCriteria,
r.RiskState,
r.Context AS 'Context',
GetRiskUserLastOrFirstName(GetRiskUserID('Creator', r.RiskID,0),r.RiskID, 'Last','') AS 'creator.lastname',
GetRiskUserLastOrFirstName(GetRiskUserID('Creator', r.RiskID,0),r.RiskID, 'First','') AS 'creator.firstname',
GetRiskUserLastOrFirstName(GetRiskUserID('Owner', r.RiskID,0),r.RiskID, 'Last','') AS 'owner.lastname',
GetRiskUserLastOrFirstName(GetRiskUserID('Owner', r.RiskID,0),r.RiskID, 'First','') AS 'owner.firstname',
GetRiskUserLastOrFirstName(GetRiskUserID('Approver',r.RiskID,0),r.RiskID, 'Last','') AS 'approver.lastname',
GetRiskUserLastOrFirstName(GetRiskUserID('Approver',r.RiskID,0),r.RiskID, 'First','') AS 'approver.firstname',
r.Likelihood AS 'OriginalLikelihood',
r.Technical AS 'OriginalTechnical',
r.Schedule AS 'OriginalSchedule',
r.Cost AS 'OriginalCost',
GREATEST(r.Technical, r.Schedule, r.Cost) AS 'OriginalConsequence',
RiskValue(r.Likelihood, GREATEST(r.Technical, r.Schedule, r.Cost),0) AS 'OriginalValue',
RiskLevel(RiskValue(r.Likelihood, GREATEST(r.Technical, r.Schedule, r.Cost),0),'') AS 'OriginalLevel',
LatestEventDate(r.RiskID, r.AssessmentDate,'') AS 'LatestEventDate',
r.AssessmentDate AS 'AssessmentDate',
(SELECT CurrentLikelihood(r.RiskID,0)) AS 'CurrentLikelihood',
(SELECT CurrentConsequence(r.RiskID,0)) AS 'CurrentConsequence',
(SELECT CurrentRiskValue(r.RiskID,0)) AS 'CurrentValue',
(SELECT RiskLevel(CurrentRiskValue(r.RiskID,0),'')) AS 'CurrentLevel'
FROM risks r;
Create Function Script
CREATE TABLE `riskevents` (
`ID` int NOT NULL AUTO_INCREMENT,
`EventID` int ,
`RiskID` int ,
`EventTitle` text,
`EventStatus` varchar(10) ,
`EventOwnerID` int ,
`ActualDate` date ,
`ScheduleDate` date ,
`BaselineDate` date ,
`ActualLikelihood` int ,
`ActualTechnical` int ,
`ActualSchedule` int ,
`ActualCost` int ,
`ScheduledLikelihood` int ,
`ScheduledTechnical` int ,
`ScheduledSchedule` int ,
`ScheduledCost` int ,
`BaselineLikelihood` int ,
`BaselineTechnical` int ,
`BaselineSchedule` int ,
`BaselineCost` int ,
PRIMARY KEY (`ID`)
)
CREATE TABLE `risklevels` (
`ID` int NOT NULL AUTO_INCREMENT,
`RiskLevelID` int ,
`RiskMaximum` float ,
`RiskHigh` float ,
`RiskMedium` float ,
`RiskMinimum` float ,
PRIMARY KEY (`ID`)
)
CREATE TABLE `riskmatrixthresholds` (
`ID` int NOT NULL AUTO_INCREMENT,
`CellID` int ,
`Likelihood` int ,
`Consequence` int ,
`Level` decimal(2,2) ,
PRIMARY KEY (`ID`)
)
CREATE TABLE `risks` (
`ID` int NOT NULL AUTO_INCREMENT,
`RiskState` varchar(10) ,
`RiskID` int ,
`RiskTitle` text CHARACTER SET latin1,
`RiskStatement` text CHARACTER SET latin1,
`ApproverID` int ,
`OwnerID` int ,
`CreatorID` int ,
`Likelihood` int ,
`Technical` int ,
`Schedule` int ,
`Cost` int ,
`ClosureCriteria` text CHARACTER SET latin1,
`CategoryID` int ,
`AssessmentDate` date ,
`CompletionDate` date ,
`ClosureDate` date ,
`Context` text,
PRIMARY KEY (`ID`),
UNIQUE KEY `risk_index` (`RiskID`)
)
CREATE TABLE `users` (
`ID` int NOT NULL AUTO_INCREMENT,
`UserID` int NOT NULL,
`LastName` char(25) ,
`FirstName` char(15) ,
`Title` char(20) ,
`Email` varchar(30) ,
`Phone` char(12) ,
`Extension` char(4) ,
`Department` char(25) ,
PRIMARY KEY (`ID`),
UNIQUE KEY `user_index` (`UserID`),
KEY `SURROGATE` (`UserID`)
)
insert into `riskevents`(`ID`,`EventID`,`RiskID`,`EventTitle`,`EventStatus`,`EventOwnerID`,`ActualDate`,`ScheduleDate`,`BaselineDate`,`ActualLikelihood`,`ActualTechnical`,`ActualSchedule`,`ActualCost`,`ScheduledLikelihood`,`ScheduledTechnical`,`ScheduledSchedule`,`ScheduledCost`,`BaselineLikelihood`,`BaselineTechnical`,`BaselineSchedule`,`BaselineCost`) values
(171,0,1,'Risk','Complete',5,'2019-06-14',NULL,'2019-06-14',5,2,2,5,NULL,NULL,NULL,NULL,5,2,2,5),
(184,0,10,'Risk','Complete',21,'2019-10-07',NULL,'2019-10-07',5,4,5,4,NULL,NULL,NULL,NULL,5,4,5,4));
insert into `risklevels`(`ID`,`RiskLevelID`,`RiskMaximum`,`RiskHigh`,`RiskMedium`,`RiskMinimum`) values
(1,1,1,0.55,0.3,0);
insert into `riskmatrixthresholds`(`ID`,`CellID`,`Likelihood`,`Consequence`,`Level`) values
(1,1,1,1,0.09),
(2,2,1,2,0.12),
(3,3,1,3,0.16),
(4,4,1,4,0.19),
(5,5,1,5,0.23),
(6,6,2,1,0.12),
(7,7,2,2,0.19),
(8,8,2,3,0.27),
(9,9,2,4,0.34),
(10,10,2,5,0.41),
(11,11,3,1,0.16),
(12,12,3,2,0.27),
(13,13,3,3,0.37),
(14,14,3,4,0.48),
(15,15,3,5,0.59),
(16,16,4,1,0.19),
(17,17,4,2,0.34),
(18,18,4,3,0.48),
(19,19,4,4,0.63),
(20,20,4,5,0.77),
(21,21,5,1,0.23),
(22,22,5,2,0.41),
(23,23,5,3,0.59),
(24,24,5,4,0.77),
(25,25,5,5,0.95);
insert into `risks`(`ID`,`RiskState`,`RiskID`,`RiskTitle`,`RiskStatement`,`ApproverID`,`OwnerID`,`CreatorID`,`Likelihood`,`Technical`,`Schedule`,`Cost`,`ClosureCriteria`,`CategoryID`,`AssessmentDate`,`CompletionDate`,`ClosureDate`,`Context`) values
(1,'Completed',1,'t','t',1,5,1,5,2,2,5,'t',NULL,'2019-06-14','2020-09-26',NULL,'t'),
(2,'Completed',2,'t','t',2,1,1,5,3,4,2,'test',NULL,'2019-05-14',NULL,NULL,'t'),
insert into `users`(`ID`,`UserID`,`LastName`,`FirstName`,`Title`,`Email`,`Phone`,`Extension`,`Department`) values
(1,1,'Admin','','Admin','a#yz.com','17890','1234',''),
(2,2,'Last','First','Engineer','a#yz.com','123890','1234','Supplier');
CREATE FUNCTION Consequence(technical int, sched int, cost int, consequence int) RETURNS int
BEGIN
select GREATEST(technical, sched, cost) into consequence;
return consequence;
END;
CREATE FUNCTION CurrentRiskEventID(riskidentifier int, eid int) RETURNS int
BEGIN
select MAX(e.EventID) into eid
FROM riskevents e
WHERE e.eventstatus not in('Open')
AND e.riskid = riskidentifier;
return riskeventid;
END;
CREATE FUNCTION CurrentConsequence(riskidentifier int, currentconsequence int) RETURNS int
BEGIN
SELECT coalesce(
(SELECT GREATEST(actualtechnical, actualschedule, actualcost)
FROM riskevents
WHERE id = CurrentRiskEventID(riskidentifier, 0)
and actualtechnical is not null
ANDactualschedule is not null
andactualschedule is not null),
(SELECT greatest(technical, schedule, cost)
from risks
Where riskid = riskidentifier)
) into currentconsequence;
return currentconsequence;
END;
CREATE FUNCTION CurrentLikelihood(riskidentifier int, currentlikelihood int) RETURNS int
BEGIN
SELECT coalesce(
(SELECT actuallikelihood
FROM riskevents
WHERE id = CurrentRiskEventID(riskidentifier, 0)),
(SELECT r.likelihood
FROM risks r
WHERE r.riskid = riskidentifier)) into currentlikelihood;
return currentlikelihood;
END;
CREATE FUNCTION CurrentRiskLevel(riskidentifier int, currentrisklevel int) RETURNS int
BEGIN
select RiskLevel(CurrentRiskValue(riskidentifier, 0), '') into currentrisklevel;
return currentrisklevel;
END;
CREATE FUNCTION CurrentRiskValue(riskidentifier int, currentriskvalue int) RETURNS int
BEGIN
SELECT RiskValue(CurrentLikelihood(riskidentifier, 0), CurrentConsequence(riskidentifier, 0), 0) into currentriskvalue;
return currentriskvalue;
END;
CREATE FUNCTION GetRiskUserID(riskusertype VARCHAR(25), riskidentifier int, riskuserid int) RETURNS int
BEGIN
SELECT COALESCE(userres.userid, 0) into riskuserid FROM
(
SELECT r.creatorid, r.ownerid, r.approverid, u.userid
FROM risks r, users u
WHERE r.riskid = (select riskidentifier) and
(
((select riskusertype) = 'Creator' AND u.userid = r.creatorid) OR
((select riskusertype) = 'Approver' AND u.userid = r.approverid) OR
((select riskusertype) = 'Owner' AND u.userid = r.ownerid)
)
) userres;
RETURN riskuserid;
END;
CREATE FUNCTION GetRiskUserLastOrFirstName(riskuserid int, riskid int, whichname char(25), firstorlastname char(25)) RETURNS char(25) CHARSET utf8 COLLATE utf8_unicode_ci
BEGIN
SELECT (case
when whichname = 'Last' then u.LastName
WHEN whichname = 'First' THEN u.FirstName
end)
into firstorlastname
FROM users u,risks r
WHERE u.UserID = riskuserid
AND r.RiskID = riskid;
return firstorlastname;
END;
CREATE FUNCTION LatestEventDate(riskidentifier int, riskassessmentdate date, latestdate date) RETURNS date
BEGIN
SELECT COALESCE(
(SELECT ActualDate FROM riskevents evt WHERE evt.eventid = CurrentRiskEventID(riskidentifier, 0) and evt.riskid = riskidentifier),
(SELECT riskassessmentdate)) into latestdate;
return latestdate;
END;
CREATE FUNCTION RiskLevel(riskvalue int, risklevel varchar(4)) RETURNS varchar(4)
begin
SELECT
CASE
WHEN riskvalue >= levels.riskhigh*100 THEN 'High'
WHEN riskvalue >= levels.riskmedium*100 THEN 'Med'
ELSE 'Low'
ENd as cat into risklevel
FROM risklevels levels;
return risklevel;
END;
CREATE FUNCTION RiskValue(likelihood int, consequence int, riskvalue int) RETURNS int
BEGIN
SELECT m.level*100 INTO riskvalue FROM riskmatrixthresholds m WHERE m.likelihood = likelihood AND m.consequence = consequence;
RETURN riskvalue;
END;
http://www.sqlfiddle.com/#!9/1d52a0/17
Note: SQL is a declarative language, not a procedural language. You tell it what you want, not how to get it. Your use of functions and so forth is procedural.
How can you make this application faster?
First, use the latest version of MySQL (8+, or MariaDB 10.4+). Later versions get faster.
Second, you have stated a requirement to use "subqueries composed of functions". That means you probably can't do much about the performance.
Why not? The subqueries buried in functions are so-called dependent subqueries. Those don't perform well. And because they're buried MySQL's query planner can't do anything useful to optimize them.
Refactoring your query to avoid using functions with SELECT operations will give the query planner visibility into your overall query. That will give it a chance to optimize things. You might replace them with views.
And don't use SELECT tablea, tableb syntax. That's been obsolete since 1992. Use
SELECT tablea JOIN tableb ON tablea.joincolumn = tableb.joincolumn.
I'd offer you more advice but I can't figure out your intent.
Application of the following changes in CurrentLikelihood() and CurrentConsequence() reduced total query execution time to exec 0.070 sec, total 0.082 sec.
Old Current Likelihood Query (producing slow and incorrect output)
SELECT coalesce(
(SELECT actuallikelihood
FROM riskevents
WHERE id = CurrentRiskEventID(riskidentifier, 0)),
(SELECT r.likelihood
FROM risks r
WHERE r.riskid = riskidentifier)) into currentlikelihood;
return currentlikelihood;
Working CurrentLikelihood Query
SELECT actuallikelihood INTO currentlikelihood
FROM riskevents
WHERE eventid = CurrentRiskEventID(riskidentifier)
AND riskid = riskidentifier;
Old CurrentConsequence Query (producing slow and incorrect output)
SELECT coalesce(
(SELECT GREATEST(actualtechnical, actualschedule, actualcost)
FROM riskevents
WHERE id = CurrentRiskEventID(riskidentifier, 0)
and actualtechnical is not null
and actualschedule is not null),
(SELECT greatest(technical, schedule, cost)
from risks
Where riskid = riskidentifier)
) into currentconsequence;
Working CurrentConsequence Query
SELECT GREATEST(actualtechnical, actualschedule, actualcost) INTO currentconsequence
FROM riskevents
WHERE eventid = CurrentRiskEventID(riskidentifier)
AND riskid = riskidentifier;
Old CurrentRiskEventID() Query
select MAX(e.EventID) into currentriskeventid
FROM riskevents e
WHERE e.eventstatus not in('Open')
AND e.riskid = riskidentifier;
Modified GetRiskEventID() function
SELECT MAX(e.EventID) INTO currentriskeventid
FROM riskevents e
WHERE e.riskid = riskidentifier AND
(e.eventstatus != 'Open'
OR
(e.EventID = 0 AND e.eventstatus = 'Open'));

mysql: Finding a max in each column of a table

I have the table below. I'm looking for the both the max value in each column AND it's matching username (all values of NULL to be ignored).
A bunch of mad googling has lead me to believe I need to find the max values and then use a second query to find the matching username?
But is there a query that can return this in one go?
ID username Vale Jorge Andrea
-------------------------------------------
01 John 2 6 NULL
02 Ted NULL 0 0
03 Marcy NULL 2 1
Output would be...
John Jorge 6
John Vale 2
Marcy Andrea 1
There's different ways of looking at it, here's a table that gives a row for each username that has a matching max value:
SELECT
username
, IF (max_vale = t.vale, max_vale, NULL) AS for_vale
, IF (max_jorge = t.jorge, max_jorge, NULL) AS for_jorge
, IF (max_andrea = t.andrea, max_andrea, NULL) AS for_andrea
FROM (
SELECT
MAX(vale) AS max_vale
, MAX(jorge) AS max_jorge
, MAX(andrea) AS max_andrea
FROM t
) y
JOIN t ON (
t.vale = max_vale
OR t.jorge = max_jorge
OR t.andrea = max_andrea
)
http://sqlfiddle.com/#!9/58e37d/5
This gives:
username for_vale for_jorge for_andrea
----------------------------------------------
John 2 6 (null)
Marty (null) (null) 1
Basically, all I'm doing is selecting the specific column max values, then using that query as the source for another query that just looks at the MAX generated columns, and filters (IF()) based on the matches found.
This is one reasonably simple way of doing it... make a table of all the maximum values and join it to the usernames on matching the value with the max. Uses the fact that NULL isn't equal to anything. I haven't attempted to order the results but that is easy enough with adding an ORDER BY clause.
select username, name, COALESCE(mv.vale, mv.jorge, mv.Andrea) as value
from table1
join
(select 'Vale' as name, max(vale) as vale, NULL as jorge, NULL as andrea from table1
union ALL
select 'Jorge', NULL, max(jorge), NULL from table1
union all
select 'Andrea', NULL, NULL, max(andrea) from table1) mv
on table1.vale = mv.vale or table1.jorge = mv.jorge or table1.andrea = mv.andrea
Output
username name value
John Vale 2
John Jorge 6
Marcy Andrea 1
To extrapolate this to more columns is reasonably straightforward (if somewhat painful) e.g. to add a column called fred you would use (changes inside **):
select username, name, COALESCE(mv.vale, mv.jorge, mv.Andrea**, mf.fred**) as value
from table1
join
(select 'Vale' as name, max(vale) as vale, NULL as jorge, NULL as andrea**, NULL as fred** from table1
union ALL
select 'Jorge', NULL, max(jorge), NULL**, NULL** from table1
union all
select 'Andrea', NULL, NULL, max(andrea)**, NULL** from table1) mv
**union all
select 'Fred', NULL, NULL, max(fred), NULL from table1) mf**
on table1.vale = mv.vale or table1.jorge = mv.jorge or table1.andrea = mv.andrea **or table1.fred = mf.fred**
If you have access to stored procedures, you can also do it like this (in a much more flexible way in terms of columns)
DROP PROCEDURE IF EXISTS list_maxes;
DELIMITER //
CREATE PROCEDURE list_maxes(tname VARCHAR(20), column_list VARCHAR(1000))
BEGIN
DECLARE maxv INT DEFAULT 0;
DECLARE cpos INT;
DECLARE colname VARCHAR(20);
-- loop through the column names
WHILE (LENGTH(column_list) > 0)
DO
SET cpos = LOCATE(',', column_list);
IF (cpos > 0) THEN
SET colname = LEFT(column_list, cpos - 1);
SET column_list = SUBSTRING(column_list, cpos + 1);
ELSE
SET colname = column_list;
SET column_list = '';
END IF;
-- find the maximum value of this column
SET #getmax = CONCAT('SELECT MAX(', colname, ') INTO #maxv FROM Table1');
PREPARE s1 FROM #getmax;
EXECUTE s1;
DEALLOCATE PREPARE s1;
-- now find the user with the maximum value
SET #finduser = CONCAT("SELECT username, '", colname, "' AS name, ", colname, ' AS value FROM ', tname,' WHERE ', colname, ' = ', #maxv);
PREPARE s2 FROM #finduser;
EXECUTE s2;
DEALLOCATE PREPARE s2;
END WHILE;
END//
DELIMITER ;
CALL list_maxes('table1', 'Vale,Jorge,Andrea')
Output
John Vale 2
John Jorge 6
Marcy Andrea 1
This is quite long but working. I combine all columns into one table using UNION ALL. Then get the max value per surname. Join this to the original table using the surname and value. Order by value in descending order.
select tv.*
from( select surname, max(val) as maxval
from (
select username,'vale' as surname,vale as val
from tbl
union all
select username,'jorge' as surname,jorge
from tbl
union all
select username,'andrea' as surname,andrea
from tbl) tab
group by surname) tt
join (
select username,'vale' as surname,vale as val
from tbl
union all
select username,'jorge' as surname,jorge
from tbl
union all
select username,'andrea' as surname,andrea
from tbl) tv
on tt.surname=tv.surname and tt.maxval=tv.val
order by tv.val desc;
As a whole other way to resolve the request, we may look at the model, e.g., the fact that what appears to be domain content is represented as columns instead of rows. This is typical of a source aggregate query (pivot or rollup to get aggregated totals or groupings, for instance), but if the underlying data spreads out, should perhaps be based on the transactional integrity of that data source (the "spread out" underlying source).
Basically, I wonder why there are Vale, Jorge and Andrea columns at all in the database. This implies it's already been summarized.
So we may look at an alternate model that is notably easier to navigate for these purposes:
CREATE TABLE IF NOT EXISTS `user` (
`id` MEDIUMINT NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `prospect` (
`id` MEDIUMINT NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `aggregate` (
`id` MEDIUMINT NOT NULL AUTO_INCREMENT,
`user_id` int(6) unsigned NOT NULL,
`prospect_id` int(6) unsigned NOT NULL,
`total` int(6) unsigned NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARSET=utf8;
SELECT
user.username
, prospect.name
, MAX(aggregate.total) AS max_aggregate
FROM aggregate
JOIN user ON user_id = user.id
JOIN prospect ON prospect_id = prospect.id
GROUP BY username
This produces:
John Andrea 6
Marty Jorge 2
Ted Jorge 5
http://sqlfiddle.com/#!9/07ba0c/1
This may not be useful to you now, but as your experience grows and your experience with advanced querying evolves, this will make more sense. The main difficulty may be that the core data is already turned, making the querying more difficult because what you want is a different dimension than what you may have already derived.

Mysql INSER INTO with SELECT and INNER JOIN WITH CASE

The following code reads:
--Code Created by Michael Berkowski
create table dvd (
dvd_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
INSERT INTO dvd VALUES (1),(2),(3),(4);
CREATE TABLE dvd_price (
dvd_price_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
dvd_id INT NOT NULL,
rental_amount INT,
time_rented_for VARCHAR(10)
);
INSERT INTO dvd_price (dvd_id, rental_amount, time_rented_for)
SELECT
dvd_id,
2 AS rental_amount,
rental_period
FROM
dvd
CROSS JOIN (
-- This is where I'm having issues
SELECT (CASE dvd.dvd_id
WHEN dvd.dvd_id = 1
THEN '1-Day'
ELSE '3-Day'
END) AS rental_period
) rental_periods
Why can I not do a CASE statement after the CROSS JOIN and how would I fix this?
I get the error, "Unknown table 'dvd' in field list:", what is a better way of writing this?
Try this way:
INSERT INTO dvd_price (dvd_id, rental_amount, time_rented_for)
SELECT
dvd_id,
2 AS rental_amount,
CASE dvd.dvd_id
WHEN dvd.dvd_id = 1
THEN '1-Day'
ELSE '3-Day'
END
FROM
dvd

difficult constraint for a mysql-table

I need a constraint for a mysql-table. The table has the fields 'id', 'ref', 'from' and 'to'. The constraint schould guarantee that there are no datasets with the same 'ref' and a time overlapping (fields 'from' and 'to').
In sql: The following statement should always return '0'.
select count(*)
from `TABLE` d1 inner join `TABLE` d2 on
d1.`ref` = d2.`ref` and d1.`id` <> d2.`id` and
d1.`to` >= d2.`from` and d1.`from`<=d2.`to`
Is there a way to handle this with constrains?
Now I have the following triggers. Thanks for your help!
DELIMITER $$
USE `devtestplandb`$$
CREATE
TRIGGER `db`.`trig1`
BEFORE INSERT ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_001();
END IF;
END$$
CREATE
TRIGGER `db`.`trig2`
BEFORE UPDATE ON `db`.`TABLE`
FOR EACH ROW
BEGIN
SET #CNT = (
select count(*)
from `TABLE` d
where d.`ref` = NEW.`ref` and
d.`ID` <> NEW.`ID` and
d.`to` >= NEW.`from` and
d.`from` <= NEW.`to`);
IF #CNT != 0 THEN
CALL error_002();
END IF;
END$$
"Is there a way to handle this with constrains?"
Yes, SQL Standard 2011 supports this kind of scenarios in readable declarative manner:
unique constraint definition
<without overlap specification> ::=
<application time period name> WITHOUT OVERLAPS
In your example:
CREATE TABLE tab (
id INT AUTO_INCREMENT PRIMARY KEY,
ref VARCHAR(100),
from_date DATE,
end_date DATE,
PERIOD FOR ref_period(from_date, end_date),
UNIQUE (ref, ref_period WITHOUT OVERLAPS)
);
And sample inserts:
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-01-01','2020-03-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-03-01','2020-05-01');
-- OK
INSERT INTO tab(ref, from_date, end_date) VALUES ('a', '2020-04-01','2020-07-01')
-- Duplicate entry 'a-2020-07-01-2020-04-01' for key 'ref
SELECT * FROM tab;
db<>fiddle demo - Maria DB 10.5