Author:

PSQuery Metadata Table Names

Sometimes it is nice to view the metadata of your PSQuery definitions.

List of Tables

You can search the PSRECDEFN table for all records beginning with PSQRY.

SELECT RECNAME, RECDESCR, PARENTRECNAME FROM PSRECDEFN 
WHERE RECNAME LIKE 'PSQRY%' AND RECTYPE = 0

The below list is as of 8.54, with a description of each.  We’ll then follow up  this post with some unique tricks about the meta data tables (coming soon).

Core Query Definition Tables

  • PSQRYDEFN-Query Definition
    • PSQRYEXPR-Query Expression
    • PSQRYBIND-Query Prompt
    • PSQRYSELECT-Query Select
      • PSQRYRECORD-Query Record
      • PSQRYFIELD-Query Field
        • PSQRYFIELDDEP-Query Field Dependency
      • PSQRYCRITERIA-Query Criteria

Other Tables

  • PSQRYFAVORITES-Query Manager Favorites Table
  • PSQRYXFORM-Query Transformation
  • PSQRYDEL-Query Definition Deletes
  • PSQRYLINK-Unknown
  • PSQRYPREFS-Unknown
  • PSQRYFLAGS-Query Global Flags Table
  • PSQRYSTATS-Query RunTime Statistics
  • PSQRYEXECLOG-Query RunTime Log
  • PSQRYTRANS-Unknown
  • PSQRYACCLSTRECS-Query Access Record List
  • PSQRYDEFNLANG-Query Definition Alt. Language
  • PSQRYBINDLANG-Query Prompt Alternate Lang.
  • PSQRYFIELDLANG-Query Field Alternate Language

Prompt For Partial Search – No Percent Sign Needed

Allow user to enter a prompt, without having to add the percent sign.  Great for searching for partial values.

Issue

If you use a Like operator in your criteria with prompts, you have to teach the user how to use the percent sign (%) to allow partial searches.  Most users learn this while using PeopleSoft pages.  But there is a way to allow the user to use a prompt field, without having to add the percent sign, and yet perform a Like operation.

Here is the issue we ran into, followed by the solution.  In criteria, if you choose the operator Like, with the left hand side being a field, the right hand side of the operation can only be a constant or prompt.  If it allowed an expression, you could sandwich the prompt between two percent signs.

Solution

The work around for Microsoft SQL Server is to make the left hand side be the following expression.  (Keep reading for the Oracle solution.) We utilized MS SQL Server’s CHARINDEX function, which is for all purposes is a FIND.  The following expression will return which character position the pattern is found in, and will return a zero if the pattern could not be found.

CHARINDEX('a', 'abcdef')

To add some more muscle around this, let’s add a prompt for the user to enter a partial string.  For our example, we’ll use the Vendor Name field (record PS_VENDOR, field NAME1).  Let’s assume this is the first prompt, which would be known as :1  (colon plus the number 1).
CHARINDEX(:1, A.NAME1)

Next, we will add the percent signs.
CHARINDEX('%' + :1 + '%', A.NAME1)

Next, we can beef up the example with Upper case functions.  That way the user can just enter the string without case concerns.
CHARINDEX(Upper('%' + :1 + '%'), Upper(A.NAME1))

Finally, we’ll finish the criteria by selecting the operator “>” (greater than), and the right hand side will be the constant value of zero (0).

Oracle Solution

For Oracle Databases, you can use INSTR.  The first two parameters are swapped though.  (And yes, I probably should have used %Concat for the + and/or || signs.  The function CHARINDEX vs. INSTR already assumes you know which database you are working in, so we might as well hardcode the concatenation symbols too.)

INSTR(Upper(A.NAME1), Upper('%' || :1 || '%'))

Final note

Be sure to let the user know (in the prompt label) that the percent sign should not be used/needed.

Also, there could be performance issues, if there is a lot of data.  The database may be unable to use an existing index when using the CHARINDEX or INSTR function.

Left Outer Join Tricks

Left Outer Joins are useful tools!  Here are two tricks to get around some issues you may discover.

A left outer join lets two tables be joined in the following manner:  All rows from the “left” table are returned, even if a match cannot be found on the “right” table.  This differs from normal joins (called an inner join), where a match has to be found for both tables (assuming you did the WHERE criteria correctly).

Practical Example: Left Outer Join

If you had an audit table with the userid in a field such as AUDIT_OPRID, and you wanted the link to the User Profile (PSOPRDEFN) table to get their full name, use a Left Outer Join.  Depending on how your site handles User Profiles, you may have decided to delete the User Profile for any users no longer in the system.  A Left Outer Join can assist: link the audit table via a left outer join to the PSOPRDEFN table.  If a match can be found, you can display the user’s full name (field is OPRDEFNDESC).  If a match isn’t found, you still get the audit record’s row, even if the user’s full name isn’t in the query results.

Trick 1: Linking Limitations

Up until version Tools 8.53, you had to outer join a new table based on the last table that was added.  In other words, if you had three tables (A, B, and C), and wanted to outer join a new table (D) to the query, it had to be for table C.  If you wanted to outer join to table A or B, most people thought you had to rebuild the query (and put them in the right order).  There was an issue with this too: what if you wanted to link A to B (via outer join), then A to C (via outer join).  The following trick will allow you to Left Outer Join (prior to Tools 8.53), without having to rewrite the query.

If you need to link A to a new table via a left outer join, but there are other tables between A and the next table letter: add table A again (which would be table D in our three table example).  Make the link be inner join, and link using the common keys of table A (they are the same table, so they better have commons keys…).  Then, add your new table (which would be table E), and select left out join to table D, and finish by selecting the fields to join E to D.  This trick is only needed if your Tools enforces that Left Outer Joins must be performed on the last table already in the query.

Trick 2: Linking Assistance Gone Wrong

No matter how you do the Left Outer Join, be aware when PeopleSoft auto populates part of the Criteria for you.  For example, several tables automatically apply the EFFDT logic, which is helpful in quickly writing the PS Query.  But for a Left Outer Join, this can cause an issue; the EFFDT logic auto added does not follow the Left Outer Join rules, and it in effect makes the new table linked via an Inner Join.  If you review the results, you’ll discover that the Left Outer Join table is acting as an Inner Join.  An easy fix is to go into the criteria of the EFFDT via the Edit button, and at the bottom of the Edit window, there is a drop down choice to make this criteria be part of the appropriate Left Outer Join.

 

Which Processes Did A User Run

This SQL is useful if you want to know which processes a user has recently run.

It shows the Process Name and Type, the Process Description, their Run Control ID, the number of runs, and a min/max on the Run date.

Note:  This SQL may have limited data if the process request table gets purged often.

In this SQL, we chose to use the PS_PMN_PRCSLIST view.  We also use a process to pull the description from the Process Defn or the Job Defn table (see alias C).

SELECT P.PRCSNAME, P.PRCSTYPE
, C.DESCR "Process Description"
, P.RUNCNTLID
, COUNT(*) "Number of Runs"
, MIN(P.RUNDTTM) "First Ran"
, MAX(P.RUNDTTM) "Last Ran"
FROM PS_PMN_PRCSLIST P
, (SELECT PRCSTYPE, PRCSNAME, DESCR FROM PS_PRCSDEFN UNION SELECT 'PSJob', PRCSJOBNAME, DESCR FROM PS_PRCSJOBDEFN) C
WHERE P.PRCSTYPE = C.PRCSTYPE
AND P.PRCSNAME = C.PRCSNAME
AND P.OPRID = 'VP1'
AND P.RUNDTTM > '07/01/2015'
--AND P.PRCSNAME <> 'FS_STREAMLN'
GROUP BY P.PRCSTYPE, P.PRCSNAME, P.RUNCNTLID, C.DESCR
ORDER BY 1,2,3,4

Find Translates and Prompt Tables Via SQL

Here are two tricks to get the translate values and the prompt table, from App Designer, without logging into App Designer.

Trick #1

A lot of times, the values you are searching for are in a table called Translates (PSXLATITEM).  Most of these are delivered by PeopleSoft.  Note that the table is effective dated, but you usually can get by without the effective dated logic.  Also, note that the table does not start with PS_, but PS (no underscore).  That means it is a tools table, not an application table.  Also, use the FIELDVALUE field to link to the actual field.  Also, you don’t usually need to outer join this, but for the LEDGER_KK and this field (KK_BUDG_TRANS_TYPE), you should, since only the budget rows use the table.

SELECT * FROM PSXLATITEM
WHERE FIELDNAME = 'KK_BUDG_TRANS_TYPE'

Trick #2

If you can’t find the fieldname in translate table, here is another trick.

Sometimes a field is tied to a prompt table and not the translate table.  The developer (on the record definition) will sometimes store the prompt table.  The below SQL lets you see what is on the App Designer (without having to log into App Designer).  Note:  If the prompt value starts with %EditTable, then that means the prompt table is dynamic and a developer will have to find this value for you, in the code.

SELECT RD.RECNAME, RD.RECTYPE, RD.RECDESCR, RI.FIELDNUM, RI.FIELDNAME, RI.RECNAME_PARENT, RI.EDITTABLE
FROM PSRECDEFN RD, PSRECFIELDDB RI
WHERE RD.RECNAME = RI.RECNAME
AND RD.RECNAME = 'LEDGER_KK'
ORDER BY RI.FIELDNUM

Enjoy.

Find Projects To Backup Before a Refresh

This script will list all of the recent projects in a non-production environment, that may need to be backed up before a refresh.

If your development environment is about to be refreshed with a copy of production, you may have the same nagging feeling I get: what if I have a development project that I forgot to back up.  True, you can have your admin dba backup development before the refresh, but restoring the backup may not be time well spent for your admin dba.

I built the following script to show all project definitions, in descending order of last touched, to show me which projects I may want to backup on my own.  It also has the last target database that was used.  If this field is blank (Last Target DB), and the project name is important work, you will want to pay close attention to these.

SELECT P.LASTUPDDTTM, P.PROJECTNAME, P.PROJECTDESCR, P.LASTUPDOPRID, O.OPRDEFNDESC, P.TGTDBNAME "Last Target DB", COUNT(*) "Count of Project Items"
FROM PSPROJECTDEFN P LEFT OUTER JOIN PSOPRDEFN O ON O.OPRID = P.LASTUPDOPRID, PSPROJECTITEM I
WHERE P.PROJECTNAME = I.PROJECTNAME
AND P.LASTUPDDTTM >= '04/20/2016'
GROUP BY P.LASTUPDDTTM, P.PROJECTNAME, P.PROJECTDESCR, P.LASTUPDOPRID, O.OPRDEFNDESC, P.TGTDBNAME
ORDER BY P.LASTUPDDTTM DESC

Call System Command Example (SQR)

Example on calling a system command in SQR.

As a bonus, this system command does more than just run your average system command.  This example does a directory listing for the path in $pathname, for anything ending with .log.  It puts the content of the directory listing into a file called directory_listing.txt.  We then open this directory_listing.txt and process each file found.  This is very useful if you have an inbound folder for one or more files, and you don’t know what filenames will be found.

Calling the System Command

!Format the Command we are going to run.
#ifdef NT !Windows
    let $COMSPEC = GETENV('COMSPEC')
    show '$COMSPEC = ' $COMSPEC
    let $cmd = $COMSPEC || ' /c ' || 'dir ' || $pathname || '*.log &gt; ' || $pathname || 'directory_listing.txt /b'
#else !Unix/Linux
    let $cmd = 'ls ' || $pathname || '*.log &gt; ' || $pathname || 'directory_listing.txt'
#end-if

!Show and Run the Command
show '$cmd = ' $cmd
call system using $cmd #status wait
show '#status = ' #status

Open and Read the File

!Open the Directory Listing txt file.
let $full_filename = $pathname || 'directory_listing.filelisting'
open $full_filename as 1 for-reading record=250 status=#fileStatus
show '#fileStatus = ' #fileStatus

while 1
    Read 1 into $Filename_To_Open:250
    if #end-file = 1
        break
    end-if
    show 'Filename is ' $Filename_To_Open
    do Open-File
    do Process-File
    !Rename on the filename, perhaps making the extension be .archive 
    !  or moving the file to an archive or worked directory.
    do Rename-File
    do Close-File
end-while

 

SQR To XML Excel Writer

Want to create Rich Excel files from SQR?

Not your boring CSV files, but formatted Excel files.  We are talking fonts, colors, formulas, column widths, leading zero formatting, and multiple worksheet tabs.  All for free.

Wait.  Did you say free?

This originally was hosted at sourceforge and then SQRTricks.com.  The zip version below has the SQC code, a manual, and an SQR Example to get you started if you skip the manual.  Enjoy!

SQR to Excel code

Create Directory Listing of Files In App Engine

Need to know dynamically which files are in a given folder?

May 2018 Update:  You can use function FindFiles to do this.

Also, apologies on the Ampersand.  We are working on making it not be &.

 &amp;file_names = FindFiles(&amp;file_loc, %FilePath_Absolute);

The code further in this article is the old way to do this.

The code below will allow you to list all of the files in a given directory, then allow you to open each file.  It could be useful to read which files have been recently added to an upload folder.  I used this code to read which files had recently been placed in the process scheduler working folder (first SQLExec in code example).

Details:  We run a system command to get a directory listing, and we send that output to a new file, in the same directory.  Then we open that new file and review which files were found.

/*Setup the Slash variable*/
If &amp;OS = "Windows" Then 
    &amp;slash = "\";
else 
    &amp;slash = "/";
End-if;
/*Get the current working folder - You may want to use a static folder where files are being uploaded to - If you use this SQLExec, process instance &amp;ProcInst*/
SQLExec("SELECT PRCSOUTPUTDIR FROM PSPRCSPARMS WHERE PRCSINSTANCE = :1", &amp;ProcInst, &amp;DirectoryLocation); 

/*Setup the filename to output to.*/
&amp;DirectoryFileName = "FileListing_" | &amp;ProcInst | ".filelisting"; 

/*Setup the folder to write the file to*/ 
&amp;DirectoryListing = &amp;DirectoryLocation | &amp;slash | &amp;DirectoryFileName; 

If &amp;OS = "Windows" Then 
    &amp;CMD_TO_RUN = "cmd.exe /c dir """ | &amp;DirectoryLocation | """ /b &gt; """ | &amp;DirectoryListing | """";
Else 
    &amp;CMD_TO_RUN = "ls """ | &amp;DirectoryLocation | """ &gt; """ | &amp;DirectoryListing | """";
End-If; 

/*It is always best to Commit the work before running Exec*/ 
CommitWork();
try 
    &amp;return_value = Exec(&amp;CMD_TO_RUN, %Exec_Synchronous + %FilePath_Absolute); 
catch Exception &amp;e 
    MessageBox(0, "", 0, 0, "Error Found. Exception is " | &amp;e | " with return value " | &amp;return_value); 
    MessageBox(0, "", 0, 0, "Command sent was " | &amp;CMD_TO_RUN); 
    MessageBox(0, "", 0, 0, "Going to Next step in App Engine."); 
end-try; 

Local File &amp;f1, &amp;f2; 
&amp;f1 = GetFile(&amp;DirectoryListing, "r", %FilePath_Absolute); 
While &amp;f1.ReadLine(&amp;RowOfData); 
/*Skip the file if the filename matches our Directory File name*/ 
    If &amp;RowOfData &lt;&gt; &amp;DirectoryFileName Then
        MessageBox(0, "", 0, 0, "Processing Filename " | &amp;RowOfData); 
        &amp;Columns_array = Split(&amp;RowOfData, "."); /*Useful to read the file extension. Use variable Upper(&amp;Columns_array [2])*/ 
        &amp;f2 = GetFile(&amp;DirectoryLocation | &amp;slash | &amp;RowOfData, "r", %FilePath_Absolute);
        While &amp;f2.ReadLine(&amp;F2RowOfData);
            /*Process the row of data, using variable &amp;F2RowOfData*/
        End-While;
    End-If;
End-While;

&amp;f1.Close();

Extract Trees via SQL

How to Extract PeopleSoft Trees into a CSV format.

If you have the need to pull tree information into Excel, use the following SQL to easily get the Tree info in a tree level style.  The SQL will also pull Leaf information if the tree has leafs.

This SQL only works on trees with 10 or fewer levels..

You will need to change the setid, tree_name and effdt.  Also, these fields are found twice in the SQL.

Oracle DB

SELECT SETID, TREE_NAME, EFFDT
, decode(TREE_LEVEL_NUM, 0, TREE_NODE, 1, TREE_NODE, '') "TN1"
, decode(TREE_LEVEL_NUM, 2, TREE_NODE, '') "TN2"
, decode(TREE_LEVEL_NUM, 3, TREE_NODE, '') "TN3"
, decode(TREE_LEVEL_NUM, 4, TREE_NODE, '') "TN4"
, decode(TREE_LEVEL_NUM, 5, TREE_NODE, '') "TN5"
, decode(TREE_LEVEL_NUM, 6, TREE_NODE, '') "TN6"
, decode(TREE_LEVEL_NUM, 7, TREE_NODE, '') "TN7"
, decode(TREE_LEVEL_NUM, 8, TREE_NODE, '') "TN8"
, decode(TREE_LEVEL_NUM, 9, TREE_NODE, '') "TN9"
, decode(TREE_LEVEL_NUM, 10, TREE_NODE, '') "TN10"
, '' "Range"
, TREE_NODE_NUM "Sort Field"
FROM PSTREENODE N
WHERE N.SETID = 'SHARE'
AND N.TREE_NAME = 'DEPT_SECURITY'
AND N.EFFDT = '01-JAN-2011'
UNION
SELECT L.SETID, L.TREE_NAME, L.EFFDT
, '', '', '', '', '', '', '', '', '', ''
, decode(l.dynamic_range, 'Y', '%', decode(L.RANGE_FROM, L.RANGE_TO, L.RANGE_FROM, L.RANGE_FROM || ' to ' || L.RANGE_TO))
, L.TREE_NODE_NUM + .1
FROM PSTREELEAF L, PSTREENODE N
WHERE N.SETID = 'SHARE'
AND N.TREE_NAME = 'DEPT_SECURITY'
AND N.EFFDT = '01-JAN-2011'
AND N.SETID = L.SETID AND N.SETCNTRLVALUE = L.SETCNTRLVALUE AND N.TREE_NAME = L.TREE_NAME AND N.EFFDT = L.EFFDT AND N.TREE_NODE_NUM = L.TREE_NODE_NUM
ORDER BY 15, 14;

MS SQL Server DB

SELECT SETID, TREE_NAME, EFFDT
, CASE TREE_LEVEL_NUM WHEN '0'  THEN TREE_NODE WHEN '1' THEN TREE_NODE ELSE '' END AS "TN1"
, CASE TREE_LEVEL_NUM WHEN '2'  THEN TREE_NODE ELSE '' END AS "TN2"
, CASE TREE_LEVEL_NUM WHEN '3'  THEN TREE_NODE ELSE '' END AS "TN3"
, CASE TREE_LEVEL_NUM WHEN '4'  THEN TREE_NODE ELSE '' END AS "TN4"
, CASE TREE_LEVEL_NUM WHEN '5'  THEN TREE_NODE ELSE '' END AS "TN5"
, CASE TREE_LEVEL_NUM WHEN '6'  THEN TREE_NODE ELSE '' END AS "TN6"
, CASE TREE_LEVEL_NUM WHEN '7'  THEN TREE_NODE ELSE '' END AS "TN7"
, CASE TREE_LEVEL_NUM WHEN '8'  THEN TREE_NODE ELSE '' END AS "TN8"
, CASE TREE_LEVEL_NUM WHEN '9'  THEN TREE_NODE ELSE '' END AS "TN9"
, CASE TREE_LEVEL_NUM WHEN '10' THEN TREE_NODE ELSE '' END AS "TN10"
, '' "Range"
, TREE_NODE_NUM "Sort Field"
FROM PSTREENODE N
WHERE N.SETID = 'SHARE'
AND N.TREE_NAME = 'ACCOUNTS'
AND N.EFFDT = '01-JAN-1901'
UNION
SELECT L.SETID, L.TREE_NAME, L.EFFDT
, '', '', '', '', '', '', '', '', '', ''
, CASE L.DYNAMIC_RANGE WHEN 'Y' THEN '%' ELSE CASE L.RANGE_FROM WHEN L.RANGE_TO THEN RTRIM(L.RANGE_FROM) ELSE RTRIM(L.RANGE_FROM) + ' to ' + RTRIM(L.RANGE_TO) END END
, L.TREE_NODE_NUM + .1
FROM PSTREELEAF L, PSTREENODE N
WHERE N.SETID = 'SHARE'
AND N.TREE_NAME = 'ACCOUNTS'
AND N.EFFDT = '01-JAN-1901'
AND N.SETID = L.SETID AND N.SETCNTRLVALUE = L.SETCNTRLVALUE AND N.TREE_NAME = L.TREE_NAME AND N.EFFDT = L.EFFDT AND N.TREE_NODE_NUM = L.TREE_NODE_NUM
ORDER BY 15, 14