Restrict the number of digits in the SQL Stored procedure Parameter - sql-server

I have an requirement to restrict the number of digits in parameter to be passed into Stored procedure. consider below stored procedure sample.
Create procedure Sp_test
#uniqueID1 varchar(100),
#uniqueID2 varchar(100),
#uniqueID3 varchar(100)
as
Begin
select x,y,z
from the table1 inner join table2 on a=b
Where UniqueID in (#unitID1,#unitID2,#unitID3)
end
Now while I pass the parameter values, I have to check the number of digits its given,
Say exec sp_test '123456','456789','12356'
The number of digits will be always above 4 or 5 digits. It should not be less than 4 digits. I need to have a check of the same in stored procedure.
NOTE: Here Icould have used single parameter to pass multiple unique IDs , but I am using it in another application where this has to be passed as different parameter.
Please help me solving it.

Maybe something as simple as using a IF..ELSE?
USE Sandbox;
GO
CREATE PROC Sample_sp #id1 varchar(5), #Id2 varchar(5) AS
IF LEN(#id1) IN (4,5) AND LEN(#id2) IN (4,5) BEGIN
PRINT 'Do your SP stuff here.';
SELECT #id1, #Id2;
END ELSE BEGIN
RAISERROR('An ID must have a length of 4 or 5 characters.',11,1);
END
GO
EXEC Sample_sp '12345', '6789'; --This will work, because both values are of length 4 or 5
GO
EXEC Sample_sp '12345', '678901'; --This will work, because the value would be truncated.
GO
EXEC Sample_sp '123456', '6789'; --This will work, because the value would be truncated.
GO
EXEC Sample_sp '1234', '6789'; --This will work, because both values are of length 4 or 5
GO
EXEC Sample_sp '123', '6789'; --Will fail, ID1 has a length of 3
GO
DROP PROC Sample_sp;
If you don't want the truncation (or at least fail if more than 5 characters are supplied), then I'd suggest increasing the value to a varchar(6). It'll still cause truncation, but considering you only care if the value of 6 (or more characters), both lengths 6 and 500 fulfil that requirement; thus if a parameter with 500 characters is passed, it'll truncate to a varchar(6) and then still fail the check and cause the error.

Related

MS SQL Server CAST to int returns unexpected output

OS type: Windows
SQL Server: 2017
Collation: SQL_Latin1_General_CP1_CI_AS
I have a stored procedure as below
USE [test]
GO
ALTER PROCEDURE [dbo].[output_test]
#output nvarchar(250)
AS
BEGIN
SELECT CAST(#output AS int)
END
Input to the procedure: '1234'
I use MultiByteToWideChar() function to convert from UTF8 to UCS2 before passing to the procedure.
The output of the MultiByteToWideChar() function is '1111'
But when the procedure is executed, I got error as below
Conversion failed when converting the nvarchar value '1234.' to data type int.
It is noted that an extra '.' is appended at the end.
But When we just retrieved the data without cast (SELECT) it has correct value as 1234
I have aallocated (strlen+1)*sizeof(SQLWCHAR) =>(4+1)*2 = 10 bytes to store the resulted wide char string, which is output from MultiByteToWideChar function. Here additional 2 bytes are allocated for null termination.
However, if that additional 2 bytes for null termination is not added then the procedure works fine.
Could anyone help me in understanding why the extra bytes causes the problem (though it is 0, it results in '.') ?
Whether there is any limitation with CAST to int when using Unicode characters ?
Thanks in advance.
TRY THIS
alter PROCEDURE Test (#YesNo VARCHAR(5) OUTPUT)
AS
BEGIN
SET NOCOUNT ON;
SET #YesNo = 1
END
GO
DECLARE #Result VARCHAR(5)
EXEC Test #YesNo = #Result OUTPUT
SELECT #Result

SQL Server 2008 R2 - Generating unique number

Requirement
I want a way to generate a new unique number (Invoice Number) in a continuous sequence (no number should be left out when generating a new number)
Valid Example: 1, 2, 3, 4
Invalid Example: 1, 2, 4, 3 (not in a continuous sequence)
Current Schema
Here is my existing table schema of the table Test
Solution i came up with
After doing some research i came up with the below code which seems to be working as of now.
DECLARE #i as int=0
While(#i<=10000 * 10000)
BEGIN
Begin Transaction
Insert Into Test(UniqueNo,[Text])values((Select IsNull(MAX(UniqueNo),0)+1 from Test with (TABLOCK)),'a')
COMMIT
SET #i = #i + 1;
END
Testing
I tried running the code from 12 different SQL Query or 12 threads you can say and currently it generates new and unique value for each records even after inserting 162,921 rows
Main Question
Can the above code result into duplicate values?
I tried it by hit-and-trial method and it works perfectly BUT when i go in-depth of Transaction Locking the select statement generates a Shared Lock for the whole table that means it will allow concurrent transactions to access the same data, right?
That means that multiple transactions can generate duplicate values, right?
Then how come i am not able to see any duplicate values yet?
EDIT
As per david's comment
I cannot use identity field because in any case if i delete a record then it would be difficult for me to fill that number up.
this is the procedure can create your countinues check number
create procedure [dbo].[aa] #var int
as
declare #isexists int=0
declare #lastnum int=0
set #isexists=(select isnull((select [text] from t000test where [text]=#var),0))
if(isnull(#isexists,0)<=0)
begin
set #lastnum=(select isnull( max([text]),0) from t000test )
if(#var>#lastnum)
begin
insert into t000test(text) values (#var)
end
else
print 'your number dose not in rang'
end
else
print 'your number exists in the data'
GO
you can test this procedure like this :
exec dbo.aa 6 --or any number u like
and this loop create your number automaticly by range u like to
declare #yourrange int =50
declare #id int =0;
while #yourrange>0
begin
exec dbo.aa #id
set #id=#id+1;
set #yourrange=#yourrange-1;
end
I am using 50 but you can use more range or less

Is there a SQL Server function equivalent to AutoNumber() of QlikView?

First of all: this is not a kind of a IDENTITY() field.
In QlikView, it is used to generate a number based on parameters send to function.
See its documentation here: https://help.qlik.com/en-US/qlikview/November2017/Subsystems/Client/Content/Scripting/CounterFunctions/autonumber.htm
In short, you send a parameter to it and it returns an integer that will identify the same arguments for the rest of script. If you send...
AutoNumber('Name 900') -> returns 1
AutoNumber('Name 300') -> returns 2
AutoNumber('Name 001') -> returns 3
AutoNumber('Name 900') -> returns 1 ... again
and because the parameter is already in the intern list of AutoNumber
I tried to build some like that in SQL Server, but is not possible use SELECTs inside scalar functions.
My need is to get something like...
INSERT INTO FacSales (SumaryID, InvoiceID, InvoiceDate
, ProductID, SaleValue, CustomerID, VendorID)
SELECT AutoNumber(sale.VendorID, sale.CustomerID, sale.ProductID)
, sale.InvoiceID
, sale.SaleDate
, details.ProductID
, etc, etc, etc.
Is there, inside SQL Server, a "native" function that perform this?
Or, is there a way to build this using a procedure/function?
Thanks.
You could use DENSE_RANK (Transact-SQL)
Returns the rank of rows within the partition of a result set, without
any gaps in the ranking. The rank of a row is one plus the number of
distinct ranks that come before the row in question.
declare #T table
(
ID int identity,
VendorID int,
CustomerID int,
ProductID int
);
insert into #T values
(1, 2, 3),
(1, 2, 3),
(1, 2, 4),
(1, 2, 3);
select sale.ID,
sale.VendorID,
sale.CustomerID,
sale.ProductID,
dense_rank() over(order by sale.VendorID,
sale.CustomerID,
sale.ProductID) as AutoNumber
from #T as sale
order by sale.ID;
Result:
ID VendorID CustomerID ProductID AutoNumber
----------- ----------- ----------- ----------- --------------------
1 1 2 3 1
2 1 2 3 1
3 1 2 4 2
4 1 2 3 1
You basically want a key value store. There are lots of ways to make one.
Here is a possible solution. It uses a stored procedure.
However, you did not say if the values are retained indefinitely or if they are just for a single call. This example shows how to do it indefinitely.
It could be modified to be for a single call or connection via careful use of temporary tables. If it is other than a call or connection then the autoNumber.AutoNumber table and the autoNumber.NextAutoNumber will need to be cleaned up on what ever that schedule is.
-- Create the table, sequence and sproc
-- Create a schema to hold our autonumber table and sequence
CREATE SCHEMA autoNumber
GO
-- Create a sequence. This just gives us a new number when ever we want.
-- This could be replaced with an identity column.
CREATE SEQUENCE autoNumber.NextAutoNumber AS [bigint]
START WITH 1
INCREMENT BY 1
NO CACHE
GO
-- Create a table to hold the auto number key value pairs.
CREATE TABLE autoNumber.AutoNumber(KeyValue varchar(255), Number bigint)
go
-- This is the stored procedure that actually does the work of getting the autonumber
CREATE PROCEDURE autoNumber.GetAutoNumber #KeyValue varchar(255), #AutoNumber bigint = -1 output AS
BEGIN
DECLARE #Number bigint = null
-- See if we already have an autonumber created for this keyvalue
-- If we do, then set #Number to that value
SELECT #Number = autoNum.Number
FROM autoNumber.AutoNumber autoNum
WHERE autoNum.KeyValue = #KeyValue
IF (#Number is null)
BEGIN
-- If #Number was not changed, then we did not find one
-- in the table for this #KeyValue. Make a new one
-- and insert it.
SET #Number = NEXT VALUE FOR autonumber.NextAutoNumber
INSERT INTO autoNumber.AutoNumber ( KeyValue, Number)
VALUES (#KeyValue, #Number)
END
-- Return our number to the caller.
-- This uses either an output parameter or a select.
IF (#AutoNumber = -1)
BEGIN
select #Number
END ELSE
BEGIN
set #AutoNumber = #Number
END
END
GO
-- End Create
-- Testing with "select"
EXEC autoNumber.GetAutoNumber 'Name 900'
EXEC autoNumber.GetAutoNumber 'Name 300'
EXEC autoNumber.GetAutoNumber 'Name 001'
EXEC autoNumber.GetAutoNumber 'Name 900'
-- Testing with output parameter
DECLARE #AutoNumber bigint
EXEC autoNumber.GetAutoNumber 'Name 900', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 300', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 001', #AutoNumber OUTPUT
SELECT #AutoNumber
EXEC autoNumber.GetAutoNumber 'Name 900', #AutoNumber OUTPUT
SELECT #AutoNumber
-- End Testing
-- Clean up
DROP PROCEDURE autoNumber.GetAutoNumber
GO
DROP TABLE autoNumber.AutoNumber
GO
drop SEQUENCE autoNumber.NextAutoNumber
DROP SCHEMA autoNumber
GO
-- End Cleanup
The closest SQL Server has is CHECKSUM function.
You can use it to calculate a hash value of any number of columns e.g.
SELECT CHECKSUM( 'abc', 123, 'zxc' )
UNION ALL
SELECT CHECKSUM( 'abc', 124, 'zxc' )
UNION ALL
SELECT CHECKSUM( 'abc', 123, 'zxc' )
Output:
-----------
53066784
53066832
53066784
I think you are looking for ROW_NUMBER().
With this sql function you can partition and order by all the field you need.
SELECT ROW_NUMBER() OVER(PARTITION BY sale.VendorID, sale.CustomerID, sale.ProductID ORDER BY sale.VendorID, sale.CustomerID, sale.ProductID)
, sale.InvoiceID
, sale.SaleDate
, details.ProductID FROM table

When exactly do we use stored procedures with output parameters?

When exactly do we use stored procedures with output parameters and when do we use stored procedures without parameters?
I base my question on an example:
Stored procedure with output parameter
CREATE PROCEDURE uspGetContactsCountByCity
#City nvarchar(60),
#ContactsCount int OUT
AS
BEGIN
SELECT #ContactsCount = COUNT(ContactID)
FROM Contacts
WHERE City = #City
END
Stored procedure executing
DECLARE #ContactsTotal INT
EXEC uspGetContactsCountByCity #ContactsCount = #ContactsTotal OUT, #city = 'Berlin'
SELECT #ContactsTotal
Results: 2
Stored procedure without output parameter
CREATE PROCEDURE uspGetContactsCountByCity2
#City nvarchar(60)
AS
BEGIN
SELECT COUNT(ContactID)
FROM Contacts
WHERE City = #City
END
Stored procedure executing:
EXEC uspGetContactsCountByCity2 #city = 'Berlin'
Results: 2
Both procedures return the same result, in same form, so what's the difference?
Basically, the result you're seeing is actually the result of your SELECT at the end of the procedure, which is doing the same thing.
Please take a look at this documentation:
If you specify the OUTPUT keyword for a parameter in the procedure definition, the stored procedure can return the current value of the parameter to the calling program when the stored procedure exits. To save the value of the parameter in a variable that can be used in the calling program, the calling program must use the OUTPUT keyword when executing the stored procedure.
So basically if you would like your stored procedure to just return just a value instead of a data set, you could use the output parameter. For example, let's take the procedures you have given as an example. They both do the same thing, this is why you got the same result. But what about changing a little bit in the first procedure that has the output parameter.
Here's an example:
create table OutputParameter (
ParaName varchar(100)
)
insert into OutputParameter values ('one'), ('two'),('three'),('one')
CREATE PROCEDURE AllDataAndCountWhereOne
#name nvarchar(60),
#count int OUT
as
Begin
SELECT #count = COUNT(*) from OutputParameter
Where ParaName = #name
select Distinct(ParaName) from OutputParameter
End
Declare #TotalCount int
Exec AllDataAndCountWhereOne #count = #TotalCount OUT, #name = 'One'
Select #TotalCount
With this example, you are getting all the distinct stored data in the table, plus getting the count of a given name.
ParaName
--------------------
one
three
two
(3 row(s) affected)
-----------
2
(1 row(s) affected)
This is one way of using the output parameter. You got both the distinct data and the count you wanted without doing extra query after getting the initial data set.
At the end, to answer your question:
Both procedures gives us the same result, in same form, so what's the difference?
You didn't make a difference in your own results, this is why you didn't really notice the difference.
Other Examples:
You could use the OUT parameter in other kinds of procedures. Let's assume that your stored procedure doesn't return anything, it's more like a command to the DB, but you still want a kind of message back, or more specifically a value. Take these two examples:
CREATE PROCEDURE InsertDbAndGetLastInsertedId
--This procedure will insert your name in the database, and return as output parameter the last inserted ID.
#name nvarchar(60),
#LastId int OUT
as
Begin
insert into OutputParameterWithId values (#name);
SELECT #LastId = SCOPE_IDENTITY()
End
or:
CREATE PROCEDURE InsertIntoDbUnlessSomeLogicFails
--This procedure will only insert into the db if name does exist, but there's no more than 5 of it
#name nvarchar(60),
#ErrorMessage varchar(100) OUT
as
Begin
set #ErrorMessage = ''
if ((select count(*) from OutputParameterWithId) = 0)
begin
set #ErrorMessage = 'Name Does Not Exist'
return
end
if ((select count(*) from OutputParameterWithId) = 5)
begin
set #ErrorMessage = 'Already have five'
return
end
insert into OutputParameterWithId values (#name);
End
These are just dummy examples, but just to make the idea more clear.
An example, based on yours would be if you introduced paging to the query.
So the result set is constrained to 10 items, and you use a total count out parameter to drive paging on a grid on screen.
Answer from ozz regarding paging does not make sense because there is no input param that implements a contraint on the number of records returned.
However, to answer the question... the results returned by these stored procedures are not the same. The first returns the record count of contacts in given city in the out param ContactsCount. While the count may also be recieved in the second implement through examining the reader.Rows.Count, the actual records are also made a available. In the first, no records are returned - only the count.

result set based on success or failure

I'm having a stored procedure which returns two result sets based on the success or failure.
SP success result set: name, id ,error,desc
SP failure result sret: error,desc
I'm using the following query to get the result of the stored procedure. It returns 0 for success and -1 for failure.
declare #ret int
DECLARE #tmp TABLE (
name char(70),
id int,
error char(2),
desc varchar(30)
)
insert into #tmp
EXEC #ret = sptest '100','King'
select #ret
select * from #tmp
If the SP is success the four field gets inserted into the temp table since the column matches.
But in case of failure the sp result set has only error and desc which does not matchs with no of columns in the temp table...
.I can't change the Sp, so I need to do some thing (not sure) in temp table to handle both failure and success.
You can't return 2 different recordsets and load the same temp table.
Neither can try and fill 2 different tables.
There are 2 options.
Modify your stored proc
All 4 columns are returned in all conditions
1st pair (name, ID) columns are NULL on error
2nd pair (error, desc) are NULL on success
If you are using SQL Server 2005 then use the TRY/CATCH to separate your success and fail code paths. The code below relies on using the new error handling to pass back the error result set via exception/RAISERROR.
Example:
CREATE PROC sptest
AS
DECLARE #errmsg varchar(2000)
BEGIN TRY
do stuff
SELECT col1, col2, col3, col4 FROM table etc
--do more stuff
END TRY
BEGIN CATCH
SELECT #errmsg = ERROR_MESSAGE()
RAISERROR ('Oops! %s', 16, 1, #errmsg)
END CATCH
GO
DECLARE #tmp TABLE ( name CHAR(70), id INT, error char(2), desc varchar(30)
BEGIN TRY
insert into #tmp
EXEC sptest '100','King'
select * from #tmp
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
END CATCH
My fault!!
Was too quick in the answer.
You need only to relv on the return value, so building up the logic against it is much better.
If you still want to use the temp table, then calling the sptest twice could be a way to deal with it (not optimal though), one time to get the return value and based on it then have 2 different temp tables you are filling up (one would be with the 4 fields, the other only with 2 fields).
declare #ret int
DECLARE #tmp TABLE (name CHAR(70), id INT, error char(2), desc varchar(30))
DECLARE #tmperror TABLE (error char(2), desc varchar(30))
EXEC #ret = sptest '100','King'
IF #ret != 0
BEGIN
INSERT INTO #tmperror
EXEC sptest '100','King';
SELECT * FROM #tmperror;
END
ELSE
BEGIN
INSERT INTO #tmp
EXEC sptest '100','King';
SELECT * FROM #tmp;
END
Keep in mind that this solution is not optimal.
Try modifying your table definition so that the first two columns are nullable:
DECLARE #tmp TABLE (
name char(70) null,
id int null,
error char(2),
desc varchar(30)
)
Hope this helps,
Bill
You cannot do this with just one call. You will have to call it once, either getting the return status and then branching depending on the status to the INSERT..EXEC command that will work for the number of columns that will be returned or Call it once, assuming success, with TRY..CATCH, and then in the Catch call it again assuming that it will fail (which is how it got to the CATCH).
Even better, would be to either re-write the stored procedure so that it returns a consistent column set or to write you own stored procedure, table-valued function or query, by extracting the code from this stored procedure and adapting it to your use. This is the proper answer in SQL.

Resources