How to insert unique value into non-identity field - sql

I'm trying to do an insert into an established table which has a primary key fields and another field (call it field1) that is unique (this other unique field has a unique constraint preventing my inserts). Field1 is not an identity field, so it does NOT autonumber. Unfortunately I can't change the table. Existing inserts are made using code to increment and all involve looping/cursors. Something like SELECT MAX(field1) + 1
So, is there anyway to do this insert without looping/cursor? This field means nothing to me, but there are already 500,000+ records using their silly numbering scheme, so I must respect that.
This is simplified (ReceiptNumber is the field I want to insert unique), but:
SET XACT_ABORT ON
Begin Transaction TransMain
Declare #nvErrMsg nvarchar(4000)
--Insert inventory receipts
Insert Into Avanti_InventoryReceipts (
ReceiptNumber , ItemNumber , ReceiptDate , OrderNumber , JobNumber , Supplier ,
LineNumber , MultiLineNumber , [Status] , QtyOrdered , QtyReceived , QtyToReceive ,
QtyBackOrdered , Cost , Wholesale , LastCost , QtyToInvoice , QtyUsed ,
ReferenceNumber , [Description] , SupplierType , Processed , DateExpected , DateReceived ,
AccountNumber , Reference2 , EmployeeCode , ExtraCode , Location , RollNumber ,
QtyIssues , Notes , NumPackages , BundleSize , ConsignmentUnitPrice , RecFromProduction ,
QtyCommitted )
SELECT ( SELECT MAX(ReceiptNumber) + 1 FROM Avanti_inventoryReceipts ) , CR.ItemNumber , Convert(char(8), GETDATE(), 112) , PONum , 'FL-INV' , PH.POVendor ,
0 , 0 , 'O' , CR.QtyOrdered , QtyReceivedToday , QtyReceivedToday ,
Case #closePO
When 'N' Then Case When ( QtyOrdered - QtyReceivedToday ) < 0 Then 0 Else ( QtyOrdered - QtyReceivedToday) End
When 'Y' Then 0
Else 0 End
, PD.TransCost * QtyReceivedToday , IH.PriceWholeSale , IH.CostLast , QtyReceivedToday , 0 ,
'' , PODetailDescription , '' , '' , '' , Convert(char(8), GETDATE(), 112) ,
'' , '' , #employeeCode , '' , 'F L E X O' , '' ,
0 , 'Flexo Materials' , 0 , 0 , 0 , '' , 0
FROM FI_CurrentReceiptData CR
LEFT JOIN Avanti_PODetails PD ON CR.PONum = PD.PONumber
LEFT JOIN Avanti_POHeader PH ON CR.PONum = PH.PONumber
LEFT JOIN Avanti_InventoryHeader IH ON CR.ItemNumber = IH.ItemNumber
IF ##ERROR <> 0
Begin
Select #nvErrMsg = 'Error entering into [InventoryReceipts] -' + [description]
From master..sysmessages
Where [error] = ##ERROR
RAISERROR ( #nvErrMsg , 16, 1 )
Goto Err_
End
Commit Transaction TransMain
Goto Exit_
Err_:
Rollback Transaction TransMain
Exit_:
SET XACT_ABORT OFF

You could do this:
insert into mytable (field1, field2, ...)
values (( SELECT MAX(field1) + 1 from mytable), 'value2', ...);

Why not looping? It should be quite efficient.
Since you already have a UNIQUE constraint on the field, you can:
Simply try to insert MAX(field1) + 1. Since there is index on UNIQUE field, MAX is fast.
If its passes, great you are done.
If it fails (which will typically be manifested as an exception in your client code), just try again until you succeed.
Most of the time, the INSERT will succeed right away. In rare instances where a concurrent user tries to insert the same value, you'll handle that gracefully by trying the "next" value.

I added an autonumber starting from 0 in client code and passed that in. Now I'm adding that value to the max receiptnumber to get a unique one. Also, I realized I already had an identity column in FI_CurrentReceiptData, but I didn't want to use that one because it won't start at 0 for each receipt set, and reseeding the identity each time seems like a waste of processor time.

Related

Creating a decision table with accompanied procedure in SQL Server

I'm trying to think of the best way to write a procedure to evaluate a decision table in SQL Server.
The way I've begun doing it (see VERY simplified example below) would work fine except for the rows where that particular parameter doesn't matter. So, any one claim could fit the scenario in Row 4, provided it's of the type medical.The query, having found a match doesn't even evaluate the other rows. Is there a better way to do this?
I was thinking I could rank the rows of the table from most complicated scenario to least and then select the top row that fits, and that'd probably work, but in future, many more rows will be added to the table and each one would require an analysis of where it would fall in the table. Some situations might even conflict.
I want the best match for the scenario.
I currently have something that does something like this:
create table TTable( RowID varchar(max)
, ClaimType varchar(max)
, Insurance varchar(max)
, Eligibilty varchar(max)
, Result varchar(max)
, Assignment varchar(max)
, Admission varchar(max))
Insert into TTable (RowID, ClaimType, Insurance, Eligibility, Result ,Assignment, Admission)
values ('1' , 'Medical' , 'Insured' , 'Eligible' , 'Approval', 'AssignNurse' , 'Admit')
,('2' , 'Medical' , 'Uninsured' , 'N/A' , 'Denial' , 'N/A' ,'N/A')
,('3' , 'Medical' , 'N/A' , 'IncomeEligible' , 'Pend' , 'AssignReviewer' , 'Pend')
,('4' ,' Medical' , 'N/A' , 'N/A' , 'Pend' , 'Pend' , 'Pend')
create procedure getdecision( #ClaimID uniqueidentifier)
declare #Type
declare #Insurance
declare #Eligibility
Select #Type = Type
From claims
where claimid = #ClaimID
select #Insurance = Insurance
#Eligibility = Eligibility
from MemberInsurance
where membemberid = (select memberid from claims where claimid = #claimid)
select RowID,
Result,
Assignment,
Admission
From ttable t
WHERE #Type = ClaimType
and Case when t.Insurance = 'N/A'
then #Type
Else t.ClaimType
End
and Case when t.Eligible = 'N/A'
then #Eligibility
else t.Eligibilty
end

TSQL Inserting values based on integer results from select statement

I'm running the following query:
select
max(count) Max
from
(select
count(iStockID) Count
from
_etblInvJrBatchLines
group by
iStockID) X
As you know, the result of this is an integer; in this case, the result was 5.
Based on the above result, I need to insert generic values into another table, that looks like this:
INSERT INTO _etblInvJrBatches
(
cInvJrNumber -- IJ0001 Plus 1
, cInvJrDescription -- Inventory Journal Batch
, cInvJrReference -- IJR10001 Plus 1
, iCreateAgentID -- 1
, bClearAfterPost -- 1
, bAllowDupRef -- 1
, bAllowEditGLContra -- 0
, iNewLineDateOpt -- 0
, iNewLineRefOpt -- 0
, cNewLineRefDef -- ''
, bNewLineRefInc -- 0
, iNewLineDescOpt -- 0
, cNewLineDescDef -- ''
, bNewLineDescInc -- 0
, iNewLineProjectOpt -- 0
, iNewLineProjectDefID -- 0
, iNewLineWarehouseOpt -- 0
, iNewLineWarehouseDefID -- 0
, bJustCleared -- 0
, iTransactionCode -- 31 (select TrCodeID where TrCode = 'ADJ')
)
SELECT
'IJ000' -- Plus 1
, 'Inventory Journal Batch'
, 'IJR1000' -- Plus 1
, 1
, 1
, 1
, 0
, 0
, 0
, ''
, 0
, 0
, ''
, 0
, 0
, 0
, 0
, 0
, 0
, (select idTrCodes from TrCodes where Code = 'ADJ')
The only problem is, it only inserts this once.
How do I insert this 5 times based on the results I get from my first select statement?
In other words, if the integer result is 24, it needs to import / insert the above 24 times.
Thank you for assisting.
Attie.
Cant you just use a tally table?
declare #count int
set #count = (select
max(count) Max
from
(select
count(iStockID) Count
from
_etblInvJrBatchLines
group by
iStockID) X) --Hardcoded 10
IF OBJECT_ID('tempdb..#tally') IS NOT NULL
/*Then it exists*/
DROP TABLE #tally
SELECT TOP (#count) --equates to more than 30 years of dates
IDENTITY(INT,1,1) AS N
INTO #tally
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
--PRINT #count
INSERT INTO _etblInvJrBatches
(
cInvJrNumber -- IJ0001 Plus 1
, cInvJrDescription -- Inventory Journal Batch
, cInvJrReference -- IJR10001 Plus 1
, iCreateAgentID -- 1
, bClearAfterPost -- 1
, bAllowDupRef -- 1
, bAllowEditGLContra -- 0
, iNewLineDateOpt -- 0
, iNewLineRefOpt -- 0
, cNewLineRefDef -- ''
, bNewLineRefInc -- 0
, iNewLineDescOpt -- 0
, cNewLineDescDef -- ''
, bNewLineDescInc -- 0
, iNewLineProjectOpt -- 0
, iNewLineProjectDefID -- 0
, iNewLineWarehouseOpt -- 0
, iNewLineWarehouseDefID -- 0
, bJustCleared -- 0
, iTransactionCode -- 31 (select TrCodeID where TrCode = 'ADJ')
)
select N, 'IJ000' -- Plus 1
, 'Inventory Journal Batch'
, 'IJR1000' -- Plus 1
, 1
, 1
, 1
, 0
, 0
, 0
, ''
, 0
, 0
, ''
, 0
, 0
, 0
, 0
, 0
, 0 from #tally where n >0 and n <= #count
Result
There are many ways to accomplish this.
INSERT only inserts one row into the target table. Ultimately you are going to have to INSERT in a loop.
Possible solutions...
Do the looping in the database...
declare #count int = (select
max(count) Max
from
(select
count(iStockID) Count
from
_etblInvJrBatchLines
group by
iStockID) X)
declare #increment int =1
while #increment <= #count
{
-- do your insert here...
#increment = #increment +1
}
Do the looping in your client code.
Retrieve the count value from your initial query
For increment = 1 to #count
'execute SQL to do insert here...
Next increment
Or better (as #scsimon hinted at)...
For increment = 1 to #count
'build the VALUES () clauses for your insert statement...
Next
'execute your insert statement
I didn't realize that T-SQL allowed multiple VALUES clauses in an INSERT. Thank you #scsimon!
If it's me, I'm doing all of this in a client with code, not in the database. I'm old school, and I don't think that solutions like this scale well when built in the database as in my first example.
Thank you everyone for your contribution!
Special thanks to #Thomas as his advice worked the best in my environment.
I altered his script a little bit to accommodate the reference numbering:
declare #count int
set #count = (select
max(count) Max
from
(select
count(iStockID) Count
from
_etblInvJrBatchLines
group by
iStockID) X) --Hardcoded 10
IF OBJECT_ID('tempdb..#tally') IS NOT NULL
/*Then it exists*/
DROP TABLE #tally
SELECT TOP (#count) --equates to more than 30 years of dates
IDENTITY(INT,1,1) AS N
INTO #tally
FROM Master.dbo.SysColumns sc1,
Master.dbo.SysColumns sc2
--PRINT #count
INSERT INTO _etblInvJrBatches
(
cInvJrNumber -- IJ0001
, cInvJrDescription -- Inventory Journal Batch
, cInvJrReference -- IJR10001
, iCreateAgentID -- 1
, bClearAfterPost -- 1
, bAllowDupRef -- 1
, bAllowEditGLContra -- 0
, iNewLineDateOpt -- 0
, iNewLineRefOpt -- 0
, cNewLineRefDef -- ''
, bNewLineRefInc -- 0
, iNewLineDescOpt -- 0
, cNewLineDescDef -- ''
, bNewLineDescInc -- 0
, iNewLineProjectOpt -- 0
, iNewLineProjectDefID -- 0
, iNewLineWarehouseOpt -- 0
, iNewLineWarehouseDefID -- 0
, bJustCleared -- 0
, iTransactionCode -- 31 (select idTrCodes from TrCodes where Code = 'ADJ')
)
select
'IJ000' + cast(N as varchar)
, 'Inventory Journal Batch'
, 'IJR1000' + cast(N as varchar)
, 1
, 1
, 1
, 0
, 0
, 0
, ''
, 0
, 0
, ''
, 0
, 0
, 0
, 0
, 0
, 0
, (select idTrCodes from TrCodes where Code = 'ADJ')
from #tally where n >0 and n <= #count
This worked like a charm!
See results:Results

SQL - Complex Stored Procedure need to change value of 'case when' from integer to VarChar after being counted

I have a website for an NFL Pool. I'm able to display User Picks and the amount of Wins user has. As of now my CASE WHEN is using integer as win or lost. I would like to change it to be where 1 = 'Won' and 0 = 'Lost' but still count the amount of wins the user has.
Is this possible?
Thanks for any help.
My code:
DECLARE #commondata TABLE (FullName VARCHAR(30), Game_1 VARCHAR(30), Game_2 VARCHAR(30), Game_3 VARCHAR(30), Game_4 VARCHAR(30), Game_5 VARCHAR(30),
GameResults_1 INT, GameResults_2 INT, GameResults_3 INT, GameResults_4 INT, GameResults_5 INT)
INSERT INTO #commondata (FullName, Game_1, Game_2, Game_3, Game_4, Game_5,
GameResults_1, GameResults_2 , GameResults_3, GameResults_4, GameResults_5)
SELECT UserPicks.FullName, UserPicks.Game_1, UserPicks.Game_2, UserPicks.Game_3, UserPicks.Game_4, UserPicks.Game_5,
(CASE WHEN UserPicks.Game_1 = WeeklyResults.GameResults_1 THEN 1 ELSE 0 END) AS GameResult_1,
(CASE WHEN UserPicks.Game_2 = WeeklyResults.GameResults_2 THEN 1 ELSE 0 END) AS GameResult_2,
(CASE WHEN UserPicks.Game_3 = WeeklyResults.GameResults_3 THEN 1 ELSE 0 END) AS GameResult_3,
(CASE WHEN UserPicks.Game_4 = WeeklyResults.GameResults_4 THEN 1 ELSE 0 END) AS GameResult_4,
(CASE WHEN UserPicks.Game_5 = WeeklyResults.GameResults_5 THEN 1 ELSE 0 END) AS GameResult_5
FROM UserPicks
JOIN WeeklyResults ON UserPicks.Week = WeeklyResults.Week
WHERE WeeklyResults.Week = 'Week1'
-- Unpivot the table.
(SELECT FullName, Game_1, Game_2, Game_3, Game_4, Game_5, SUM(Points) As Total
FROM
(SELECT *
FROM #commondata) pvt
UNPIVOT
(Points FOR Game IN
(GameResults_1, GameResults_2, GameResults_3, GameResults_4, GameResults_5)
)AS Total
GROUP BY FullName, Game_1, Game_2, Game_3, Game_4, Game_5)
Try this, I took the liberty to reformat, remove redundant lines and use table acronyms to make it easier to read/understand:
DECLARE #commondata TABLE (FullName VARCHAR(30)
, Game_1 VARCHAR(30)
, Game_2 VARCHAR(30)
, Game_3 VARCHAR(30
, Game_4 VARCHAR(30)
, Game_5 VARCHAR(30)
, GameResults_1 VARCHAR(4)
, GameResults_2 VARCHAR(4)
, GameResults_3 VARCHAR(4)
, GameResults_4 VARCHAR(4)
, GameResults_5 VARCHAR(4));
INSERT INTO #commondata
SELECT UP.FullName
, UP.Game_1
, UP.Game_2
, UP.Game_3
, UP.Game_4
, UP.Game_5
, IIF(UP.Game_1 = WR.GameResults_1, 'Won', 'Lost') AS GameResult_1
, IIF(UP.Game_2 = WR.GameResults_2, 'Won', 'Lost') AS GameResult_2
, IIF(UP.Game_3 = WR.GameResults_3, 'Won', 'Lost') AS GameResult_3
, IIF(UP.Game_4 = WR.GameResults_4, 'Won', 'Lost') AS GameResult_4
, IIF(UP.Game_5 = WR.GameResults_5, 'Won', 'Lost') AS GameResult_5
FROM UserPicks UP
JOIN WeeklyResults WR ON UP.Week = WR.Week
WHERE WR.Week = 'Week1';
-- Unpivot the table.
SELECT FullName
, Game_1
, Game_2
, Game_3
, Game_4
, Game_5
, SUM(IIF(Result='Won', 1, 0)) As Total
FROM #commondata
UNPIVOT (
Result FOR Game IN (GameResults_1
, GameResults_2
, GameResults_3
, GameResults_4
, GameResults_5)
) AS Total
GROUP BY FullName
, Game_1
, Game_2
, Game_3
, Game_4
, Game_5;

Using a case statement in the group by

I have the below sql. I am wanting my results to be grouped if the boolean is checked 0, but not grouped if the boolean is checked 1. Is the code below a good way of doing this? I feel like it should work but it is throwing the error "InvoicePeriodID is invalid in the select list because it is not contained in an aggraagte function or group by" Any advice?
----insert invoices for rebates grouped by date, address, contract, rebate note, and order type
declare #invoices table(InvoicePeriodID int, InvoiceStartDate datetime, InvoiceEndDate datetime, JDEAddressNo float, ContractID int, RebateNoteID int, JDEOrderType char(2), RebateInvoiceID int)
insert #invoices (InvoicePeriodID , InvoiceStartDate , InvoiceEndDate , JDEAddressNo , ContractID , RebateNoteID, JDEOrderType)
select
i.InvoicePeriodID
, i.InvoiceStartDate
, i.InvoiceEndDate
, i.JDEAddressNo
, i.ContractID
, i.RebateNoteID
, i.JDEOrderType
from
#inv i
group by
case i.InvoiceSeparately
when 1 then null
when 0 then i.InvoicePeriodID
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.InvoiceStartDate
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.InvoiceEndDate
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.JDEAddressNo
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.ContractID
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.RebateNoteId
end
, case i.InvoiceSeparately
when 1 then null
when 0 then i.JDEOrderType
end
I think it is more readable and easier to do what you want this way
select
i.InvoicePeriodID
, i.InvoiceStartDate
, i.InvoiceEndDate
, i.JDEAddressNo
, i.ContractID
, i.RebateNoteID
, i.JDEOrderType
from
#inv i
WHERE i.InvoiceSeparately = 0
group BY i.InvoicePeriodID
,i.InvoiceStartDate
,i.InvoiceEndDate
, i.JDEAddressNo
, i.ContractID
,i.RebateNoteId
, i.JDEOrderType
UNION ALL
select
i.InvoicePeriodID
, i.InvoiceStartDate
, i.InvoiceEndDate
, i.JDEAddressNo
, i.ContractID
, i.RebateNoteID
, i.JDEOrderType
from
#inv i
WHERE i.InvoiceSeparately = 1

Why do I get sub query returning more than one row?

I am using Advantage Database Server.
I have one table Areas and I want to find out all the child nodes of parent area.
The table and column names are:
Areas
(
AreaID INTEGER
, Name NVARCHAR(50)
, Code NVARCHAR(50)
)
The stored procedure is:
CREATE PROCEDURE GetAreaLocations
(
AreaID INTEGER
, AreaOutID INTEGER OUTPUT
, AreaName NVARCHAR(100) OUTPUT
, AreaCode NVARCHAR(50) OUTPUT
, WithParent NVARCHAR(100) OUTPUT
, DepthSpace NVARCHAR(50) OUTPUT
, Depth INTEGER OUTPUT
, ParentID INTEGER OUTPUT
)
BEGIN
DECLARE
AID INTEGER
, depthid INTEGER
, tempdepth INTEGER
, depthspaceid NVARCHAR(50)
;
AID = (SELECT AreaID FROM __input);
depthid = 1;
depthspaceid = '';
INSERT INTO
__output
SELECT TOP 50
A.AreaID
, A.Name
, A.Code
, (SELECT Name + '->' + A.Name FROM Areas WHERE AreaID = A.ParentID)
, depthspaceid
, depthid
, AID
FROM
Areas A
WHERE
A.ParentID = AID
ORDER BY
A.AreaID ASC
;
IF (SELECT COUNT(AreaOutID) FROM __output) > 0 THEN
SELECT TOP 1
AID = AreaOutID
, depthid = Depth
FROM
__output
WHERE
ParentID = AID
ORDER BY
AreaOutID ASC
;
WHILE depthid > 0 DO
WHILE AID > 0 DO
INSERT INTO
__output
SELECT
AreaID
, Name
, Code
, Name
, (SELECT CASE WHEN WithParent IS NULL THEN '' ELSE WithParent + '->' + Name END FROM __output WHERE AreaOutID=AID)
, depthspaceid
, depthid + 1
, AID
FROM
Areas
WHERE
ParentID = AID
;
AID = ISNULL((SELECT TOP 1 AreaOutID FROM __output WHERE Depth=depthid and AreaOutID > AID ORDER BY AreaOutID ASC),0);
END WHILE;
tempdepth = depthid;
AID = 0;
depthid = 0;
SELECT TOP 1
depthid = Depth
, AID = AreaOutID
FROM
__output
WHERE
depthid > tempdepth
ORDER BY
depthid ASC
, AreaOutID ASC
;
WND WHILE;
END IF;
END;
I'm getting an error about a subquery returning more than one row.
What's causing it and how to fix it?
If you use a subquery as an expression you have to make sure that it doesn't return more than one row.
This can be done in the subquery by:
SELECT TOP 1
SELECT DISTINCT (Won't work for all cases)
GROUP BY (Won't work for all cases)
SELECT COUNT(*)
Careful constructed WHERE conditions and Constraints on the database
You can also turn the subquery into a JOIN when it is used as part of another query, but this will lead to more rows in the main query if not done correctly.
In other databases there are also EXISTS and NOT EXISTS or ANY clauses to solve this problem.
Without seeing the data and seeing that all other subqueries have TOP 1 included, I think that the following subquery must be returning >1 row:
(SELECT CASE WHEN WithParent IS NULL THEN '' ELSE WithParent + '->' + Name END FROM __output WHERE AreaOutID=AID)