How to create dynamic stored procedure in SQL Anywhere? - database

I'm having an issue with creating dynamic sql statement in SQL Anywhere.
CREATE PROCEDURE pplAnalysis
AS
BEGIN
DECLARE #Sql NVARCHAR(4000)
SELECT #Sql = "select * from cds.ppl"
EXECUTE(#Sql)
END
When I execute this procedure, I get an Column 'select * from cds.ppl' not found error.
Can you please tell me what am I doing wrong?

The issue had to do with syntax and the RESULT clause; after adding semicolons, RESULT clause, and used SET to initialize the Sql variable, here is what worked (tested in SQL Anywhere Network Server Version 12.0.1):
drop proc pplAnalysis;
CREATE PROCEDURE pplAnalysis()
RESULT (cnot CHAR(5), cnull CHAR(5), vnot VARCHAR(5), vnull VARCHAR(5), explanation VARCHAR(50))
BEGIN
DECLARE #Sql NVARCHAR(4000);
SET #Sql = 'select cnot, cnull, vnot, vnull, explanation from dbo.spaces';
EXECUTE ( #Sql );
END;
spaces is a table in the dbo schema and those columns are the same type specified in RESULT
Tested these two ways to execute the procedure and both returned result:
call pplAnalysis();
cnot cnull vnot vnull explanation
----- ----- ----- ----- --------------------------------------------------
Execution time: 0.027 seconds
Procedure completed
or
exec pplAnalysis;
cnot cnull vnot vnull explanation
----- ----- ----- ----- --------------------------------------------------
Execution time: 0.018 seconds
For more details:
Returning result sets from procedures
Create procedure statement

Try first saving the result of the query in a temporal table, and then do a SELECT from the temporal table:
SELECT #Sql = "select into #temp * from cds.ppl"
EXECUTE(#Sql)
SELECT * FROM #temp

Use single quotes.
SELECT #Sql = 'select * from cds.ppl'

After some research, I have edited my answer.
Regarding the EXECUTE ( string-expression ) statement, yes you have to use single quotes instead of double quotes for the string expression. This page mentions:
It lets you execute dynamically prepared statements, such as
statements that are constructed using the parameters passed in to a
procedure. Literal strings in the statement must be enclosed in single
quotes, and the statement must be on a single line.
Which will eliminate the column not found error but the procedure will return this other error:
Result set not permitted in '<batch statement>'
Same error returned when trying to execute this statement alone:
execute ('select * from sysusers')
With probable cause:
You attempted to execute a SELECT statement in a context where a
result set is not permitted.
See my most recent answer for the solution.
And regarding schemas, here's how to refer to objects:
It is always good practice to refer to database objects by a schema
name and the object name, separated by a period (.). For a complete example, to SELECT records from the Employee table in the HumanResources schema of the current database would look like:
SELECT * FROM HumanResources.Employee
To reference an object located in a remote database, the fully
qualified object name includes the server name and the database name.
For example, to SELECT records from the Employee table in the
HumanResources schema in the AdventureWorks database on MyServer would
look like:
SELECT * FROM MyServer.AdventureWorks.HumanResources.Employee
I tested that in SQL Anywhere 12 and it works the same. And even though I was not familiar with schemas, what I'm suggesting you below is actually using schemas, dbowner would be the schema name:
1) select * from dbname.dbowner.tablename
2) select * from dbowner.tablename
3) select * from dbname..tablename (assumes table exists in the dbo schema)
Bottom line.... In your select statement cds.ppl has to be a table named ppl created in the cds schema.
Or if cds is your database name and ppl your table name created in the dbo schema, you are missing a dot:
select * from cds..ppl

Related

Why is the table inside a non-met IF being validated before condition is met, resulting in error if table does not exist?

I am trying to execute a procedure with a parameter, and depending on the value of the parameter, three different IF conditions will be evaluated to verify which query it will execute from a linked server.
But when I execute the query, it seems to be checking if the tables inside all the IF exists before starting the query. And I know that only one of the table exists, that is why I am using the parameter, so it shouldn't fail. but I anyhow get the following error:
Msg 7314, Level 16, State 1, Line 25
The OLE DB provider "Microsoft.ACE.OLEDB.16.0" for linked server "LinkedServer" does not contain the table "D100". The table either does not exist or the current user does not have permissions on that table.
So in this code, assume that the parameter is 300. then I get the message above.
Do you know, if there is a way, to limit the query to do not check all the tables, but only the one where the IF condition will be met?
ALTER PROCEDURE[dbo].[Import_data]
#p1 int = 0
AS
BEGIN
SET NOCOUNT ON;
IF(#p1 = 100)
BEGIN
DROP TABLE IF EXISTS Table1
SELECT [Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table1
FROM[LinkedServer]...[D100]
END
IF(#p1 = 200)
BEGIN
DROP TABLE IF EXISTS Table2
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table2
FROM[LinkedServer]...[D200]
END
IF(#p1 = 300)
BEGIN
DROP TABLE IF EXISTS Table3
SELECT[Field1], [Field2], [Field3], [Field4], [Field5], [Field6]
INTO Table3
FROM[LinkedServer]...[D300]
END
END
I have tried googling it, but I found mostly workarounds as running a sub procedure, but it is not really a clean solution, I think.
Okay, it seems I that I found the answer. Even with an IF statement, the SQL Server validates the entire query before executing it, so the way to overcome it, is to use a Dynamic SQL Query.
"SQL Server Dynamic SQL is a programming technique that allows you to construct SQL statements dynamically at runtime. It allows you to create more general purpose and flexible SQL statement because the full text of the SQL statements may be unknown at compilation."
This is how the query looks now. so instead of multiple IF statements, the query changes dynamically depending on the parameter.
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = N'DROP TABLE IF EXISTS Table1;
SELECT [Field1]
,[Field2]
,[Field3]
,[Field4]
,[Field5]
,[Field6]
INTO Table1
FROM [LinkedServer]...[D' + CONVERT(nvarchar(3),#p1) + N']'
EXEC sp_executesql #SQL

How to insert into table the results of a dynamic query when the schema of the result is unknown a priori?

Observe the following simple SQL code:
CREATE TABLE #tmp (...) -- Here comes the schema
INSERT INTO #tmp
EXEC(#Sql) -- The #Sql is a dynamic query generating result with a known schema
All is good, because we know the schema of the result produced by #Sql.
But what if the schema is unknown? In this case I use Powershell to generate a Sql query like that:
SET #Sql = '
SELECT *
INTO ##MySpecialAndUniquelyNamedGlobalTempTable
FROM ($Query) x
'
EXEC(#Sql)
(I omit some details, but the "spirit" of the code is preserved)
And it works fine, except that there is a severe limitation to what $Query can be - it must be a single SELECT statement.
This is not very good for me, I would like to be able to run any Sql script like that. The problem, is that no longer can I concatenate it to FROM (, it must be executed by EXEC or sp_executesql. But then I have no idea how to collect the results into a table, because I have no idea of the schema of that table.
Is it possible in Sql Server 2012?
Motivation: We have many QA databases across different Sql servers and more often than not I find myself running queries on all of them in order to locate the database most likely to yield best results for my tests. Alas, I am only able to run single SELECT statements, which is inconvenient.
We use SP and OPENROWSET for this purpose.
At first create SP based on a query you need, than use OPENROWSET to get data into temp table:
USE Test
DECLARE #sql nvarchar(max),
#query nvarchar(max)
SET #sql = N'Some query'
IF OBJECT_ID(N'SomeSPname') IS NOT NULL DROP PROCEDURE SomeSPname
SET #query =N'
CREATE PROCEDURE SomeSPname
AS
BEGIN
'+#sql+'
END'
EXEC sp_executesql #query
USE tempdb
IF OBJECT_ID(N'#temp') IS NOT NULL DROP TABLE #temp
SELECT *
INTO #temp
FROM OPENROWSET(
'SQLNCLI',
'Server=SERVER\INSTANCE;Database=Test;Trusted_Connection=yes;',
'EXEC dbo.SomeSPname')
SELECT *
FROM #temp

'Invalid column' error from stored procedure query

I have a problem with a query to store results from a stored procedure in a temporary table. See below for a simplified version of the query itself:
USE [ADataBaseName]
GO
CREATE TABLE #temp(
SrcUD16 VARCHAR(200))
DECLARE #return_value int
INSERT INTO #temp(
SrcUD16)
EXEC #return_value = [dbo].[AStoredProcedure]
#PartKey = 100,
#Entity_Name = N'SomeEntityName,
#PeriodDesc = N'QA - 2014',
#CategoryKey = 12
SELECT * FROM #temp
GO
The stored procedure creates two other temporary tables to be joined in a Select statement:
SELECT SrcUD16 FROM #temp2 LEFT JOIN #temp3
ON #temp2.MinMaxCIC = #temp3.SrcUD15
Executing the first statement above will give me the following error:
'Invalid column name 'SrcUD16'
However, manually executing the stored procedure does yield the expected results for the field SrcUD16. I can also change the fieldname from SrcUD16 to SrcUD15 which results in a working query albeit incorrect column heading (i.e. ScrUD16 data with 'SrcUD15' as heading).
I strongly suspect this error has something to do with the order in which the temp tables are created and what the query recognizes as allowed field headings. Perhaps someone with better understanding has soem pointers for me?
Thanks in advance

Use the result of a system stored procedure as a queryable table

Note: the highest linked question does not solve the problem for system stored procedures, but it's close. With help of the commenters, I came to a working answer.
Trying to use statements such as the following for sp_spaceused, throws an error
SELECT * INTO #tblOutput exec sp_spaceused 'Account'
SELECT * FROM #tblOutput
The errors:
Must specify table to select from.
and:
An object or column name is missing or empty. For SELECT INTO statements, verify each column has a name. For other statements, look for empty alias names. Aliases defined as "" or [] are not allowed. Change the alias to a valid name.
When I fully declare a table variable, it works as expected, so it seems to me that the stored procedure does return an actual table.
CREATE TABLE #tblOutput (
name NVARCHAR(128) NOT NULL,
rows CHAR(11) NOT NULL,
reserved VARCHAR(18) NOT NULL,
data VARCHAR(18) NOT NULL,
index_size VARCHAR(18) NOT NULL,
unused VARCHAR(18) NOT NULL)
INSERT INTO #tblOutput exec sp_spaceused 'Response'
SELECT * FROM #tblOutput
Why is it not possible to use a temp table or table variable with the result set of EXECUTE sp_xxx? Or: does a more compact expression exist than having to predefine the full table each time?
(incidentally, and off-topic, Googling for the exact term SELECT * INTO #tmp exec sp_spaceused at the time of writing, returned exactly one result)
TL;DR: use SET FMTONLY OFF with OPENQUERY, details below.
It appears that the link provided by Daniel E. is only part of the solution. For instance, if you try:
-- no need to use sp_addlinkedserver
-- must fully specify sp_, because default db is master
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'exec somedb.dbo.sp_spaceused ''Account''')
you will receive the following error:
The OLE DB provider "SQLNCLI10" for linked server "LOCALSERVER\SQL2008" supplied inconsistent metadata for a column. The name was changed at execution time.
I found the solution through this post, and then a blog-post on OPENQUERY, which in turn told me that until SQL2008, you need to use SET FMTONLY OFF. The final solution, which is essentially surprisingly simple (and easier to accomplish since there is no need to specify a loopback linked server), is this:
SELECT * FROM OPENQUERY(
[SERVERNAME\SQL2008],
'SET FMTONLY OFF
EXEC somedb.dbo.sp_spaceused ''Account''')
In addition, if you haven't set DATA-ACCESS, you may get the following error:
Server 'SERVERNAME\SQL2008' is not configured for DATA ACCESS.
This can be remedied by running the following command:
EXEC sp_serveroption 'SERVERNAME\SQL2008', 'DATA ACCESS', TRUE
We cannot SELECT from a stored procedure thats why SELECT * INTO ..Exec sp_ will not work.
To get the result set returned from a store procedure we can INSERT INTO a table.
SELECT INTO statement creates a table on fly and inserts data from the source table/View/Function. The only condition is source table should exist and you should be able to Select from it.
Sql Server doesn't allow you to use SELECT from sp_ therefore you can only use the INSERT INTO statement when executing a stored procedure this means at run time you can add the returned result set into a table and Select from that table at later stage.
INSERT INTO statement requires the destination table name, An existing table. Therefore whether you use a Temp Table, Table variable or Sql server persistent table you will need to create the table first and only they you can use the syntax
INSERT INTO #TempTable
EXECUTE sp_Proc
Using [YOUR DATABASE NAME]
CREATE TABLE [YOURTABLENAME]
(Database_Name Varchar(128),
DataBase_Size VarChar(128),
unallocated_Space Varchar(128),
reserved Varchar(128),
data Varchar(128),
index_size Varchar(128),
unused Varchar(128)
);
INSERT INTO dbo.[YOUR TABLE NAME]
(
Database_Name,
DataBase_Size,
unallocated_Space,
reserved,
data,
index_size,
unused
)
EXEC sp_spaceused #oneresultset = 1
--To get it to return it all as one data set add the nonresultset=1 at the end and viola good to go for writing to a table. :)

SQL Server 2008: Creating dynamic Synonyms?

in my SQL Server 2008 database I have a number of different tables with the same structure. I query them in different stored procedures. My first try was to pass the table name to the stored procedure, like:
CREATE PROCEDURE MyTest
#tableName nvarchar(255)
AS
BEGIN
SELECT * FROM #tableName
END
But we can't use parameters for table names in SQL. So I asked you and tried the solution with using Synonyms instead of a parameter for the table name:
CREATE PROCEDURE MyTest
#tableName nvarchar(255)
AS
BEGIN
EXEC SetSimilarityTableNameSynonym #tbl = #tableName;
SELECT * FROM dbo.CurrentSimilarityTable
END
SetSimilarityTableNameSynonym is a SP to set the Synonym dbo.CurrentSimilarityTable to the passed value (the specific table name). It looks like:
CREATE PROCEDURE [dbo].[SetSimilarityTableNameSynonym]
#tbl nvarchar(255)
AS
BEGIN
IF object_id('dbo.CurrentSimilarityTable', 'SN') IS NOT NULL
DROP SYNONYM CurrentSimilarityTable;
-- Set the synonym for each existing table
IF #tbl = 'byArticle'
CREATE SYNONYM dbo.CurrentSimilarityTable FOR dbo.similarity_byArticle;
...
END
Now, as you probably see, the problem is with concurrent access to the SPs which will "destroy" each others assigned synonym. So I tried to create dynamic synonyms for each single SP-call with a GUID via NewID()
DECLARE #theGUID uniqueidentifier;
SET #theGUID=NEWID()
SET #theSynonym = 'dbo.SimTabSyn_' + CONVERT(nvarchar(255), #theGUID);
BUT ... I can't use the dynamical created name to create a synonym:
CREATE SYNONYM #theSynonym FOR dbo.similarity_byArticle;
doesn't work.
Has anybody an idea, how to get dynamical synonyms running? Is this even possible?
Thanks in advance,
Frank
All I can suggest is to run the CREATE SYNONYM in dynamic SQL. And this also means your code is running at quite high rights (db_owner or ddl_admin). You may need EXECUTE AS OWNER to allow it when you secure the code.
And how many synonyms will you end up with for the same table? If you have to do it this way, I'd use OBJECT_ID not NEWID and test first so you have one synonym per table.
But if you have one synonym per table then why not use the table name...?
What is the point is there creating 1 or more synonyms for the same table, given the table names are already unique...
I'd fix the database design.
Why would you want multiple concurrent users to overwrite the single resource (synonym)?
If your MyTest procedure is taking a the table name as a parameter, why not simply do dynamic SQL? You can validate the #tableName against against a hardcoded list of tables that this procedure is allowed to select from, or against sys.tables

Resources