How to remove redundant conditions in **IN** query Sql - sql-server

I have this kind of query. But i need to optimize this query so how can omit redundant conditions with same split function.
DECLARE #Filter nvarchar(20)
SELECT #Filter ='5,22,3'
SELECT * FROM Employee e
WHERE e.code IN
(
CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (5, 16, 20, 23, 33, 49, 62, 90, 91, 92, 93, 94))>0) THEN 5 ELSE 0 END
,CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (22, 18))>0) THEN 46 ELSE 0 END
,CASE WHEN((SELECT count(*) FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
WHERE d.splitdata IN (3, 28))>0) THEN 3 ELSE 0 END
)

As #Damien_The_Unbeliever said, avoid split strings as table of values. You do not need to split the same string multiple times. Instead you can use a temporary table variable.
DECLARE #SplitStrings TABLE
(
splitdata int
)
INSERT #SplitStrings
SELECT splitdata FROM dbo.FNSPLITSTRING(SUBSTRING(#Filter,1,LEN(#Filter)-1), ',') d
DECLARE #EmployeeCodes TABLE
(
splitdata INT,
code int
)
INSERT #EmployeeCodes (splitdata, code)
VALUES (5, 5), (16, 5), (20, 5), (23, 5), (33, 5), (49, 5), (62, 5), (90, 5), (91, 5), (92, 5), (93, 5), (94, 5),
(22, 46), (18, 46),
(3, 3), (28, 3)
SELECT e.*
FROM Employee e
JOIN #EmployeeCodes ec
ON e.code = ec.code
JOIN #SplitStrings ss
ON ec.splitdata = ss.splitdata
I hope this is the direction you are looking at.
Note: Assuming that you do not need 0 as employee code.

I have done new way. Less complex and omit redundant code.
DECLARE #Filter nvarchar(20)
SELECT #Filter ='5,22,3'
SELECT Distinct e.EmployeeId FROM Employee e
CROSS JOIN dbo.fnSplitString(#Filter, ',') AS d
WHERE
(e.code = 5 AND d.splitdata IN ('5', '16', '20', '23', '33', '49', '62', '90', '91', '92', '93', '94'))
OR (e.code = 46 AND d.splitdata IN ('22', '18'))
OR (e.code = 3 AND d.splitdata IN ('3', '28'))

Related

t-sql - Apply function to a column without sqlFunction

I've to transform a numeric code in an alphabetic code like this:
1234 -> ABCD
where 0 = 0, 1 = A, 2 = b, etc..
This is my function and how to use:
Create function dbo.DecodeNumToChar
(
#change varchar(10),
#foo varchar(10)
) returns varchar(10)
as
begin
DECLARE #II int = 1,
#result varchar(10)
;WITH x AS
(
SELECT #II as ii, STUFF(#foo,#II,1,SUBSTRING(#change,CAST(SUBSTRING(#foo,#II,1) AS INT)+1,1)) AS AA
UNION ALL
--SELECT #II+1
SELECT ii+1, STUFF(AA,ii+1,1,SUBSTRING(#change,CAST(SUBSTRING(#foo,ii+1,1) AS INT)+1,1)) AS AA
FROM x
where ii+1 <= LEN(#foo)
)
select top 1 #result = AA from x order by ii desc
return #result
end
--------------------------------------------
select brand_code, dbo.DecodeNumToChar('0ABCDEFGHI', brand_code)
from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
This function working well, but in production DB we do not have permission to create functions.
I've tryed to transform this function into CTE like this
declare #change varchar(9) = 'ABCDEFGHI'
DECLARE #II int = 0
;WITH x AS
(
SELECT TOP (10) n = ROW_NUMBER() OVER (ORDER BY Number)
FROM master.dbo.spt_values ORDER BY Number
),
innerCTE as
(
SELECT x.n, SUBSTRING(t.brand_code, x.n, 1) chnum,
case SUBSTRING(t.brand_code, x.n, 1)
when '0' then '0'
else char(65-1+SUBSTRING(t.brand_code, x.n, 1))
end chalfa, t.brand_code
FROM x INNER JOIN (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
ON x.n <= LEN(t.brand_code)
),
CTE as
(
select n, chnum, chalfa, brand_code, stuff(brand_code, n, 1, chalfa) as code
from innerCTE
union all
select n+1, chnum, chalfa, brand_code, STUFF(code, n+1, 1, chalfa) as code
from cte
where n+1 <= LEN(cte.brand_code)
)
--select * from innerCTE
select * from CTE;
or using CROSS APPLY like this example: Example_1
or using CROSS APPLY with PIVOT like this example: Example_2
but my experience on SQL is low and I have not been able to get the correct result.
I'd like to have this:
brand_code decoded_code
1234 ABCD
5834 EHCD
9905 II0E
0250 0BE0
thanks
If using SQL 2017+
DECLARE #integerValues TABLE ([I] INT);
INSERT INTO #integerValues ([I])
VALUES
(1234),
(6485834),
(99084705),
(1124601);
SELECT
T.[I],
STRING_AGG(
CASE SUBSTRING(T.[S], V.[number] + 1, 1)
WHEN '9' THEN 'I'
WHEN '8' THEN 'H'
WHEN '7' THEN 'G'
WHEN '6' THEN 'F'
WHEN '5' THEN 'E'
WHEN '4' THEN 'D'
WHEN '3' THEN 'C'
WHEN '2' THEN 'B'
WHEN '1' THEN 'A'
ELSE '0'
END,
'') WITHIN GROUP (ORDER BY T.[I]) [S]
FROM
(SELECT [I], CAST([I] AS VARCHAR(10)) [S] FROM #integerValues) T
JOIN
[master]..[spt_values] V ON V.[number] < LEN(T.[S])
WHERE
V.[type] = 'P';
or, if 2016 or earlier
DECLARE #integerValues TABLE ([I] INT);
INSERT INTO #integerValues ([I])
VALUES
(1234),
(6485834),
(99084705),
(1124601);
SELECT
[I],
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
REPLACE(
CAST([I] AS VARCHAR(10)),
'1',
'A'),
'2',
'B'),
'3',
'C'),
'4',
'D'),
'5',
'E'),
'6',
'F'),
'7',
'G'),
'8',
'H'),
'9',
'I') [S]
FROM
#integerValues;
The following solution uses a FOR XML subquery and some mod (%) operations against the integer value to split each digit, then a mapping table to relate each digit with a character. The FOR XML returns the digits back in order.
DECLARE #IntegerValues TABLE (Integer INT)
INSERT INTO #IntegerValues (Integer)
VALUES
(1234),
(6485834),
(99084705),
(1124601)
SELECT
T.Integer,
Conversion = (
SELECT
'' + M.Character -- Must have no declared alias (for xml)
FROM
(VALUES
(1, T.Integer % 10),
(2, T.Integer / 10 % 10),
(3, T.Integer / 100 % 10),
(4, T.Integer / 1000 % 10),
(5, T.Integer / 10000 % 10),
(6, T.Integer / 100000 % 10),
(7, T.Integer / 1000000 % 10),
(8, T.Integer / 10000000 % 10),
(9, T.Integer / 100000000 % 10),
(10, T.Integer / 1000000000 % 10),
(11, T.Integer / 10000000000 % 10)
) AS X(OrdinalPosition, SplitDigit)
INNER JOIN (VALUES
(0, '0'),
(1, 'A'),
(2, 'B'),
(3, 'C'),
(4, 'D'),
(5, 'E'),
(6, 'F'),
(7, 'G'),
(8, 'H'),
(9, 'I')
) AS M(Digit, Character) ON X.SplitDigit = M.Digit
WHERE
X.OrdinalPosition <= FLOOR(LOG10(T.Integer)) + 1 -- This expression returns the number of digits
ORDER BY
X.OrdinalPosition DESC
FOR XML
PATH('')
)
FROM
#IntegerValues AS T
Result:
Integer Conversion
1234 ABCD
6485834 FDHEHCD
99084705 II0HDG0E
1124601 AABDF0A
There is probably a cleaner way to write the mappings and the mod operations, but this should give an idea.
I've find a solution using recursive CTE.
declare #change varchar(10) = '0ABCDEFGHI'
DECLARE #II int = 1;
with BRANDS as
(
select * from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code)
),
CTE as
(
select #II as ii, BRAND_CODE,
STUFF(brand_code,#II,1,SUBSTRING(#change,CAST(SUBSTRING(brand_code,#II,1) AS INT)+1,1)) AS AA
from BRANDS
union all
select c.ii+1, b.BRAND_CODE,
STUFF(AA,c.ii+1,1,SUBSTRING(#change,CAST(SUBSTRING(AA,c.ii+1,1) AS INT)+1,1)) AS AA
from BRANDS b
inner join CTE c on b.BRAND_CODE = c.BRAND_CODE
where c.ii < LEN(b.BRAND_CODE)
)
select BRAND_CODE, AA as NewCode from CTE where ii = len(brand_code) order by BRAND_CODE, ii
This is the code for the suggestion from #Martin Smith to use 10 nested replace.
DECLARE #change char(10) = '0ABCDEFGHI';
--------------------------------------------
select brand_code,
REPLACE( REPLACE( REPLACE( REPLACE( REPLACE(
REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( brand_code
, '0', SUBSTRING( #change, 1, 1)) , '1', SUBSTRING( #change, 2, 1))
, '2', SUBSTRING( #change, 3, 1)) , '3', SUBSTRING( #change, 4, 1))
, '4', SUBSTRING( #change, 5, 1)) , '5', SUBSTRING( #change, 6, 1))
, '6', SUBSTRING( #change, 7, 1)) , '7', SUBSTRING( #change, 8, 1))
, '8', SUBSTRING( #change, 9, 1)) , '9', SUBSTRING( #change, 10, 1))
from (VALUES('1234'),('5834'),('9905'),('0250')) as t(brand_code);

I want to convert some data to pivot in SQL Server with join and dynamic

The table, at last, is my target.
This is my demo database
create database pvtestDb;
go
use pvtestDb;
go
create table custTransaction
(
id int,
custNum int,
value nvarchar(50)
)
go
create table customers
(
id int,
custName nvarchar(50)
)
insert into Customers(id, custName)
values (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd'),
(5, 'eee'), (6, 'fff'), (7, 'ggg'), (8, 'hhh'), (9, 'iii')
insert into custTransaction (id, custNum, value)
values (1, 3, 'a'), (1, 4, 'b'), (1, 5, 'c'),
(2, 3, 'd'), (2, 4, 'e'), (2, 6, 'f'),
(3, 3, 'g'), (3, 8, 'h'), (3, 9, 'i')
select * from customers
select * from custTransaction
select custName, custNum, value
from customers
join custTransaction on custTransaction.id = customers.id
I tried code like this but not worked at all
SELECT
custNum, [a], [b], [c], [d]
FROM
customers
JOIN
custTransaction ON custTransaction.id = customers.id
PIVOT
(COUNT([custName])
FOR [custName] IN ([a], [b], [c], [d])) AS p
I need to join between the two tables in first.
Any hints would be appreciated as I am stuck with this situation
Here's approach with dynamic SQL
declare #customers varchar(8000)
declare #sql varchar(8000)
select #customers = stuff((
select ',' + quotename(custName)
from customers
for xml path('')
), 1, 1, '')
set #sql = 'select
id, ' + #customers + '
from (
select
ct.id, c.custName, ct.value
from
customers c
join custTransaction ct on ct.custNum = c.id
) t
pivot (
max(value) for custName in (' + #customers + ')
) p'
exec (#sql)
Output
id aaa bbb ccc ddd eee fff ggg hhh iii
----------------------------------------------------------------
1 NULL NULL a b c NULL NULL NULL NULL
2 NULL NULL d e NULL f NULL NULL NULL
3 NULL NULL g NULL NULL NULL NULL h i

Find duplicates with different values in T-sql?

I got situation to find duplicates from different sourceport system.
for Ex: I got table like below:
declare #table table (id int,portnumber int, [sourceport] varchar(50), sourcereportedDate datetime )
insert into #table values (1, 1111, 'north' , '2016-08-20 09:44:30.847')
insert into #table values (2, 1111, 'north' , '2016-08-21 09:44:30.847')
insert into #table values (3, 1111, 'north' , '2016-08-22 09:44:30.847')
insert into #table values (4, 2222, 'north' , '2016-08-20 09:44:30.847')
insert into #table values (5, 2222, 'north' , '2016-08-26 09:44:30.847')
insert into #table values (6, 2222, 'south' , '2016-08-22 09:44:30.847')
insert into #table values (7, 3333, 'south' , '2016-08-10 09:44:30.847')
insert into #table values (8, 3333, 'north' , '2016-08-12 09:44:30.847')
insert into #table values (9, 4444, 'north' , '2016-08-20 09:44:30.847')
insert into #table values (10, 5555, 'south' , '2016-08-21 09:44:30.847')
insert into #table values (11, 5555, 'south' , '2016-08-27 09:44:30.847')
insert into #table values (12, 6666, 'south' , '2016-08-10 09:44:30.847')
insert into #table values (13, 6666, 'north' , '2016-08-21 09:44:30.847')
insert into #table values (14, 6666, 'south' , '2016-08-09 09:44:30.847')
Now I want to find duplicates with 'portnumber' should be same and 'sourceport' should be different. if 'portnumber' same and 'sourceport' same it should not be duplicate.
and also I need additional column which holds the Id of greatest 'sourcereportedDate' date
I want get output like below:
(4, 2222, 'north' , '2016-08-20 09:44:30.847',5)
(5, 2222, 'north' , '2016-08-26 09:44:30.847','latest')
(6, 2222, 'south' , '2016-08-22 09:44:30.847',5)
(7, 3333, 'south' , '2016-08-10 09:44:30.847',8)
(8, 3333, 'north' , '2016-08-12 09:44:30.847','latest')
(12, 6666, 'south' , '2016-08-10 09:44:30.847',13)
(13, 6666, 'north' , '2016-08-21 09:44:30.847','latest')
(14, 6666, 'south' , '2016-08-09 09:44:30.847',13)
Thanks in advance.
Please try this (i still think it can be further optimized)-
;with DupWithMaxDate as (
select
a.portnumber,
sourcereportedDate = max(a.sourcereportedDate)
from #table a
left join #table b on b.portnumber = a.portnumber and b.sourceport <> a.sourceport
where b.portnumber is not null
group by a.portnumber
),
DupWithMaxID as (
select
a.portnumber,
max_id = a.id
from DupWithMaxDate x
inner join #table a on a.portnumber = x.portnumber and a.sourcereportedDate = x.sourcereportedDate
)
select
a.id,
a.portnumber,
a.sourceport,
a.sourcereportedDate,
new_column = case when a.id = x.max_id then 'Latest' else convert(varchar(10), x.max_id) end
from DupWithMaxID x
inner join #table a on a.portnumber = x.portnumber
Updated above query -
;with DuplicateWithMaxID as (
select
portnumber = a.portnumber,
max_id = a.id,
rank_id = row_number() over (partition by a.portnumber order by a.sourcereportedDate desc)
from #table a
inner join #table b on b.portnumber = a.portnumber and b.sourceport <> a.sourceport
)
select
a.id,
a.portnumber,
a.sourceport,
a.sourcereportedDate,
new_column = case when a.id = x.max_id then 'Latest' else convert(varchar(10), x.max_id) end
from
DuplicateWithMaxID x
inner join #table a on a.portnumber = x.portnumber
where
x.rank_id = 1
Try this:
;
with
dis as
(
select distinct portnumber, sourceport
from #table
),
dup as
(
select portnumber
from dis
group by portnumber
having count(1) > 1
),
mx as
(
select
dup.portnumber,
max(t.sourcereportedDate) as sourcereportedDate
from
dup
join
#table as t
on
t.portnumber = dup.portnumber
group by
dup.portnumber
),
mxi as
(
select
mx.portnumber,
t.id
from
mx
left join
#table as t
on
t.portnumber = mx.portnumber
and t.sourcereportedDate = mx.sourcereportedDate
)
select
t.id,
t.portnumber,
t.sourceport,
t.sourcereportedDate,
case when t.id = mxi.id
then 'latest'
else cast(mxi.id as varchar(10))
end as latest
from
dup
join
#table as t
on
t.portnumber = dup.portnumber
join
mxi
on
mxi.portnumber = t.portnumber
left join
mx
on
mx.portnumber = t.portnumber
and mx.sourcereportedDate = t.sourcereportedDate

SQL Server: String insertion by position

I want to insert a set of values into a given string at specified positions. I couldn't find an existing solution for this so I created the following query which does so. I am wondering if there is a way to simplify it, especially the portion which queries the cte and creates the NewString. Also, is there a way to construct the cte recursion without involving the ID column? The ID (and the rest) seems to work fine, just wondering if it could be tweaked to make it more organic/elegant or if there is a better solution overall. The different groups are really only for testing purposes; each group corresponds to a possible insertion position scenario. Thus, there will not be groups involved when I use it.
declare
#String varchar(200),
#StringLen int,
#GroupID int,
#PositionMax int
declare #Chars table (
ID int identity,
GroupID int,
Position int,
Value varchar(20)
)
select
#String = 'abcde',
#StringLen = len(#String),
#GroupID = 1
--Affix
--[P]refix
--[I]nfix
--[S]uffix
insert #Chars
select
GroupID,
Position,
Value
from (
values
(1, 0, 'X'), --P
(2, 2, 'Y'), --I
(3, 5, 'Z'), --S
(4, 0, 'X'), --P
(4, 2, 'Y'), --I
(5, 2, 'Y'), --I
(5, 5, 'Z'), --S
(6, 0, 'X'), --P
(6, 5, 'Z'), --S
(7, 0, 'X'), --P
(7, 2, 'Y'), --I
(7, 5, 'Z'), --S
(8, 2, 'Y1'), --I
(8, 4, 'Y2'), --I
(9, 0, 'X'), --P
(9, 2, 'Y1'), --I
(9, 4, 'Y2'), --I
(10, 2, 'Y1'), --I
(10, 4, 'Y2'), --I
(10, 5, 'Z'), --S
(11, 0, 'X'), --P
(11, 2, 'Y1'), --I
(11, 4, 'Y2'), --I
(11, 5, 'Z') --S
) as T(GroupID, Position, Value)
order by GroupID, Position
;with cte (
ID,
GroupID,
LeftString,
Value,
RightString,
Position
) as (
select
ID,
GroupID,
LeftString,
Value,
RightString,
Position
from (
select
row_number() over (partition by GroupID order by ID) as RowNumber,
ID,
GroupID,
cast(left(#String, Position) as varchar(200)) as LeftString,
Value,
cast(right(#String, #StringLen - Position) as varchar(200)) as RightString,
Position
from #Chars
) as C
where RowNumber = 1
union all
select
vc.ID,
vc.GroupID,
cast(left(RightString, vc.Position - cte.Position) as varchar(200)) as LeftString,
vc.Value,
cast(right(RightString, #StringLen - vc.Position) as varchar(200)) as RightString,
vc.Position
from #Chars vc
join cte cte
on cte.GroupID = vc.GroupID
and cte.ID + 1 = vc.ID
)
select
GroupID,
case
when LSLenSumMax < #StringLen
then NewString + RightString
else NewString
end as NewString
from (
select
GroupID,
max(LSLenSum) as LSLenSumMax,
RightString,
stuff((
select
LeftString + Value
from cte cte
where cte.GroupID = cteLR.GroupID
for xml path(''), type
).value('(./text())[1]', 'varchar(max)'), 1, 0, '') as NewString
from (
select
GroupID,
(select sum(len(LeftString)) from cte cteL where cteL.groupID = cte.groupID) as LSLenSum,
(select top 1 RightString from cte cteR where cteR.groupID = cte.groupID order by cteR.ID desc) as RightString
from cte cte
) as cteLR
group by
GroupID,
RightString
) as C
order by GroupID
You can implement a custom
aggregate function... Or try this: A recursive scalar function:
CREATE FUNCTION dbo.GetPrefixTo (#GroupID INT, #Position INT, #String CHAR(200))
RETURNS Char(200)
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #str CHAR(200) = NULL
DECLARE #beforePosition INT = 0
IF (SELECT COUNT(*) FROM Chars WHERE GroupID = #GroupID AND Position < #Position) > 0
BEGIN
SELECT #beforePosition = MAX(Position) FROM chars WHERE GroupID = #GroupID AND Position < #Position
select #str = RTRIM(dbo.GetPrefixTo(#GroupID, #beforePosition, substring(#String, 1, #Position))) +
+ RTrim(Value) + substring(#String, #Position + 1, len(#string) + 1)
FROM Chars WHERE GroupID = #GroupID AND Position = #Position
END
ELSE
SELECT #str = substring(#String, 1, #Position) + RTrim(Value) + substring(#String, #Position + 1, len(#string) + 1) FROM Chars
WHERE GroupID = #GroupID AND Position = #Position
RETURN #str
END
And group by GroupID and aggregate max(position):
SELECT groupID, max(Position)
, dbo.GetPrefixTo(groupID, max(Position), 'abcde')
FROM Chars
GROUP BY GroupID
This is the result:
1 0 Xabcde
2 2 abYcde
3 5 abcdeZ
4 2 XabYcde
5 5 abYcdeZ
6 5 XabcdeZ
7 5 XabYcdeZ
8 4 abY1cdY2e
9 4 XabY1cdY2e
10 5 abY1cdY2eZ
11 5 XabY1cdY2eZ
========================================================================

Deleting records from SQL Server table without cursor

I am trying to selectively delete records from a SQL Server 2005 table without looping through a cursor. The table can contain many records (sometimes > 500,000) so looping is too slow.
Data:
ID, UnitID, Day, Interval, Amount
1 100 10 21 9.345
2 100 10 22 9.367
3 200 11 21 4.150
4 300 11 21 4.350
5 300 11 22 4.734
6 300 11 23 5.106
7 400 13 21 10.257
8 400 13 22 10.428
Key is: ID, UnitID, Day, Interval.
In this example I wish to delete Records 2, 5 and 8 - they are adjacent to an existing record (based on the key).
Note: record 6 would not be deleted because once 5 is gone it is not adjacent any longer.
Am I asking too much?
See these articles in my blog for performance detail:
SQL Server: deleting adjacent values
SQL Server: deleting adjacent values (improved)
The main idea for the query below is that we should delete all even rows from continuous ranges of intervals.
That is, if for given (unitId, Day) we have the following intervals:
1
2
3
4
6
7
8
9
, we have two continuous ranges:
1
2
3
4
and
6
7
8
9
, and we should delete every even row:
1
2 -- delete
3
4 -- delete
and
6
7 -- delete
8
9 -- delete
, so that we get:
1
3
6
8
Note that "even rows" means "even per-range ROW_NUMBER()s" here, not "even values of interval".
Here's the query:
DECLARE #Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT)
INSERT INTO #Table VALUES (1, 100, 10, 21, 9.345)
INSERT INTO #Table VALUES (2, 100, 10, 22, 9.345)
INSERT INTO #Table VALUES (3, 200, 11, 21, 9.345)
INSERT INTO #Table VALUES (4, 300, 11, 21, 9.345)
INSERT INTO #Table VALUES (5, 300, 11, 22, 9.345)
INSERT INTO #Table VALUES (6, 300, 11, 23, 9.345)
INSERT INTO #Table VALUES (7, 400, 13, 21, 9.345)
INSERT INTO #Table VALUES (8, 400, 13, 22, 9.345)
INSERT INTO #Table VALUES (9, 400, 13, 23, 9.345)
INSERT INTO #Table VALUES (10, 400, 13, 24, 9.345)
INSERT INTO #Table VALUES (11, 400, 13, 26, 9.345)
INSERT INTO #Table VALUES (12, 400, 13, 27, 9.345)
INSERT INTO #Table VALUES (13, 400, 13, 28, 9.345)
INSERT INTO #Table VALUES (14, 400, 13, 29, 9.345)
;WITH rows AS
(
SELECT *,
ROW_NUMBER() OVER
(
PARTITION BY
(
SELECT TOP 1 qi.id AS mint
FROM #Table qi
WHERE qi.unitid = qo.unitid
AND qi.[day] = qo.[day]
AND qi.interval <= qo.interval
AND NOT EXISTS
(
SELECT NULL
FROM #Table t
WHERE t.unitid = qi.unitid
AND t.[day] = qi.day
AND t.interval = qi.interval - 1
)
ORDER BY
qi.interval DESC
)
ORDER BY interval
) AS rnm
FROM #Table qo
)
DELETE
FROM rows
WHERE rnm % 2 = 0
SELECT *
FROM #table
Update:
Here's a more efficient query:
DECLARE #Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT)
INSERT INTO #Table VALUES (1, 100, 10, 21, 9.345)
INSERT INTO #Table VALUES (2, 100, 10, 22, 9.345)
INSERT INTO #Table VALUES (3, 200, 11, 21, 9.345)
INSERT INTO #Table VALUES (4, 300, 11, 21, 9.345)
INSERT INTO #Table VALUES (5, 300, 11, 22, 9.345)
INSERT INTO #Table VALUES (6, 300, 11, 23, 9.345)
INSERT INTO #Table VALUES (7, 400, 13, 21, 9.345)
INSERT INTO #Table VALUES (8, 400, 13, 22, 9.345)
INSERT INTO #Table VALUES (9, 400, 13, 23, 9.345)
INSERT INTO #Table VALUES (10, 400, 13, 24, 9.345)
INSERT INTO #Table VALUES (11, 400, 13, 26, 9.345)
INSERT INTO #Table VALUES (12, 400, 13, 27, 9.345)
INSERT INTO #Table VALUES (13, 400, 13, 28, 9.345)
INSERT INTO #Table VALUES (14, 400, 13, 29, 9.345)
;WITH source AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY unitid, day ORDER BY interval) rn
FROM #Table
),
rows AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY unitid, day, interval - rn ORDER BY interval) AS rnm
FROM source
)
DELETE
FROM rows
WHERE rnm % 2 = 0
SELECT *
FROM #table
I don't think what you're asking for is possible — but you may be able to get close. It appears you can almost do it by finding records with a self-join like this:
SELECT t1.id
FROM
table t1 JOIN table t2 ON (
t1.unitid = t2.unitid AND
t1.day = t2.day AND
t1.interval = t2.interval - 1
)
but the problem is, that'll find id=6 as well. However, if you create a temporary table from this data, it may be much smaller than your original data, and thus far faster to scan with a cursor (to fix the id=6 problem). You can then do a DELETE FROM table WHERE id IN (SELECT id FROM tmp_table) to kill the rows.
There may be a way to fix the ID=6 problem w/o a cursor, but if so, I don't see it.
There is the WHILE statement, which is an alternative to the cursor. That combined with table variables might let you do the same thing within a performance bound you're OK with.
DECLARE #Table TABLE (ID INT, UnitID INT, [Day] INT, Interval INT, Amount FLOAT)
INSERT INTO #Table VALUES (1, 100, 10, 21, 9.345)
INSERT INTO #Table VALUES (2, 100, 10, 22, 9.367)
INSERT INTO #Table VALUES (3, 200, 11, 21, 4.150)
INSERT INTO #Table VALUES (4, 300, 11, 21, 4.350)
INSERT INTO #Table VALUES (5, 300, 11, 22, 4.734)
INSERT INTO #Table VALUES (6, 300, 11, 23, 5.106)
INSERT INTO #Table VALUES (7, 400, 13, 21, 10.257)
INSERT INTO #Table VALUES (8, 400, 13, 22, 10.428)
DELETE FROM #Table
WHERE ID IN (
SELECT t1.ID
FROM #Table t1
INNER JOIN #Table t2
ON t2.UnitID = t1.UnitID
AND t2.Day = t1.Day
AND t2.Interval = t1.Interval - 1
LEFT OUTER JOIN #Table t3
ON t3.UnitID = t2.UnitID
AND t3.Day = t2.Day
AND t3.Interval = t2.Interval - 1
WHERE t3.ID IS NULL)
SELECT * FROM #Table
Lieven is so close - it worked for the test set, but if I add a few more records it starts to miss some.
We cannot use any odd/even criteria - we have no idea how the data falls.
Add this data and retry:
INSERT #Table VALUES (9, 100, 10, 23, 9.345)
INSERT #Table VALUES (10, 100, 10, 24, 9.367)
INSERT #Table VALUES (11, 100, 10, 25, 4.150)
INSERT #Table VALUES (12, 100, 10, 26, 4.350)
INSERT #Table VALUES (13, 300, 11, 25, 4.734)
INSERT #Table VALUES (14, 300, 11, 26, 5.106)
INSERT #Table VALUES (15, 300, 11, 27, 10.257)
INSERT #Table VALUES (16, 300, 11, 29, 10.428)

Resources