I want to add comments to my SQL CLR functions (as I do to other SQL objects I am creating or editing - functions, procedures and views). Unfortunately, I am not able to do this for the SQL CLR objects.
For example, the following code:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =================================================================================================================================
-- Author: gotqn
-- Create date: 2015-03-25
-- Description: Converts a string that has been encoded for transmission in a URL into a decoded string.
-- Usage Example:
/*
SELECT [dbo].[fn_UrlDecode]('http://stackoverflow.com/search?q=tql+sql+server');
*/
-- =================================================================================================================================
CREATE FUNCTION [dbo].[fn_UrlDecode] (#value NVARCHAR(MAX))
RETURNS NVARCHAR(MAX)
AS EXTERNAL NAME [Utils].[Utils].[UrlDecode]
GO
when the function is script from the SQL Management studio is going to produce this:
SET ANSI_NULLS OFF
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE FUNCTION [dbo].[fn_UrlDecode](#value [nvarchar](max))
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS
EXTERNAL NAME [Utils].[Utils].[UrlDecode]
GO
I try to fix this moving the comments part after the AS as this is the way comments are added for views, but it fails again. Then I try to put the comments after the CREATE clause, after the EXTERNAL NAME ... clause, but nothing changed.
Is there a way to fix this behaviour?
While #Damien is correct as to why the comments are not saved, there is still a somewhat work-around to store comments: Extended Properties.
For example:
EXEC sys.sp_addextendedproperty #name = N'comments', #value = N'
-- =================================================================================================================================
-- Author: gotqn
-- Create date: 2015-03-25
-- Description: Converts a string that has been encoded for transmission in a URL into a decoded string.
-- Usage Example:
/*
SELECT [dbo].[fn_UrlDecode](''http://stackoverflow.com/search?q=tql+sql+server'');
*/
-- =================================================================================================================================
', #level0type = 'SCHEMA', #level0name = N'dbo',
#level1type = 'FUNCTION', #level1name = N'fn_UrlDecode';
You just need to escape your embedded single-quotes.
Then you can retrieve them via:
SELECT [value]
FROM sys.fn_listextendedproperty(N'comments', 'SCHEMA', N'N'dbo',
'FUNCTION', N'fn_UrlDecode', NULL, NULL);
Minor additional note: if you won't ever decode URLs that are more than 4000 characters long (and I am pretty sure that you won't run into many that are even over 2048 characters), then you would be better served to use NVARCHAR(4000) for both input and output datatypes as that will be quite a bit faster than if either, or both, are NVARCHAR(MAX).
Basically, if it's a type not listed as having data stored in sys.sql_modules then the original text that created the object is not retained and so comments aren't retained. No CLR object stores such text.
This is the expected behavior. Even when you write a native TSQL script adding comments right before the routine signature, build it against the DBMS and do right click/edit to see the code the comments won't be there. Go ahead and give a try to this approach:
CREATE FUNCTION [dbo].[fn_UrlDecode]
(
#value [nvarchar](max)
)
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS
/*
***All the comments goes here***
*/
EXTERNAL NAME [Utils].[Utils].[UrlDecode]
GO
Hope it helps!
Committing Necromancy.
Solution first...reason second...
Simply wrap the CLR function in a standard function and put the comments in the standard function.
Overkill? Perhaps, but as I said, I have a reason and need in my current situation.
As an employee of a contractor to a large organization, there are tons of fingers in every pie, and the hands attached to those fingers are usually long gone and forgotten...but maintenance of their legacy code lives on. This is especially noticeable when external assemblies are loaded in a database, and there is a need to change or augment existing libraries...somewhere...but where. The coder is gone, and the documentation of the process is buried somewhere long forgotten.
So, for me, the primary reason for commenting such functions is to make it easy to identify the name of the Assembly and the Repository for the Source Code. Other than starting out with properly documented code in the first place (remember this is legacy cruft) I am open to other suggestions about better places/ways to do this...I am all ears.
Providing this information in a comment, somewhere in or related to the function or assembly, is very helpful. The simple wrapper noted above is a very KISS method to achieve this goal.
Related
I need to work through how to take stored procedure functions from
region-us.INFORMATION_SCHEMA.ROUTINES
and modify the backticks that default to coming through around the project and place them around the dataset.schema.table()
The reason is more for uniform results across our system than a technical error need.
currently when I run this query
SELECT
replace(ddl, 'CREATE PROC', 'CREATE OR REPLACE PROC'),
FROM region-us.INFORMATION_SCHEMA.ROUTINES
where lower(routine_type) = 'procedure'
It will return the below:
`project-data-sandbox`.schema.MySP()
`project-data-sandbox`.schema.YourSP(MySP)
`project-data-sandbox`.inv.partnumber(orderid)
`project-data-sandbox`.inv_part.part_number(part_id)
I have tried the below query
SELECT
REGEXP_REPLACE(ddl, r"project-data-sandbox`.", "project-data-sandbox.") AS replaced_word
, REGEXP_REPLACE(ddl, r'`([a-zA-Z]+(-[a-zA-Z]+)+)`\.[a-zA-Z]+\.[a-zA-Z]+\(\)','Apples') tester
FROM region-us.INFORMATION_SCHEMA.ROUTINES
where lower(routine_type) = 'procedure'
I get part of what I want. However, the problem is our stored procedures can be named any sort of names and they could require objects to be passed to them.
I added the tester column to see if I could replace the project string with another word (or regex) but it isn't even replacing it with apples yet.
which I would want turned into this:
`project-data-sandbox.schema.MySP`()
`project-data-sandbox.schema.YourSP`(MySP)
`project-data-sandbox.inv.partnumber`(orderid)
`project-data-sandbox.inv_part.part_number`(part_id)
I'm working through Regexp_replace but I'm having difficulty figuring out how to get the backtick between the parenthesis and the last letter.
Thanks for any help!
Hi I want to take input from user in my
DO BEGIN and END block...
I tried to use /prompt inside but it doesnt work.
Do we have any other way that we can use it inside function or block in postgres?
Important note: PLpgSQL is server side only language - there is not any possibility do any user interactivity operation. You have to collect input on client side before start of PLpgSQL code, and user input push there as parameters.
DO statement is server side statement, so you cannot do any interactive action there.
DO statement doesn't support parameters so it is not easy push any parameters inside DO statement, but is possible with custom configuration variables:
\prompt 'enter some text: ' psqlvar
\o /dev/null
select set_config('psql.psqlvar', :'psqlvar', false);
\o
do $$
DECLARE var text = current_setting('psql.psqlvar');
BEGIN
RAISE NOTICE 'entered text is: %', var;
END;
$$;
The function set_config is used to moving a content of client variable :psqlvar to server side - session variable psql.psqlvar. The content of this server side variable is taken by function current_setting.
You have to separate in your mind server side and client side content. The DO statement is evaluated on server side. The psql \prompt command is evaluated on client side.
As bad ideas go, asking for user input in a stored procedure is up there with using usernames as session identifiers. This is a really, really bad idea. Yes there are ways in some environmnets to do this. But just because you can does not mean you should. For example I have heard of people using pl/python to make a network connection back to the client computer and ask for more information. However, this is frankly DailyWTF territory. It also assumes a protocol and listener on the client to ask for this request and therefore doesn't work from pgadmin.
Now, DO creates an anonymous function without arguments and immediately executes it. This seems to be where your real problem is.
Your best solution is to just create an actual function with arguments and accept the input there. Then it can be used anywhere. And you can re-use, modify security, etc. This sounds like it is exactly what you need.
Failing that you could preprocess your do block before you send it.
Your best design where you want to reuse PL/PGSQL code with inputs is to use a function (see CREATE FUNCTION) instead of DO (note you can put these in another schema if that is a concern).
I am trying to check the input length, to see if it's less than 7. It should show an error message, but the code below doesn't work. What's wrong with it?
CREATE OR REPLACE PROCEDURE prc_staffContact(IN_staffID IN CHAR, IN_staffContact IN VARCHAR) IS
v_staffName VARCHAR(50);
v_staffID CHAR(6);
v_staffContact VARCHAR(11);
BEGIN
SELECT s.staffName, s.staffID, s.staffContact
INTO v_staffName, v_staffID, v_staffContact
FROM staff s
WHERE staffID = IN_staffID;
IF (LENGTH(IN_staffContact) < 7 )
THEN
DBMS_OUTPUT.PUT_LINE('Error. Contact number at least 7 digits.');
ELSE
UPDATE staff
SET staffContact = IN_staffContact
WHERE staffID = IN_staffID;
DBMS_OUTPUT.PUT_LINE('=============================================================================');
DBMS_OUTPUT.PUT_LINE('The contact number of [ ' ||v_staffName || ' ] has been updated successfully.');
DBMS_OUTPUT.PUT_LINE('New contact number: [ ' ||v_staffContact || ' ].');
DBMS_OUTPUT.PUT_LINE('=============================================================================');
END IF;
END;
/
You said something that appears to be contradictory:
it shows [PL/SQL procedure successfully completed.] but doesn't checking even my input is less than 7 characters. and the data doesn't update also.
You described that you're doing as:
i save it under procedure1.sql and i start it in sql plus. that is my 1st call. after that i call the exec prc_staffContact('100001', '0000')
Together those suggest that when you say the data isn't updated, what you really mean is you don't get the contact number/new contact number message from the else branch, and I think you're assuming that means the update doesn't happen either, so it didn't execute either branch. But you must have gone into either the if or the else, by definition.
So if you didn't get either message, then you haven't done:
set serveroutput on
in SQL*Plus before calling exec. That setting is off by default, unless you have it turned on in your login.sql or glogin.sql, so you have to turn it on explicitly if you want to see dbms_output messages.
In this case, for the validation, (a) you probably want the select inside the elsef` too, partly because (b) if the passed values doesn't exist you'll get a no_data_found exception, and (c) you might want to consider throwing an exception if the length is less than 7 rather than (only) displaying a message. Someone else calling this might not have serverout on either, or might be using a different client that doesn't have that option.
You've also got v_staffID defined as char(6). Apart from wondering why that isn't a varchar2, the length you've given it means that if IN_staffID is 7 chars or more, the select into will get a 'character string buffer too small' error. I'd declare that as:
v_staffID staff.staffID%TYPE;
... to avoid issues like that, and the same for the other fields that relate to table columns.
And your 'success' message is showing the old contact number, not the new one. Not sure you need v_staffContact at all.
Look at your code carefully. May be your variable types not compatible or stored procedure parameters not compatible with other variables (or table columns, column's types). I tested your code in my database, all success. but may be error not data found in your select statement, or may be buffer too small error. Hope this help you. thanks
I would like to know after much searching how I would match different variations of input to one sql row using standard TSQL. Here is the scenario:
I have in my sql row the following text: I love
I then have the following 3 inputs all of which should return a match to this row:
"I want to tell you we all love StackOverflow"
"I'm totally in love with StackOverflow"
"I really love StackOverflow"
As you can see I have bolded the reason for the match to try and make it clearer to you why they match. The I in I'm is deliberately matched too so it would be good if we could include that in matches.
I thought about splitting the input string which I done using the following TSQL:
-- Create a space delimited string for testing
declare #str varchar(max)
select #str = 'I want to tell you we all love StackOverflow'
-- XML tag the string by replacing spaces with </x><x> tags
declare #xml xml
select #xml = cast('<x><![CDATA['+ replace(#str,' ',']]></x><x><![CDATA[') + ']]></x>' as xml)
-- Finally select values from nodes <x> and trim at the same time
select ltrim(rtrim(mynode.value('.[1]', 'nvarchar(12)'))) as Code
from (select #xml doc) xx
cross apply doc.nodes('/x') (mynode)
This gets me all the words as separate rows but then I could not work out how to do the query for matching these.
Therefore any help from this point or any alternate ways of matching as required would be more than greatly appreciated!
UPDATE:
#freefaller pointed me to the RegEx route and creating a function I have been able to get a bit further forward, therefore +1 #freefaller, however I now need to know how I can get it to look at all my table rows rather than the hard-coded input of 'I love' I now have the following select statements:
SELECT * FROM dbo.FindWordsInContext('i love','I want to tell you we all love StackOverflow',30)
SELECT * FROM dbo.FindWordsInContext('i love','I''m totally in love with StackOverflow',30)
SELECT * FROM dbo.FindWordsInContext('i love','I really love StackOverflow',30)
The above returns me the number of times matched and the context of the string matched, therefore the first select above returns:
Hits Context
1 ...I want to tell you we all love StackOv...
So based on the fact we now have the above can anyone tell me how to make this function look at all of the rows for matches and then return the row/rows that have a match?
One option would be to use Regular Expressions via SQLCLR objects as explained here.
I have never myself created SQLCLR objects, so cannot comment on the ease of this method. I am however, a great fan of Regular Expressions and would recommend their use for most text search / manipulation
Edit: In response to the comment, I have no experience of SQLCLR, but assuming you get that working, something like the following simple untested TSQL might work...
SELECT *
FROM mytable
WHERE dbo.RegexMatch(#search, REPLACE(myfield, ' ', '.*?')) = 1
I have managed to come up with an answer to my own question so thought I thought I would post here in case anyone else has similar requirements in the future. Basically it relies upon the SQL-CLR regular expression functionality and runs with minimal impact to performance.
Firstly enable SQL-CLR on your server if not already available (you need to be sysadmin):
--Enables CLR Integration
exec sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
Then you will need to create the assembly in SQL (Don't forget to change your path from D:\SqlRegEx.dll and use SAFE permission set as this is the most restrictive and safest set of permissions but won't go into detail here.) :
CREATE ASSEMBLY [SqlRegEx] FROM 'D:\SqlRegEx.dll' WITH PERMISSION_SET = SAFE
Now create the actual function you will call:
CREATE FUNCTION [dbo].[RegexMatch]
(#Input NVARCHAR(MAX), #Pattern NVARCHAR(MAX), #IgnoreCase BIT)
RETURNS BIT
AS EXTERNAL NAME SqlRegEx.[SqlClrTools.SqlRegEx].RegExMatch
Finally and to complete and answer my own question we can then run the following TSQL:
SELECT *
FROM your_table
WHERE dbo.RegexMatch(#search, REPLACE(your_field, ' ', '.*?'), 1) = 1
SELECT *
FROM your_table
WHERE dbo.RegexMatch(#search, REPLACE(REVERSE(your_field), ' ', '.*?'), 1) = 1
I hope this will help someone in what should be a simple search option in the future.
I have some stored procedure
CREATE PROC MyProc ( #FullName NVARCHAR(200) = NULL )
AS --.............
When I call this proc as exec MyProc 'Some english text' it works good.
But if call it as exec MyProc 'Русский текст' that is using Russian alphabet, it doesn't work properly.
And the call exec MyProc N'Русский текст' works good again.
I have a client application and... I need to add N prefix to parameters? If yes, how do I do it?
The N would only be needed (manually) if you are concatenating a string in the .net code.
It's automatic if you declare a SQLParameter as nvarchar: the framework takes care of it for you.
So your client code is incorrect and opens you up to SQL injection
Anyway, the N says that the string literal is unicode.
N means National language character set also well known as Unicode.
See on Microsoft KB: You must precede all Unicode strings with a prefix N when you deal with Unicode string constants in SQL Server
What kind of client application do you use?
if it is .Net - just specify that parameter has Unicode type.
If it is OLEDB/ADO - the same thing, param must be marked as unicode, and pass wchar_t instead of char
Update
Sample from msdn (pay attention to NVarChar):
yourCommand.Parameters.Add(
"#FullName", SqlDbType.NVarChar, 80).Value = "toasters";
And please avoid SQL injection ;)