Making a single Transaction for multiple items in SQL SERVER 2012 - sql-server

Help me for my POS. I'm doing a Transaction for fast-food. I'm really confuse on building codes for the transaction. I want to do it like this
ID ! Transaction ID ! Product Name !
ID ! TRANSACTIONID ! PRODUCT
1 TR1 DISH1
2 TR1 DISH2
3 TR2 DISH3
4 TR3 DISH4
5 TR3 DISH5
6 TR3 DISH2
ITS MORE LIKE THAT. Sorry if my code has no clarity please understand me. This is my first time asking. Thanks
The below code is my pattern for inserting it into database but transaction ID won't be like on top.
Private Sub TransactionUpdate()
Dim pn, pp, pq, pt As String
If ListView1.Items.Count = Nothing Then Exit Sub
PanelOrder()
For Each item As ListViewItem In ListView1.Items
pn = item.SubItems(0).Text
pp = item.SubItems(1).Text
pq = item.SubItems(2).Text
pt = item.SubItems(3).Text
SQL.AddParam("#transactionstate", "Served")
SQL.AddParam("#productname", pn)
SQL.AddParam("#employeeid", txtusername.Text.ToUpper)
SQL.AddParam("#employeename", btnlogin.Text)
SQL.AddParam("#productprice", pp)
SQL.AddParam("#productquantity", pq)
SQL.AddParam("#producttotal", pt)
SQL.ExecQuery("Insert Into Emp_Transaction(ProductName,EmployeeName,TotalPrice,Transaction_Date,Transaction_Time,ProductQuantity,TransactionState) " &
"Values(#productname,#employeename,#producttotal,GETDATE(),GETDATE(),#productquantity,#transactionstate)")
'TransactionID,ProductName,EmployeeName,TotalPrice,Transaction_Date,Transaction_Time,ProductQuantity,TransactionState
SQL.ExecQuery("Update Emp_Transaction " &
"Set ProductID=(SELECT Product.ProductID from Product Where Product.ProductName=Emp_Transaction.ProductName), EmployeeID=(SELECT Employees.EmployeeID from Employees Where Employees.Name=Emp_Transaction.EmployeeName)")
Next
If SQL.HasException(True) Then Exit Sub
End Sub

Personally, I would write a stored procedure to do the insert, and then I would call the stored procedure with the same parameters.
The procedure would look like this
CREATE PROCEDURE dbo.AddTransaction #productname VARCHAR(100)
,#employeename VARCHAR(100)
,#producttotal INTEGER
,#productquantity INTEGER
,#transactionstate VARCHAR(100)
AS
BEGIN
DECLARE #ProductId INT, #EmployeeId INT;
SELECT #ProductId = productID FROM dbo.Product WHERE ProductName = #productname;
SELECT #EmployeeId = SELECT EmployeeID FROM dbo.Employees WHERE name = #EmployeeName;
INSERT INTO dbo.Emp_Transaction(ProductName,EmployeeName,TotalPrice,Transaction_Date,Transaction_Time,ProductQuantity,TransactionState
,ProductId, EmployeeId)
VALUES(#productname,#employeename,#producttotal,GETDATE(),GETDATE(),#productquantity,#transactionstate
,#ProductId, #EmployeeID );
END;
Of course, you might have to change the datatypes a bit.
Having said that, there seems to be at least one problem with your Emp_Transaction table in that it is not "normalised".
Having both ProductId and ProductName in the table is not normalised because one is directly derived from the other one.
You should probably remove ProductName
The same goes for EmployeeId and EmployeeName
Another detail is that it is better practice to always specify the schema name of your table, like dbo.Emp_Transaction instead of Emp_Transaction.

You should try the following approach.Try first running your insert statement in the database directly.This will help you to understand if your query is correct or not.Once you do that then you should put the query into your code and then debug the code.

Related

PL SQL "Array Type" To TSQL translation

This is my initial PL/SQL code :
TYPE VarcharArray IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER;
and i use it in the following code :
PROCEDURE Create(inFatherId IN VARCHAR2, inBarcode IN VarcharArray, inItemId IN VarcharArray)
IS
myCount NUMBER(38);
sampleId_FromDb NUMBER(38);
itemId_FromDb NUMBER(38);
BEGIN
myCount := inBarcode.COUNT;
FOR i IN 1..myCount
LOOP
SELECT ITEM.Id INTO itemId_FromDb FROM ITEM WHERE FatherId = inFatherId AND CampaignItemId = inItemId(i);
SELECT SAMPLE_SEQUENCE.NEXTVAL INTO sampleId_FromDb FROM DUAL;
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId) VALUES(sampleId_FromDb, inBarcode(i), itemId_FromDb);
END LOOP;
END;
I've seen that the array type can be translated into MS SQL with Table-Valued Parameters, however how can i iterate in a similar fashion so that i include in the iteration the thee operations ?
In the current PL/SQL implementation i send up to 50.000 elements in the array and the performance is decent. I would desire something similar also in MS SQL.
There's no need to be looping and inserting one row at a time. That's just a way to make your code slower. Since tables don't have any order in them, you need to add one column to define the order. Your type would be like this:
CREATE TYPE VarcharArray AS TABLE(ID int, Item VARCHAR(100));
Then, you can rewrite your procedure as a single INSERT statement.
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#Barcode AS VarcharArray READONLY,
#ItemId AS VarcharArray READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bc.Item,
i.Id
FROM ITEM i
JOIN #ItemId ii ON i.CampaignItemId = ii.Item
JOIN #Barcode bc ON ii.ID = bc.ID
WHERE i.FatherId = #FatherId;
You could also create a table with both values and prevent any ordering problems that could occur.
CREATE TYPE BarcodeItems AS TABLE(Item VARCHAR(100), Barcode VARCHAR(100));
GO
CREATE PROCEDURE SomeProcedure(
#FatherId AS VARCHAR, --This might need a length or will be defaulted to length 1
#BarcodeItems AS BarcodeItems READONLY
)
AS
INSERT INTO CAMPAIGN_SAMPLES(Id, Barcode, ItemId)
SELECT NEXT VALUE FOR SAMPLE_SEQUENCE,
bi.Item,
i.Id
FROM ITEM i
JOIN #BarcodeItems bi ON i.CampaignItemId = bi.Item
WHERE i.FatherId = #FatherId;

Setting the value of a column in SQL Server based on the scope_identity() of the insert

I have a stored procedure in a program that is not performing well. Its truncated version follows. The MyQuotes table has an IDENTITY column called QuoteId.
CREATE PROCEDURE InsertQuote
(#BinderNumber VARCHAR(50) = NULL,
#OtherValue VARCHAR(50))
AS
INSERT INTO MyQuotes (BinderNumber, OtherValue)
VALUES (#BinderNumber, #OtherValue);
DECLARE #QuoteId INT
SELECT #QuoteId = CONVERT(INT, SCOPE_IDENTITY());
IF #BinderNumber IS NULL
UPDATE MyQuotes
SET BinderNumber = 'ABC' + CONVERT(VARCHAR(10),#QuoteId)
WHERE QuoteId = #QuoteId;
SELECT #QuoteId AS QuoteId;
I feel like the section where we derive the binder number from the scope_identity() can be done much, much, cleaner. And I kind of think we should have been doing this in the C# code rather than the SQL, but since that die is cast, I wanted to fish for more learned opinions than my own on how you would change this query to populate that value.
The following update avoids needing the id:
UPDATE MyQuotes SET
BinderNumber = 'ABC' + CONVERT(VARCHAR(10), QuoteId)
WHERE BinderNumber is null;
If selecting QuoteId as a return query is required then using scope_identity() is as good a way as any.
Dale's answer is better, however this can be useful way too:
DECLARE #Output TABLE (ID INT);
INSERT INTO MyQuotes (BinderNumber, OtherValue) VALUES (#BinderNumber, #OtherValue) OUTPUT inserted.ID INTO #Output (ID);
UPDATE q SET q.BinderNumber = 'ABC' + CONVERT(VARCHAR(10),o.ID)
FROM MyQuotes q
INNER JOIN #Output o ON o.ID = q.ID
;
Also, if BinderNumber is always linked to ID, it would be better to just create computed column
AS 'ABC' + CONVERT(VARCHAR(10),ID)

I want my stored procedure to only populate one instance of a name

I'm creating a stored procedure that populates two tables tblAirport and tblCountry. tblCountry gets its country names from tblAirport but I only want one instance of the country name to show up in `tblCountry. So far for my stored procedure I have this
DECLARE #PK INT = (SELECT PK FROM tblAirport WHERE strName = #strName)
IF #PK IS NULL
INSERT INTO tblAirport (ICAOCode,IATACode,strName,strCity,strCountry,degLat,minLat,secLat,Equator,degLong,minLong,secLong,Meridian,strElevation)
VALUES (#ICAOCode,#IATACode,#strName,#strCity,#strCountry,#degLat,#minLat,#secLat,#Equator,#degLong,#minLong,#secLong,#Meridian,#strElevation)
SET #PK = (SELECT PK FROM tblAirport WHERE strName = #strName);
IF EXISTS (SELECT * FROM tblCountry WHERE strCountry = #strCountry)
SET #strCountry = #strCountry + 'x'
INSERT INTO tblCountry (strCountry)
VALUES (#strCountry)
I tried using IF EXISTS (SELECT * FROM tblCountry WHERE strCountry = #strCountry)
SET #strCountry = #strCountry + 'x' just to show any duplicate countries but I don't know how to eliminate the duplicates from my table. I'm new to SQL and I've only learned the IF EXISTS function. Any suggestions would be great. Thank you!
This is how to handle a multiline IF ELSE (https://technet.microsoft.com/en-us/library/ms182717(v=sql.110).aspx)
IF NOT EXISTS (SELECT * FROM tblCountry WHERE strCountry = #strCountry)
BEGIN
INSERT INTO tblCountry (strCountry) VALUES (#strCountry)
END;
In general though, I'd be concerned about a procedure that uses the data to drive the possible values in a lookup list, especially something like countries that should probably be pre-defined up front. You'd hate for them to enter free-form duplicates that are really the same country with a slightly different spelling.

SQL using UPDLOCK in query to update top 1 record after filtering and ordering table

I have a stored procedure as follows:
CREATE PROCEDURE [dbo].[RV_SM_WORKITEM_CHECKWORKBYTYPE]
(
#v_ServiceName Nvarchar(20)
,#v_WorkType Nvarchar(20)
,#v_WorkItemThreadId nvarchar(50)
)
AS BEGIN
;WITH updateView AS
(
SELECT TOP 1 *
FROM rv_sm_workitem WITH (UPDLOCK)
WHERE stateofitem = 0
AND itemtype = #v_worktype
ORDER BY ITEMPRIORITY
)
UPDATE updateView
SET assignedto = #v_ServiceName,
stateofitem = 1,
dateassigned = getdate(),
itemthreadid = #v_WorkItemThreadId
OUTPUT INSERTED.*
END
It does the job I need it to do, namely, grab 1 record with a highest priority, change it's state from Available(0) to Not-Available(1), and return the record for work to be done with it. I should be able to have many threads (above 20) use this proc and have all 20 constantly running/grabbing a new workitem. However I am finding that beyond 2 threads, addition threads are waiting on locks; I'm guessing the UPDLOCK is causing this.
I have 2 questions, is there a better way to do this?
Can I do this without the UPDLOCK in the cte since the update statement by default uses UPDLOCK? Note, at any given time, there are over 400,000 records in this table.
I had to so something similar once and this is what I would suggest:
AS BEGIN
DECLARE #results table (id int, otherColumns varchar(50))
WHILE (EXISTS(SELECT TOP 1 * FROM #results))
BEGIN
;WITH updateView AS
(
SELECT TOP 1 *
FROM rv_sm_workitem
WHERE stateofitem = 0
AND itemtype = #v_worktype
ORDER BY ITEMPRIORITY
)
UPDATE updateView
SET assignedto = #v_ServiceName,
stateofitem = 1,
dateassigned = getdate(),
itemthreadid = #v_WorkItemThreadId
OUTPUT INSERTED.* into #results
where stateofitem = 0
END
END
This ensures that the call cannot not allow a item to be double processed. (because of the where clause on the update statement).
There are other variations of this idea, but this is an easy way to convey it. This is not production ready code though, as it will continually circle in the while loop until there is something to process. But I leave it to you to decide how to break out or not loop and return empty (and let the client side code deal with it.)
Here is the answer that helped me when I had this issue.

Setting variable SQL Procedure

I am working on a database that contains customers, products, timesheets, etc for a store. The question I am working on involves creating a procedure that will change an "on/off" column to off (the product is available (1) by default, and this procedure turns it to 0) I have writen the procedure fine:
create proc p_fudgemart_deactivate_product
(
#product_id int
)
as
begin
update fudgemart_products
set product_is_active = 0
where product_id = #product_id
end
but the issue then comes when we are given a product NAME, and need to write a select statement to change that product to unavailable. I know that this requires the use of a variable, but I cannot figure out how to set the variable to the product id of that product. I was thinking something along the lines of:
Declare #prod_name_id int
set #prod_name_id= (select product_id from fudgemart_products
where product_name = 'Slot Screwdriver')
execute p_fudgemart_deactivate_product product_id #prod_name_id
Am I able to use a select in my variable declaration like this?
actually you're on the right track. try something like this:
declare #prod_name_id int
select #prod_name_id = product_id
from fudgemart_products
where product_name = 'Slot Screwdriver'
exec p_fudgemart_deactivate_product
#product_id = #prod_name_id
If you are using SQL Server 2008 or later, you can declare and assign in one statement:
DECLARE #prod_name_id int = ( SELECT product_id
FROM fudgemart_products
WHERE product_name = 'Slot Screwdriver'
);
EXECUTE p_fudgemart_deactivate_product #product_id = #prod_name_id;

Resources