How to check are there JSON Functions by SQL query? - sql

There are JSON Function in SQL 2016 like JSON_VALUE, JSON_QUERY and other.
I would like to use it in my queries, but I still have old servers with SQL 2014, for example, that are not allowed to use the new functionality.
Can I check are there functions like JSON_VALUE by query? Something like
IF operator_exists('JSON_VALUE')
SELECT JSON_VALUE([Value], '$.MyPath')
FROM [dbo].[MyTable]
WHERE [Name] = 'MyProperty'
ELSE
SELECT ''
Thanks.
UPDATE
If I use ckecking like this (thanks Rigerta Demiri)
DECLARE #compatibility_level int
SELECT #compatibility_level= compatibility_level FROM sys.databases WHERE name = 'MyDbName'
IF (#compatibility_level >= 130)
BEGIN
SELECT JSON_VALUE([Value], '$.MyPath')
FROM [dbo].[MyTable]
WHERE [Name] = 'MyProperty'
END
SELECT 'not allowed'
... I get the following SQL exception (on 2014 SQL Studio):
'JSON_VALUE' is not a recognized built-in function name
May be 2014 MSSQL interpretator try to parse all blocks of code and cannot understand what is JSON_VALUE?

Since it depends on the version of SQL Server that you have installed and since you have different instances (even older ones than SQL Server 2016) you can just check if the compatibility level of the database where you are trying to query is equal to 130.
You could do the following:
declare #compatibility_level int
select #compatibility_level= compatibility_level from sys.databases where name = 'TestDB'
if (#compatibility_level >= 130)
begin
declare #jsoninfo nvarchar(max)
set #jsoninfo=N'{
"info":{
"type":1,
"address":{
"town":"bristol",
"county":"avon",
"country":"england"
},
"tags":["sport", "water polo"]
},
"type":"basic"
}'
select json_value(#jsoninfo,'$.info.address.town') as town
end
The OPENJSON function is available only under compatibility level 130
(or higher).
as you can read in the documentation.
EDIT:
What you got happens because apparently "SQL Server doesn't know or care which branch of a conditional will be entered; it validates all of the statements in a batch anyway." as stated in the answer of this post: T-Sql appears to be evaluating “If” statement even when the condition is not true.
So, the workaround would be to create the whole statement as a dynamic string.
Like this:
declare #compatibility_level int
select #compatibility_level= compatibility_level from sys.databases where name = 'TradingDWH'
if (#compatibility_level >= 130)
begin
declare #sql nvarchar(max);
set #sql = ' declare #jsoninfo nvarchar(max) ' + ' set #jsoninfo=N''{ "info":{' + ' "type":1, "address":{ "town":"bristol", "county":"avon", "country":"england" }, "tags":["sport", "water polo"] }, "type":"basic" }'
set #sql = #sql + 'select json_value(#jsoninfo,''$.info.address.town'') as town'
select #sql
--exec sp_executesql #sql
-- or your own query, like this:
declare #sql2 nvarchar(max);
declare #MyProperty nvarchar(100) = 'YourProperty'
set #sql2 = ' SELECT JSON_VALUE([Value], ''$.MyPath'') '
set #sql2 = #sql2 + 'FROM [dbo].[MyTable] WHERE [Name] = #MyProperty '
select #sql2
--exec sp_executesql #sql2, N'#MyProperty nvarchar(100)', #MyProperty
end
else
begin
select 'Version prior to 130!' as [message]
end
One of many resources where you can read more about dynamic SQL is Don’t Fear Dynamic SQL.

Related

Select into tables dynamically with variables

I have some code to create tables based on a set of dates I define.
Example, I have 5 dates, and they are aren't consecutive. For any of these dates, I want to create a table and I am currently using a Select into.
I am having to do this 5 times, even though the only thing changing is the name of the new table created and the date. Is there a way to do this in an elegant way.
I started writing some code, but I am struggling to get it to loop through all the dates I want. The way I have written it currently, I only works if I edit the date at the start.
DECLARE #MyDate DATE;
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
SELECT *
into #TableName
FROM Original_Table
WHERE Query_Date = #MyDate;
Is this a one time thing or do you have to do this on a regular basis?
If it's the first, than I would just do it and get it over with.
If it's the latter, then I suspect something is very wrong with the way that system is designed - but assuming that can't be changed, you can create a stored procedure that will do this using dynamic SQL.
Something like this can get you started:
CREATE PROCEDURE dbo.CreateTableBasedOnDate
(
#MyDate DATE,
-- sysname is a system data type for identifiers: a non-nullable nvarchar(128).
#TableName sysname
)
AS
-- 200 is long enough. Yes, I did the math.
DECLARE #Sql nvarchar(200) =
-- Note: I'm not convinced that quotename is enough to protect you from sql injection.
-- you should be very careful with what user is allowed to execute this procedure.
N'SELECT * into '+ QUOTENAME(#TableName) +N'
FROM Original_Table
WHERE Query_Date = #MyDate;';
-- When dealing with dynamic SQL, Print is your best friend.
-- Remark this row and unremark the next only once you've verified you get the correct SQL
PRINT #SQL;
--EXEC sp_ExecuteSql #Sql, N'#MyDate Date', #MyDate
GO
Usage:
EXEC CreateTableBasedOnDate '2018-01-01', 'zohar';
Use dynamic SQL:
DECLARE #MyDate DATE, #TableName varchar(50);
SET #MyDate = '2019-01-01';
SET #TableName = 'Table1';
DECLARE #sql NVARCHAR(4000);
DECLARE #params NVARCHAR(4000);
SELECT #sql=N'
SELECT *
INTO ' + QUOTENAME(#TableName) + '
FROM Original_Table
WHERE Query_Date = #MyDate;';
SELECT #params = N'#MyDate DATE';
EXEC sys.sp_executesql #sql, #params, #MyDate=#MyDate
Note that dynamic SQL can be dangerous as it opens up a path for SQL injection. Its fine if you are just using it in your own local scripts, but take care if you e.g. wrap this in a procedure that is more widely accessible.
I would use dynamic SQL although I would add another variables for the schema:
DECLARE
#MyDate nVarchar(50) = '2019-01-01',
#Schema nVarchar (50) = 'dbo',
#TableName nVarchar(250) = 'Table1',
#SQL nVarchar(500);
Set #SQL = '
SELECT *
into '+ QUOTENAME(#Schema)+'.'+ QUOTENAME(#TableName) +'
FROM Original_Table
WHERE Query_Date = '+ #MyDate +';
'
--print #SQL
Exec(#SQL)
You can use the print statement to see how the SQL will look before executing this properly. You may also want to look at adding this as a stored procedure.

Execute openrowset stored procedure, parameter use

Little bit of background information:
I have a stored procedure, lets call it SP1. SP1 calls another stored procedure: SP2. SP2 calls another stored procedure: SP3.
Now, the first stored procedure (SP1) returns a resultset. The resultset are parameters for SP2, this is done with a cursor.
Because of these nested inserts and executes, i have to use an openrowset dynamic SQL string to execute my stored procedures.
This is my query:
DECLARE #P_Source varchar(255) = 'test'
DECLARE #P_Location varchar(255) = 'test'
DECLARE #sql varchar(max)
SET #sql = 'INSERT INTO #tmp
SELECT *
FROM OPENROWSET (
''SQLOLEDB'',
''Server=(local);TRUSTED_CONNECTION=YES;'',
''set fmtonly off
EXECUTE dbo.SP1
#P_Source = '''''+#P_Source+'''''''
,#P_Location = '''''+#P_Location+'''''''
)'
exec(#sql)
(I have ofcourse created the table #tmp). I have more parameters to be exact (12), all varchar, but I left them out to not make it messy.
I'm getting the following error
Msg 102, Level 15, State 1, Line 12
Incorrect syntax near ','.
am I using the openrowset command in the correct way with the corresponding procedure parameters?
All those quotes get confusing. By doing select #sql prior to the exec you can see what SQL Server is going to try and do. Based on the query you've provided #sql currently contains:
INSERT INTO #tmp
SELECT *
FROM OPENROWSET (
'SQLOLEDB',
'Server=(local);TRUSTED_CONNECTION=YES;',
'set fmtonly off
EXECUTE dbo.SP1
#P_Source = ''test'''
,#P_Location = ''test'''
)
To help you build up to the final solution you could try having a dummy SP1 that takes two numeric parameters - that'll eliminate some quotes for you to worry about. Once you have that working you can proceed to add string parameters until you get what you want.
For sql linked server use OPENQUERY
https://learn.microsoft.com/en-us/sql/t-sql/functions/openquery-transact-sql
and sp_executesql
DECLARE #P_Source varchar(255) = 'test'
DECLARE #P_Location varchar(255) = 'test'
DECLARE #SQL NVARCHAR(MAX) = '',
#QUERY NVARCHAR(MAX) = '',
#Params NVARCHAR(500) = N'DECLARE #P_Source VARCHAR(255),#P_Location VARCHAR(255); ',
#ParamsValue NVARCHAR(500) = N'SELECT #P_Source = '''''+#P_Source+''''', #P_Location = '''''+#P_Location+''''';'
SET #Query = N'set fmtonly off; EXECUTE dbo.SP1 #P_Source, #P_Location'
SET #SQL = 'SELECT * FROM OPENQUERY([Local],'' sys.sp_executesql ' + #Params + #ParamsValue + #Query +''' )'
INSERT INTO #Tmp
EXEC (#SQL)

Is it possible to set a part of a select statement in a variable

I have a query of which the select-part is really long. I'd like to split this in several pieces, especially because some parts are in there twice or even more often.
What I'd like is something like the following:
Declare #SQLPart as varchar(1000)
Set #SQLPart = 'Field1,
case ... as Field2,'
Select ..., #SQLPart, ... From .....
Unfortunately this results error messages. I tried something like EXEC(#SQLPart) as well but of course this also didn't work. How would I solve this?
Yes, dynamic sql and sp_executesql:
CREATE TABLE ##Temp (Field1 int, Field2 int)
Declare #SQLPart nvarchar(1000)
Set #SQLPart = N'Field1, Field2 '
DECLARE #SQL nvarchar(1000) = N'SELECT ' + #SQLPart + 'FROM ##Temp'
PRINT #SQL
EXEC sp_executesql #SQL
DROP TABLE ##Temp
Your SQL code must be nvarchar type.
Alse sp_executesql is better than EXECUTE function, when you have many similar queries, sp_executesql caches executaion plans, and it can be better in perfomance.
You can use dynamic sql here,and use a EXECUTE keyword to execute this dynamic query
Declare #SQLPart as varchar(1000)
Set #SQLPart = 'Field1,
case ... as Field2,'
EXECUTE ('SELECT ....,'+#SQLPart+',... FROM ...')
SQL Server does not support Macro-Substitution, so you would have to use Dynamic SQL.
Declare #SQL varchar(max) ='Select ... ' + #SQLPart + '... from ...'
Exec(#SQL)

Avoid using dynamic from clause

How can i avoid using dynamic from clause? Even if i don't know the database name, i prefer to use a static statement, like this:
select *
into #tempTable
from #DBName.Invoices
where InvoiceId = 5.
I got this error: Msg 102, Level 15, State 1, Line 6
Incorrect syntax near '.'.
I need to use select into clause because the column names may be different from each databases;
Thanks!
Unfortunately you will have to use dynamic SQL for this, see below for an example
Declare #DBNAME NVARCHAR(MAX) = 'xxx'
Declare #SQL NVARCHAR(MAX) ='select *
into #tempTable
from ' + #DBName + '.Invoices
where InvoiceId = 5.'
execute sp_executesql #SQL
How can i avoid using dynamic from clause? Even if i don't know the database name, i prefer to use a static statement
SQL wont accept columnnames,tablenames,databasenames as parameters.so unless you you avoid them,you cant avoid dynamic sql..
Change your query to dynamic sql to avoid error..But again you will have a problem with temp tables scope
--This will fail ,because temp table falls under different scope
Declare #sql nvarchar(4000)
set #sql='
select *
into #tempTable
from #DBName.Invoices
where InvoiceId = 5'
---one option is to use global temp tables
declare #dbname varchar(1000)
set #dbname=db_name()
declare #sql nvarchar(4000)
set #sql='select *
into ##tempTable
from '+#DBName+'.dbo.test_Delete '
exec(#sql)
select * from ##temptable
But be carefull with above approach,since above temp table have global scope..
You also can use Openrowset ,some thing like below
select * into #temp from openrowset
('SQLNCLI','Server=yourinstancename;Trusted_Connection=yes;', 'select * form table')

Stored procedure causing syntax error

I have written this stored procedure :
CREATE PROCEDURE [dbo].[spGetCuisines]
#RestaurantID INT ,
#CuisineID NVARCHAR(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
SET #sql = 'SELECT CuisineID, CuisineName
FROM dbo.Cuisine1
WHERE CuisineID IN (
SELECT dbo.Dishes1.CuisineID
FROM dbo.Dishes1
WHERE DishID IN ( SELECT DishID
FROM dbo.RestaurantDish
WHERE RestaurantID = '
+ CAST(#RestaurantID AS NVARCHAR(MAX)) + ' ) )'
IF #CuisineID <> ''
BEGIN
SET #sql += 'AND Cuisine1.CuisineID IN('
+ CAST(#CuisineID AS NVARCHAR(MAX)) +')'
END
EXECUTE sp_executesql #sql;
END
I am using 3 tables with their columns listed below:
Dishes1
DishID
DishName
CuisineID
Price
Cuisine1
CuisineID
CuisineName
Type
DateCreated
DateModified
DateDeleted
RestaurantDish
RestaurantDishID
RestaurantID
DishID
but my stored procedure gives me syntax error on this line:
SET #sql += 'AND Cuisine1.CuisineID IN('+ CAST(#CuisineID AS NVARCHAR(MAX)) +')'
it says:
incorrect syntax near "+"
Can somebody guide me? Does the SQL Server version have something to do with this?
The syntax you are using is only valid on SQL Server 2008 and above. On SQL Server 2005, you'll have to change:
SET #sql += ...
To:
SET #sql = #sql + ...
There's absolutely no need to use dynamic SQL here - so don't ! Also: prefer JOIN over subqueries - joins are typically faster, and quite frankly - code is much easier to read!
Just use:
CREATE PROCEDURE [dbo].[spGetCuisines]
#RestaurantID INT ,
#CuisineID NVARCHAR(200)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
SELECT
c.CuisineID, c.CuisineName
FROM
dbo.Cuisine1 c
INNER JOIN
dbo.Dishes1 d ON d.CuisineID = c.CuisineID
INNER JOIN
dbo.Restaurant1 r ON r.DishID = d.DishID
WHERE
r.RestaurantID = #RestaurantID
AND (#CuisineID = '' OR c.CuisineID = #CuisineID)
END
And Aaron Bertrand is absolutely right, of course - this only works if you pass in a single CuisineID as string.
If your #CuisineID parameter contains multiple values then you need something like this instead:
WHERE
r.RestaurantID = #RestaurantID
AND (#CuisineID = '' OR c.CuisineID IN dbo.Split(#CuisineID))
Using a function Split you can split up a comma-separated list of ID's into a table variable and use the IN operator to match to a list of possible values.
are you perhaps passing in a comma delimited string??
If so, there is a better way to handle this, see here:
http://codebetter.com/raymondlewallen/2005/10/26/quick-t-sql-to-parse-a-delimited-string/