0

I have a package that allows me to create a table from the output of a stored procedure REF_CURSOR variable. I use this with DevPress XPO Source in order to return large results to my client application.

I used to create a solid table, add a key, index it and return the new table name to the client which is provided to the XPO source, and it is working. However, using solid tables is not the best solution, so I started using a GTT.

If I execute the package in TOAD, my data is preserved, but if I execute the command from C#, there is no data in my table right after execution. The connection is not closed yet, so I am not 100% sure why my data is not there.

Is there something in the Connection Context that I can set to make sure that all executions happen in the same session? There is an execute immediate statement to populate the table, and I think that TOAD might use the same context when a I execute the package.

Here is some of my code:

FUNCTION Build_Table_from_Cursor(REF_CURSOR SYS_REFCURSOR, ID NUMBER, AddKeyField CHAR) RETURN VARCHAR2 AS
  QueryCursor SYS_REFCURSOR;
  CursorNumber NUMBER;
  p_tablename varchar2(30);
  pk_name varchar2(30);
BEGIN

   QueryCursor := REF_CURSOR;
   CursorNumber := DBMS_SQL.TO_CURSOR_NUMBER(QueryCursor);
   p_tablename := 'TEMPTABLE';

   UTIL.create_table_from_cursor(CursorNumber, p_tablename);  --This creates the GTT with all the columns
   Execute immediate 'TRUNCATE TABLE ' || p_tablename;  --To Add the key this must be done otherwise there is an error

   pk_name := substr(p_tablename, INSTR(p_tablename, '.') + 1);

   IF(AddKeyField = 'Y') THEN    --Sometimes the Key field already exists
     EXECUTE IMMEDIATE 'ALTER TABLE ' || p_tablename || ' ADD (KEY_FIELD_   NUMBER)';
   END IF;

   EXECUTE IMMEDIATE 'CREATE UNIQUE INDEX ' || p_tablename || 'KEY_INDEX ON ' || p_tablename || '   (KEY_FIELD_)';
   EXECUTE IMMEDIATE 'ALTER TABLE ' || p_tablename || ' ADD CONSTRAINT pk_' || pk_name || ' PRIMARY KEY( KEY_FIELD_ )';

   QueryCursor := DBMS_SQL.TO_REFCURSOR(CursorNumber);

   PDS.UTIL.POPULATE_TABLE_FROM_CURSOR(QueryCursor, p_tablename, 1000);  --This populates the table

   EXECUTE IMMEDIATE 'UPDATE ' || p_tablename || ' SET KEY_FIELD_ = ROWNUM';
  COMMIT; 
  return p_tablename;
END Build_Table_from_Cursor;

This works perfectly when I execute in TOAD.

When I run this

using (var conn = factory.CreateConnection(Dal.ConnectionStrings[connectionString].ConnectionString))
{
    conn.Open();
    using (var cmd = factory.CreateCommand(CommandType.StoredProcedure, storedProcedureName))
    {
        var storedProcedureRow = commandExecuteDataSet.StoredProcedure[0];
        foreach (var parametersRow in commandExecuteDataSet.Parameters)
        {
            cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter(parametersRow.Name, parametersRow.Value ?? "", GetDBTypeFromString(parametersRow.OracleDbType)));
        }
        cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter(storedProcedureRow.RefCursorName, DbType.Object, ParameterDirection.Output, true));
        cmd.ExecuteNonQuery();
        var refCursor = cmd.Parameters[storedProcedureRow.RefCursorName].Value;
        cmd.Parameters.Clear();
        cmd.CommandText = "SERVERMODE_UTIL.Build_Table_from_Cursor";
        cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter("REF_CURSOR", refCursor));
        cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter("ID", biID));
        cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter("AddKeyField", "Y"));
        cmd.Parameters.Add(CustomDbProviderFactory.CreateParameter("p_tablename", DbType.String, ParameterDirection.ReturnValue));

        cmd.ExecuteNonQuery();
        var tempTableName = cmd.Parameters["p_tablename"].Value.ToString();
        tempTableName = tempTableName.Substring(tempTableName.IndexOf(".") + 1);
    }
}

As part of a larger package, this is the code that is executed to create the GTT

l_statement := 'CREATE GLOBAL TEMPORARY TABLE ' || l_tablename || ' (' || CHR(13) || CHR(10) || l_statement || CHR(13) || CHR(10) || ') ON COMMIT PRESERVE ROWS';
execute immediate l_statement;
Jaques
  • 2,215
  • 1
  • 18
  • 35
  • 1
    This is a single-user application? The definition of a global temporary table is visible to everyone so if two users try to run the code with the same temporary table name, you'll get an error. In general, creating objects at runtime in Oracle is heavily frowned upon. My guess is that you're declaring the table to be `on commit delete rows` (the default) and that your front-end is implicitly committing between populating the table and querying it. – Justin Cave Sep 25 '15 at 10:20
  • It is not a single-user-application, but I create a new GTT for the execution and the table is dropped afterwards because the procs can have different result sets. The table is created with `ON COMMIT PRESERVE ROWS`. I'm adding the code above – Jaques Sep 25 '15 at 10:24
  • @JustinCave, what is a better solution then apart from creating a GTT? The reason why I need to do this is because some clients have slow connections, and to stream a million records to the client time-out. That is why I use XPO but unfortunate you need to have a key column for it to work properly, because XPO select from the server chunks of Data as you go down the Grid etc. – Jaques Sep 25 '15 at 10:33
  • If there are multiple sessions, you've got an issue. If my session creates the temporary table and is the process of populating it and your session tries to do the same, your session will fail. Unless you are planning on serializing the application so that I have to wait to create, populate, and read from the table until you're done. I have no idea what XPO is and Google doesn't return anything obvious in the first page of results. If you want to stream millions of rows to the client, you're probably doing something wrong. – Justin Cave Sep 25 '15 at 10:37
  • Plus, it's completely non-obvious to me how a temporary table impacts a client timeout. If you have to send millions of rows to a client machine for some reason, why would it matter whether the data was coming from a query against a temporary table or from a query against one or more permanent tables? – Justin Cave Sep 25 '15 at 10:39
  • Thanks for your comments. XPO is eXpress Persistent Objects from DevExpress. The difference is that XPO does not stream all the records, but as you scroll the Grid like I said. I used to use solid tables, but it impacts the logs etc. Each execution will have its own GTT table, so 2 users will never have the same table at the same time, so errors is not my concern. – Jaques Sep 25 '15 at 10:46
  • What I do realize now though, If User A create the GTT and populates it, Will User B be able to see the data if the session of User A is still open? – Jaques Sep 25 '15 at 10:47
  • The data is local to the session. The definition of the table is visible to everyone. If you can guarantee that the table name is unique, you'd just have the excessive locking, hard parsing, and lack of caching to deal with. I have no idea what a "solid table" is. Any random query from a front end client will require the client to fetch data in batches. It won't, unless the client specifically asks for a million rows, send a million rows over the network. It makes no difference whether the query is against a permanent table or against a temporary table. – Justin Cave Sep 25 '15 at 10:52

0 Answers0