A video game exists with 2 stages: Set-Up, and Gameplay. Players can use Set-Up, to alter a database, which affects Gameplay. However some features of Set-Up must account for other features of Set-Up when interacting with the database, and must therefore alter statements of execution depending on other factors in the database.
For example:
In Set-Up, a player can either choose to enable or disable a setting called "complex commerce", which creates new rows in table "Buildings" with the IDs "BUILDING_WAYSTATION", and "BUILDING_MINT", among many other changes in the database. A player can choose to enable a second setting called "easier commerce" which updates the table "ModiferArguments", increasing the value in the column "VALUE" for the column ID associated through many key constraints to "BUILDING_MARKET" (which will always exist, no matter what) in table "Buildings". However, if "complex commerce" is enabled, then we will want to change update the column "VALUE" differently for market, as well as other buildings introduced by "complex commerce" setting inserts.
Is it possible to perform this kind of logical interaction within an SQLite statement?
Code attempt:
IF
(
EXISTS
(
SELECT
*
FROM
Buildings
WHERE
BuildingType = 'BUILDING_WAYSTATION'
;
)
)
THEN
(
Update
ModifierArguments
SET
Value = Value+1
WHERE
ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY'
;
Update
ModifierArguments
SET
Value = Value+2
WHERE
ModifierID = 'MINT_TRADE_ROUTE_CAPACITY'
;
Update
ModifierArguments
SET
Value = Value+3
WHERE
ModifierID = 'WAYSTATION_TRADE_ROUTE_CAPACITY'
;
)
ELSE
(
Update
ModifierArguments
SET
Value = Value+2
WHERE
ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY'
)
;
I am very sorry about the eye-sore formatting, but I wanted to make sure that the logic of what I am trying to do is as clear as possible.
Code theory:
Using IF and EXISTS we can see in the database whether or not the "complex commerce" set of database updates have occurred, simply by seeing if there is a "Building" table entry row with the Id of "BUILDING_WAYSTATION". This allows to choose which set of UPDATEs to execute for the setting of "easier commerce". If "complex commerce" has not been enabled, then we only need to update a single value to "2". However, if it has been enabled, then that single value must instead be updated to "1" AND we must update other values that would otherwise not exist.
Alternatively:
I have looked at using CASE, but I am not sure if it is capable of fulfilling the same purpose, simply just substituting IF for CASE WHEN. I have also looked at using iif(), but have the same issue in that it does not seem fit for purpose outside of replacing return data from SELECT statements upon display.
You can use CASE expressions to apply your logic:
UPDATE ModifierArguments
SET Value = Value +
CASE
WHEN EXISTS (SELECT * FROM Buildings WHERE BuildingType = 'BUILDING_WAYSTATION')
THEN CASE ModifierID
WHEN 'MARKET_TRADE_ROUTE_CAPACITY' THEN 1
WHEN 'MINT_TRADE_ROUTE_CAPACITY' THEN 2
WHEN 'WAYSTATION_TRADE_ROUTE_CAPACITY' THEN 3
END
ELSE CASE WHEN ModifierID = 'MARKET_TRADE_ROUTE_CAPACITY' THEN 2 ELSE 0 END
END
WHERE ModifierID IN ('MARKET_TRADE_ROUTE_CAPACITY', 'MINT_TRADE_ROUTE_CAPACITY', 'WAYSTATION_TRADE_ROUTE_CAPACITY');
Related
as a rule, it's better to hide the single-row fetches inside a function, so instead of:
BEGIN
SELECT name
INTO l_name
FROM mytable
WHERE primary_key = id_primary_key;
it would be better to develop a
PACKAGE mypackage
IS
FUNCTION fnc_name (id_primary_key IN mytable.primary_key%TYPE)
RETURN mytable.name%TYPE;
and executing
BEGIN
l_name := mypackage.fnc_name (id_primary_key);
But what about updating?
I mean, if I decide to develop the same solution for updating but in that case every time I need to update only few columns of the table, how would you develop such an API?
Oracle version 10g
Thanks!
Mark
You are starting to develop a "table API" or "TAPI". These are problematic, for the reason you have mentioned: if the TAPI's update procedure updates all 20 columns of the table from 20 parameters, but in a particular case you only need to update 3 columns, how should you call it? One way is to simply have to pass all 20 values even though you are not changing most of them. Another is to give each parameter a "funny" default like CHR(0) and have the API update be like:
UPDATE mytable
SET column1 = CASE WHEN p_column1 = CHR(0) THEN column1 ELSE p_column1 END,
...
A different (I'd say better) approach is the "Transaction API" or XAPI. Here you build a separate procedure for each business transaction that might need to update the table. For example:
PROCEDURE terminate_employee
( p_empid INTEGER
, p_termination_date DATE
, p_termination_reason VARCHAR2
);
This procedure will use a simple SQL update statement to update the 3 columns that need to be updated when terminating an employee.
Some would say SQL is an API to the database!
Since SQLite doesn't support TRUE and FALSE, I have a boolean keyword that stores 0 and 1. For the boolean column in question, I want there to be a check for the number of 1's the column contains and limit the total number for the table.
For example, the table can have columns: name, isAdult. If there are more than 5 adults in the table, the system would not allow a user to add a 6th entry with isAdult = 1. There is no restriction on how many rows the table can contain, since there is no limit on the amount of entries where isAdult = 0.
You can use a trigger to prevent inserting the sixth entry:
CREATE TRIGGER five_adults
BEFORE INSERT ON MyTable
WHEN NEW.isAdult
AND (SELECT COUNT(*)
FROM MyTable
WHERE isAdult
) >= 5
BEGIN
SELECT RAISE(FAIL, "only five adults allowed");
END;
(You might need a similar trigger for UPDATEs.)
The SQL-99 standard would solve this with an ASSERTION— a type of constraint that can validate data changes with respect to an arbitrary SELECT statement. Unfortunately, I don't know any SQL database currently on the market that implements ASSERTION constraints. It's an optional feature of the SQL standard, and SQL implementors are not required to provide it.
A workaround is to create a foreign key constraint so isAdult can be an integer value referencing a lookup table that contains only values 1 through 5. Then also put a UNIQUE constraint on isAdult. Use NULL for "false" when the row is for a user who is not an adult (NULL is ignored by UNIQUE).
Another workaround is to do this in application code. SELECT from the database before changing it, to make sure your change won't break your app's business rules. Normally in a multi-user RDMS this is impossible due to race conditions, but since you're using SQLite you might be the sole user.
I am testing the trigger named, "tulockout" listed below, with this alter statement..."alter user testuser account lock;" to see if the trigger log a record of what happened in table, "log_table_changes".
However, certain values are not accurately logging into the table, "log_table_changes". To be specific v_dusr.start_dt is returning NULL when the trigger, "tulockout" fires off after I execute "alter user testuser account lock;" statement.
I am not certain as to why. Can you please assist?
How can I fix this issue? Thanks.
create or replace trigger tulockout
after alter on schema
declare
cursor v_abc is
select du.username, max(us.start_dt)
from dba_users du, user_session us, users_info ui
where ui.db_user_name = du.username
and ui.db_user_name = us.user_name
and ui.db_user_name = ora_login_user;
v_dusr v_abc%ROWTYPE;
begin
if(ora_sysevent = 'ALTER' and v_dusr.username = ora_dict_obj_name and
v_dusr.account_status = 'LOCKED') then
insert into log_table_changes(username,
lastlogin_date,
notes,
execute_date,
script_name
)
values(
v_dusr.username,
v_dusr.start_dt,
ora_dict_obj_type||', '||
ora_dict_obj_name||' has been locked out.',
sysdate,
ora_sysevent
);
end;
You are declaring a cursor, and a record based on that; but you don't ever execute the cursor query or populate the variable.
Your cursor query is currently missing a group-by clause so will error when run, because of the aggregate function. You don't really need to include the user name in the select list though, as you already know that value. You are, though, later referring to the v_duser.account_status field, which doesn't exist in your cursor query/rowtype, so you need to add (and group by) that too.
The trigger also needs to be at database, not schema, level; and unless you intend to record who performed the alter command, you don't ned to refer to ora_login_user - looking that user's status up doesn't seem very helpful.
You don't really need a cursor at all; a select-into would do, something like (assuming there will always be a row returned from the joins to your user_session and users_info tables; which implies they store the username in the same case as dba_users does - although I'm not sure why you are joining to users_info at all?):
create or replace trigger tulockout
after alter on database
declare
v_start_dt user_session.start_dt%TYPE;
v_account_status dba_users.account_status%TYPE;
begin
select du.account_status, max(us.start_dt)
into v_account_status, v_start_dt
from dba_users du
join user_session us on us.db_user_name = du.username
-- join not needed?
-- join users_info ui on ui.db_user_name = us.user_name
where du.username = ora_dict_obj_name
group by du.account_status;
if(ora_sysevent = 'ALTER' and ora_dict_obj_type = 'USER'
and v_account_status = 'LOCKED') then
insert ...
and then use those date and status variables and ora_dict_obj_name(the user that was altered) in the insert.
I've also switched to modern join syntax, and tweaked the conditions a bit.
Untested, but should give you the idea.
You could make it even easier by doing a single insert ... select against those tables, removing the need for local variables.
I'm completely new to sqlite, so bear with me. I am updating a database and need to copy values within the same table, named "custom". I used pragma to get the table info, it's:
0|ticket|integer|0||0
1|name|text|0||0
2|value|0||0
Using select * from custom where ticket = (some value) I get, among other results,
(some value)|block|
(some value)|required|(another value)
I want to copy (another value) to "block" anywhere this value exists in "required". How do I make that happen? Everything I've tried has failed miserably to this point.
My pseudo code version would be something like
update custom
where required has a value
copy it to block
How do I turn that into actual sqlite commands?
UPDATE custom
SET value = (SELECT value
FROM custom AS c2
WHERE c2.ticket = custom.ticket
AND c2.name = 'required')
WHERE name = 'block'
AND value IS NULL
Is it possible to write a SQLite trigger and/or stored procedure that would return alternate data in a SELECT request if the data is not recent? I know how this could be done when selecting a specific record, because then the record ID could be passed to a stored procedure for evaluation. But I want a SELECT * FROM TABLE statement to obscure only relevant data, regardless of the conditions specified in a subsequent WHERE clause. Each individual record must be evaluated. This could be accomplished by modifying the existing data or by simply returning a false value. For instance, something like this pseudocode:
for (RECORD in ALL_RECORDS_REQUESTED) {
// test if field contains value of interest and is more than one hour old
if ( ( fieldOfInterest LIKE '%VALUE_I_AM_CENSORING%' ) && ( recordTimestamp > ( recordTimestamp + 60 ) ) ) {
// change value of field before returning (or simply return false value)
fieldOfInterest = 'value I want you to see';
}
}
Suffice is to say that I do not want a trigger to modify any data when it is initially INSERTED or UPDATED in the table, as I want the original value to be available for at least one hour. After that I want it to be changed if any SELECT statements request the record(s).
Is this possible in SQLite or would this require modifying the application that interacts with the database? I am aware of how to code this within the application but would prefer to handle this within the database itself.
This could also be accomplished by a trigger that automatically appends a condition to every SQL statement, allowing the exclusion of data:
WHERE NOT EXISTS (SELECT fieldOfInterest, recordTimestamp
FROM tblOfInterest
WHERE fieldOfInterest LIKE '%VALUE_I_AM_CENSORING%'
AND recordTimestamp > recordTimestamp + 60)
Or something to this effect....
Triggers only fire whenever a DELETE, INSERT, or UPDATE of a particular database table occurs, or whenever an UPDATE occurs on on one or more specified columns of a table.
You could use a VIEW to accomplish what you want, but then all data access would have to go through that view.