Cognos is a wonderful BI platform, but it does not handle write-backs to the database very well. By no means should Cognos be considered a data entry platform, but there are times when you may want to store comments associated with a specific report. Other times you may want to give users the ability to enter and save targets for specific data. It is even possible to set up a system to store the prompt selections in a report in a table.
The solution presented here was created in 10.2.1, but will work in every version since 8.4.
At the most basic level, writing back data is very simple. You run a stored procedure which takes specific parameters and passes them to the database. A system for entering comments is a good example:
Multiple comments:
To begin with, let’s take a look at the stored procedure:
ALTER PROCEDURE [dbo].[insertDateComment] @dateKey numeric(18,0) , @comment varchar(1000) AS BEGIN SET NOCOUNT ON; INSERT dbo.datecomments (DateKey,comment) VALUES (@dateKey, @comment) ; SELECT @@ROWCOUNT as "rowCount" END
You can’t get much simpler than this. It accepts two values, dateKey as a numeric and comment as a string, and inserts them into a table. In real-world usage more fields would be necessary, such as the username running the procedure, or the report name, or any other metadata you would want tracked. Since we’re using this in a Cognos report, a value must be returned. Even a simple “select 1 as whatever” would be sufficient.
In Framework, import the SP and populate the input parameters with prompt macros:
There are two reports shown, the first report is a simple list showing date and comment (if exists). That’s accomplished by joining the comments table and the time dimension.
The second report contains a conditional block, a singleton, and two input boxes. When the report runs, the conditional block checks if the comment parameter is populated. If not, it renders the first page containing a hidden input for the dateKey, and a visible textbox for the comment. The user enters a comment and presses the refresh button. Again, the report runs, checking if the comments parameter is populated. Since it is this time, it renders the singleton block. The singleton contains the reference to the procedure. Since the parameter names in the textboxes match the parameter names from the procedure, it runs those. As the return value from the procedure is useless to the end user I hide it by checking “rowCount” in the properties of the singleton, and dropping a text item with a descriptive “data entered” message.
So far this has all been simple. But what happens when you want to enter multiple values? One at a time simply won’t do. The trick here is to dynamically change the name of the parameter in the framework. This method is a little more complex, as it uses HTML items and a little bit of JavaScript.
In this method the stored procedure is upserting the value into the table. The SQL for the procedure is using the merge function, which is not the most efficient way of doing an upsert.
ALTER PROCEDURE [dbo].[upsertValue] @DateKey [numeric](18, 0) , @Value [numeric](18, 0) AS BEGIN SET NOCOUNT ON; MERGE dbo.DataEntry as t USING (select @DateKey as DateKey, @Value as Value) as n on t.DateKey = n.DateKey WHEN MATCHED then update set t.Value = @Value WHEN NOT MATCHED THEN INSERT (DateKey,Value) VALUES (@DateKey, @Value) ; SELECT @@ROWCOUNT as "rowCount" END
The reference to the procedure uses prompt macros, but with a slight twist.
The prompt for the macro is set to #prompt(prompt(‘DateKey’,’integer’,’0′)+’Value’,’integer’,’0′)#
The DateKey prompt will actually change the parameter name for the Value prompt. So for date 1100, the parameter for the value would be 1100Value.
In the report, a simple list of dates is entered, with an HTML item
The HTML item is a report expression with the expression:
'<input type="text" name="p_'+number2string([DataEntry].[Day Key])+'Value" value="' +case when [Value] is null then '' else number2string([DataEntry].[Value]) end +'" onchange="updateDateArr('+number2string([DataEntry].[Day Key])+')" />'
Each row will generate an input box, the name will be the dateKey + ‘Value’ (just like the parameter in Framework). Parameters are populated by inputs with a specific name. So an input with the name p_123Hello will populate the parameter 123Hello. You would be able to use that parameter in a query, or on the page with paramDisplayValue(‘123Hello’).
When a change is made to the input, it will be added to a list of dateKeys, that list is to ensure the procedure is run on updated values only:
<script> var fW = (typeof getFormWarpRequest == "function" ? getFormWarpRequest() : document.forms["formWarpRequest"]); if ( !fW || fW == undefined) {fW = ( formWarpRequest_THIS_ ? formWarpRequest_THIS_ : formWarpRequest_NS_ );} var preFix = ""; if (fW.elements["cv.id"]){preFix = fW.elements["cv.id"].value;} var nameSpace = "oCV" + preFix; var dateArr = []; Array.prototype.unique = function() { var o = {}, i, l = this.length, r = []; for(i=0; i<l;i+=1) o[this[i]] = this[i]; for(i in o) r.push(o[i]); return r; }; function updateDateArr(dateKey){ dateArr.push(dateKey); dateArr=dateArr.unique(); document.getElementById('p_UpdatedDates').value=dateArr.join(','); } </script> <input type="hidden" name="p_UpdatedDates" id="p_UpdatedDates">
Whenever the updateDateArr is called, it will add the new dateKey into the array, make the array unique, and then push it into the p_UpdatedDates input.
In the previous method, the stored procedure was only called when the Comments parameter was populated. The same thing is possible here. Create a conditional block with the Boolean variable paramValue(‘UpdatedDates’) is not null.
Inside the yes block, drag in a new list, stick in the dateKey, and filter that query with
dateKey in (#prompt('UpdatedDates','token')#)
Make sure to set the pagination options of the list. Check “Repeat every page” and uncheck “Allow contents to break across pages” and “Allow horizontal pagination”. This will ensure that the entire list appears in one page. In the same thread, go to File -> Report Properties, and set “Page break by data container for interactive HTML” to Yes. Without setting this option, the next list would be pushed to the next page.
When the report is run with updated values, that list will generate only those rows with updated dates.
Put a list in that list, drag in the rowCount from the upsertValue procedure, and define a master detail connection from Day Key to the parameter DateKey:
Now when you run it, it will update the values.
Hiding the list is a simple as wrapping it with a hidden div:
<div style=”display:none”> to the left of it, </div> to the right.
Here it is:
This post is a bit unusual in that it has several files attached. Three reports, the framework model, and an SQL Server 2012 backup.
Reports:
Data-Entry-Reports.zip (1804 downloads)
Model:
Data Entry - Model.zip (1404 downloads)
Database backup (SQL Server 2012):
Data Entry Database (1489 downloads)
Wonderfull post paul…we have used a combination of tm1 and cognos bi for rewriting back into db. i knew about the possibility of using stored procedure & macros, but this article is just great to understand how to do and what happens behind. ty paul and al the best for 2014 🙂
Thanks Mike! I appreciate it.
My experience with TM1 (several years ago) showed me that the writeback only works from the TM1 portlets. I’ve been trying to get some of my clients to do a demo of TM1, since there’s an API, I’ve been wondering if it’s possible to do a writeback from inside a cognos report.
I’ve also fixed the data entry database link.
Paul,
Can you downgrade the cpf file to be loaded in Cognos Framework 10.1.1?
The report too…
Thanks.
You should be able to downgrade the reports simply by changing the version number in the xml. I’ll see about downgrading the model on Sunday.
Joseph, in the model.xml, try changing http://www.developer.cognos.com/schemas/bmt/60/5 to http://www.developer.cognos.com/schemas/bmt/60/3 (or less).
In the report XMLs, change http://developer.cognos.com/schemas/report/11.0/ to http://developer.cognos.com/schemas/report/8.0/
FM and RS look at the those versions to determine if it can open them. The rest of the XML should be identical.
Brilliant.
Also, I really liked you method of making an array unique. I was not aware that when looping through an object JS will only bring unique values.
Hi Paul,
Is the database link broken ? Its downloading a 4 KB file of a strange format 🙁
Best Regards
Dev
Sorry for the late reply, this somehow got into the spam folder. It looks like it’s working for me. If it’s still not working, use this link: https://cognospaul.com/samples/Data%20Entry%20Database.bak
Thanks a lot.
Paul, how would I adapt your technique for capturing selections in a static list drop-down instead of a free form comment?
A static list in each row? Same basic idea, but a bit more complicated. You need to generate the HTML of the select prompt. 3 HTML Items and a Repeater (not a repeater table) should do it.
HTML 1 (text):
<select name=”p_myPrompt”>
<repeater properties on disp and use>
HTML 2 (report expression):
‘<option value=”‘ + [Query].[UseValue] +’>’+[Query].[Disp]+'</option>’
HTML 3 (text outside of repeater):
</select>
That should generate a drop down list in each row.
Paul,
Thanks so much for your reply. Will try and give you feedback.
Regards,
Shawn
Hey Paul,
Okay, a Repeater requires a new Query. What data should populate my Query? Is it the same data as in my static list? And, yes, your assumption is correct that I would need a dropdown static list for each row.
For example, if I had a Status static list with value 1-Call, 2-No Call, 3-Other, how would I structure my repeater and integrate that into your current report? Can you send me a report spec with the Repeater if you don’t mind? Would this go into the Upsert Multiple Values report?
Best regards,
Shawn
Hi Paul,
This post was so helpful in understanding, Can you please share the procedure with Oracle to update /write-back multiple row inputs into the table from a cognos report.
I tried to replicate your procedure with version of mine on Oracle as below.. My query was not successful even for compilation.
create or replace PROCEDURE Update_L_LOAD_FACTOR_NEW (
P_AIRLINE AIRLINE%TYPE,
P_HOUR HOUR_L%TYPE,
P_LOAD_FACTOR_P LOAD_FACTOR_P%TYPE,
P_TRANSFER_P TRANSFER_P%TYPE,
P_DIRECT_P DIRECT_P%TYPE ,
p_recordset out SYS_REFCURSOR) as
BEGIN
Merge into L_LOAD_FACTOR t
using (select * from L_LOAD_FACTOR a)
on (a.AIRLINE=P_AIRLINE and a.HOUR_L=P_HOUR)
when not MATCHED then
INSERT (t.AIRLINE,t.HOUR_L,t.LOAD_FACTOR_P,t.TRANSFER_P,t.DIRECT_P)
values (P_AIRLINE,P_HOUR,P_LOAD_FACTOR_P,P_TRANSFER_P,P_DIRECT_P)
When MATCHED then
UPDATE set t.DIRECT_P=P_DIRECT_P , t.TRANSFER_P=P_TRANSFER_P , t.LOAD_FACTOR_P=P_LOAD_FACTOR_P
where (t.AIRLINE=P_AIRLINE and t.HOUR_L=P_HOUR);
open p_recordset for select * from SDS_DXB.L_LOAD_FACTOR;
end Update_L_LOAD_FACTOR_NEW;
write back works in MS SQL server DB but it does not work in Oracle backend. any sample procedure for Oracle Backend?
Unfortunately I don’t have any example sps for Oracle. None of my current clients give me DB permissions to do, so I’m stuck with my developer licensed MSSQL server on my laptop.
When making your SP, make sure that you have a select at the end. Cognos needs that to be able to process the results.
What I’d like to do is click a button that says ‘reviewed’ which then writes to a database the customer id for that row and the time stamp it was clicked in to a table.. no text to input.. how would I go about that please Paul?