While loop with multiple conditions in T-SQL - sql

Let me start out by saying I know I KNOW that these kind of loops are horrible and you shouldn't use them in Transact SQL. But, for some purposes (those purposes being irrelevant so don't ask me "what're you trying to do!?") ya just have to. I don't want to, but I gotta.
Anyway. Is there some way to have a while loop in T-SQL terminate on a complex conditional statement? like, in C# I'd just say while (i > -10 && i < 10) , because I want the loop to terminate when the sentinel value is between -10 and 10, but I just... can't figure out how to do it.
It's probably excruciatingly simple... or.. impossible. Please advise.
Right now, I've just got
WHILE #N <> 0
BEGIN
--code and such here
END

You must look at declaration of WHILE statement:
WHILE Boolean_expression
{ sql_statement | statement_block | BREAK | CONTINUE }
First of all you can use complex Boolean_expression as Dan said:
WHILE #N > -1 AND #N <10
BEGIN
END
If you want to add more flexibility to you code you can use IF with BREAK, something like this:
WHILE #N > -1 AND #N <10
BEGIN
-- code
IF (SELECT MAX(ListPrice) FROM Production.Product) > $500
BREAK
END
-- code
END
to go out of cycle or use CONTINUE to skip one cycle.

I feel like a complete idiot. And I'm sure I look like one too.
I tried all that stuff, trying to get the condition to work. I just could not figure out why it wasn't working.
It just hit me. I was changing the code while I was debugging it, and it wasn't executing my changes.
I'm pretty embarrassed. I was doing it right after all. I was just... doing it wrong.

Related

Why are module and procedure names repeated after the body?

In Modula-2 and Oberon each module and procedure declaration must end with the name of the module or procedure. It is not needed in Pascal. I have never really understood the motivation for this. Can someone enlighten me?
After reading some (I am not an expert) I would wager this is just a syntax demand of the function for better readability.
I'll go one step further, and say in large, old, badly written procedures/function in other languages, this is sometimes done without the language requiring it. I've often seen:
int veryLongC++Function() {
...
...
... 3000 code lines
} //veryLongC++Function
So a reader jumping near the end knows what in the mess they are looking at. August mentions in the comment this is much less robust when not enforced by the compiler - in case of a name change.
Another important aspect is nested procedures - here the explicit ending makes things much more readable - checkout chapter 7 for an example - a nested procedure is declared between a declaration and before the BEGIN. The syntax makes this looks much better (in my opinion).
So long story short - I think the main benefit is readability.
It's possible you get something like this:
MODULE A;
...
PROCEDURE B;
...
PROCEDURE C;
...
BEGIN (* C *)
...
END C;
BEGIN (* B *)
...
END B;
BEGIN (* A *)
...
END A.
In that case, for readability you have three bodies (and more if you have defined nested procedures and functions) at the end of your code. In order to see which one is the one ending in the END clause, it's better if the compiler can check that you ar closing things correctly (as you see, I even put it ---as a comment, but would be nice if the compiler accepted it as a valid identifier and checked just to be sure things match correctly--- at the start BEGIN body clause)

suppress log10 errors for missing values in SAS

I have the following bit of code that was provided by someone to establish if a mobile number begins with a 7:
if int(abs(mobile_telephone1)/10**int(log10(abs(mobile_telephone1)))) ne 7
then do .....
This works perfectly but if the value is missing I get large error messages and I was wondering if there is a way to suppress the error messages for this particular statement?
You can't suppress errors for a specific function (usually), but you could disable them before your datastep, then re-apply them after
options errors=0 ;
data xyz ;
/* some code */
run ;
options errors=25 ;
But why not just do it far more simply without running the risk of any errors...
data xyz ;
tel = 7123455678 ;
chartel = put(tel,12.) ;
if chartel =: '7' then istel = 1 ;
run ;
To more specifically answer your question (although Chris' solution is definitely the way to go), you can use the coalesce function.
if log10(coalesce(mobile_telephone1,1)) ...
Coalesce returns the first nonmissing value. In this case of course 0 is also an unacceptable result, so I use 1 as it seems harmless.
Also, a somewhat better math-based solution would be
if (mod(mobile_telephone1,1e10) - mod(mobile_telephone1,1e9) = 7e9) ...
Don't need to use log10s and such, just compare the modulos.

Block-scope for local variable

Is there a better way to create an arbitrary block in VB.Net to limit the scope of a local variable? If have tried "If 1 Then", but it just looks kludgy.
If 1 Then
Dim table = InputParameter1
Dim new_row = table.AddRow
new_row.field(1) = InputParameter3.user_value
End If
I just don't want to have table and new_row accessible later in the procedure.
Re-factor your procedure so there's another procedure in which table and new_row do their thing. If you really don't need them to be visible later on, then they are doing a distinct sub-task that should be refactored anyway.
Generally speaking, when you can partition your code into N segments, where each segment has no dependency on the others, then you are looking at N tasks and preferably N procedures.
This goes back to the rule that any procedure should do one thing, and multiple segments are a sign that it's doing more than one thing.
With Option Strict On, that'd have to be If True Then, but the more preferred option (as far as I have seen) is Do ... Loop While False.
But, no there is no syntax like a simple Begin ... End (or { ...}).
If you can work with an IDisposable, and have it disposed at the end of the block, Using newVariable = AnIDisposable gives you the closest to what you're describing, but like I said, it is disposed at the end.

Matching multiple variations of input to one sql row

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.

BREAK description in BOL

SQL Server 2005 BOL says about BREAK:
Exits the innermost loop in a WHILE or IF…ELSE statement.
I don't understand this part about IF…ELSE. It looks like you can put BREAK inside IF. Actually BREAK works only inside WHILE, and control flow pass after loop's END. BREAK not in a WHILE context fires error, and that's completely OK.
My question is - why documentation mention IF...ELSE?
BTW, SQL Server 2000 BOL says only this:
Exits the innermost WHILE loop.
SQL 2008 BOL says:
Exits the innermost loop in a WHILE
statement or an IF…ELSE statement
inside a WHILE loop.
which is probably what the SQL 2005 documentation should have said.
The SQL2008 explanation still seems somewhat confusing however. To me it implies that break in an IF…ELSE statement inside a WHILE loop would just exit the IF…ELSE. This is not the case.
DECLARE #i INT = 0
WHILE #i<10
BEGIN
IF(#i=3)
BEGIN
PRINT 'i=3'
BREAK
END
ELSE
BEGIN
PRINT 'i<>3'
END
SET #i = #i+1
END
PRINT 'out of while'
Prints
i<>3
i<>3
i<>3
i=3
out of while