Hot to get stored procedure SQL text? - stored-procedures

Is there any way to retrieve (and update) the actual stored procedure SQL text from a data dictionary?
A sample use case for this would be to write a replacement tool for the Advantage Data Architect. However at the moment I really need this to workaround a bug in ADS 9.1.
I imagine there must be something like:
EXECUTE PROCEDURE sp_GetStoredProcedureProperty('PROCNAME', 'SQLTEXT');
I found a system procedure called sp_ModifyProcedureProperty that can be used to modify some parts of a SP:
http://devzone.advantagedatabase.com/dz/WebHelp/Advantage10.1/index.html?master_sp_modifyprocedureproperty.htm

There is a system table in the dictionary called system.storedprocedures which has a field named SQL_Script that does what I need.
Example:
SELECT SQL_Script FROM system.storedprocedures WHERE Name = 'PROCNAME';

Related

PL SQL - Procedure - table that holds a record of users who executed a procedure

Hi and apologies in advance if the question has already been asked. I haven't been able to come across the answer.
I'm wondering if there is a table that holds a record of oracle usernames that have executed a particular procedure or function.
I'm trying to create a procedure that can be called as a subprogram by another procedure. The procedure which i'm looking to create will create a log entry every time the other procedure is executed. Example below;
User_Name = The Oracle user name of the person who executes the function.
Name = The name of the procedure or function.
LastCompileDT = The date/time the function or procedure was last compiled.
I'm a bit stuck on where to source the data from.
I've come across the all_source table but it only gives me the owner of the procedure and not the executing user.
Any feedback would be greatly appreciated.
Thanks
There might be a couple of ways to do that. Maybe someone else can suggest a method of extracting all this data from one data dictionary view. However, my method would be like this:
User_Name: use the keyword USER. It returns the Oracle user that executed the procedure:
SELECT USER FROM DUAL;
However, if you are interested in the OS user who executed that procedure, then you can use the following
SELECT sys_context( 'userenv', 'os_user' ) FROM DUAL;
More on this here. To my knowledge, this can be fetched on the fly only, and it is not logged anywhere by default. So you need to run it when you call the procedure.
Procedure Name: &
LastCompileDT : can be fetched from the view USER_OBJECTS
SELECT OBJECT_NAME, LAST_DDL_TIME
FROM USER_OBJECTS
WHERE OBJECT_TYPE = 'PROCEDURE'
AND OBJECT_NAME = '<YOUR PROCEDURE NAME>';
Rather than rolling your own audit, you could use the inbuilt auditing table provided.
See https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_4007.htm
--Create a test procedure as an example
CREATE PROCEDURE my_test_proc
AS
BEGIN
NULL;
END my_test_proc;
--Turning on auditing executions of the proc
AUDIT EXECUTE ON my_test_proc BY ACCESS WHENEVER SUCCESSFUL;
--Run the proc
EXEC my_test_proc;
--check audit history
SELECT *
FROM dba_common_audit_trail cat
WHERE cat.object_name = 'MY_TEST_PROC';
The dba_common_audit_trail table has columns DB_USER, and OBJECT_NAME for your User_Name/Name.
For the last compiled time see Hawk's answer, or if you want to see a history of last DDL times you can add this to the audit
--Turn on auditing of creating procs
AUDIT CREATE PROCEDURE BY ACCESS;

Delphi knows if there's a new row after query refresh

I'm running a MySQL table with orders table from E-commerce running on VPS(Centos6).
I'm doing querys on desktop application that notify when there's a new order.
I have a query (TFDQuery) running at FormCreate event. I'm refreshing this query with a TTimer.
Is there some NATIVE way to know if a new row exists after refresh?
What i'm doing for now?
I'm counting rows number with query.RecordCount; After query on FormCreate, setting it to a public var. At the Timer event I'm doing the same thing but with local var.
-
public
a : integer;
....
FormCreate event after query result:
a := query.RecordCount;
Timer event:
var
b: Integer;
begin
query.Refresh();
b:=query.RecordCount;
if (b>a) then
begin
//do what i want to do
a := query.RecordCount;
end;
end;
Well, everything works fine. But is this the right way? I've been searching around for a case like mine, but i didn't find anything.
Is there some native way to do it ?
I do have DevExpress components.
From your reference to FDQuery, I assume you're using FireDAC, so in theory you should be able to do this using a TFDEventAlerter, which can receive events from various RDMSs and feed them to your app as Delphi-style events.
See http://docwiki.embarcadero.com/RADStudio/XE8/en/Database_Alerts_%28FireDAC%29
Unfortunately now that you've mentioned in a comment that your RDMS is MySQL, I don't think TFDEventAlerter will help, because I don't think its amongst the RDMSs that TFDEventAlerter supports. I don't think MySQL can provide the types of notification that TFDEventAlerter needs. But don't take my word for it, try it.
Btw, if your table rows have a row ID column, then a way to find out whether rows have been added by another user with less load on the server than doing a full refresh is to make a record of the highest ID your query returns and periodically do a
Select Count(*) from mytable where ID > :ID
and only refresh your query if that returns a value greater than zero.
Btw, in some Delphi versions prior to XE8, there is a TFDEventAlerter demo:
C:\Users\Public\Documents\Embarcadero\Studio\14.0\Samples\Object Pascal\Database\FireDAC\Samples\Comp Layer\TFDEventAlerter.
but this demo seems to be missing from XE8 (in my set-up, at any rate) and the ones from earlier versions won't compile in XE8 because of changes made in the FireDAC.Stan.Intf unit.

how do i execute a stored procedure with vici coolstorage?

I'm building an app around Vici Coolstorage (asp.net version). I have my classes created and mapped to my database tables and can pull a list of all records fine.
I've written a stored procedure where the query jumps across databases that aren't mapped with Coolstorage, however, the fields in the query result map directly to one of my classes. The procedure takes 1 parameter.
so 2 questions here:
how do i execute the stored procedure? i'm doing this
CSParameterCollection collection = new CSParameterCollection();
collection.Add("#id", id);
var result = Vici.CoolStorage.CSDatabase.RunQuery("procedurename", collection);
and getting the exception "Incorrect syntax near 'procedurename'." (i'm guessing this is because it's trying to execute it as text rather than a procedure?)
and also, since the class representing my table is defined as abstract, how do i specify that result should create a list of MyTable objects instead of generic or dynamic or whatever objects? if i try
Vici.CoolStorage.CSDatabase.RunQuery<MyTable>(...)
the compiler yells at me for it being an abstract class.
There's a shortcut in CoolStorage to run a stored procedure. Simply prefix the stored procedure name with "!":
CSDatabase.RunQuery("!procedurename", collection);

Delphi ClientDataset Read-only

I am currently testing with:
A SQLConnection which is pointed towards an IB database.
A SQLDataset that has a SQLConnection field set to the one above.
A DatasetProvider that has the SQLDataset in (2) as its Dataset field value.
A ClientDataset, with the ProviderName field pointing to the provider in (3).
I use the following method (borrowed from Alister Christie) to get the data...
function TForm1.GetCurrEmployee(const IEmployeeID: integer): OleVariant;
const
SQLSELEMP = 'SELECT E.* FROM EMPLOYEE E WHERE E.EMPLOYEEID = %s';
begin
MainDM.SQLDataset1.CommandText := Format(SQLSELEMP, [Edit1.Text]);
Result := MainDM.DataSetProvider1.Data;
end;
Which populates the DBGrid with just one record. However, when I manually edit the record, click on Post, then try to commit the changes, using
MainDM.ClientDataset1.ApplyUpdates(0); // <<<<<<
It bombs, with the message "SQLDataset1: Cannot modify a read-only dataset."
I have checked the ReadOnly property of the Provider, and of the ClientDataset, and the SQL has no joins.
What could be causing the error?
It appears that your ClientDataSet.Data property is being populated from the Data property of the DataSetProvider. With the setup you described, you should be able to simply call ClientDataSet.Open, which will get the data from the DataSetProvider.
BTW, the default behavior of the DataSetProvider when you call the ClientDataSet.ApplyUpdates method is to send a SQL query to the connection object, and not the DataSet from which the data was obtained (assuming a homogeneous query). Make sure that your DataSetProvider.ResolveToDataSet property is not set to true.
Finally, on an unrelated note, your code above appears to be open to a SQL injection attack (though I have not tested this). It is safer to use a parameter to define the WHERE clause. If someone enters the following into Edit1 you might be in trouble (assuming the InterBase uses the drop table syntax): 1;drop table employee;
Check the LiveMode property of the TIBDataSet.

How to prevent Delphi ADO from loading the entire table into memory?

I am not a Delphi programmer, but I I got an old Delphi 7 application that I need to fix and it is using ADO.
The database table (MS Accesss) contains +100,000 rows and when I set the ADOTable.Active=true it starts to load the entire table into RAM and that takes a lot of memory and time.
How can I prevent ADO to load the entire table? I tried to set the MaxRecords but it does not help.
Basically all we do is att program startup:
// Connect to database
DataModule.MyADOConnection.Connected:=true;
DataModule.MeasurementsADOTable.MaxRecords:=1;
// Open datatables
DataModule.MeasurementsADOTable.Active:=true;
After setting Active=true it starts to load the entire measurements into RAM and it takes TIME!
We are using the MSDASQL.1 provider. Perhaps it does not support the MaxRecords property?
How do I add some limiting query into this data object to only "load TOP 1 * from Measurements" ?
You could use TADOQuery to limit the result set with a sql query. Or you could use TADOTable and set the CursorLocation to a Server side cursor to prevent the client loading the complete resultset in memory.
You could use that adoTable with an Server OpenForwardOnly cursor and
an TCLientDataset with PacketRecords set to nonzero value. Worked
wonderfully when I had to write an app to pump data from MSSQL to
Oracle on a customized way with tables with millions of records.
EDIT -> It would be something on the lines of this:
procedure ConfigCDSFromAdoQuery(p_ADOQ: TADOQuery; p_CDS: TClientDataset; p_Prov: TDatasetProvider);
begin
If p_ADOQ.Active then p_ADOQ.Close;
p_ADOQ.CursorLocation := clServer;
p_ADOQ.CursorType := ctOpenForwardOnly;
p_Prov.Dataset := p_ADOQ;
p_CDS.SetProvider(p_Prov);
p_CDS.PacketRecords := 100;
p_CDS.Open;
end ;
I've done this all by code, but most of that you can do in design-time.
This article is BDE specific, but applies to ADO or most client data access libraries.
http://dn.codegear.com/article/28160
I would recommend using TADODataSet (it's "closer" to the ADO layer than TADOQuery) and selecting only the data the client needs by providing a custom search form (date range, list of specific items, etc)
Good luck
On your datamodule where "MeasurementsADOTable" currently resides, drop a TADOQuery and name it "MeasurementsADOQuery"
Set the Connection property of MeasurementsADOQuery to MyADOConnection (assuming this is the case based on the little code snippet provided.)
I'm also assuming that you are displaying a grid or otherwise using a DataSource - change the DataSource component's "DataSet" property from MeasurementsADOTable to MeasurementsADOQuery
Edit the actual query to be executed by setting the SQL property of MeasurementsADOQuery. (In runtime before opening: Measurements.SQL.Text := 'select top 10 * from measurements order by whatever')
Analyze/change all references in code from MeasurementsADOTable to MeasurementsADOQuery
Dont make the adotable active on startup and turning it true later is one way but still not really gonna help....use a adodataset and populate that rather as needed during runtime with your connection text. Only relevant data will be retrieved making it much faster.
use adoquery
If you do not need any row and just want insert new row use sql command like this
'select * from myTable where id=-1'
Since Id is autonumber no rows will return .
or
'select * from myTable where 1=-1'
But I think it is not good way for Insering data. Using adocommand is sure much better.
if you want X rows
'select top X * from myTable '
In furthering Fabrico's answer above, I have legacy application which has a table with 177000 rows and 212 columns. Upon trying to open this table I get the error 'table already open' and no records are available for update. setting Table.CursorLocation := clUseServer;
fixed this issue for me.
I have found ADO + Access w/Delphi to be painfully slow, for lots of things (big table reads like you're describing, but also inserts as well, etc). My answer became "Quit using ADO and Access altogether." Never did understand why it performed so poorly, especially when earlier technologies seemed not to.

Resources