Identifying repeated fields in SQL query - sql

I have an SQL query that returns a column like this:
foo
-----------
1200
1200
1201
1200
1200
1202
1202
1202
It has already been ordered in a specific way, and I would like to perform another query on this result set to ID the repeated data like this:
foo ID
---- ----
1200 1
1200 1
1201 2
1200 3
1200 3
1202 4
1202 4
1202 4
It's important that the second group of 1200 is identified as separate from the first. Every variation of OVER/PARTITION seems to want to lump both groups together. Is there a way to window the partition to only these repeated groups?
Edit:
This is for Microsoft SQL Server 2012

Not sure this will be the fastest results...
select main.num, main.id from
(select x.num,row_number()
over (order by (select 0)) as id
from (select distinct num from num) x) main
join
(select num, row_number() over(order by (select 0)) as ordering
from num) x2 on
x2.num=main.num
order by x2.ordering
Assuming the table "num" has a column "num" that contains your data, in the order-- of course num could be made into a view or a "with" for your original query.
Please see the following sqlfiddle

Here is one way to do this without a CURSOR
-- Create a temporay table
DECLARE #table TABLE
(
SeqID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
foo INT,
id int null
)
DECLARE #i INT
DECLARE #j INT
DECLARE #k INT
declare #tFoo INT
declare #oldFoo INT
SET #k = 0
set #oldFoo = 0
-- Insert data into the temporary table
INSERT INTO #table(foo)
SELECT 1200
INSERT INTO #table(foo)
SELECT 1200
INSERT INTO #table(foo)
SELECT 1201
INSERT INTO #table(foo)
SELECT 1200
INSERT INTO #table(foo)
SELECT 1200
INSERT INTO #table(foo)
SELECT 1202
INSERT INTO #table(foo)
SELECT 1202
INSERT INTO #table(foo)
SELECT 1202
-- Get the max and min SeqIDs to loop through
SELECT #i = MIN(SeqID) FROM #table
SELECT #j = MAX(SeqID) FROM #table
-- Loop through the temp table using the SeqID indentity column
WHILE (#i <= #j)
BEGIN
SELECT #tFoo = foo FROM #table WHERE SeqID = #i
if #oldFoo <> #tFoo
set #k = #k + 1
update #table set id = #k where SeqID = #i
SET #oldFoo = #tFoo
-- Increment the counter
SET #i = #i + 1
END
SELECT * from #table

You can do it without using cursors but it does not look nice (at least what I came up with). So 1) I assume you have PK column which orders your main values.
Then 2) I assume you have an ID column which you want to set.
create table tbl(foo int, pk int, id int);
insert into tbl(foo, pk) values (1100, 5);
insert into tbl(foo, pk) values (1200, 10);
insert into tbl(foo, pk) values (1200, 20);
insert into tbl(foo, pk) values (1201, 30);
insert into tbl(foo, pk) values (1200, 40);
insert into tbl(foo, pk) values (1200, 50);
insert into tbl(foo, pk) values (1202, 60);
insert into tbl(foo, pk) values (1202, 70);
insert into tbl(foo, pk) values (1202, 80);
insert into tbl(foo, pk) values (1202, 90);
SQL Fiddle here: http://sqlfiddle.com/#!6/fdaaa/2
update tbl
set
ID = 1
update t
set
t.ID = m.RN2
from
tbl t
join
(
select
y1.RN as RN1, y1.PK as PK1,
y2.RN as RN2, y2.PK as PK2
FROM
(
SELECT
ROW_NUMBER() OVER(ORDER BY x.pk1 ASC) AS rn,
x.pk1 AS pk
FROM
(
SELECT t1.pk AS pk1, t2.pk AS pk2
FROM
tbl t1
LEFT JOIN tbl t2 ON
(
(t1.pk < t2.pk AND t1.foo = t2.foo)
AND
(
NOT EXISTS
(
SELECT tMid.pk FROM
tbl tMid WHERE
tMid.pk < t2.pk
AND
tMid.pk > t1.pk
)
)
)
) x WHERE x.pk2 IS NULL
) y1
left join
(
SELECT
ROW_NUMBER() OVER(ORDER BY x.pk1 ASC) AS rn,
x.pk1 AS pk
FROM
(
SELECT t1.pk AS pk1, t2.pk AS pk2
FROM
tbl t1
LEFT JOIN tbl t2 ON
(
(t1.pk < t2.pk AND t1.foo = t2.foo)
AND
(
NOT EXISTS
(
SELECT tMid.pk FROM
tbl tMid WHERE
tMid.pk < t2.pk
AND
tMid.pk > t1.pk
)
)
)
) x WHERE x.pk2 IS NULL
) y2 on y1.RN = y2.RN - 1
) m on
(
(t.pk > m.pk1 and ((m.pk2 is not null ) and (t.pk <= m.pk2)))
-- or
-- (t.pk<=m.pk1)
)

This is my solution using a cursor and a temporary table to hold the results.
DECLARE #foo INT
DECLARE #previousfoo INT = -1
DECLARE #id INT = 0
DECLARE #getid CURSOR
DECLARE #resultstable TABLE
(
primaryId INT IDENTITY(1, 1) NOT NULL PRIMARY KEY,
foo INT,
id int null
)
SET #getid = CURSOR FOR
SELECT originaltable.foo
FROM originaltable
OPEN #getid
FETCH NEXT
FROM #getid INTO #foo
WHILE ##FETCH_STATUS = 0
BEGIN
IF (#foo <> #previousfoo)
BEGIN
SET #id = #id + 1
END
INSERT INTO #resultstable VALUES (#foo, #id)
SET #previousfoo = #foo
FETCH NEXT
FROM #getid INTO #foo
END
CLOSE #getid
DEALLOCATE #getid

Related

Nested while loop in SQL Server is not showing the expected result

I am trying to connect records from two different tables so I can display the data in a tabular format in an SSRS tablix.
The code below does not return the expected results.
As is, for each item in Temp_A the loop updates everything with the last item in Temp_C. Here is the code:
CREATE TABLE #Temp_A
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_A ([ID], [Name])
VALUES (1, 'A'), (2, 'B')
CREATE TABLE #Temp_C
(
[ID] INT,
[Name] VARCHAR(255)
)
INSERT INTO #Temp_C ([ID], [Name])
VALUES (1, 'C'), (2, 'D')
CREATE TABLE #Temp_Main
(
[Temp_A_ID] INT,
[Temp_A_Name] VARCHAR(255),
[Temp_C_ID] INT,
[Temp_C_Name] VARCHAR(255),
)
DECLARE #MIN_AID int = (SELECT MIN(ID) FROM #Temp_A)
DECLARE #MAX_AID int = (SELECT MAX(ID) FROM #Temp_A)
DECLARE #MIN_DID int = (SELECT MIN(ID) FROM #Temp_C)
DECLARE #MAX_DID int = (SELECT MAX(ID) FROM #Temp_C)
WHILE #MIN_AID <= #MAX_AID
BEGIN
WHILE #MIN_DID <= #MAX_DID
BEGIN
INSERT INTO #Temp_Main([Temp_A_ID], [Temp_A_Name])
SELECT ID, [Name]
FROM #Temp_A
WHERE ID = #MIN_AID
UPDATE #Temp_Main
SET [Temp_C_ID] = ID, [Temp_C_Name] = [Name]
FROM #Temp_C
WHERE ID = #MIN_DID
SET #MIN_DID = #MIN_DID + 1
END
SET #MIN_AID = #MIN_AID + 1
SET #MIN_DID = 1
END
SELECT * FROM #Temp_Main
DROP TABLE #Temp_A
DROP TABLE #Temp_C
DROP TABLE #Temp_Main
Incorrect result:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 2 D
1 A 2 D
2 B 2 D
2 B 2 D
Expected results:
Temp_A_ID | Temp_A_Name | Temp_C_ID | Temp_C_Name
----------+-------------+-----------+---------------
1 A 1 C
1 A 2 D
2 B 1 C
2 B 2 D
What am I missing?
You seem to want a cross join:
select a.*, c.*
from #Temp_A a cross join
#Temp_C c
order by a.id, c.id;
Here is a db<>fiddle.
There is no need to write a WHILE loop to do this.
You can use insert to insert this into #TempMain, but I don't se a need to have a temporary table for storing the results of this query.

Find missing numbers in a sequence in MS SQL

Say i have a table with an integer column Id. I need to find the missing numbers in a sequence with a maximum returned amount.
If the table is empty and i'm asking for 10, it should return the numbers 1-10.
If the table has 1-5 and i'm asking for 10, it should return the numbers 6,7,8,9,10,11,12,13,14,15.
If the table has 1,2,4,6,9 and im asking for 10, it should return the numbers 3,5,7,8,10,11,12,13,14,15
How can i achive this in one single query using MS SQL?
Thanks in advance!
Try this:
If you need to get more numbers, just increase the WHERE Number<=100.
DECLARE #Tab1 TABLE (ID INT)
INSERT INTO #Tab1 VALUES(1)
INSERT INTO #Tab1 VALUES(3)
INSERT INTO #Tab1 VALUES(5)
INSERT INTO #Tab1 VALUES(7)
INSERT INTO #Tab1 VALUES(9)
;WITH CTE AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1 FROM CTE
WHERE Number<=100
)
SELECT TOP 5 *
FROM CTE
WHERE Number NOT IN(SELECT ID FROM #Tab1)
ORDER BY Number
OPTION (maxrecursion 0);
Existing values:
Number
1
3
5
7
9
OutPut:
Number
2
4
6
8
10
Hope this helps you.
This should work
There are also a system table with numbers
declare #T table (i int primary key);
insert into #T values (1), (2), (4), (6), (9);
declare #count int = 10;
declare #size int = (select count(*) from #T);
with cte as
( select 1 as num
union all
select num + 1
from cte
where num + 1 <= (#count + #size)
)
select top (#count) cte.num
from cte
left join #T t
on t.i = cte.num
where t.i is null
order by cte.num
option ( MaxRecursion 0 );

Replace cursors with queries

Let's say I have a booking covering 6 hours and 3 discounts covering 2 hours each. I want to split my booking into 3 parts so I can allocate 2 hours per discount.
It would return something like this:
BookingId 1 | DiscountId 1 | Qty 2
BookingId 1 | DiscountId 2 | Qty 2
BookingId 1 | DiscountId 3 | Qty 2
I would then insert those records this into another table.
I'm using an heavily optimized query to determine the number of hours available for each discount. However, I can't find a "good" way to allocate my booking to each discount without using a cursor.
(...)
WHILE ##FETCH_STATUS = 0
BEGIN
IF #RequiredQty = 0
RETURN
IF #RequiredQty <= #AvailableQty
BEGIN
INSERT INTO discount.Usage (DiscountId, BookingId, Quantity)
VALUES (#DiscountId, #BookingId, #RequiredQty)
SET #RequiredQty = 0
END
IF #RequiredQty > #AvailableQty
BEGIN
INSERT INTO discount.Usage (DiscountId, BookingId, Quantity)
VALUES (#DiscountId, #BookingId, #AvailableQty)
SET #RequiredQty -= #AvailableQty
END
FETCH NEXT FROM ecursor INTO #DiscountId, #AvailableQty
END
DEALLOCATE ecursor
I tried building the corresponding query but I can't select and assign variables at the same time. Using a cursor is not really a problem (besides some potential performance issues) but I was just curious to see if with the newest SQL Server we can convert our old cursors to something better?
Thanks,
Seb
You can useCTE RECURSIVE to make a Table.
like this.
DECLARE #BookingId INT = 1;
DECLARE #RequiredQty INT = 2;
DECLARE #Hours INT = 7;
CREATE TABLE #T
(
BookingId INT,
DiscountId INT,
Quantity INT
)
;WITH CTE([Count],[Quantity],Rk) AS
(
SELECT
CASE
WHEN [HOURS] - #RequiredQty > #RequiredQty THEN #RequiredQty
ELSE [HOURS] - #RequiredQty
END ,
T.HOURS,1
FROM
(
SELECT #Hours [HOURS]
) AS T
UNION ALL
SELECT CASE
WHEN CTE.[Quantity] - #RequiredQty > #RequiredQty THEN #RequiredQty
ELSE CTE.[Quantity] - #RequiredQty
END AS [Count],
CTE.[Quantity] - #RequiredQty,
RK + 1
FROM CTE
WHERE CTE.[Quantity] - #RequiredQty > 0
)
INSERT INTO #T(BookingId,DiscountId,Quantity)
SELECT #BookingId,Rk,[Count] FROM CTE
option (maxrecursion 0)
select * from #T
SQLDEMO
This is another approach, but don't know if this code has better performance than cursor.
DECLARE #DiscountStocks TABLE (Id INT IDENTITY(1,1), DiscountId INT, LastQty INT)
INSERT INTO #DiscountStocks (DiscountId, LastQty) VALUES (1, 5)
INSERT INTO #DiscountStocks (DiscountId, LastQty) VALUES (2, 2)
INSERT INTO #DiscountStocks (DiscountId, LastQty) VALUES (3, 1)
DECLARE #DiscountBookings TABLE (Id INT IDENTITY(1,1), DiscountId INT, BookingId INT, Qty INT)
DECLARE #BookingDiscount TABLE (Id INT IDENTITY(1,1), BookingId INT, DiscountId INT, Qty INT)
INSERT INTO #BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 1, 4)
INSERT INTO #BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 2, 2)
INSERT INTO #BookingDiscount (BookingId, DiscountId, Qty) VALUES (1, 3, 1)
INSERT INTO #BookingDiscount (BookingId, DiscountId, Qty) VALUES (2, 1, 1)
INSERT INTO #BookingDiscount (BookingId, DiscountId, Qty) VALUES (2, 2, 2)
SELECT BD.Id AS BDId, DS.Id AS DSId, DS.LastQty, BD.Qty
, DS.LastQty - (SELECT SUM(Qty) FROM #BookingDiscount WHERE Id <= BD.Id AND DiscountId = BD.DiscountId) AS QtyAfterSubstract
INTO #LastDiscountStock
FROM #DiscountStocks DS
INNER JOIN #BookingDiscount BD ON DS.DiscountId = BD.DiscountId
ORDER BY BD.Id, DS.Id
INSERT INTO #DiscountBookings (DiscountId, BookingId, Qty)
SELECT DSId, BDId, Qty
FROM #LastDiscountStock
WHERE QtyAfterSubstract >= 0
DROP TABLE #LastDiscountStock
SELECT * FROM #DiscountBookings

How to generate a new code

Using VB.Net and SQL Server
Table1
Id Value .....
1001P0010001 100
1001P0010002 200
1001P0010003 300
1001P0010004 400
...
I have n columns and rows in table1, from the table1, I want to copy all the column details with new id no...
id no is like this 1001P0020001, 1001P0020002, .......
P002 is next id, P003 is the next Id.....
The first 4 digits and last 4 digits will remain as read from table1, middle 4 digits should change to next series
Expected output
Id Value .....
1001P0010001 100
1001P0010002 200
1001P0010003 300
1001P0010004 400
1001P0020001 100
1001P0020002 200
1001P0020003 300
1001P0020004 400
...
Which the best way to do this?
I can do it in VB.Net or a SQL query...? Please suggest ways of doing this.
CREATE TABLE CocoJambo (
Id CHAR(12) NOT NULL,
Value INT NULL,
CHECK( Id LIKE '[0-9][0-9][0-9][0-9][A-Z][0-9][0-9][0-9][0-9][0-9][0-9][0-9]' )
);
GO
CREATE UNIQUE INDEX IUN_CocoJambo_Id
ON CocoJambo (Id);
GO
INSERT CocoJambo (Id, Value)
SELECT '1001P0010001', 100
UNION ALL SELECT '1001P0010002', 200
UNION ALL SELECT '1001P0010003', 300
UNION ALL SELECT '1001P0010004', 400
UNION ALL SELECT '1001P0020001', 100
UNION ALL SELECT '1001P0020002', 200
UNION ALL SELECT '1001P0020003', 300
UNION ALL SELECT '1001P0020004', 400;
GO
-- Test 1: generating a single Id
DECLARE #Prefix CHAR(5),
#Sufix CHAR(4);
SELECT #Prefix = '1001P',
#Sufix = '0001';
BEGIN TRAN
DECLARE #LastGeneratedMiddleValue INT,
#LastValue INT;
SELECT #LastGeneratedMiddleValue = y.MiddleValue,
#LastValue = y.Value
FROM
(
SELECT x.MiddleValue, x.Value,
ROW_NUMBER() OVER(ORDER BY x.MiddleValue DESC) AS RowNum
FROM
(
SELECT CONVERT(INT,SUBSTRING(a.Id,6,3)) AS MiddleValue, a.Value
FROM CocoJambo a WITH(UPDLOCK) -- It will lock the rows (U lock) during transaction
WHERE a.Id LIKE #Prefix+'%'+#Sufix
) x
) y
WHERE y.RowNum=1;
SELECT #LastGeneratedMiddleValue = ISNULL(#LastGeneratedMiddleValue ,0)
SELECT #Prefix
+RIGHT('00'+CONVERT(VARCHAR(3),#LastGeneratedMiddleValue +1),3)
+#Sufix AS MyNewId,
#LastValue AS Value
COMMIT TRAN;
GO
-- Test 2: generating many Id's
BEGIN TRAN
DECLARE #Results TABLE (
Prefix CHAR(5) NOT NULL,
Sufix CHAR(4) NOT NULL,
LastGeneratedMiddleValue INT NOT NULL,
LastValue INT NULL
);
INSERT #Results (Prefix, Sufix, LastGeneratedMiddleValue, LastValue)
SELECT y.Prefix, y.Sufix, y.MiddleValue, y.Value
FROM
(
SELECT x.Prefix, x.MiddleValue, x.Sufix, x.Value,
ROW_NUMBER() OVER(PARTITION BY x.Prefix, x.Sufix ORDER BY x.MiddleValue DESC) AS RowNum
FROM
(
SELECT SUBSTRING(a.Id,1,5) AS Prefix,
CONVERT(INT,SUBSTRING(a.Id,6,3)) AS MiddleValue,
SUBSTRING(a.Id,9,4) AS Sufix,
a.Value
FROM CocoJambo a WITH(UPDLOCK) -- It will lock the rows (U lock) during transaction
) x
) y
WHERE y.RowNum=1;
SELECT r.*,
r.Prefix
+RIGHT('00'+CONVERT(VARCHAR(3),r.LastGeneratedMiddleValue +1),3)
+r.Sufix AS MyNewId,
r.LastValue AS Value
FROM #Results r;
COMMIT TRAN;
GO
insert into table1 (id, value)
select
l +
replicate('0', 3 - lenght(m)) + m +
r,
value
from (
select
left(id, 5) l,
cast(cast(substring(id, 6, 3) as integer) + 1 as varchar(3)) m,
right(id, 4),
value
from table1
) s

Select record when it is the only record and not linked to in another table to a particular record

I am looking to select the id of a record from #table1 when that record is the only record in that table and is not currently linked in #t1Tot2 to a particular id from another table.
The following query below works, but I am wondering if there is a better way. It is setup to currently to return 55 the id of the only record added to table #table1. Inserting another record into #table1 would cause it to return no records ( good ), and linking #t2id in #t1Tot2 would make it return none as well ( good ). Is there a better way? Thanks.
DECLARE #t2id INT
SET #t2id = 1 --Record to link to
DECLARE #table1 TABLE
(
t1id int
)
DECLARE #t1Tot2 TABLE
(
t1id INT,
t2id int
)
INSERT INTO #table1
( t1id )
VALUES ( 55 -- t1id - int
)
--Will cause the query below to return no records because of having more than 1 record to be linked to
-- INSERT INTO #table1
-- ( t1id )
--VALUES ( 2 -- t1id - int
-- )
--Will cause the query below to return no records because of already being linked to the t1id
--INSERT INTO #t1Tot2
--( t1id, t2id )
--VALUES ( 55, -- t1id - int
--#t2id -- t2id - int
--)
SELECT MAX(a.t1id)
FROM #table1 a
LEFT JOIN #t1Tot2 b ON a.t1id = b.t1id AND b.t2id = 1
HAVING COUNT(1) = 1 AND SUM( CASE WHEN b.t2id IS NULL THEN 0 ELSE 1 END ) = 0
--declare #table1 table (t1id int)
--declare #t1Tot2 table (t1id int)
select a.t1id
from #table1 a
where (select count(*) from (select top 2 * from #table1) x) = 1
and not exists (
select * from #t1Tot2 b
where b.t1id = a.t1id)
select a.aid
from a
left outer join b on a.bid=b.bid
where b.bid is null
group by a.aid
having count(*) = 1
should work as well sql server specific sql btw.