I am using T-SQL in SQLCMD mode in SSMS to run various subfile from one file, and the principle file (princ.sql) is where I need to declared and set a variable #MyInteger and run a subfile:
DECLARE #MyInteger INT
SET #MyInteger = 20
:setvar path "your absolute file path here"
:r $(path)\"query.sql"
:r $(path)\"uses Myingteger too.sql"
This works fine; as a minimal example, consider query.sql and 'uses MyInteger too.sql' to have the same one line
SELECT #MyInteger
and i do get back two instances of 20.
My trouble is that I would like to be able to run query.sql on its own using a locally defined variable #SubMyInteger.
My ideal situation for query.sql is
IF #MyInteger is declared
SET #SubMyInteger = #MyInteger
ELSE
SET #SubMyInteger = 90
SELECT #SubMyInteger
Then when I run query.sql, #MyInteger would not have been declared, so #SubMyInteger would be assigned 90, and 90 would be returned. I would also get back two 20s as before if i ran princ.sql
For the IF statement, I have so far tried two things:
IF OBJECT_ID('#MyInteger') IS NOT NULL
and
IF #MyInteger IS NOT NULL
I get the error
Must declare scalar variable "#MyInteger".
If I try declaring #MyInteger and then run princ.sql, I get the error
The variable '#MyInteger' has already been declared.
even if the declare statement is conditioned with an IF.
Is there a way to achieve what I want here?
Thanks
Related
I've been trying to pass in a symbol from a process outside KDB and am wishing to use this symbol to create a variable where we then load a specific file in case it doesn't exist.
//create some sample data and write to disk
.data.area52.varName1: ([] name:`billy`allen`terry; val:90 100 200);
.data.area52.varName2: 10 #200;
.data.area53.varName1: 9 #100;
.data.area53.varName2: ([] name:`joe`jim`bob; val:10 20 30);
.data.area53.varName3: `random;
`:area52 set .data.area52;
`:area53 set .data.area52;
I am trying to pass in the parameter for reference area52for example which will test a conditional if it exists (do nothing, else) then create the variable loading viaget `:area52
First, I wrote a conditional below which seems to check (however trhe `.areaXX isn't parametrized yet.
// fresh instance of KDB
.data.area52: $[`area52 in key `.data; .data.area52; get `:area52
And then stumbled onto the link here kdb+: use string as variable name
Which gets me most of the way there.
Is there a way to parameterize the beginning of the lambda below passing in some combination of `.area52 ? Much of this variable can be assembled / edited outside KDB and only passing in the
for an example, we can have several hundred `.areaXX that we could pass into KDB as data is changed and refreshed.
{.data.area52:()!(); #[`.data; `area52;:; (get `:area52)]} [];
To safeguard against the situation where the namespace .data doesn't exist you could use the following lambda which takes parameter area52 as an argument.
q){(` sv ``data,x) set ()!(); #[`.data; x; :; get hsym x]}`area52
`.data
Tying it back to the original problem, this lambda will create the variable if it does not exist by reading from the file of the same name:
/ .data does not exist yet
q).data
'.data
[0] .data
^
/ create variable if necessary
q){if[not x in key `.data; (` sv ``data,x)set get hsym x]}`area52
/ check .data
q).data
| ::
area52| ``varName1`varName2!(::;+`name`val!(`billy`allen`terry;90 100 200);20..
I need to be able to use variables in table names - I basically have the same set of tables used for different types of data, so I would like to just have one dashboard and swapping between all types instead of always having to set up multiple identical dashboards.
My query is something like:
select * from table_$variable_name;
Where my list of possible variable is something like cat, dog, bird
I can seem to make this work, if I only put the variable as shown above I get the following error
Error 1146: Table 'table_$variable_name' doesn't exist
If I enclose it in curly brackets, I get this error instead.
Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '{bird}' at line 1
(i.e. with the selected variable actually being visible this time)
I'm not sure if the issue is having underscores in the table names, I tried putting underscores around my variables too to check and I had no luck with that.
Another thing I tried was gradually adding on to the table name, so e.g.
select * from table_$variable;
Still returns an error, but I can see the table name starting to form correctly
Error 1146: Table 'table_bird_' doesn't exist
However, as soon as I add another underscore, the variable is not picked up abymore
```Error 1146: Table 'table_$variable_' doesn't exist``
I'm sure it's something silly I am missing in the syntax of the query - anyone has any suggestions?
Using this https://grafana.com/docs/grafana/latest/variables/templates-and-variables/ for reference
As #arturomp suggests, use
${var:raw}
At least in my case, this was the solution that worked.
I found double square brackets work. e.g.
Rather than
select * from table_$variable_name;
use
select * from table_[[variable_name]];
The following lines of Q code all throw an error, because when the statement "local" is parsed, the local variable is not in the correct scope.
{local:1; value "local"}[]
{[local]; value "local"}[1]
{local:1; eval parse "local"}[]
{[local]; eval parse "local"}[1]
Is there a way to reach the local variable from inside the parsed string?
Note: This is a simplification of the actual problem I'm grappling with, which is to write a function that executes a query, accepting a list of columns which it should return. I imagine the finished product looking something like this:
getData:{[requiredColumns, condition]
value "select ",(", " sv string[requiredColumns])," from myTable where someCol=condition"
}
The condition parameter in this query is the one that isn’t recognised and I do realise I could append it’s value rather than reference it inside a string, but the real query uses lots of local variables including tables etc, so it’s not as easy as just pulling all the variables out of the string before calling value on it.
I'm new to KDB and Q, so if anyone has a better way to achieve the same effect I'm happy to be schooled on the proper way to achieve this outcome in Q. Would still be interested to know in the variable access thing is possible though.
In the first example, you are right that local is not within the correct scope, as value is looking for the global variable local.
One way to get around this is to use a namespace, which will define the variable globally, but can only be accessed by calling that namespace. In the modified example below I have defined local in the .ns namespace
{.ns.local:1; value ".ns.local"}[]
For the problem you are facing with selecting, if requiredColumns is a symbol list of columns you can just use the take operator # to select them.
getData:{[requiredColumns] requiredColumns#myTable}
For more advanced queries using variables you may have to use functional select form, explained here. This will allow you to include variables in the where and by clause of the select statement
The same example in functional form would be (no by clause, only select and where):
getData:{[requiredColumns;condition] requiredColumns:(), requiredColumns;
?[myTable;enlist (=;`someCol;condition);0b;requiredColumns!requiredColumns]}
The first line ensures that requiredColumns is a list even if the user enters a single column name
value will look for a variable in the global scope that's why you are getting an error. You can directly use local variables like you are doing that in your function.
Your function is mostly correct, just need a slight correction to append condition(I have mentioned that below). However, a better approach would be to use functional select in this case.
Using functional select:
q) t:([]id:`a`b; val:3 4)
q) gd: {?[`t;enlist (=;`val;y);0b;((),x)!(),x]}
q) gd[`id;3] / for single column
Output:
id
-
1
q) gd[`id`val;3] / for multiple columns
In case your condition column is of type symbol, then enlist your condition value like:
q) gd: {?[`t;enlist (=;`id;y);0b;((),x)!(),x]}
q) gd[`id;enlist `a]
You can use parse to get a functional form of qsql queries:
q) parse " select id,val from t where id=`a"
?
`t
,,(=;`id;,`a)
0b
`id`val!`id`val
Using String concat(your function):
q)getData:{[requiredColumns;condition] value "select ",(", " sv string[requiredColumns])," from t where id=", .Q.s1 condition}
q) getData[enlist `id;`a] / for single column
q) getData[`id`val;`a] / for multi columns
In this wonderful answer is proposed GUC-pattern to use run-time parameters to detect current user inside trigger (as one solution). It seemed to suit to me too. But problem is: when I declare the variable in postgresql.conf it is usable inside trigger and I can access it from queries, but can't change it:
# SET rkdb.current_user = 'xyzaaa';
ERROR: syntax error at or near "current_user"
LINE 1: SET rkdb.current_user = 'xyzaaa';
The error message is misleading, so I did not dig it a while, but now it seems this user (database owner) has no permissions to change params set in global configuration.
I can set any other params:
# SET jumala.kama = 24;
SET
And read it back:
# SHOW jumala.kama;
jumala.kama
-------------
24
(1 row)
I can't SHOW globally set params:
# SHOW rkdb.current_user;
ERROR: syntax error at or near "current_user"
LINE 1: SHOW rkdb.current_user;
^
but I can reach it with current_setting() function:
# select current_setting('rkdb.current_user');
current_setting
-----------------
www
(1 row)
So my guess is, my database owner does not have permissions to access this param. How could I:
set needed permissions?
or even better
set run-time params with database owner rights?
current_user is an SQL standard function, so your use of that name confuses the parser.
Either use a different name or surround it with double quotes like this:
rkdb."current_user"
Some context before the question.
Imagine file FileA having around 50 fields of different types. Instead of all programs using the file, I tried having a service program, so the file could only be accessed by that service program. The programs calling the service would then receive a DataStructure based on the file structure, as an ExtName. I use SQL to recover the information, so, basically, the procedure would go like this :
Datastructure shared by service program :
D FileADS E DS ExtName(FileA) Qualified
Procedure called by programs :
P getFileADS B Export
D PI N
D PI_IDKey 9B 0 Const
D PO_DS LikeDS(FileADS)
D LocalDS E DS ExtName(FileA) Qualified
D NullInd S 5i 0 Array(50) <-- Since 50 fields in fileA
//Code
Clear LocalDS;
Clear PO_DS;
exec sql
SELECT *
INTO :LocalDS :nullind
FROM FileA
WHERE FileA.ID = :PI_IDKey;
If SqlCod <> 0;
Return *Off;
EndIf;
PO_DS = LocalDS;
Return *On;
P getFileADS E
So, that procedure will return a datastructure filled with a record from FileA if it finds it.
Now my question : Is there any way I can assign the %nullind(field) = *On without specifying EACH 50 fields of my file?
Something like a loop
i = 1;
DoW (i <= 50);
if nullind(i) = -1;
%nullind(datastructure.field) = *On;
endif;
i++;
EndDo;
Cause let's face it, it'd be a pain to look each fields of each file every time.
I know a simple chain(n) could do the trick
chain(n) PI_IDKey FileA FileADS;
but I really was looking to do it with SQL.
Thank you for your advices!
OS Version : 7.1
First, you'll be better off in the long run by eliminating SELECT * and supplying a SELECT list of the 50 field names.
Next, consider these two web pages -- Meaningful Names for Null Indicators and Embedded SQL and null indicators. The first shows an example of assigning names to each null indicator to match the associated field names. It's just a matter of declaring a based DS with names, based on the address of your null indicator array. The second points out how a null indicator array can be larger than needed, so future database changes won't affect results. (Bear in mind that the page shows a null array of 1000 elements, and the memory is actually relatively tiny even at that size. You can declare it smaller if you think it's necessary for some reason.)
You're creating a proc that you'll only write once. It's not worth saving the effort of listing the 50 fields. Maybe if you had many programs using this proc and you had to create the list each time it'd be a slight help to use SELECT *, but even then it's not a great idea.
A matching template DS for the 50 data fields can be defined in the /COPY member that will hold the proc prototype. The template DS will be available in any program that brings the proc prototype in. Any program that needs to call the proc can simply specify LIKEDS referencing the template to define its version in memory. The template DS should probably include the QUALIFIED keyword, and programs would then use their own DS names as the qualifying prefix. The null indicator array can be handled similarly.
However, it's not completely clear what your actual question is. You show an example loop and ask if it'll work, but you don't say if you had a problem with it. It's an array, so a loop can be used much like you show. But it depends on what you're actually trying to accomplish with it.
for old school rpg just include the nulls in the data structure populated with the select statement.
select col1, ifnull(col1), col2, ifnull(col2), etc. into :dsfilewithnull where f.id = :id;
for old school rpg that can't handle nulls remove them with the select statement.
select coalesce(col1,0), coalesce(col2,' '), coalesce(col3, :lowdate) into :dsfile where f.id = :id;
The second method would be easier to use in a legacy environment.
pass the key by value to the procedure so you can use it like a built in function.
One answer to your question would be to make the array part of a data structure, and assign *all'0' to the data structure.
dcl-ds nullIndDs;
nullInd Ind Dim(50);
end-ds;
nullIndDs = *all'0';
The answer by jmarkmurphy is an example of assigning all zeros to an array of indicators. For the example that you show in your question, you can do it this way:
D NullInd S 5i 0 dim(50)
/free
NullInd(*) = 1 ;
Nullind(*) = 0 ;
*inlr = *on ;
return ;
/end-free
That's a complete program that you can compile and test. Run it in debug and stop at the first statement. Display NullInd to see the initial value of its elements. Step through the first statement and display it again to see how the elements changed. Step through the next statement to see how things changed again.
As for "how to do it in SQL", that part doesn't make sense. SQL sets the values automatically when you FETCH a row. Other than that, the array is used by the host language (RPG in this case) to communicate values back to SQL. When a SQL statement runs, it again automatically uses whatever values were set. So, it either is used automatically by SQL for input or output, or is set by your host language statements. There is nothing useful that you can do 'in SQL' with that array.