SQL query for user input - sql

I wrote a SQL query that takes input (customer id via an application) from a user and returns the record but if the id is zero it displays "Invalid ID". The query I have written does show the data correctly when i set an id (SET #CID = ###) but not sure if this is the correct way to do it. Also how can I restrict the user to input a minimum of three digits. Below is the query
USE WideWorldImporters
GO
DECLARE #CID int;
SET #CID = #CID
IF #CID > 0
BEGIN
SELECT CustomerID, sum(TransactionAmount) as TotalAmount
FROM Sales.CustomerTransactions
Where CustomerID = #CID
Group by customerID
RETURN
END
ELSE
Begin
PRINT 'Invalid ID'
END;

Based on your current code. I make the assumption that CustomerID is actually an int. Meaning excess 0 infront of the number are not present. (I.e. 0011 is not an actual ID. Since the column is numeric it will be stored as 11).
With that in mind, you can write the current code into a procedure like such, that will not do a select under the circumstances you described (NULL, minimum of 3 digits and not less than 0).
CREATE PROCEDURE dbo.foo
#CID INT
AS
BEGIN
IF (#CID >= 1000) --This works because it's a numeric column. NULL is not greater than 1000 either.
--And anything less than 1000 would not be 3 digits.
BEGIN
SELECT CustomerID, SUM(TransactionAmount) as TotalAmount
FROM Sales.CustomerTransactions
WHERE CustomerID = #CID
GROUP BY CustomerID
END
ELSE
BEGIN
--SELECT 'Invalid ID' Use this if you want a single data row to be returned.
-- Note this column would be a varchar!
PRINT 'Invalid ID' -- Print goes to the output window,
-- not the results window like a query would.
END
END
Execution in SQL server would be done as
EXEC dbo.foo 1100
This because there's only 1 parameter, so you don't have to identity it.
Alternatively the way to call a procedure and explicitly assign parameter values is.
EXEC dbo.foo #CID = 1100

Related

How to pull data from SQL Server database using a stored procedure

I am tasked to create a stored procedure for a publisher application to retrieve employee data from our SQL Server.
The aim: We have EmpTable (lets call this the source).
We also have EmpData table.
It has a column called 'Status' with a default value: 'UNPROCESSED'. Our aim is to create a SP(lets call this SP1) so that: it polls all the data from the EmpTable in batches of 100 rows. It should then continue polling data from this table until SP returns zero records.
Once the above completed processing, another SP(lets call this SP2) is created/called to change the status to 'COMPLETED'. If the record processing is failed permanently in the app, that is due to errors such as schema or validation errors (none retryable), SP2 to change the status to FAILED. The results is then populated in the EmpData table We aim to run this batch job once a day. Hope this make sense
I am wondering how this can be queried. I started the query:
DECLARE #id_check INT
DECLARE #batchSize INT
DECLARE #results INT
SET #results = 1 --stores the row count after each successful batch
SET #batchSize = 100 --How many rows you want to operate on each batch
SET #id_check = 0 --current batch
-- when 0 rows returned, exit the loop
WHILE (#results > 0)
BEGIN
SELECT * -- This is just an example to generalize result for now
FROM empdata
SET #results = ##ROWCOUNT
-- next batch
SET #id_check = #id_check + #batchSize
END
The result I am aiming for is to return batch 1 to return 100 values, then batch 2 to return the next 100 and so on
Any help would be appreciated!
Unfortunately without clear requirements its hard to assist you.
However, if what you want is to pull all the matching records, but in batches (which doesn't make a lot of sense), then you can use the following stored procedure.
It will return the first 100 rows which meet whatever criteria you have. Then when they are loaded in your app, you call the SP again, passing in the maximum ID you received in the previous recordset.
Your app continues to call this SP until no rows are returned.
CREATE OR ALTER PROCEDURE dbo.MyTestProcedure1
(
-- Pass in the last ID processed
#LastIdChecked int = 0
)
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
SET #LastIdChecked = COALESCE(#LastIdChecked,0);
DECLARE #BatchSize int = 100;
-- Return the next #BatchSize records after the last Id checked
SELECT TOP(#BatchSize) *
FROM dbo.EmpTable
WHERE Id > #LastIdChecked
ORDER BY Id ASC;
RETURN 0;
END;
A more expected process would be, you use the SP to pull your first 100 records, then you fully process them in your app. Then you call the SP again, and the SP knows which records have been processed and filters them out in the WHERE clause. Then you run this until completion.
That solution would look like this:
CREATE OR ALTER PROCEDURE dbo.MyTestProcedure2
AS
BEGIN
SET NOCOUNT, XACT_ABORT ON;
DECLARE #BatchSize int = 100;
-- Return the next #BatchSize records
SELECT TOP(#BatchSize) *
FROM dbo.EmpTable T
WHERE {Record is unprocessed}
-- e.g. if you are expeceting 1 record a day per dbo.EmpTable record.
-- WHERE NOT EXISTS (
-- SELECT 1
-- FROM dbo.EmpData D
-- WHERE T.EmpId = D.EmpId AND D.Date = CONVERT(date, GETDATE())
--)
ORDER BY Id ASC;
RETURN 0;
END;

Generate a unique column sequence value based on a query handling concurrency

I have a requirement to automatically generate a column's value based on another query's result. Because this column value must be unique, I need to take into consideration concurrent requests. This query needs to generate a unique value for a support ticket generator.
The template for the unique value is CustomerName-Month-Year-SupportTicketForThisMonthCount.
So the script should automatically generate:
AcmeCo-10-2019-1
AcmeCo-10-2019-2
AcmeCo-10-2019-3
and so on as support tickets are created. How can ensure that AcmeCo-10-2019-1 is not generated twice if two support tickets are created at the same time for AcmeCo?
insert into SupportTickets (name)
select concat_ws('-', #CustomerName, #Month, #Year, COUNT())
from SupportTickets
where customerName = #CustomerName
and CreatedDate between #MonthStart and #MonthEnd;
One possibility:
Create a counter table:
create table Counter (
Id int identify(1,1),
Name varchar(64)
Count1 int
)
Name is a unique identifier for the sequence, and in your case name would be CustomerName-Month-Year i.e. you would end up with a row in this table for every Customer/Year/Month combination.
Then write a stored procedure similar to the following to allocate a new sequence number:
create procedure [dbo].[Counter_Next]
(
#Name varchar(64)
, #Value int out -- Value to be used
)
as
begin
set nocount, xact_abort on;
declare #Temp int;
begin tran;
-- Ensure we have an exclusive lock before changing variables
select top 1 1 from dbo.Counter with (tablockx);
set #Value = null; -- if a value is passed in it stuffs us up, so null it
-- Attempt an update and assignment in a single statement
update dbo.[Counter] set
#Value = Count1 = Count1 + 1
where [Name] = #Name;
if ##rowcount = 0 begin
set #Value = 10001; -- Some starting value
-- Create a new record if none exists
insert into dbo.[Counter] ([Name], Count1)
select #Name, #Value;
end;
commit tran;
return 0;
end;
You could look into using a TIME type instead of COUNT() to create unique values. That way it is much less likely to have duplicates. Hope that helps

Stored procedure with AS MERGE not returning anything?

EDIT: Sequential invoice numbering is the law in multiple countries.
EDIT: Poor variable naming on my part suggested I wanted to use my generated Id as a key. This is not the case. Should have stuck with 'invoiceNumber'.
I have the exact same question as posed here https://stackoverflow.com/a/24196374/1980516
However, since the proposed solution threw a syntax error, I've adapted it to use a cursor.
First, there is the stored procedure that generates a new Nr, for a given Business+Year combination:
CREATE PROCEDURE PROC_NextInvoiceNumber #businessId INT, #year INT, #Nr NVARCHAR(MAX) OUTPUT
AS MERGE INTO InvoiceNextNumbers ini
USING (VALUES (#businessId, #year)) Incoming(BusinessId, Year)
ON Incoming.BusinessId = ini.BusinessId AND Incoming.Year = ini.Year
WHEN MATCHED THEN UPDATE SET ini.Nr = ini.Nr + 1
WHEN NOT MATCHED BY TARGET THEN INSERT (BusinessId, Year, Nr)
VALUES(#businessId, #year, 1)
OUTPUT INSERTED.Nr;
Then, using that stored procedure, I've created an INSTEAD OF INSERT trigger:
CREATE TRIGGER TRIG_GenerateInvoiceNumber ON Invoices INSTEAD OF INSERT
AS
BEGIN
DECLARE #BusinessId INT
DECLARE #InvoiceId INT
DECLARE #BillingDate DATETIME2(7)
-- Cursors are expensive, but I don't see any other way to call the stored procedure per row
-- Mitigating factor: Mostly, we're only inserting one Invoice at a time
DECLARE InsertCursor CURSOR FAST_FORWARD FOR
SELECT BusinessId, Id, BillingDate FROM INSERTED
OPEN InsertCursor
FETCH NEXT FROM InsertCursor
INTO #BusinessId, #InvoiceId, #BillingDate
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #year INT
SET #year = year(#BillingDate)
DECLARE #Number NVARCHAR(MAX)
EXEC PROC_NextInvoiceNumber #BusinessId, #year, #Number OUTPUT
-- SET #Number = 'this works'
INSERT INTO Invoices (BusinessId, Id, BillingDate, Number)
VALUES (#BusinessId, #InvoiceId, #BillingDate, #Number)
FETCH NEXT FROM InsertCursor
INTO #BusinessId, #InvoiceId, #BillingDate
END
CLOSE InsertCursor
DEALLOCATE InsertCursor
END
If I uncomment SET #Number = 'this works', then in my database that exact string ('this works') is successfully set in Invoice.Number.
Somehow, my OUTPUT parameter is not set and I can't figure out why not.. Can someone shed a light on this?
EDIT update in response to comments (thank you):
I have a composite key (BusinessId, Id) for Invoice. The desired end result is a unique Invoice Identifier Number of the form '20180001' that is a continuous sequence of numbers within the businessId. So business 1 has invoice Numbers 20180001, 20180002, 20180003 and business 2 also has invoice numbers 20180001, 20180002, 20180003. (But different composite primary keys)
I don't want that cursor either, but I saw no other way within the framework as suggested by the question I refer to up above.
Manual call of PROC_NextInvoiceNumber with existing business id and year returns NULL.
If I try to set Id in PROC_NextInvoiceNumber, I get A MERGE statement must be terminated by a semi-colon (;). if I set it inside the MERGE or The multi-part identifier "INSERTED.Nr" could not be bound. if I set outside the MERGE.
Your OUTPUT parameter is never set. You are using the OUTPUT clause of the MERGE statement to create a result set. This is unrelated to assigning a value to a parameter.
MERGE INTO..
USING ... ON ...
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED BY TARGET THEN INSERT ...
OUTPUT INSERTED.Nr; /* <-- HERE this is the OUTPUT *clause* */
Change the code to actually assign something to #Nr:
SET #Nr = ...
The typical way is to use the OUTPUT clause to store the desired value into a table variable and then assign the value to the desired output *variable:
DECLARE #t TABLE (Nr NVARCHAR(MAX));
MERGE INTO..
USING ... ON ...
WHEN MATCHED THEN UPDATE ...
WHEN NOT MATCHED BY TARGET THEN INSERT ...
OUTPUT INSERTED.Nr INTO #t;
SELECT #Nr = Nr FROM #t;

How to check if value is inserted successfully or not?

I have a procedure where I insert values into my table.
declare #fName varchar(50),#lName varchar(50),#check tinyint
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
EDITED:
Now I want check if it inserted successfully set #check = 0 else #check = 1
You can use ##ROWCOUNT server variable immediately after the insert query to check number of affected rows by the insert operation.
declare #fName varchar(50) = 'Abcd',
#lName varchar(50) = 'Efgh'
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
PRINT ##ROWCOUNT --> 0- means no rows affected/nothing inserted
--> 1- means your row has been inserted successfully
For your requirement, you could use a Case statement(as per comment):
--If you need #check as a bit type please change Int to bit
DECLARE #check Int = CASE WHEN ##ROWCOUNT = 0 THEN 1 ELSE 0 END
You need to use ##ROWCOUNT
It returns the number of rows affected by the last statement. If the number of rows is more than 2 billion, use ROWCOUNT_BIG.
##ROWCOUNT is both scope and connection safe.
In fact, it reads only the last statement row count for that
connection and scope.
It’s safe to use ##ROWCOUNT in SQL Server even when there is a trigger
on the base table. The trigger will not skew your results; you’ll get
what you expect. ##ROWCOUNT works correctly even when NOCOUNT is set.
so you query should be:
declare #fName varchar(50), #lName varchar(50), #check tinyint = 0
...
INSERT INTO myTbl(fName,lName) values(#fName,#lName)
if ##ROWCOUNT>0
set #check = 1
You can use ##rowcount after insert table, like this:
DECLARE #check int
INSERT INTO Employees (Name,Email,Phone,[Address])
VALUES('Test','test#mail.com','','')
if(##ROWCOUNT>0)
SET #check=1
SELECT #check;
In SQL-Sever you can use OUTPUT clause to check if values are inserted successfully.
By following query
declare #fName varchar(50),#lName varchar(50)
INSERT INTO myTbl(fName,lName) OUTPUT inserted.* values(#fName,#lName) ;
IF the values are inserted it will show output of inserted values. You can also store these values into new table.
I can say you can simply check the previous numbers of rows before inserting the new.
for example previous you have 50 rows. just store the numbers of rows in a variable and check after the insert query if numbers of row increased or no.
yes little extra chunk of code but easy to understand

How to deal with Stored Procedure?

Hello I am new in creating stored procedure can you help me how to do this.
Error:
Incorrect syntax near the keyword 'AS'.
Must declare scalar variable #Serial.
CREATE PROCEDURE sp_SIU
-- Add the parameters for the stored procedure here
#Serial varchar(50),
#Part varchar(50),
#Status varchar(50),
AS
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
/*SET NOCOUNT ON;*/
-- Insert statements for procedure here
--where in my form if i enter serial number it will show select values
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
--Then if is correct it will Update Status on combobox
Update Table1 SET
Status=#Status
where SerialNumber=#SerialNumber
--then Insert Serial Number,Parnumber to Table 2
DECLARE #Count int
select #Count = Count(SerialNumber) from Table1 WHERE SerialNumber = #Serial
IF #Count = 0
BEGIN
INSERT INTO Table2 (SerialNumber,PArtNumber)
VALUES
(#Serial, #Part)
END
RETURN #Count
RETURN
Edit: Moved Updated info posted as an answer into Question
Oops my post is not that kind a miss.
It is possible to join this 3 sql string in one stored procedure?
Scenario:
{
What i have to do in my form is that i will enter serial number to txtserial.text by using the select sql it will show serialnumber,partnumber and status on lblserial.text,lblpartnumber.text and lblstatus.text.
And i will compare:
txtserial.text == lblserial.text
txtpartnumber.text == lblpartnumber.text
for my error handler.
{
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
}
Then if they are equal then:
I will update my Status from cbostatus.text if serial and part is correct then use sql upate.
{
Update Table1 SET
Status=#Status,
Modifiedby=#username,
DateModified=#Date
where SerialNumber=#Serial
}
Then insert serialnumber, using sql insert to another table.
{
INSERT INTO Table2 (SerialNumber,DateCreated,Createdby)
VALUES
(#Serial,#date,#username)
}
something likethis.
")
You have a rogue comma here
#Status varchar(50),
AS
and the name lurches between #Serial and #SerialNumber are these intended to be 2 different parameters?
Also what is the purpose of this line?
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
Currently it will just send back a 3 column result set to the calling application. Is that what it is intended to do (it doesn't seem to match the following comment which seems to imply it is meant to be some kind of check)?
Yes, you can execute 3 SQL statements inside one stored procedure. You probably want to declare some local variables inside your sproc to hold the intermediate results, i.e.
CREATE PROCEDURE BLAHBLAH
#SerialNumber VarChar(50)
AS
BEGIN
DECLARE #partnumber varchar(50);
SELECT #partnumber = partnumber FROM Table WHERE serialnumber = #SerialNumber;
...
SELECT #partnumber; --- return as recordset
RETURN #partnumber; --- return as return value
END
Then you can later insert #partnumber, test #partnumber, return #partnumber etc. I don't quite understand what you want to do; seems like you mostly want to look up a partnumber based on a serial number, but you want to do some uniqueness tests also. It would help if you could clarify the goal a bit more.
I recommend you ignore the user interface stuff for the moment. Write yourself some nice clean stored procedures that encapsulate the transaction and will do the right thing even if fired off at the same time from two different connections. Get everything working to your satisfaction in your SQL environment. Then go back to the user interface.
Oops my post is not that kind a miss.
It is possible to join this 3 sql string in one stored procedure?
Scenario:
What I have to do in my form is that I will enter serial number to txtserial.text by using the select sql it will show serialnumber,partnumber and status on lblserial.text,lblpartnumber.text and lblstatus.text.
AndI will compare:
txtserial.text == lblserial.text
txtpartnumber.text == lblpartnumber.text
for my error handler.
{
Select SerialNumber,PartNumber,Status from Table1 where SerialNUmber = #Serial
}
Then if they are equal then:
I will update my Status from cbostatus.text if serial and part is correct then use sql update.
{
Update Table1
SET Status = #Status,
Modifiedby = #username,
DateModified = #Date
where SerialNumber = #Serial
}
Then insert serialnumber, using sql insert to another table.
{
INSERT INTO Table2(SerialNumber, DateCreated, Createdby)
VALUES(#Serial, #date, #username)
}
something like this.