I have tables that looks like this:
tbl1
+---------+
|c_no |
+---------+
|1 |
+---------+
tbl2
+----------+---------+
|tbl1_c_no |s_name |
+----------+---------+
|1 |A |
|1 |D |
+----------+---------+
My form:
◘ The 1st block's base table usage is tbl1.
◘ C_NO field is auto generated using sequence. (required).
◘ S_GR is just an unbound item. (not required).
◘ The 2nd block's base table usage is tbl2 and is multiple row.
◘ S_NAME. (required)
◘ 1st block is like the parent of 2nd block.
◘ 1st and 2nd block is linked using c_no and tbl1_c_no
For example if I wanted to add some data, it's like this:
Then press F10 for saving:
tbl1 will be:
+---------+
|c_no |
+---------+
|1 |
|2 |
+---------+
tbl2 will be:
+----------+---------+
|tbl1_c_no |s_name |
+----------+---------+
|1 |A |
|1 |D |
|2 |B |
|2 |C |
|2 |E |
+----------+---------+
And my problem is that I wanted to fetch s_names from my 3rd table into 2nd block.
tbl3
+----------+---------+
|s_gr |s_name |
+----------+---------+
|80 |F |
|85 |G |
|84 |H |
|84 |I |
|80 |J |
+----------+---------+
Like this:
then after leaving S_GR field, it will fetch S_NAME from tbl3 that S_GR = 80 into the 2nd block
You can create two blocks :
for the 1st one, to have a block with no base table, create manually just by touching Data Blocks node
with mouse's cursor and then toggling the create icon (a green plus
sign ) and type a name blk_no. And add a field s_no on the canvas.
for the 2nd one use Data Block Wizard and choose Table or View type
for the type of the block. There select the table(tbl1)'s both columns
(s_no and name) as Database Items.
And then, the forms must invoke
Layout Wizard automatically as default, where choose only name column
as displayed and leave s_no hidden as to be . Name the block as blk_names. This is a base-table block, and Data Source Name of the block blk_names is the table tbl1.
By the way, set Number of Records Displayed property to 10 as an example, and convert the name of the field name to snames as in your question.
Set block's WHERE Clause (in Database node) as s_no = :blk_no.s_no at the Property Palette. After
all, create a KEY-NEXT-ITEM trigger on s_no field with the inline
code :
go_block('blk_names');
execute_query;
At the runtime you can enter an integer value( let's give 1 as an example ) for s_no and populate the names field by pressing enter key ( the records with A and D will appear )
A button might be added with WHEN-BUTTON-PRESSED trigger having the code :
go_block('blk_names');
delete tbl2;
first_record;
while :blk_names.s_no is not null
loop
insert into tbl2 values(:snames);
next_record;
end loop;
commit;
to populate and re-populate the table tbl2( in this case tbl2 is populated with the records A and D ).
P.S. To suppress the message
FRM-40352: Last Record of Query retrieved
add an ON-MESSAGE trigger at the forms level wtih the code :
if message_code = 40352 then
null;
end if;
Related
I have sample data as follows:
taskname |skillname |user |Partition
--------------------------------------
taskAAAA |skill1111 |user3 |1
|skill2222 | |1
taskBBBB |skill1111 |user2 |2
taskCCCC |skill3333 |user1 |3
taskDDDD |skill1111 |user4 |4
|skill2222 | |4
If there are two skills belongs to a task, taskname and user will not repeat itself in taskname column and user column.
I manage to put partition to the same taskname. But I need to sort by user in ascending order and the records will follow its partition. The result in this case will be as follow:
taskname |skillname |user |Partition
--------------------------------------
taskCCCC |skill3333 |user1 |3
taskBBBB |skill1111 |user2 |2
taskAAAA |skill1111 |user3 |1
|skill2222 | |1
taskDDDD |skill1111 |user4 |4
|skill2222 | |4
Anyone can help me?
ANSI SQL supports NULLS LAST:
order by user nulls last
Not all databases support this construct. It is easily replace by a two-key search:
order by (case when user is not null then 1 else 2 end), -- "NULLS LAST"
user
First option is using an Order By keyword and NULLS LAST
select * from table order by user NULLS LAST
If your SQL doesn't support NULLS you can use the value IS NULL expression
select * from table order by user IS NULL, user
If the user's field is null then the expression IS NULL returns 1 else 0. So the rows with not null value(0) will be first, and the rows with null value(1) will be last when a ascending sort.
Next rows will be sorted by value of user's field.
I have a problem to produce a SSRS report that is like this:
This is how my output from stored procedure looks like:
Company Code | Company Name | Product Code | Product Name
ICE001 | Nestle | ICE001a | Drumstick Chocolate
ICE001 | Nestle | ICE001b | Drumstick KitKat
ICE001 | Nestle | ICE001c | Drumstick Chocolate
ICE002 | Walls | ICE002a | Cornetto Chocolate
ICE002 | Walls | ICE002b | Cornetto Latte
ICE002 | Walls | ICE002c | Cornetto La Liga
So how can I achieve this report structure in SSRS with the current stored procedure? Is it achievable?
Actually, yes you can. These are the steps:
Drag a table into the SSRS.
Make it 2 columns and 1 row.
On the first column, select the field you want as the sub data which in this case, Product Code.
In 2nd column, put in Product Name.
Now for the main data. Right click on the first column, select:
Insert Row > Outside Group - Above
Put the main data field which is Company Code at the first row, first column.
Put the Company Name expression at the second column, first row of the table.
Design as your preferred and generate your report :)
Doubt it. But if you adjust your dataset as below and use a table report it is.
select distinct code,company from icecream
union
select productcode,productname from icecream
I would like to consult / gather some ideas, from you guys, on possible solutions for unique constraint definition over nullable columns in oracle.
Let's have a table of customers
PK(ID), first_name, last_name are pretty obvious
EXT_CODE is Unique, visible in application, used to synchronization with 3rd party systems, means, that it's external ID first time delivered by other system, then remains unchanged whole lifetime
Example: update clients set first_name = 'ABC' where ext_code = 'ABC'
+---+-----------+-----------+-----------+
|ID |FIRST_NAME |LAST_NAME |EXT_CODE |
+---+-----------+-----------+-----------+
|1 |Peter |Pletan |ABC |
|2 |John |Dollar |DEF |
|3 |Mia |Zin |GHI |
|4 |Jasper |Blau |NULL |
|5 |George |Khan |NULL |
-----------------------------------------
Until now, everything is ok, I have EXT_CODE unique per this table, so there is always only one row returned, when update from external system is requested. When there is client with ext_code = null, it cannot be maintained from external system, because where something = null, never returns anything. There can be only one client with same EXT_CODE, but any number of those without this EXT_CODE (column is nullable)
Now comes the difficult part.
I decided, that in this table, data for more (independent) customers could be stored. For this reason, I added new column called CUSTOMER_CODE.
This code splits the table virtually into separate spaces, while every customer can see only her data.
For this purpose, oracle vpd (virtual private database) has been introduced.
Every customer uses her own oracle user
On logon, customer code is loaded
Predicate WHERE CUSTOMER_CODE = 'my_code' (loaded in step2) is appended to every query
Modified table might look following
+---------------+---+-----------+-----------+-----------+
|CUSTOMER_CODE |ID |FIRST_NAME |LAST_NAME |EXT_CODE |
+---------------+---+-----------+-----------+-----------+
|C1 |1 |Peter |Pletan |ABC |
|C1 |2 |John |Dollar |DEF |
|C1 |3 |Mia |Zin |GHI |
|C1 |4 |Jasper |Blau |NULL |
|C1 |5 |George |Khan |NULL |
|C2 |6 |Paul |Walker |1 |
|C2 |7 |Simon |Sleeper |2 |
|C2 |8 |Lian |Driver |3 |
|C2 |9 |Cor |Pilot |NULL |
|C2 |10 |Martin |Oldman |NULL |
---------------------------------------------------------
That is considered general overview. When customer C1 logs in, she sees only rows 1-5, while C2 6-10.
Here come the issues
Due to UNIQUE constraint on EXT_CODE, customer C1 and C2 cannot have same ext_code - following two rows already breaks the constraint
+---------------+---+-----------+-----------+-----------+
|CUSTOMER_CODE |ID |FIRST_NAME |LAST_NAME |EXT_CODE |
+---------------+---+-----------+-----------+-----------+
|C1 |1 |Peter |Pletan |ABC |
|C2 |2 |John |Dollar |ABC |
That is easily fixable, by instead of UNIQUE(ext_code), i make UNIQUE(CUSTOMER_CODE, EXT_CODE), what causes another issue - I can no more have 2 rows with ext_code empty, because C1, NULL and C1, NULL is the same from oracle's point of view. Example for these rows, ID=4,5. I could have these prior to customer introduction.
What are my possibilities now ?
1. Functional based index (drop unique constraint) - index, which would set both values null if any is null, so it doesn't get indexed at all => might be a solution, but indexes are not deferable in opposite to unique constraints
Trigger - which checks the data and throw exception (only if both values are not null)
Make ext_code not null - place regular unique constraint over combination (ext_code, customer_code) => not viable option
Other ideas - I would like to hear from you.
You haven't said which version of Oracle you're using, but from 11g you can use a virtual column with a unique constraint:
alter table customer add (unq_col varchar2(24) -- or necessary size
generated always as (case when ext_code is null then null
else customer_code||'~'||ext_code end));
alter table customer add (constraint unq_col_con unique (unq_col));
The generated column can be built any way you consider safe - with a delimiter if you can identify a character that can never be in one of the columns, or padding, or whatever is suitable.
Then trying to duplicate a code within a customer fails:
update customer set ext_code = 'ABC' where ext_code = 'DEF'
Error report -
SQL Error: ORA-00001: unique constraint (SCHEMA.UNQ_COL_CON) violated
00001. 00000 - "unique constraint (%s.%s) violated"
But with a different customer is OK:
update customer set ext_code = 'ABC' where ext_code = '1';
1 row updated.
customers:
+------------+--------------+
| cid | Name |
+------------+--------------+
| 1 | Bob |
| 2 | John |
| 3 | Jane |
+------------+--------------+
accounts:
+------------+--------------+
| aid | type |
+------------+--------------+
| 1 | Checking |
| 2 | Saving |
| 3 | Checking |
+------------+--------------+
transactions:
+------------+--------------+--------------+--------------+
| tid | cid | aid | type |
+------------+--------------+--------------+--------------+
| 1 | 1 | 1 | Open |
| 2 | 2 | 3 | Open |
| 3 | 1 | 2 | Open |
| 4 | 2 | 3 | Deposit |
+------------+--------------+--------------+--------------+
I am trying to write a trigger that writes to a logs table when a new account is successfully opened.
Right now I have this:
CREATE OR REPLACE TRIGGER acc_opened
BEFORE INSERT ON transactions
FOR EACH ROW
DECLARE
c_name customers.name%TYPE;
BEGIN
IF :new.type = 'Open' THEN
SELECT name into c_name
FROM customers c
WHERE c.cid = :new.cid;
INSERT INTO logs (who, what) VALUES (c_name, 'An account has been opened');
END;
/
The code that I have doesn't work and don't know where to go from here.
The trigger completes, but when it fires, I get this error message:
PLS-00103: Encountered the symbol "END" when expecting one of the following: (begin case declare exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execut commit forall merge pipe purge
As with your previous question, if you want to refer to a particular column of the new row of data, you need to use the :new pseudo-record. So, at a minimum,
SELECT cid
INTO c_id
FROM transactions t
WHERE t.aid = aid;
would need to be
SELECT cid
INTO c_id
FROM transactions t
WHERE t.aid = :new.aid;
Beyond that, are you sure that the row exists in the transactions table before the row is inserted into the accounts tale? Assuming that you have normal foreign key constraints, I would generally expect that you would insert a row into the accounts table before inserting the row into the transactions table.
The name transactions also seems pretty odd. If that is really just mapping the customer ID to the account ID, transactions seems like a rather poor name. If that table actually stores transactions, I'm not sure why it would have a customer ID. But if it does store transactions, there must be some other table that maps customers to accounts.
In your updated trigger, you are missing the END IF statement
CREATE OR REPLACE TRIGGER acc_opened
BEFORE INSERT ON transactions
FOR EACH ROW
DECLARE
c_name customers.name%TYPE;
BEGIN
IF :new.type = 'Open'
THEN
SELECT name
into c_name
FROM customers c
WHERE c.cid = :new.cid;
INSERT INTO logs (who, what)
VALUES (c_name, 'An account has been opened');
END IF;
END;
Let's say we have the following table structures:
documents docmentStatusHistory status
+---------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+---------+ +--------------------+ +----------+
| ... | | docId | | ... |
+---------+ | statusId | +----------+
| ... |
+--------------------+
It may be obvious, but it's worth mentioning, that the current status of a document is the last Status History entered.
The system was slowly but surely degrading in performance and I suggested changing the above structure to:
documents docmentStatusHistory status
+--------------+ +--------------------+ +----------+
| docId | | docStatusHistoryId | | statusId |
+--------------+ +--------------------+ +----------+
| currStatusId | | docId | | ... |
| ... | | statusId | +----------+
+--------------+ | ... |
+--------------------+
This way we'd have the current status of a document right where it should be.
Because the way the legacy applications were built I could not change the code on legacy applications to update the current status on the document table.
In this case I had to open an exception to my rule to avoid triggers at all costs, simply because I don't have access to the legacy applications code.
I created a trigger that updates the current status of a document every time a new status is added to the status history, and it works like a charm.
However, in an obscure and rarely used situation there is a need to DELETE the last status history, instead of simply adding a new one. So, I created the following trigger:
create or replace trigger trgD_History
after delete on documentStatusHistory
for each row
currentStatusId number;
begin
select statusId
into currentStatusId
from documentStatusHistory
where docStatusHistoryId = (select max(docStatusHistoryId)
from documentStatusHistory
where docId = :old.docId);
update documentos
set currStatusId = currentStatusId
where docId = :old.docId;
end;
And thats where I got the infamous error ORA-04091.
I understand WHY I'm getting this error, even though I configured the trigger as an AFTER trigger.
The thing is that I can't see a way around this error. I have searched the net for a while and couldn't find anything helpful so far.
In time, we're using Oracle 9i.
The standard workaround to a mutating table error is to create
A package with a collection of keys (i.e. docId's in this case). A temporary table would also work
A before statement trigger that initializes the collection
A row-level trigger that populates the collection with each docId that has changed
An after statement trigger that iterates over the collection and does the actual UPDATE
So something like
CREATE OR REPLACE PACKAGE pkg_document_status
AS
TYPE typ_changed_docids IS TABLE OF documentos.docId%type;
changed_docids typ_changed_docids := new typ_changed_docids ();
<<other methods>>
END;
CREATE OR REPLACE TRIGGER trg_init_collection
BEFORE DELETE ON documentStatusHistory
BEGIN
pkg_document_status.changed_docids.delete();
END;
CREATE OR REPLACE TRIGGER trg_populate_collection
BEFORE DELETE ON documentStatusHistory
FOR EACH ROW
BEGIN
pkg_document_status.changed_docids.extend();
pkg_document_status.changed_docids( pkg_document_status.changed_docids.count() ) := :old.docId;
END;
CREATE OR REPLACE TRIGGER trg_use_collection
AFTER DELETE ON documentStatusHistory
BEGIN
FOR i IN 1 .. pkg_document_status.changed_docids.count()
LOOP
<<fix the current status for pkg_document_status.changed_docids(i) >>
END LOOP;
pkg_document_status.changed_docids.delete();
END;
seems to be a duplicate of this question
check out Tom Kyte's take on that