i have some stored processes with identical names. To identify which process is running at the moment, i need to know the id of the stored process in the metadata. Can i retrieve the STP-id somewhere? I could not find a variable which holds the id.
I only found symget('sysjobid'); which returns the unix-processid, not the id of the stored process.
Typical a stored process id looks like this:
A5DF0R0G.B80001L7
I need to know the id from within the process which is running, to retrieve some properties of the process from the metadata.
Any other solution to identify the process exactly in the metadata would also be welcome, but i can not use his name, because it can occur several times for differents processes.
for example something like:
put 'name:' &_program; /*this already works and returns the name of the stored process*/
put 'id:' ?; /*need to know the id of the process, because name is not distinct*/
It is actually pretty easy now that I look at it.
I created this sample STP (named "Doms Hello World") in the "My Folder" folder.
data _temp;
X = "HELLO WORLD";
path = "&_PROGRAM";
format type ID $200.;
rc= metadata_pathobj("",path,"StoredProcess",type,ID);
run;
proc print data=_temp noobs;
run;
You can use the metadata_pathobj() function to get the ID and TYPE of an element by the path.
This returns
X path type ID rc
HELLO WORLD /User Folders/dpazzula/My Folder/Doms Hello World ClassifierMap A5XQ9K3Z.BA0002BQ 1
In both EG and via the Web App.
I don't think a stored process has an ID, but it is unique in terms of its location and name.
User _PROGRAM macro variable to determine what stored procedure is running. It will have a format of "/SAS Folder/Stored Procedure Folder/Stored Procedure Name".
Something like A5DF0R0G.B80001L7 ID of the stored procedure useful when running IOM applications, but I don't think it would be that useful when it comes determining what stored procedure created something and where it was saved at the time, so I would go with "_PROGRAM".
In case you after the ID anyways, use this code (credit: https://support.selerity.com.au/entries/23169736-Example-Data-Step-View-of-Stored-Procedures-in-Metadata):
******************************************************************************
* Purpose: Create a dynamic view of Stored Procedures registered in Metadata
* Notes : You must establish a Metadata connection prior to running
******************************************************************************;
data work.stplist(drop=_: label="SAS Stored Process List") / view=work.stplist;
length id $17 _uri name description _modified _created location _location $256;
length created modified 8;
format created modified datetime.;
label id="Metadata ID"
name="Stored Process Name"
description="Description"
location="Folder Location"
created="Created"
modified="Last Modified";
_nobj=1;
_n=1;
call missing(id, _uri, name, description, _modified, _created, _location);
do while(_n le _nobj);
_nobj=metadata_getnobj("omsobj:ClassifierMap?#PublicType='StoredProcess'",_n,_uri);
_rc=metadata_getattr(_uri,"Id",id);
_rc=metadata_getattr(_uri,"Name",name);
_rc=metadata_getattr(_uri,"Desc",description);
_rc=metadata_getattr(_uri,"MetadataCreated",_created);
_rc=metadata_getattr(_uri,"MetadataUpdated",_modified);
created=input(_created,anydtdtm.);
modified=input(_modified,anydtdtm.);
* Get folder object the current STP is in *;
_rc=metadata_getnasn(_uri,"Trees",1,_uri);
* Get folder name the current STP is in *;
_rc=metadata_getattr(_uri,"Name",location);
_tree=1;
* Loop up the folder hierarchy *;
do while (_tree>0);
* Get the parent folder object *;
_tree=metadata_getnasn(_uri,"ParentTree",1,_uri);
if _tree > 0 then do;
* If there was a parent folder, get the name *;
_rc=metadata_getattr(_uri,"Name",_location);
* Construct the path *;
location=catx('/',_location,location);
end;
end; * Folder Hierachy *;
location = '/'||location;
output;
_n=_n+1;
end;
run;
Regards,
Vasilij
Related
Is it possible to get the list of libraries assigned (pre & non-pre assigned) to an application server in SAS Metadata?
I can use dictionary.libnames but it lists only pre-assigned libraries.
Assuming you want to just find out all of the available libraries, and have an account (such as sasadm#saspw) which can see them, then you should be able to iterate using the metadata_getnobj function. Something like this:
nobj=metadata_getnobj("omsobj:SASLibrary?#Id contains '.'",n,uri);
The example from the documentation otherwise should match what you're doing:
data _null_;
length uri $256;
nobj=0;
n=1;
/* Determine how many machine objects are in this repository. */
nobj=metadata_getnobj("omsobj:SASLibrary?#Id contains '.'",n,uri);
put nobj=; /* Number of machine objects found. */
put uri=; /* URI of the first machine object. */
run;
You could then iterate through those, with a do n = 1 by 1 until (n lt 0); loop or similar, and use the metadata_getattr function to obtain the information you want about each uri. You could look at this SAS Communities question for example; the code there should work (their issue was not the code, but their machine setup). Something like this:
data _null_;
length uri $256;
nobj=0;
n=1;
uri=' ';
length name engine libref $256;
call missing(of name engine libref);
nobj=metadata_getnobj("omsobj:SASLibrary?#Id contains '.'",n,uri);
/* Determine how many machine objects are in this repository. */
do n = 1 to nobj;
nobj=metadata_getnobj("omsobj:SASLibrary?#Id contains '.'",n,uri);
rc=metadata_getattr(uri,'Name',name);
rc=metadata_getattr(uri,'Engine',engine);
rc=metadata_getattr(uri,'Libref',libref);
put name= engine= libref=;
end;
run;
This would only include metadata libraries - not libraries that are active, but defined only in SAS code. For the latter, you do need to use dictionary.libnames.
The fastest approach for this, if you have a LOT of libraries, is to use proc metadata.
The below is an extract from a SASjs core macro (this one: https://github.com/sasjs/core/blob/main/meta/mm_getlibs.sas)
/* get list of libraries */
filename response temp;
proc metadata in=
'<GetMetadataObjects>
<Reposid>$METAREPOSITORY</Reposid>
<Type>SASLibrary</Type>
<Objects/>
<NS>SAS</NS>
<Flags>%eval(2048+256+8)</Flags>
<Options/>
</GetMetadataObjects>'
out=response;
run;
/* create an XML map to read the response */
filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASLibrary">';
put '<TABLE name="SASLibrary">';
put '<TABLE-PATH syntax="XPath">//Objects/SASLibrary</TABLE-PATH>';
put '<COLUMN name="LibraryId">><LENGTH>17</LENGTH>';
put '<PATH syntax="XPath">//Objects/SASLibrary/#Id</PATH></COLUMN>';
put '<COLUMN name="LibraryName"><LENGTH>256</LENGTH>>';
put '<PATH syntax="XPath">//Objects/SASLibrary/#Name</PATH></COLUMN>';
put '<COLUMN name="LibraryRef"><LENGTH>8</LENGTH>';
put '<PATH syntax="XPath">//Objects/SASLibrary/#Libref</PATH></COLUMN>';
put '<COLUMN name="Engine">><LENGTH>12</LENGTH>';
put '<PATH syntax="XPath">//Objects/SASLibrary/#Engine</PATH></COLUMN>';
put '</TABLE></SXLEMAP>';
run;
libname _XML_ xml xmlfileref=response xmlmap=sxlemap;
/* sort the response by library name */
proc sort data=_XML_.saslibrary out=work.metalibs;
by libraryname;
run;
To find the ones assigned to a specific app server, as Joe also mentions, you'd need to iterate further with each library id to get the attributes. For that it can help to have a metadata browser. If you don't have Base SAS (which has metabrowse built in) feel free to contact me and I'll send you a tool for that.
Joe's answer can be further condensed as
/* Create metadata libraries listing/inventory */
data META_LIBS (drop=i rc ouri);
length NAME $256 LIBREF $8 ouri $35;
call missing(of _char_);
do i=1 by 1 while(metadata_getnobj("omsobj:SASLibrary?#Id contains '.'", i, ouri) > 0);
rc = metadata_getattr(ouri, 'Name', NAME);
rc = metadata_getattr(ouri, 'Libref', LIBREF);
output;
end;
run;
See its detailed explanation in this section Creating metadata libraries inventory and identifying duplicate LIBREF.
Hello Progress4GL Developers,
I am trying to use progress to consume a SOAP API. At the moment, I am just using a opensource sample SOAP service called: http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL.
The following program works a dream (i.e user inputs country code such as "ESP" and details about Spain are showed to user). The result is stored in temp-table1 and temp-table2:
/******* Sample Application to show Progress4GL Consuming a SOAP API *************/
/******* VARIABLES ***************************************************************/
DEFINE VARIABLE lReturn AS LOGICAL NO-UNDO.
DEFINE VARIABLE hServer AS HANDLE NO-UNDO.
DEFINE VARIABLE hPortType AS HANDLE NO-UNDO.
DEFINE VARIABLE capitalCity as LONGCHAR NO-UNDO.
DEFINE VARIABLE iCntryCode as char no-undo label "Country Code".
DEFINE VARIABLE oResponse as LONGCHAR no-undo.
/******* CONNECTION SETTINGS TO SOAP SERVICE *************************************/
CREATE SERVER hServer.
lReturn = hServer:CONNECT("-WSDL http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL").
IF lReturn = NO THEN DO:
MESSAGE
"Could not connect to WebService server".
END.
/******* SETTING PORT TYPE *******************************************************/
RUN CountryInfoServiceSoapType SET hPortType ON SERVER hServer.
IF NOT VALID-HANDLE(hPortType) THEN DO:
MESSAGE
"Could not establish portType".
END.
/****** USER UPDATE FORM**********************************************************/
UPDATE iCntryCode.
/****** NAME OF DATA FUNCTION TO RUN *********************************************/
RUN FullCountryInfo IN hPortType (INPUT iCntryCode, OUTPUT oResponse) NO-ERROR.
/****** TEMP TABLE TO HOLD ALL CAPITAL CITY FIELDS *******************************/
DEFINE TEMP-TABLE temptable1
SERIALIZE-NAME "sCapitalCity"
FIELD capitalCity AS CHAR XML-NODE-TYPE "text".
/****** DATASET WHICH RUNS CONVERSION ********************************************/
DEFINE DATASET dsa SERIALIZE-NAME "FullCountryInfoResult" FOR temptable1.
/***** TEMP TABLE TO HOLD ALL CURRENCY FIELDS ************************************/
DEFINE TEMP-TABLE temptable2
SERIALIZE-NAME "sCurrencyISOCode"
FIELD currencyCode AS CHAR XML-NODE-TYPE "text".
/****** DATASET WHICH RUNS CONVERSION ********************************************/
DEFINE DATASET dsb SERIALIZE-NAME "FullCountryInfoResult" FOR temptable2.
/****** POPULATE DATASET AND TEMP-TABLES FROM SOAP RESPONSE **********************/
DATASET dsa:READ-XML( "longchar", oResponse, ?, ?, ? ).
DATASET dsb:READ-XML( "longchar", oResponse, ?, ?, ? ).
FOR EACH temptable1 no-lock, each temptable2 no-lock:
DISPLAY temptable1.capitalcity temptable2.currencyCode.
END.
PAUSE 100.
/****** STOP CONNECTION TO SERVER ************************************************/
DELETE PROCEDURE hPortType.
hServer:DISCONNECT().
DELETE OBJECT hServer.
However, this sample web-service also has a procedure called FullCountryInfoAllCountries. This will give response for all countries. I have tried to re-factor the code above so that all temp-table1 and temp-table2 will be populated with all countries however I am having difficulty parsing the XML data.
I have tried experimenting with the NAMESPACE-URI, SERIALIZATION-NAME, and XML-NODE-NAME statements, but can't get it to work. The problem I have is that the XML response from FullCountryInfoAllCountries is structured differently to FullCountryInfo, and I always recieve an error along the lines of 'Namespace not found', or it will return nothing in the temp-tables.
Many Thanks in Advance!
You can peek at the output by messaging the response (commented out), this can then be mapped to dataset, temp-table and field names:
define variable hs as handle no-undo.
define variable hp as handle no-undo.
define variable lcresponse as longchar no-undo.
define temp-table tt serialize-name "tCountryInfo"
field sISOCode as character
field sName as character
.
define dataset ds serialize-name "FullCountryInfoAllCountriesResult" for tt.
create server hs.
hs:connect( "-WSDL http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso?WSDL" ).
run CountryInfoServiceSoapType set hp on server hs.
run FullCountryInfoAllCountries in hp ( output lcresponse ).
// message string( substring( lcresponse, 1, 30000 ).
dataset ds:read-xml( "longchar", lcresponse, ?, ?, ? ).
for each tt:
display tt.
end.
delete procedure hp.
hs:disconnect().
delete object hs.
I'm running a process that lists jobs I want to check the modification date on. I list the jobs in a dataset and then pass these to macro variables with a number.
e.g.
Data List_Prep;
Format Folder
Code $100.;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
Run;
%Macro List_Check();
Data List;
Set List_Prep;
Job + 1;
Call Symput (Cats("Folder", Job), Strip(Folder));
Call Symput (Cats("Code", Job), Strip(Code));
Run;
%Put Folder1 = &Folder1;
%Put Folder2 = &Folder2;
%MEnd;
%List_Check;
It prints the %Put statement just fine for foler 1, but folder 2 doesn't work right.
Folder1 = C:\FilePath\Job ABC
WARNING: Apparent symbolic reference Y not resolved.
Folder2 = C:\FilePath\Job X&Y
When I then go in to a loop to check the datasets, again, it work, so looks for Folder1, Code1 etc, but I still get the warnings.
How can I stop these warnings? I've tried %Str("&") instead, but still get the issue.
The %superq() macro function is a great way to mask macro triggers that are already in a macro variable. You could either remember to quote the values when using them,
%put Folder1 = %superq(Folder1) ;
or you could adjust your process to quote them right after creating them.
data List_Prep;
length Folder Code $100;
Folder = 'C:\FilePath\Job ABC'; Code = '01 Job Name.sas'; Output;
Folder = 'C:\FilePath\Job X&Y'; Code = '01 Another Job.sas'; Output;
run;
data List;
set List_Prep;
Job + 1;
length dummy $200 ;
call symputx(cats("Folder", Job), Folder);
dummy = resolve(catx(' ','%let',cats("Folder", Job),'=%superq(',cats("Folder", Job),');'));
call symputx(cats("Code", Job), Code);
dummy = resolve(catx(' ','%let',cats("Code", Job),'=%superq(',cats("Code", Job),');'));
drop dummy;
run;
P.S. Don't use FORMAT to define variables. Use statements like LENGTH or ATTRIB that are designed for defining variables. FORMAT is for attaching formats to variable, not for defining them. The only reason that using FORMAT worked is that it had the side effect of SAS defining the variable's type and length to match the format that you attached to it because it was the first place you referenced the variable in the data step.
You can prevent SAS from trying to resolve the ampersand in the value by using the %superq function
%put Folder2 = %superq(Folder2);
Sorry for the English language skills!
SAS version 9.3
It is necessary to automate the process of obtaining the source code of Job. I know the way through deploy/redeploy, but it is not suitable for the creation of deploy metadata.
Through macro:
data job_source_code;
length uri source_uri $256.;
length text $1000.;
_rc = metadata_getnobj("omsobj:Job?#Name='JOB_NAME'", 1, uri);
_rc = METADATA_GETNASN(uri, 'SourceCode', 1, source_uri);
_rc = METADATA_GETATTR(source_uri, 'StoredText', text);
run;
But the field text is always empty.
What am I doing wrong? Is there any other way to automate the process of obtaining the source code of Job?
I know it's too late that I am answering the question. Recently I have got the same request, before trying did a search in the Web to see if I can get any code. But was not able to find any, luckily I found your code and worked on completing the remaining part. Thanks.
data server_details_in_smc_1;
length uri $256 Name PublicType TransId_uri $100 text f_Direct SourceCode_Location $1000.;
nobj=1;
n=1;
do while(nobj >= 0);
n=n+1;
nobj=metadata_getnobj("omsobj:Job?#Id contains '.'",n,uri);
if (nobj > 0) then
do;
arc=metadata_getattr(uri,"Name",Name);
arc=metadata_getattr(uri,"PublicType",PublicType);
TransId_obj=metadata_getnasn(uri,'SourceCode',1,TransId_uri);
arc = metadata_getnasn(TransId_uri,"Directories",1,f_Direct);
arc = metadata_getattr(f_Direct,"DirectoryName",SourceCode_Location);
output ;
end;
end;
keep Name PublicType SourceCode_Location ;
run;
You can use batch file to deploy jobs from commend line.
I've never done this. But you can find description of this procedure in DI Stuio User's Guide:
http://support.sas.com/documentation/cdl/en/etlug/65807/HTML/default/viewer.htm#p1jxhqhaz10gj2n1pyr0hbzozv2f.htm
I want to select all the binary data from a column of a SQL database (SQL Server Enterprise) using C++ query. I'm not sure what is in the binary data, and all it says is .
I tried this (it's been passed onto me to study off from) and I honestly don't 100% understand the code at some parts, as I commented):
SqlConnection^ cn = gcnew SqlConnection();
SqlCommand^ cmd;
SqlDataAdapter^ da;
DataTable^ dt;
cn->ConnectionString = "Server = localhost; Database=portable; User ID = glitch; Pwd = 1234";
cn->Open();
cmd=gcnew SqlCommand("SELECT BinaryColumn FROM RawData", cn);
da = gcnew SqlDataAdapter(cmd);
dt = gcnew DataTable("BinaryTemp"); //I'm confused about this piece of code, is it supposed to create a new table in the database or a temp one in the code?
da->Fill(dt);
for(int i = 0; i < dt->Rows->Count-1; i++)
{
String^ value_string;
value_string=dt->Rows[i]->ToString();
Console::WriteLine(value_string);
}
cn->Close();
Console::ReadLine();
but it only returns a lot of "System.Data.DataRow".
Can someone help me?
(I need to put it into a matrix form after I extract the binary data, so if anyone could provide help for that part as well, it'd be highly appreciated!)
dt->Rows[i] is indeed a DataRow ^. To extract a specific field from it, use its indexer:
array<char> ^blob=dt->Rows[i][0];
This extracts the first column (since you have only one) and returns an array representation of it.
To answer the question in your code, the way SqlDataAdapter works is like this:
you build a DataTable to hold the data to retrieve. You can fill in its columns, but you're not required to. Neither are you required to give it a name.
you build the adapter object, giving it a query and a connection object
you call the Fill method on the adapter, giving it the previously created DataTable to fill with whatever your query returns.
and you're done with the adapter. At this point you can dispose of it (for example inside a using statement if you're using C#).