SQL Trigger - Maximo Workorders - Supervisor/Owner Initialization - sql

I am working with an IBM Maximo database that issues workorders automatically. When the workorders are issued (aka Inserted into the database workorder table), I would like to auto-assign a supervisor, owner, and owner group based on a set of criteria. This only needs to happen IF the supervisor, owner, and owner group aren't already assigned. Often times a "parent workorder" has the information, but it needs to be copied into the "child" workorders (as you will see in the criteria below). The criteria for ALL of the triggers is:
WHERE status<>'COMP'
AND historyflag=0
AND istask=0
Here is the criteria for the trigger:
-If the Owner Group and Supervisor have a value, skip the record. (Do nothing)
-If the Owner Group and/or Supervisor is blank or null, and the corresponding PARENT Work
Order field is not Null, copy the Owner Group and/or Supervisor from the
PARENT Work Order record.
-If the Parent Work Order Owner Group and/or Supervisor is blank or null, then
assign the Owner Group and Supervisor per the table values below: (I have removed names for security's sake, but all the columns are correct, i.e. B3 is supposed to have SuperA as the supervisor)
Site / OwnerGroup / Supervisor
ABC / #ABCGroup / #ABCSupervisor
DEF / #DEFGroup / #DEFSupervisor
**NOTE:
SITE is not a table column, it is really the first 3 characters of the workorder.location field. For example, the location could be ABC-1234, meaning it is at site ABC, building 1234 (unfortunately, these are NOT stored in separate columns, they are only present together in the location column). In this SQL query, all buildings at a location are serviced by the same ownergroup/supervisor, so all other queries we currently use are using workorder.location='ABC%'
I've done plenty of selects, updates, and stored procedures, but this is my first trigger and want to make sure I don't royally screw up the database! Any and all help is greatly appreciated!
For those unfamiliar with Maximo, the table is:
dbo.workorder
and the fields are:
location,ownergroup,supervisor
UPDATE1:
Here is some additional information that may be of importance.
Locations:
First off, workorder.location will contain values such as ABC-1234, meaning it is at site ABC, building 1234 (though these are NOT separate values, it's combined). In this SQL query, all buildings at a location are serviced by the same ownergroup/supervisor, so all queries use something similar to workorder.location='ABC%'.
Here is what I would like the logic to look like for the final query:
If the supervisor field is missing, first look to see if it has a parent, and if so, does the parent have a supervisor? If not, assign based on the table above.
If the ownergroup field is missing, first look to see if it has a parent, and if so, does the parent have an ownergroup? If not, assign based on the table above.
This is why I am thinking a case statement maybe the best option. Also, I currently have a list of variables such as "#ASupervisor, #B1Supervisor, #B2Supervisor,...etc" so that I can change them in the future if need be. To save a lot of redundant code, is it possible to do something like:
(in this example, location is ABC-1234, ownergroup SHOULD be #ABCGroup, supervisor should be #ABCSupervisor, where #ABCGroup and #ABCSupervisor are set earlier in the code)
If the supervisor field is missing, first look to see if it has a parent, and if so, does the parent have a supervisor (then copy it's supervisor)? If not, assign supervisor X.
Where X = '#' + '(the first three characters of the location)' + 'Supervisor' (in this example, X=#ABCSupervisor)
Is this possible??
UPDATE 2:
I have spoken with the person who asked for this database change and we have changed some thinking here. First, parent locations and child locations should always be the same (if they're not, that's a WHOLE OTHER issue). All sites (first 3 letters of location) should have the same ownergroup and supervisor, so essentially we can just look to see if a workorder entry has a NULL value in either field, then assign it based on the location. I believe the following code will work (but would like someone to review it before I implement it on the system)
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER dbo.AutoAssign
ON dbo.workorder
AFTER INSERT,UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #ABCSupervisor varchar(30)
DECLARE #DEFSupervisor varchar(30)
DECLARE #ABCOwnerGroup varchar(20)
DECLARE #DEFOwnerGroup varchar(20)
/*EDIT VARIABLES IF FUTURE CHANGES*/
--SET Supervisor values HERE;
SET #ABCSupervisor='JOHNDOE'
SET #XYZSupervisor='JANEDOE'
--SET OwnerGroup values HERE:
SET #ABCOwnerGroup='ALPHATEAM'
SET #XYZOwnerGroup='OMEGATEAM'
--UPDATES
UPDATE dbo.workorder
SET ownergroup='#'+SUBSTR(location,1,3)+'OwnerGroup'
WHERE status<>'COMP'
AND historyflag=0
AND istask=0
AND ownergroup IS NULL
AND location IS NOT NULL
UPDATE dbo.workorder
SET supervisor='#'+SUBSTR(location,1,3)+'Supervisor'
WHERE status<>'COMP'
AND historyflag=0
AND istask=0
AND supervisor IS NULL
AND location IS NOT NULL
END
GO
The only issues I see here are that I do not join of some sort on the "inserted" table so that it only affects those entries (and not the entire table every time). If I could get some help on that, it would be greatly appreciated!!

if you have done some update-statements you are on the right way.
put the virtual table INSERTED in your update-statement and join it with a unique-key, e.g. SerialNo: that would be something like that:
create trigger Trig_WorkOrder for insert, update as
BEGIN
update wo
set location = pwo.location, ...
from dbo.workorder as wo, inserted as i, dbo.parentworkorder as pwo
where wo.serialNo = i.SerialNo -- join with the actual table-entry
and wo.pwo_id = pwo.id -- join with the parentworkorder
and i.ownergroup is null -- do it if new value is empty
and (pwo.ownergroup is not null or pwo.ownergroup <> '') -- do it if parentvalue is not empty
and (pwo.Supervisor is not null or pwo.Supervisor <> '') -- do it if parentvalue is not empty
update wo
set location = pwo.location, ...
from dbo.workorder as wo, inserted as i, dbo.standardworkorder as pwo
where wo.serialNo = i.SerialNo
and wo.location = pwo.location
and wo.ownergroup is null
and (pwo.ownergroup is null or pwo.ownergroup = '')
and (pwo.Supervisor is null or pwo.Supervisor = '')
END
I would sugest to store the default values in a separate table, for convenient joining if the parentvalues are empty.
Put two update-statements inside the trigger an code the where-clause to make sure that only one statement executes...
peace and good luck

If you are using Maximo 7.x, automation scripts can do what you want.
For example, we use an automation script that checks to see if the child work order's field is null. If it is, Maximo will take the parent work order value and fill it. In our case, it a custom field called WOPM1.
WorkOrderSet = mbo.getMboSet("PARENT")
if WorkOrderSet.getMbo(0) is not None:
SUPERVISOR = WorkOrderSet.getMbo(0).getString("SUPERVISOR")
Alternatively, if you have a basic lookup to fill in OwnerGroup and Supervisor based on the first three of the location, you can do a if then type logic to fill in data in the child work order when there is a match. v_location is the variable I have defined for a parent work order location:
if v_location == 'ABC':
mbo.setValue("ownergroup","ABCOwnerGroup")
mbo.setValue("supervisor","ABCSupervisor")

For what you are trying to achieve, I'm not sure using a trigger is the best solution in Maximo - using triggers ignores all MBO business logic. Maybe you could consider customizing MBO classes or using Maximo Escalations to do this taks. Using triggers could also get you in trouble when applying Fix Packs or upgrading Maximo so if you choose to go this way, be sure to backup the triggers before any such action.

Related

Trigger to update value with no of records

trying to set up a trigger but struggling to it to work the way i want, i want to update a field oppo_pono with the no of opportunity records created for a particular company record
so 1 company can have multiple opportunities and i want to record the no of master opportunities created for a company, so the first master opp created for a company would be set to 1 and so on
ive set the trigger up below but its setting the oppo_pono with the count from all companies rather then the one i am creating the opportunity for
my trigger below
USE [CRM]
GO
/****** Object: Trigger [dbo].[GeneratePNo] Script Date: 1/7/2021 3:55:27 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER TRIGGER [dbo].[GeneratePNo]
ON [dbo].[Opportunity]
FOR insert
AS
declare #OppPrimary Int
declare #company Int
declare #compid Int
declare #type nvarchar(40)
declare #childopp nchar(1)
declare #pono int
Select #OppPrimary = Oppo_OpportunityId,
#company = Oppo_PrimaryCompanyId,
#compid = comp_companyid,
#type = Oppo_Type,
#childopp = oppo_childoppo,
#pono = oppo_pono
FROM Inserted inner join company on Oppo_PrimaryCompanyId = #compid
Begin
UPDATE [Opportunity] SET oppo_pono = (select count(*) from vSearchListOpportunity where Oppo_Deleted is null and #type = 'Master' and Oppo_PrimaryCompanyId = ) +1
WHERE Oppo_OpportunityId =#OppPrimary
End
As mentioned in the comments, you have not taken into account the inserted pseudo-table having multiple rows. You also have a number of outright syntax errors.
EDIT: Following your comments I think I understand what you are trying to do. My original solution will not work here because indexed view cannot have ranking functions, but I have modified it to work with what you need.
Ideally, you wouldn't care about the actual IDs lining up, and just use an IDENTITY column, but often an ID series per group is needed.
Generally a view with correct indexing will be the more performant option, but it depends what you need.
Using a View
I will show you a solution that can be used for a lot of different types of aggregations which normally require triggers. This only works for your problem if you intend to have the numbering change if a row gets deleted out the middle of the grouping. If you want the numbering to remain then use a trigger instead
I am unsure the exact relation of Opportunity, Company and vSearchListOpportunity (seems to be a view on Opportunity) but you should be able to modify this to suit.
Create an indexed view on the data, and include a row number for each row:
CREATE VIEW vOpportunityNumbered
AS
SELECT
o.Oppo_OpportunityId,
o.Oppo_PrimaryCompanyId,
o.Oppo_Type,
o.oppo_childoppo,
ROW_NUMBER() OVER
(PARTITION BY o.Oppo_PrimaryCompanyId ORDER BY o.Oppo_OpportunityId)
-- Order by primary key to get deterministic ordering
FROM Opportunity AS o
WHERE o.Oppo_Deleted IS NULL;
GO
Now, to support this view, we cannot index it directly, as mentioned. We can, however, create an index on the base table that will support it:
CREATE UNIQUE CLUSTERED INDEX opp_CompanyOpportunity
ON Opportunity (Oppo_PrimaryCompanyId, Oppo_OpportunityId)
-- note the ordering of the columns
INCLUDE (Oppo_Type, oppo_childoppo)
WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY = ON) -- ONLY FOR SQL2019
;
GO
This view will now give you a sequential row numbering of Opportunity for each distinct Company.
Triggers
If you wish for the IDs to always remain the same no matter what happens to intervening rows, you will need a trigger (i.e. a deleted row will leave a gap in the numbers).
Every trigger has two tables, inserted and deleted, which contain the data that was changed. For update triggers, both tables have data, a row in each for each changed row.
This means that the trigger is executed once per statement, and these tables contain all the relevant rows. You cannot, however, update them directly; you must join the real tables to them.
So let's take a look at how to write a trigger. Again, I'm somewhat guessing as to the relations of the tables:
CREATE OR ALTER TRIGGER [dbo].[GeneratePNo]
ON [dbo].[Opportunity]
AFTER INSERT -- FOR is an alternative syntax, AFTER is more usual
AS
-- No need for BEGIN and END, the whole batch until GO is the trigger
SET NOCOUNT ON; -- Prevent DONE_IN_PROC rowcount messages
IF (NOT EXISTS (SELECT 1 FROM inserted))
RETURN; -- Bail-out early if no rows
-- We do not declare variables because we cannot store multiple rows in variables
UPDATE
o
SET oppo_pono =
ISNULL( --If there are no other rows we would get a null
(SELECT MAX(allO.oppo_pono)
FROM Opportunity allO
-- no need for the following two filters as the oppo_pono needs to be unique anyway
-- where allO.Oppo_Deleted is null and allO.type = 'Master'
WHERE allO.Oppo_PrimaryCompanyId = inserted.Oppo_PrimaryCompanyId
), 0) + 1
FROM inserted i
JOIN Opportunity o ON o.Oppo_OpportunityId = i.Oppo_OpportunityId;
-- We join inserted table on primary key always
GO
There are more efficient ways to write that update, but it depends whether you are inserting a lot of rows. An INSTEAD OF trigger will also be more performant here, but I haven't attempted that as I don't have your table definition.

Is it possible to create and execute 2 triggers on one table

I am trying to find some workaround to create and execute multiple triggers for different tables on one table. I have 2 tables
Person Detail
Address
The way this table is designed is when a user updates Person's address it creates a new record into address table instead of updating the existing one and I want to insert the changes into auditlog table when a user updates person details or address.
I was able to create a trigger for person table but dont know how can i make it work by using or calling multiple triggers on Person Table
Following is the code for Person table update trigger
CREATE TRIGGER [dbo].[tr_tblPersonDetail_ForUpdate]
ON [dbo].[PersonDetail]
FOR Update
AS
BEGIN
Declare #Id int,
Declare #OldFirstName varchar(50), #NewFirstName varchar(50)
BEGIN
Select #Id=personId,#NewFirstName = NewFirstName from Inserted
select #OldFirstName = NewFirstName from deleted where #id = personId
if(#OldFirstName <> #NewFirstName )
Insert into AuditLog values('some value','AfterValue','Before Value')
Yes, one table may have multiple triggers. You do need to exercise caution, however - you may not want every update to fire both triggers, or you may have need to execute the triggers in a particular order.
It really sounds like you want an INSTEAD OF UPDATE trigger for the requirement of
creates a new record into address table instead of updating the existing one
If you need your trigger to execute only if specific field(s) were updated, use UPDATED() to test for them.
You can use sp_settriggerorder to dictate which trigger should execute and which one last.
In your case, I would suggest an AFTER UPDATE for any operation that changes only the person's name, and then an INSTEAD OF UPDATE which handles cases where the address is changed (which will also need to handle the name change).
If you choose to continue down the path of using triggers at all.

SQL Transaction Set Temporary Value

I am relatively new to databases and SQL, and I am not clear on how or whether transactions may relate to a problem that I am trying to solve. I want to be able to temporarily set a value in a database table, run some query, and then clear out the value that was set, and I don't want any operations outside of the transaction to be able to see or alter the temporary value that was set.
The reason I am doing this is so that I can create predefined views that query certain data depending on variables such as the current user's id. In order for the predefined view to have access to the current user's id, I would save the id into a special table just before querying the view, then delete the id immediately afterward. I don't want to worry about some other user overwriting the current user's id while the transaction is in process. Is this a proper use for a transaction?
I am using H2 if that makes a difference.
SET #tempVar=value;
I don't know if you really need to go through the pain of creating a temp table and setting the value. This seems far simpler.
You can then do - SELECT * FROM TABLE_NAME WHERE COLUMN=#tempVar;
I think you want a Procedure or Function. Both can take a parameter as input.
ex.
CREATE PROCEDURE pr_emp
(
#input INT
)
AS
SELECT *
FROM myTable
WHERE emp_id = #input
ex.
CREATE FUNCTION v_empid (#input INT)
RETURNS TABLE
AS
RETURN
SELECT * FROM myTABLE WHERE emp_id = #input;
These could let you to access information for an empid. For example:
SELECT * FROM v_empid(32)

Automatically fill row with value based on inserted id

I have a table where the user is able to insert the ID of a Node that corresponds to a title elsewhere in the database. I want this tile to be automatically inserted into the row after the user has chosen the id.
This is my table:
I need to have the "SommerhusNavn" column be automatically filled with values based on the "SommerhusId" inserted.
I am using a third party to handle the CRUD functionality, where the user picks the ID from a dropdown. I already know in which table the title for the ID is located, I'm just not sure how to fill the row with the insert statement. Would I need to run a separate query for this to happen?
Edit:Solution
CREATE TRIGGER [dbo].[BlokeredePerioderInsert]
ON dbo.BlokeredePerioder
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
UPDATE BlokeredePerioder SET SommerhusNavn = text FROM umbracoNode AS umbNode
where SommerhusId = umbNode.id
END
GO
Yes, you need to run additional UPDATE query. Let's assume that you have the TitlesTable, with columns ID and Title. Then it should look like:
UPDATE MyTable SET SommerhusNavn = Title FROM TitlesTable AS A
WHERE SommerhusId = A.ID
AND SommerhusNavn IS NOT NULL --not necessary
Perhaps i'm not understanding, but why can't you use send the value across in the initial update?
Can you use a trigger on the database side?
Alternatively, you'll need to send a update across, following the insert.

Change column value after INSERT if the value fits criteria?

I have never really worked with Triggers before in MSSQL but I think it'll be what I need for this task.
The structure of the table is as such:
ID|****|****|****|****|****|****|****|TOUROPERATOR
The Tour Operator Code is the code that tells us what company owned the flight we carried out for them. Two of those codes (there are 24 in total) are outdated. Our users requested that those two be changed but the tour operator code is pulled from a database we don't control. The FlightData table however, we do control. So I was thinking a trigger could change the tour operator code if it was one of the two outdated ones, to the correct ones instead respectively when they were inserted.
So I went into good ol' SQL Management Studio and asked to make a trigger. It gave me some sample code and here is my Pseudo Code below:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER ChangeProvider
ON FlightData
AFTER INSERT
AS
BEGIN
IF(TheInsertedValue == Criteria)
UPDATE FlightData
SET TheInsertedValue = NewValue
ENDIF
END
GO
I am not that good with this type of Database Programming so excuse my mistakes.
How would I go about doing this?
You could add a computed column to your table instead of adding a trigger.
Then the new column could just use a case statement to either show
the original TourOperator column value or the new value you wanted.
You'd add a new column to your table like this
TourOperatorCorrect = CASE WHEN TourOperator = 'Whatever value' THEN 'ChangedValue'
--I just want to use what I have already in the TourOperator column
ELSE TourOperator
END AS VARCHAR(50)
Basics of computed columns are here - https://msdn.microsoft.com/en-ie/library/ms188300.aspx
Your misconception here is that the trigger runs once per inserted value - it is in fact run once per insert statement, so you can and will find more than one row inserted at once.
You'll find that your inserted values are in the pseudo table inserted, which has the same structure as your FlightData table in this case. You write a select statement against that, specifying any criteria you wish.
However, it's not immediately clear what your logic is - does the FlightData table you are updating in your trigger only have one row? Do you update every row in the table with the newest inserted value? It is hard to understand what you are trying to now, and what the purpose of the table and this trigger are - let alone what you would want to do if you inserted more than one row at once.
When inserted table contains mutiple rows,your code will fail,so change code to work with inserted table as whole
UPDATE F
SET f.TheInsertedValue = i.value
from inserted i
join
Flighttable F
on f.matchingcolumn=i.matchingcolumn
and i.somevalue='criteria'