Cannot perform multi-row insert on trigger when inserting record with unique id - sql

Here is my trigger:
ALTER TRIGGER DONORINFO_INSERT
ON [dbo].[DONORINFO] INSTEAD OF INSERT
AS
DECLARE #sequence AS VARCHAR(50) = ''
DECLARE #tranLen VARCHAR(10)
SET #sequence = (SELECT TOP 1 SUBSTRING([DONORID], 3, 8)
FROM [dbo].[DONORINFO]
ORDER BY [DONORID] DESC)
IF (#sequence IS NULL OR #sequence = '')
BEGIN
SELECT #sequence = REPLICATE('0', 7 ) + '1'
END
ELSE
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
DECLARE #DONORID AS [nvarchar](50) = 'DN' + CONVERT(VARCHAR, #sequence)
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT #DONORID, inserted.DONORNAME
FROM inserted
In the first lines of the script, I'm reading the DONORINFO table in which I checked if the unique id exists. After that, I will insert the record into that table. I tested the first time, the insert into select script works but for the second time around, it fails and sends and a violation of primary key error.
But if I tested row by row insert, it works.
This is the row by row insert script that works.
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
VALUES ('DN00000001', 'test')
If I run it twice, the records will be like this:
DONORID DONORNAME
---------------------
DN00000001 test
DN00000002 test
This is the insert into select script that doesn't work:
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'',
[NameOfDonor]
FROM
[dbo].[_TEMPENDOWMENTFUND] AS ENDF
WHERE
[ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME]
FROM [dbo].[DONORINFO])
The _TEMPDOWMENTFUND is a table I created that will store the data that was migrated from an Excel worksheet, the purpose of the trigger is that it will generate a unique DONORID for every record inserted on the DONORINFO table.
Now my problem is that, I want to perform the insert into select statement which is a multiple row insert, but I'm having a hard time figuring out what is going wrong to the trigger I created.
Any help would be appreciated. Thanks.

Make #sequence totally int (and probably rename it to last_id), add characters in the very end.
To number rows use ROW_NUMBER() in final select from INSERTED:
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'DN' + REPLICATE('0', ABS(#len_you_need - LEN(t.generated_id))) + CAST (t.generated_id as varchar(100)),
t.DONORNAME
FROM
(
SELECT
i.DONORNAME,
#sequence+ROW_NUMBER()OVER(ORDER BY i.DONORNAME) as generated_id
FROM inserted i
) t
#len_you_need - is the length of DONORID you need. I guess this may be a constant of 8 characters. In your source you are calculating this here:
SELECT #tranLen = LEN(#sequence)
t.rn is a "sequence" value generated in subquery given above, which has t alias. Renamed it to generated_id for clarity.
This block:
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
is unnecessary anymore.

Here is the complete solution #ivan-starostin helped me answer.
ALTER TRIGGER DONORINFO_INSERT ON [dbo].[DONORINFO]
INSTEAD OF INSERT, UPDATE
AS
DECLARE #sequence AS VARCHAR(50) = ''
DECLARE #tranLen VARCHAR(10)
SET #sequence = (SELECT TOP 1 SUBSTRING([DONORID], 3, 8) FROM [dbo].[DONORINFO] ORDER BY [DONORID] DESC)
IF (#sequence IS NULL OR #sequence = '')
BEGIN
SELECT #sequence = REPLICATE('0', 7 )
END
ELSE
BEGIN
SELECT #tranLen = LEN(#sequence)
SELECT #sequence = #sequence + 1
SELECT #tranLen = ABS(#tranLen - LEN(CAST(#sequence AS INT)))
SELECT #sequence = REPLICATE('0', #tranLen) + #sequence
END
INSERT INTO [dbo].[DONORINFO] ([DONORID], [DONORNAME])
SELECT
'DN' + REPLICATE('0', ABS(8 - LEN(t.generated_id))) + CAST (t.generated_id as varchar(100)),
t.DONORNAME
FROM
(
SELECT
i.DONORNAME,
#sequence+ROW_NUMBER()OVER(ORDER BY i.DONORNAME) as generated_id
FROM inserted i
) t
So if I ran those two different insert into select below...
INSERT INTO [dbo].[DONORINFO] ([DONORNAME])
SELECT
[NameOfDonor]
FROM [dbo].[_TEMPENDOWMENTFUND] AS ENDF
WHERE [ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME] FROM [dbo].[DONORINFO])
INSERT INTO [dbo].[DONORINFO] ([DONORNAME])
SELECT
[NameOfDonor]
FROM [dbo].[_TEMPENDOWED] AS ENDF
WHERE [ENDF].[NameOfDonor] NOT IN (SELECT [DONORNAME] FROM [dbo].[DONORINFO])
The donorid iteration will be something like this with these two different table sources (I already omitted donor names because of confidentiality).
DONORID DONORNAME (from _TEMPENDOWMENTFUND)
------------------------
DN00000001 test
DN00000002 test
DN00000003 test
DN00000004 test
DN00000005 test
DN00000006 test
DN00000007 test
DONORID DONORNAME (from _TEMPENDOWED)
------------------------
DN00000007 test
DN00000008 test
DN00000009 test
DN00000010 test
DN00000011 test
DN00000012 test
DN00000013 test

Related

T-SQL Pivot Row to Column

I am using SQL Server 2012 and have a table that has the following columns:
ID, Date, CustomFieldName, CustomFieldValue
The CustomFieldName column has 100 values (I know how stupid this sounds) but for the sake of simplicity lets say they are CustomField1, CustomField2, CustomField3
I would like to create a pivot where the out put looks like
ID, Date, CustomField1, CustomField2, CustomField3 where the Max date of CustomFieldVaue's is aggregated.
I have failed horribly in this, but have some progress (though my max isnt right and getting a lot of wrong data)
Any help would be appreciated!
SELECT [date],[id], [CustomField1], [CustomField2], [CustomField3]
from
(
SELECT [date], [id], [CustomFieldValue], [CustomFieldName],
row_number() over(partition by [CustomFieldName] order by [CustomFieldValue]) rn
from CustomTable
) as st
pivot
(
max([CustomFieldValue])
FOR [CustomFieldName] in ([CustomField1], CustomField2, [CustomField3])
) as pivottable
order by [id]
Hope I got it right, you want to pivot the rows (COlumnName1,2,...etc) as columns, so I've made a little script that's ready to run.
I recommend CTE's when it comes to pivoting, makes it easier, if you want to see the whole structure of the query just do a select #xSqlString
set nocount on;
create table
#testTable
(
ID int identity(1,1),
[Date] datetime default getdate(),
CustomFieldName nvarchar(50),
CustomFieldValue date
);
declare
#i int = 0,
#xSqlStringPivot nvarchar(max) = '',
#xSqlString nvarchar(max) = '';
while(#i<=100)
begin
set
#xSqlStringPivot += concat('CustomFieldName',cast(#i as nvarchar(50)),char(13), case when #i<100 then ', ' else '' end);
insert into #testTable
(
CustomFieldName,
CustomFieldValue
)
values
(
concat('CustomFieldName', cast(#i as nvarchar(50))),
dateAdd(day,-#i,getdate())
);
set
#i += 1;
end;
select * from
#testTable
set
#xSqlString =
(
'with ctePiv as
(
select
t.CustomFieldName,
t.CustomFieldValue
from
#testTable t
)
select
*
from
ctePiv
pivot
(
max(customFieldValue) for customFieldName in
(
'+ #xSqlStringPivot +'
)
)p'
);
exec sp_executeSQL #xSqlString
drop table #testTable;
Edit 1
I am referencing the custom table on the while block, basically I'm iterating 100 times to populate the table with 100 rows. This is just to simulate your case.
while(#i<=100)
begin
set
#xSqlStringPivot += concat('CustomFieldName',cast(#i as nvarchar(50)),char(13), case when #i<100 then ', ' else '' end);
insert into #testTable
(
CustomFieldName,
CustomFieldValue
)
values
(
concat('CustomFieldName', cast(#i as nvarchar(50))),
dateAdd(day,-#i,getdate())
);
set
#i += 1;
end;
#xSqlStringPivot is just a small trick to make a list of elements (CustomFieldName0, CustomFieldName1, etc) and to concatenate it to a dynamic SQL string, notice that I'm doing this in the while block, I just concatenate 'CustomField' with the current iteration number and with a carry feed (space).

SQL: How Do you Declare multiple paramaters as one?

I am attempting to do the following
1. Link two tables via a join on the same database
2. Take a column that exists in both FK_APPLICATIONID(with a slight difference,
where one = +1 of the other I.e. Column 1 =1375 and column 2 = 1376
3. In one of the tables exist a reference number (QREF1234) and the other
contains 11 phonenumbers
4. I want to be able to enter the Reference number, and it returns all 11
phonenumbers as a single declarable value.
5. use "Select * from TableD where phonenum in (#Declared variable)
Here is what I have so far,
Use Database 1
DECLARE #Result INT;
SELECT #Result = D.PhoneNum1,
FROM Table1
JOIN TABLE2 D on D.FK_ApplicationID= D.FK_ApplicationID
where TABLE1.FK_ApplicationID = D.FK_ApplicationID + 1
and QREF = 'Q045569/2'
Use Database2
Select * from Table3 where PhoneNum = '#result'
I apologise to the people below who didn't understand what I was trying to achieve, and I hope this clears it up.
Thanks
There are a few options but the best answer depends on what you are really trying to achieve.
There is a SQL trick whereby you can concatenate values into a variable, for example;
create table dbo.t (i int, s varchar(10))
insert dbo.t values (1, 'one')
insert dbo.t values (2, 'two')
insert dbo.t values (3, 'three')
go
declare #s varchar(255)
select #s = isnull(#s + ', ', '') + s from t order by i
select #s
set #s = null
select #s = isnull(#s + ', ', '') + s from t order by i desc
select #s
Alternatively, if you just want one value then you can use the TOP keyword, for example;
select top 1 #s = s from t order by i
select #s
select top 1 #s = s from t order by i desc
select #s
Alternatively, you can use three-part-naming and just join across the databases, something like;
SELECT T.*
FROM DB1.dbo.Table1
JOIN DB1.dbo.Table2 D
ON D.FK_ApplicationID = D.FK_ApplicationID
JOIN DB2.dbo.Table T
ON T.PhoneNum = RIGHT(D.PhoneNum1, 11)
WHERE DB1.dbo.FK_ApplicationID = D.dbo.FK_ApplicationID + 1
AND Hidden = 'VALUE'
Hope this helps,
Rhys

Auto increment Alphanumeric ID in MSSQL

I have an existing Stored procedure that generate employee ID. The employee ID have a format of EPXXXX, EP then 4 numeric values. I want my stored procedure to be shorten.
given the table (tblEmployee) above. Below is the stored procedure for inserting the new employee with the new employee number. The process is I have to get the last employee id, get the last 4 digits (which is the number), convert it to integer, add 1 to increment, check if the number is less than 10, 100 or 1000 or equal/greater than 1000, add the prefix before inserting the new records to the table.
create procedure NewEmployee
#EmployeeName VARCHAR(50)
AS
BEGIN
SET NOCOUNT ON
DECLARE #lastEmpID as VARCHAR(6)
SET #lastEmpID =
(
SELECT TOP 1 Employee_ID
FROM tblEmployee
ORDER BY Employee_ID DESC
)
DECLARE #empID as VARCHAR(4)
SET #empID =
(
SELECT RIGHT(#lastEmpID, 4)
)
DECLARE #numEmpID as INT
#numEmpID =
(
SELECT CONVERT(INT, #empID) + 1
)
DECLARE #NewEmployeeID as VARCHAR(6)
IF #numEmp < 10
SET #NewEmployee = SELECT 'EP000' + CONVERT(#EmpID)
IF #numEmp < 100
SET #NewEmployee = SELECT 'EP00' + CONVERT(#EmpID)
IF #numEmp < 1000
SET #NewEmployee = SELECT 'EP0' + CONVERT(#EmpID)
IF #numEmp >= 1000
SET #NewEmployee = SELECT 'EP' + CONVERT(#EmpID)
INSERT INTO tblEmployee(Employee_ID, Name)
VALUES (#NewEmployeeID, #EmployeeName)
END
Try this one -
CREATE PROCEDURE dbo.NewEmployee
#EmployeeName VARCHAR(50)
AS BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tblEmployee(Employee_ID, Name)
SELECT
'EP' + RIGHT('0000' + CAST(Employee_ID + 1 AS VARCHAR(4)), 4)
, #EmployeeName
FROM (
SELECT TOP 1 Employee_ID = CAST(RIGHT(Employee_ID, 4) AS INT)
FROM dbo.tblEmployee
ORDER BY Employee_ID DESC
) t
END
I'm not suggesting over what you have currently but, i'd do this way. This is the way I've implemented in my application. Which im gonna give you. Hope you Like this. This is fully Dynamic and Works for all the Transaction you could have.
I've a table Which hold the Document Number as :
CREATE TABLE INV_DOC_FORMAT(
DOC_CODE VARCHAR(10),
DOC_NAME VARCHAR(100),
PREFIX VARCHAR(10),
SUFFIX VARCHAR(10),
[LENGTH] INT,
[CURRENT] INT
)
Which would hold the Data Like :
INSERT INTO INV_DOC_FORMAT(DOC_CODE,DOC_NAME,PREFIX,SUFFIX,[LENGTH],[CURRENT])
VALUES('01','INV_UNIT','U','',5,0)
INSERT INTO INV_DOC_FORMAT(DOC_CODE,DOC_NAME,PREFIX,SUFFIX,[LENGTH],[CURRENT])
VALUES('02','INV_UNIT_GROUP','UG','',5,0)
And, i'd have a fUNCTION OR Procedure but, i've an function here Which would generate the Document Number.
CREATE FUNCTION GET_DOC_FORMAT(#DOC_CODE VARCHAR(100))RETURNS VARCHAR(100)
AS
BEGIN
DECLARE #PRE VARCHAR(10)
DECLARE #SUF VARCHAR(10)
DECLARE #LENTH INT
DECLARE #CURRENT INT
DECLARE #FORMAT VARCHAR(100)
DECLARE #REPEAT VARCHAR(10)
IF NOT EXISTS(SELECT DOC_CODE FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE)
RETURN ''
SELECT #PRE= PREFIX FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #SUF= SUFFIX FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #LENTH= [LENGTH] FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SELECT #CURRENT= [CURRENT] FROM INV_DOC_FORMAT WHERE DOC_CODE=#DOC_CODE
SET #REPEAT=REPLICATE('0',(#LENTH-LEN(CONVERT(VARCHAR, #CURRENT))))
SET #FORMAT=#PRE + #REPEAT +CONVERT(VARCHAR, #CURRENT+1) + #SUF
RETURN #FORMAT
END
You can use the Function like :
INSERT INTO INV_UNIT(UNIT_CODE,UNIT_NAME,UNIT_ALIAS,APPROVED,APPROVED_USER_ID,APPROVED_DATE)
VALUES(DBO.GET_DOC_FORMAT('01'),#Unit_Name,#Unit_Alias,#APPROVED,#APPROVED_USER_ID,#APPROVED_DATE)
--After Transaction Successfully complete, You can
UPDATE INV_DOC_FORMAT SET [CURRENT]=[CURRENT]+1 WHERE DOC_CODE='01'
Or, you can create an Single Procedure which would handle all the things alone too.
Hope you got the way...
Hence,
Looking at your Way, you are making an Mistake.
You are getting
SET #lastEmpID =
(
SELECT TOP 1 Employee_ID
FROM tblEmployee
ORDER BY Employee_ID DESC
)
Last employee id, and then you are manipulating the rest of the ID. This would create or reuse the ID that was generated earlier however deleted now.
Suppose EMP0010 was there. After some day that EMP has been Deleted. So, When you again create an Employeee next time, You gonna have Same Emp ID you had before for anohter Employe but no more exits however. I dont think thats a good idea.
And, Instead of this :
DECLARE #NewEmployeeID as VARCHAR(6)
IF #numEmp < 10
SET #NewEmployee = SELECT 'EP000' + CONVERT(#EmpID)
IF #numEmp < 100
SET #NewEmployee = SELECT 'EP00' + CONVERT(#EmpID)
IF #numEmp < 1000
SET #NewEmployee = SELECT 'EP0' + CONVERT(#EmpID)
IF #numEmp >= 1000
SET #NewEmployee = SELECT 'EP' + CONVERT(#EmpID)
Which you used to repeat an Zero. You would use Replicate Function() of SQL. Like above on the Example of Mine.
SET #REPEAT=REPLICATE('0',(#LENTH-LEN(CONVERT(VARCHAR, #CURRENT))))
I don't think you need a Stored Procedure , Try using Ranking Functions
select
'EP'+RIGHT('000000'+ CAST(ROW_NUMBER() OVER (ORDER BY Name) AS VARCHAR(6)), 4)
AS [emp_code]
,
Name
FROM emp1 WITH(NOLOCK)
SQL Fiddle
EDIT
select
'EP'+RIGHT('000000'+ CAST((ROW_NUMBER() OVER (ORDER BY Name)+10) AS VARCHAR(6)), 4)
AS [emp_code] --^Add the last Emp no.
,
Name
FROM emp1 WITH(NOLOCK)
SQL Fiddle
of course the accepted answer is working fine, but it is not working if we have numm in previous values. so modified it as below, hope this will help others as well
CREATE PROCEDURE dbo.NewEmployee
#EmployeeName VARCHAR(50)
AS BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.tblEmployee(Employee_ID, Name)
SELECT
'EP' + RIGHT('0000' + CAST(Employee_ID + 1 AS VARCHAR(4)), 4)
, #EmployeeName
FROM (
SELECT TOP 1 Employee_ID = CAST(RIGHT(Employee_ID, 4) AS INT)
FROM dbo.tblEmployee
ORDER BY Employee_ID DESC
) t
END

The most elegant way to generate permutations in SQL server

Given a the following table:
Index | Element
---------------
1 | A
2 | B
3 | C
4 | D
We want to generate all the possible permutations (without repetitions) using the elements.
the final result (skipping some rows) will look like this:
Results
----------
ABCD
ABDC
ACBD
ACDB
ADAC
ADCA
...
DABC
DACB
DBCA
DBAC
DCAB
DCBA
(24 Rows)
How would you do it?
After making some perhaps snarky comments, this problem stuck in my brain all evening, and I eventually came up with the following set-based approach. I believe it definitely qualifies as "elegant", but then I also think it qualifies as "kinda dumb". You make the call.
First, set up some tables:
-- For testing purposes
DROP TABLE Source
DROP TABLE Numbers
DROP TABLE Results
-- Add as many rows as need be processed--though note that you get N! (number of rows, factorial) results,
-- and that gets big fast. The Identity column must start at 1, or the algorithm will have to be adjusted.
-- Element could be more than char(1), though the algorithm would have to be adjusted again, and each element
-- must be the same length.
CREATE TABLE Source
(
SourceId int not null identity(1,1)
,Element char(1) not null
)
INSERT Source (Element) values ('A')
INSERT Source (Element) values ('B')
INSERT Source (Element) values ('C')
INSERT Source (Element) values ('D')
--INSERT Source (Element) values ('E')
--INSERT Source (Element) values ('F')
-- This is a standard Tally table (or "table of numbers")
-- It only needs to be as long as there are elements in table Source
CREATE TABLE Numbers (Number int not null)
INSERT Numbers (Number) values (1)
INSERT Numbers (Number) values (2)
INSERT Numbers (Number) values (3)
INSERT Numbers (Number) values (4)
INSERT Numbers (Number) values (5)
INSERT Numbers (Number) values (6)
INSERT Numbers (Number) values (7)
INSERT Numbers (Number) values (8)
INSERT Numbers (Number) values (9)
INSERT Numbers (Number) values (10)
-- Results are iteratively built here. This could be a temp table. An index on "Length" might make runs
-- faster for large sets. Combo must be at least as long as there are characters to be permuted.
CREATE TABLE Results
(
Combo varchar(10) not null
,Length int not null
)
Here's the routine:
SET NOCOUNT on
DECLARE
#Loop int
,#MaxLoop int
-- How many elements there are to process
SELECT #MaxLoop = max(SourceId)
from Source
-- Initialize first value
TRUNCATE TABLE Results
INSERT Results (Combo, Length)
select Element, 1
from Source
where SourceId = 1
SET #Loop = 2
-- Iterate to add each element after the first
WHILE #Loop <= #MaxLoop
BEGIN
-- See comments below. Note that the "distinct" remove duplicates, if a given value
-- is to be included more than once
INSERT Results (Combo, Length)
select distinct
left(re.Combo, #Loop - nm.Number)
+ so.Element
+ right(re.Combo, nm.Number - 1)
,#Loop
from Results re
inner join Numbers nm
on nm.Number <= #Loop
inner join Source so
on so.SourceId = #Loop
where re.Length = #Loop - 1
-- For performance, add this in if sets will be large
--DELETE Results
-- where Length <> #Loop
SET #Loop = #Loop + 1
END
-- Show results
SELECT *
from Results
where Length = #MaxLoop
order by Combo
The general idea is: when adding a new element (say "B") to any string (say, "A"), to catch all permutations you would add B
to all possible positions (Ba, aB), resulting in a new set of strings. Then iterate: Add a new element (C) to each position in a string
(AB becomes Cab, aCb, abC), for all strings (Cba, bCa, baC), and you have the set of permutations. Iterate over each result set with
the next character until you run out of characters... or resources. 10 elements is 3.6 million permutations, roughly 48MB with the above algorithm, and 14 (unique) elements would hit 87 billion permutations and 1.163 terabytes.
I'm sure it could eventually be wedged into a CTE, but in the end all that would be is a glorified loop. The logic
is clearer this way, and I can't help but think the CTE execution plan would be a nightmare.
DECLARE #s VARCHAR(5);
SET #s = 'ABCDE';
WITH Subsets AS (
SELECT CAST(SUBSTRING(#s, Number, 1) AS VARCHAR(5)) AS Token,
CAST('.'+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS Permutation,
CAST(1 AS INT) AS Iteration
FROM dbo.Numbers WHERE Number BETWEEN 1 AND 5
UNION ALL
SELECT CAST(Token+SUBSTRING(#s, Number, 1) AS VARCHAR(5)) AS Token,
CAST(Permutation+CAST(Number AS CHAR(1))+'.' AS VARCHAR(11)) AS
Permutation,
s.Iteration + 1 AS Iteration
FROM Subsets s JOIN dbo.Numbers n ON s.Permutation NOT LIKE
'%.'+CAST(Number AS CHAR(1))+'.%' AND s.Iteration < 5 AND Number
BETWEEN 1 AND 5
--AND s.Iteration = (SELECT MAX(Iteration) FROM Subsets)
)
SELECT * FROM Subsets
WHERE Iteration = 5
ORDER BY Permutation
Token Permutation Iteration
----- ----------- -----------
ABCDE .1.2.3.4.5. 5
ABCED .1.2.3.5.4. 5
ABDCE .1.2.4.3.5. 5
(snip)
EDBCA .5.4.2.3.1. 5
EDCAB .5.4.3.1.2. 5
EDCBA .5.4.3.2.1. 5
first posted a while ago here
However, it would be better to do it in a better language such as C# or C++.
Just using SQL, without any code, you could do it if you can crowbar yourself another column into the table. Clearly you need to have one joined table for each of the values to be permuted.
with llb as (
select 'A' as col,1 as cnt union
select 'B' as col,3 as cnt union
select 'C' as col,9 as cnt union
select 'D' as col,27 as cnt
)
select a1.col,a2.col,a3.col,a4.col
from llb a1
cross join llb a2
cross join llb a3
cross join llb a4
where a1.cnt + a2.cnt + a3.cnt + a4.cnt = 40
Am I correctly understanding that you built Cartesian product n x n x n x n, and then filter out unwanted stuff? The alternative would be generating all the numbers up to n! and then using factorial number system to map them via element encoding.
Simpler than a recursive CTE:
declare #Number Table( Element varchar(MAX), Id varchar(MAX) )
Insert Into #Number Values ( 'A', '01')
Insert Into #Number Values ( 'B', '02')
Insert Into #Number Values ( 'C', '03')
Insert Into #Number Values ( 'D', '04')
select a.Element, b.Element, c.Element, d.Element
from #Number a
join #Number b on b.Element not in (a.Element)
join #Number c on c.Element not in (a.Element, b.Element)
join #Number d on d.Element not in (a.Element, b.Element, c.Element)
order by 1, 2, 3, 4
For an arbitrary number of elements, script it out:
if object_id('tempdb..#number') is not null drop table #number
create table #number (Element char(1), Id int, Alias as '_'+convert(varchar,Id))
insert #number values ('A', 1)
insert #number values ('B', 2)
insert #number values ('C', 3)
insert #number values ('D', 4)
insert #number values ('E', 5)
declare #sql nvarchar(max)
set #sql = '
select '+stuff((
select char(13)+char(10)+'+'+Alias+'.Element'
from #number order by Id for xml path (''), type
).value('.','NVARCHAR(MAX)'),3,1,' ')
set #sql += '
from #number '+(select top 1 Alias from #number order by Id)
set #sql += (
select char(13)+char(10)+'join #number '+Alias+' on '+Alias+'.Id not in ('
+stuff((
select ', '+Alias+'.Id'
from #number b where a.Id > b.Id
order by Id for xml path ('')
),1,2,'')
+ ')'
from #number a where Id > (select min(Id) from #number)
order by Element for xml path (''), type
).value('.','NVARCHAR(MAX)')
set #sql += '
order by 1'
print #sql
exec (#sql)
To generate this:
select
_1.Element
+_2.Element
+_3.Element
+_4.Element
+_5.Element
from #number _1
join #number _2 on _2.Id not in (_1.Id)
join #number _3 on _3.Id not in (_1.Id, _2.Id)
join #number _4 on _4.Id not in (_1.Id, _2.Id, _3.Id)
join #number _5 on _5.Id not in (_1.Id, _2.Id, _3.Id, _4.Id)
order by 1
This method uses a binary mask to select the correct rows:
;with src(t,n,p) as (
select element, index, power(2,index-1)
from table
)
select s1.t+s2.t+s3.t+s4.t
from src s1, src s2, src s3, src s4
where s1.p+s2.p+s3.p+s4.p=power(2,4)-1
My original post:
declare #t varchar(4) = 'ABCD'
;with src(t,n,p) as (
select substring(#t,1,1),1,power(2,0)
union all
select substring(#t,n+1,1),n+1,power(2,n)
from src
where n < len(#t)
)
select s1.t+s2.t+s3.t+s4.t
from src s1, src s2, src s3, src s4
where s1.p+s2.p+s3.p+s4.p=power(2,len(#t))-1
This is one of those problems that haunts you. I liked the simplicity of my original answer but there was this issue where I was still building all the possible solutions and then selecting the correct ones. One more try to make this process more efficient by only building the solutions that were correct yielded this answer. Add a character to the string only if that character didn't exist in the string. Patindex seemed like the perfect companion for a CTE solution. Here it is.
declare #t varchar(10) = 'ABCDEFGHIJ'
;with s(t,n) as (
select substring(#t,1,1),1
union all
select substring(#t,n+1,1),n+1
from s where n<len(#t)
)
,j(t) as (
select cast(t as varchar(10)) from s
union all
select cast(j.t+s.t as varchar(10))
from j,s where patindex('%'+s.t+'%',j.t)=0
)
select t from j where len(t)=len(#t)
I was able to build all 3.6 million solutions in 3 minutes and 2 seconds. Hopefully this solution will not get missed just because it's not the first.
Current solution using a recursive CTE.
-- The base elements
Declare #Number Table( Element varchar(MAX), Id varchar(MAX) )
Insert Into #Number Values ( 'A', '01')
Insert Into #Number Values ( 'B', '02')
Insert Into #Number Values ( 'C', '03')
Insert Into #Number Values ( 'D', '04')
-- Number of elements
Declare #ElementsNumber int
Select #ElementsNumber = COUNT(*)
From #Number;
-- Permute!
With Permutations( Permutation, -- The permutation generated
Ids, -- Which elements where used in the permutation
Depth ) -- The permutation length
As
(
Select Element,
Id + ';',
Depth = 1
From #Number
Union All
Select Permutation + ' ' + Element,
Ids + Id + ';',
Depth = Depth + 1
From Permutations,
#Number
Where Depth < #ElementsNumber And -- Generate only the required permutation number
Ids Not like '%' + Id + ';%' -- Do not repeat elements in the permutation (this is the reason why we need the 'Ids' column)
)
Select Permutation
From Permutations
Where Depth = #ElementsNumber
Assuming your table is named Elements and has 4 rows, this is as simple as:
select e1.Element + e2.Element + e3.Element + e4.Element
from Elements e1
join Elements e2 on e2.Element != e1.Element
join Elements e3 on e3.Element != e2.Element AND e3.Element != e1.Element
join Elements e4 on e4.Element != e3.Element AND e4.Element != e2.Element AND e4.Element != e1.Element
Way too much rust on my SQL skills, but i took a different tack for a similar problem and thought it worth sharing.
Table1 - X strings in a single field Uno
Table2 - Y strings in a single field Dos
(SELECT Uno, Dos
FROM Table1
CROSS JOIN Table2 ON 1=1)
UNION
(SELECT Dos, Uno
FROM Table1
CROSS JOIN Table2 ON 1=1)
Same principle for 3 tables with an added CROSS JOIN
(SELECT Tres, Uno, Dos
FROM Table1
CROSS JOIN Table2 ON 1=1
CROSS JOIN Table3 ON 1=1)
although it takes 6 cross-join sets in the union.
--Hopefully this is a quick solution, just change the values going into #X
IF OBJECT_ID('tempdb.dbo.#X', 'U') IS NOT NULL DROP TABLE #X; CREATE table #X([Opt] [nvarchar](10) NOT NULL)
Insert into #X values('a'),('b'),('c'),('d')
declare #pSQL NVarChar(max)='select * from #X X1 ', #pN int =(select count(*) from #X), #pC int = 0;
while #pC<#pN begin
if #pC>0 set #pSQL = concat(#pSQL,' cross join #X X', #pC+1);
set #pC = #pC +1;
end
execute(#pSQL)
--or as single column result
IF OBJECT_ID('tempdb.dbo.#X', 'U') IS NOT NULL DROP TABLE #X; CREATE table #X([Opt] [nvarchar](10) NOT NULL)
Insert into #X values('a'),('b'),('c'),('d')
declare #pSQL NVarChar(max)=' as R from #X X1 ',#pSelect NVarChar(Max)=' ',#pJoin NVarChar(Max)='', #pN int =(select count(*) from #X), #pC int = 0;
while #pC<#pN begin
if #pC>0 set #pJoin = concat(#pJoin ,' cross join #X X', #pC+1) set #pSelect = concat(#pSelect ,'+ X', #pC+1,'.Opt ')
set #pC = #pC +1;
end
set #pSQL = concat ('select X1.Opt', #pSelect,#pSQL ,#pJoin)
exec(#pSQL)
create function GeneratePermutations (#string nvarchar(4000))
RETURNS #Permutations
TABLE(
name nVARCHAR(500)
)
AS
begin
declare #SplitedString table(name nvarchar(500))
insert into #SplitedString
select *
from string_split(#string,' ')
declare #CountOfWords as int
set #CountOfWords = (select count(*) from #SplitedString)
;with cte_Permutations (name, level) as (
select convert(nvarchar(500), name), 1 as level from #SplitedString
union all
select convert(nvarchar(500),splited.name+','+cte_Permutations.name),level+1
from #SplitedString splited ,cte_Permutations
where level < #CountOfWords
)
insert into #Permutations
select name
from cte_Permutations
where level = #CountOfWords
order by name
return
end
select *
From (
select 1 id,'a b c' msg
union all
select 2 id,'d e' msg
) p
cross apply dbo.GeneratePermutations(p.msg)

SQL query to find Missing sequence numbers

I have a column named sequence. The data in this column looks like 1, 2, 3, 4, 5, 7, 9, 10, 15.
I need to find the missing sequence numbers from the table. What SQL query will find the missing sequence numbers from my table? I am expecting results like
Missing numbers
---------------
6
8
11
12
13
14
I am using only one table. I tried the query below, but am not getting the results I want.
select de.sequence + 1 as sequence from dataentry as de
left outer join dataentry as de1 on de.sequence + 1 = de1.sequence
where de1.sequence is null order by sequence asc;
How about something like:
select (select isnull(max(val)+1,1) from mydata where val < md.val) as [from],
md.val - 1 as [to]
from mydata md
where md.val != 1 and not exists (
select 1 from mydata md2 where md2.val = md.val - 1)
giving summarised results:
from to
----------- -----------
6 6
8 8
11 14
I know this is a very old post but I wanted to add this solution that I found HERE so that I can find it easier:
WITH Missing (missnum, maxid)
AS
(
SELECT 1 AS missnum, (select max(id) from #TT)
UNION ALL
SELECT missnum + 1, maxid FROM Missing
WHERE missnum < maxid
)
SELECT missnum
FROM Missing
LEFT OUTER JOIN #TT tt on tt.id = Missing.missnum
WHERE tt.id is NULL
OPTION (MAXRECURSION 0);
Try with this:
declare #min int
declare #max int
select #min = min(seq_field), #max = max(seq_field) from [Table]
create table #tmp (Field_No int)
while #min <= #max
begin
if not exists (select * from [Table] where seq_field = #min)
insert into #tmp (Field_No) values (#min)
set #min = #min + 1
end
select * from #tmp
drop table #tmp
The best solutions are those that use a temporary table with the sequence. Assuming you build such a table, LEFT JOIN with NULL check should do the job:
SELECT #sequence.value
FROM #sequence
LEFT JOIN MyTable ON #sequence.value = MyTable.value
WHERE MyTable.value IS NULL
But if you have to repeat this operation often (and more then for 1 sequence in the database), I would create a "static-data" table and have a script to populate it to the MAX(value) of all the tables you need.
SELECT CASE WHEN MAX(column_name) = COUNT(*)
THEN CAST(NULL AS INTEGER)
-- THEN MAX(column_name) + 1 as other option
WHEN MIN(column_name) > 1
THEN 1
WHEN MAX(column_name) <> COUNT(*)
THEN (SELECT MIN(column_name)+1
FROM table_name
WHERE (column_name+ 1)
NOT IN (SELECT column_name FROM table_name))
ELSE NULL END
FROM table_name;
Here is a script to create a stored procedure that returns missing sequential numbers for a given date range.
CREATE PROCEDURE dbo.ddc_RolledBackOrders
-- Add the parameters for the stored procedure here
#StartDate DATETIME ,
#EndDate DATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Min BIGINT
DECLARE #Max BIGINT
DECLARE #i BIGINT
IF OBJECT_ID('tempdb..#TempTable') IS NOT NULL
BEGIN
DROP TABLE #TempTable
END
CREATE TABLE #TempTable
(
TempOrderNumber BIGINT
)
SELECT #Min = ( SELECT MIN(ordernumber)
FROM dbo.Orders WITH ( NOLOCK )
WHERE OrderDate BETWEEN #StartDate AND #EndDate)
SELECT #Max = ( SELECT MAX(ordernumber)
FROM dbo.Orders WITH ( NOLOCK )
WHERE OrderDate BETWEEN #StartDate AND #EndDate)
SELECT #i = #Min
WHILE #i <= #Max
BEGIN
INSERT INTO #TempTable
SELECT #i
SELECT #i = #i + 1
END
SELECT TempOrderNumber
FROM #TempTable
LEFT JOIN dbo.orders o WITH ( NOLOCK ) ON tempordernumber = o.OrderNumber
WHERE o.OrderNumber IS NULL
END
GO
Aren't all given solutions way too complex?
wouldn't this be much simpler:
SELECT *
FROM (SELECT row_number() over(order by number) as N from master..spt_values) t
where N not in (select 1 as sequence union
select 2 union
select 3 union
select 4 union
select 5 union
select 7 union
select 10 union
select 15
)
This is my interpretation of this issue, placing the contents in a Table variable that I can easily access in the remainder of my script.
DECLARE #IDS TABLE (row int, ID int)
INSERT INTO #IDS
select ROW_NUMBER() OVER (ORDER BY x.[Referred_ID]), x.[Referred_ID] FROM
(SELECT b.[Referred_ID] + 1 [Referred_ID]
FROM [catalog].[dbo].[Referrals] b) as x
LEFT JOIN [catalog].[dbo].[Referrals] a ON x.[Referred_ID] = a.[Referred_ID]
WHERE a.[Referred_ID] IS NULL
select * from #IDS
Just for fun, I decided to post my solution.
I had an identity column in my table and I wanted to find missing invoice numbers.
I reviewed all the examples I could find but they were not elegant enough.
CREATE VIEW EENSkippedInvoicveNo
AS
SELECT CASE WHEN MSCNT = 1 THEN CAST(MSFIRST AS VARCHAR (8)) ELSE
CAST(MSFIRST AS VARCHAR (8)) + ' - ' + CAST(MSlAST AS VARCHAR (8)) END AS MISSING,
MSCNT, INV_DT FROM (
select invNo+1 as Msfirst, inv_no -1 as Mslast, inv_no - invno -1 as msCnt, dbo.fmtdt(Inv_dt) AS INV_dT
from (select inv_no as invNo, a4glidentity + 1 as a4glid
from oehdrhst_sql where inv_dt > 20140401) as s
inner Join oehdrhst_sql as h
on a4glid = a4glidentity
where inv_no - invno <> 1
) AS SS
DECLARE #MaxID INT = (SELECT MAX(timerecordid) FROM dbo.TimeRecord)
SELECT SeqID AS MissingSeqID
FROM (SELECT ROW_NUMBER() OVER (ORDER BY column_id) SeqID from sys.columns) LkUp
LEFT JOIN dbo.TimeRecord t ON t.timeRecordId = LkUp.SeqID
WHERE t.timeRecordId is null and SeqID < #MaxID
I found this answer here:
http://sql-developers.blogspot.com/2012/10/how-to-find-missing-identitysequence.html
I was looking for a solution and found many answers. This is the one I used and it worked very well. I hope this helps anyone looking for a similar answer.
-- This will return better Results
-- ----------------------------------
;With CTERange
As (
select (select isnull(max(ArchiveID)+1,1) from tblArchives where ArchiveID < md.ArchiveID) as [from],
md.ArchiveID - 1 as [to]
from tblArchives md
where md.ArchiveID != 1 and not exists (
select 1 from tblArchives md2 where md2.ArchiveID = md.ArchiveID - 1)
) SELECT [from], [to], ([to]-[from])+1 [total missing]
From CTERange
ORDER BY ([to]-[from])+1 DESC;
from to total missing
------- ------- --------------
6 6 1
8 8 1
11 14 4
DECLARE #TempSujith TABLE
(MissingId int)
Declare #Id Int
DECLARE #mycur CURSOR
SET #mycur = CURSOR FOR Select Id From tbl_Table
OPEN #mycur
FETCH NEXT FROM #mycur INTO #Id
Declare #index int
Set #index = 1
WHILE ##FETCH_STATUS = 0
BEGIN
if (#index < #Id)
begin
while #index < #Id
begin
insert into #TempSujith values (#index)
set #index = #index + 1
end
end
set #index = #index + 1
FETCH NEXT FROM #mycur INTO #Id
END
Select Id from tbl_Table
select MissingId from #TempSujith
Create a useful Tally table:
-- can go up to 4 million or 2^22
select top 100000 identity(int, 1, 1) Id
into Tally
from master..spt_values
cross join master..spt_values
Index it, or make that single column as PK.
Then use EXCEPT to get your missing number.
select Id from Tally where Id <= (select max(Id) from TestTable)
except
select Id from TestTable
You could also solve using something like a CTE to generate the full sequence:
create table #tmp(sequence int)
insert into #tmp(sequence) values (1)
insert into #tmp(sequence) values (2)
insert into #tmp(sequence) values (3)
insert into #tmp(sequence) values (5)
insert into #tmp(sequence) values (6)
insert into #tmp(sequence) values (8)
insert into #tmp(sequence) values (10)
insert into #tmp(sequence) values (11)
insert into #tmp(sequence) values (14)
DECLARE #max INT
SELECT #max = max(sequence) from #tmp;
with full_sequence
(
Sequence
)
as
(
SELECT 1 Sequence
UNION ALL
SELECT Sequence + 1
FROM full_sequence
WHERE Sequence < #max
)
SELECT
full_sequence.sequence
FROM
full_sequence
LEFT JOIN
#tmp
ON
full_sequence.sequence = #tmp.sequence
WHERE
#tmp.sequence IS NULL
Hmmmm - the formatting is not working on here for some reason? Can anyone see the problem?
i had made a proc so you can send the table name and the key and the result is a list of missing numbers from the given table
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
create PROCEDURE [dbo].[action_FindMissing_Autoincremnt]
(
#tblname as nvarchar(50),
#tblKey as nvarchar(50)
)
AS
BEGIN
SET NOCOUNT ON;
declare #qry nvarchar(4000)
set #qry = 'declare #min int '
set #qry = #qry + 'declare #max int '
set #qry = #qry +'select #min = min(' + #tblKey + ')'
set #qry = #qry + ', #max = max('+ #tblKey +') '
set #qry = #qry + ' from '+ #tblname
set #qry = #qry + ' create table #tmp (Field_No int)
while #min <= #max
begin
if not exists (select * from '+ #tblname +' where '+ #tblKey +' = #min)
insert into #tmp (Field_No) values (#min)
set #min = #min + 1
end
select * from #tmp order by Field_No
drop table #tmp '
exec sp_executesql #qry
END
GO
SELECT TOP 1 (Id + 1)
FROM CustomerNumberGenerator
WHERE (Id + 1) NOT IN ( SELECT Id FROM CustomerNumberGenerator )
Working on a customer number generator for my company. Not the most efficient but definitely most readable
The table has one Id column.
The table allows for Ids to be inserted at manually by a user off sequence.
The solution solves the case where the user decided to pick a high number