Related
I have a situation where I have to update a incremental value to a column based on the value on the same column from the previous row for the same group of records.
Rule for updating 'COUNT' column is:
For the very 1st row of a particular REFNO,
If Amount 1 = Amount 2 then
COUNT = 1
Else
COUNT = 0
For all other rows (excluding the 1st row) of a particular REFNO:
If Amount 1 = Amount 2 then
COUNT = COUNT from previous row for the same REFNO + 1
Else
COUNT = COUNT from previous row for the same REFNO
So the result should look like below:
Though the sample data which I have shown has only 14 records the actual table I am updating is going to have few million rows in them. So I am searching for a solution which will do a set based update rather than row by row processing !!
You can update from a CTE that uses window functions to calculate the number.
The SQL below first calculates a row_number for the equal amounts.
Then for the others that don't have an equal amount, the maximum of the previous row_number is taken.
WITH CTE AS
(
SELECT *,
(CASE
WHEN [Amount 1] = [Amount 2]
THEN rn
ELSE MAX(rn) OVER (PARTITION BY [REFNO] ORDER BY [ROW ID] ASC ROWS UNBOUNDED PRECEDING)
END) as rnk
FROM (
SELECT
[ROW ID], [REFNO], [Amount 1], [Amount 2], [COUNT],
(CASE
WHEN [Amount 1] = [Amount 2]
THEN ROW_NUMBER() OVER (PARTITION BY [REFNO], IIF([Amount 1] = [Amount 2],0,1) ORDER BY [ROW ID] ASC)
ELSE 0
END) AS rn
FROM PAYMENT
) q
)
UPDATE CTE
SET [COUNT] = rnk;
A test on db<>fiddle here
Try it's
declare #t table (
rowid int identity,
refno int,
amount1 int,
amount2 int
)
insert into #t(refno,amount1,amount2) values (1000000,100,200)
insert into #t(refno,amount1,amount2) values (1000000,250,250)
insert into #t(refno,amount1,amount2) values (1000000,300,300)
insert into #t(refno,amount1,amount2) values (1000000,400,400)
insert into #t(refno,amount1,amount2) values (1000010,400,100)
insert into #t(refno,amount1,amount2) values (1000010,200,100)
insert into #t(refno,amount1,amount2) values (1000010,100,300)
insert into #t(refno,amount1,amount2) values (1000021,400,400)
insert into #t(refno,amount1,amount2) values (1000021,200,100)
insert into #t(refno,amount1,amount2) values (1000032,200,200)
insert into #t(refno,amount1,amount2) values (1000032,300,300)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
insert into #t(refno,amount1,amount2) values (1000033,200,100)
select rowid,refno,amount1,amount2,rw-1 as count
from (
select
row_number() over(partition by amount1,amount2 order by rowid) rw,*
from #t) as src
This code works for that particular set, but no guarantee that there will be a situation when it won't work:
CREATE TABLE #tmp(
RowID INT IDENTITY(1,1),
RefNo INT,
Amount1 INT,
Amount2 INT
)
INSERT INTO #tmp(RefNo,Amount1,Amount2)
SELECT * FROM (VALUES
(100000,100,200),
(100000,250,250),
(100000,300,300),
(100000,400,400),
(100000,400,100),
(100010,200,100),
(100010,100,300),
(100010,400,400),
(100021,200,100),
(100021,200,200),
(100032,300,300),
(100032,200,100),
(100033,200,100),
(100033,200,100)) AS x(a,b,c)
;WITH Try1 AS (SELECT t1.*, [Count] =
CASE WHEN t1.Amount1 != t1.Amount2 AND
(t2.RowId IS NULL OR t2.Amount1 != t2.Amount2) THEN 0
WHEN t1.Amount1 != t1.Amount2 AND t2.Amount1 = t2.Amount2 THEN t2.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NULL THEN t1.RowId
WHEN t1.Amount1 = t1.Amount2 AND t2.RowId IS NOT NULL THEN t1.RowId
END
, NextRefNo = CASE WHEN t2.RowId IS NULL THEN 1 ELSE 0 END
FROM #tmp AS t1
OUTER APPLY ( SELECT * FROM #tmp AS t2
WHERE t2.RowId = t1.RowID - 1 AND t2.RefNo = t1.RefNo) AS t2)
, Try2 AS (SELECT RowID, RefNo, Amount1, Amount2, [Count]
, NextRefNo = ISNULL(t2.NextRefNo,0)
FROM Try1 AS t1
OUTER APPLY ( SELECT NextRefNo FROM Try1 AS t2
WHERE t2.[Count] > 0 AND t2.NextRefNo = 1
AND t2.RefNo = t1.RefNo ) AS t2)
SELECT RowID, RefNo, Amount1, Amount2
, [Count] = DENSE_RANK() OVER(PARTITION BY RefNo ORDER BY [Count]) - 1 + NextRefNo
FROM Try2
ORDER BY RowID;
I need a function to return the longest sequence of digits in a string, for example:
P0123/99282 returns 99282,
P9-123BB-12339 returns 12339,
12345/54321 returns 12345 (should return first instance when length is the same).
I have developed this but this is very slow, I wonder if there is something faster than this:
DECLARE #str NVARCHAR(40) = N'P0120993/123-AB1239'
DECLARE #x XML
;WITH e1(n) AS(SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN (SELECT 1 as t UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1) AS b),
n(Number) AS(SELECT n = ROW_NUMBER() OVER (ORDER BY n) FROM e2)
SELECT #x = CAST(N'<A>'+ REPLACE((SELECT CAST(CAST((
SELECT
CASE WHEN SUBSTRING(#str, Number, 1) like N'[^0-9]' THEN N' ' ELSE SUBSTRING(#str, Number, 1) end
FROM n
WHERE Number <= LEN(#str) FOR XML Path(''))
AS xml) AS nvarchar(max))),N' ',N'</A><A>')+ N'</A>' AS XML)
SELECT TOP 1
case when t.value('.', 'nvarchar(max)') = N'' then null else t.value('.', 'nvarchar(max)') end AS inVal
FROM
#x.nodes('/A') AS x(t)
ORDER BY
LEN(t.value('.', 'nvarchar(max)')) DESC;
EXPLANATION:
The max length of the string I will pass is 40, and what I do is to generate a sequence of numbers from one to forty, extract the Nth character from the string where N is the sequence value but if the character is not a digit then I replace with a white space, then I return the XML as string enlcosing with <A>XXX</A>
to then convert to xml and then query that and return the first item order by it's length desc.
thanks,
While I'm not 100% sure how much better this would be with performance, here is an approach that breaks down the strings into any potential numeric combination and returns the first with the longest length:
DECLARE #foo TABLE(ID varchar(40));
INSERT #foo VALUES('P0123/99282'),('P9-123BB-12339'),('12345/54321');
;WITH NumbersTable AS
(
SELECT TOP (40) n = ROW_NUMBER() OVER (ORDER BY Number)
FROM master.dbo.spt_values
ORDER BY Number
), Results AS
(
SELECT f.Id, SUBSTRING(f.ID, t1.n, t2.n) numericvalues,
row_number() over (partition by f.Id
order by LEN(SUBSTRING(f.ID, t1.n, t2.n)) desc) rn
FROM NumbersTable t1
INNER JOIN #foo AS f
ON t1.n <= LEN(f.ID)
INNER JOIN NumbersTable t2
ON t2.n <= LEN(f.ID)
WHERE SUBSTRING(f.ID, t1.n, t2.n) NOT LIKE '%[^0-9]%'
)
SELECT *
FROM Results
WHERE rn = 1
This creates a numbers table from 1 to 40 (since that was your max length), and then using joins creates every substring variation of the data that have a numeric value using NOT LIKE '%[^0-9]%', and then establishes a row_number based on the len of that substring.
Fiddle Demo
I have a table as
CREATE TABLE #FinalRates
(
id int primary key identity(1,1),
RateDesc nvarchar(50),
Amt decimal(18,2)
)
insert into #FinalRates values('100',200)
insert into #FinalRates values('100',300)
insert into #FinalRates values('50-80',100)
insert into #FinalRates values('50-80',300)
insert into #FinalRates values('30-50',500)
insert into #FinalRates values('30-50',250)
Looking for an output as
RateDesc Amount1 Amount2
100 200 300
50-80 100 300
30-50 500 250
I have done this as
;with cte as(
select
RateDesc
,Amounts=
STUFF((Select ','+ cast(cast(Amt as int) as varchar(10))
from #FinalRates T1
where T1.RateDesc=T2.RateDesc
FOR XML PATH('')),1,1,'')
from #FinalRates T2
group by T2.RateDesc
)
select
RateDesc,
Amount1 = PARSENAME(REPLACE(Amounts,',','.'),2),
Amount2 = PARSENAME(REPLACE(Amounts,',','.'),1)
From Cte
Drop table #FinalRates
Can the same be done using PIVOT?
That's so complicated. How about this?
select ratedesc,
max(case when seqnum = 1 then amt end) as Amount1,
max(case when seqnum = 2 then amt end) as Amount2
from (select ft.*,
row_number() over (partition by ratedesc order by id) as seqnum
from #finalrates fr
) fr
group by ratedesc;
You could use a similar approach using pivot but conditional aggregation often performs better.
Plus, if you know you have no holes in id, you can do:
select ratedesc,
max(case when id % 2 = 1 then amt end) as Amount1,
max(case when id % 2 = 0 then amt end) as Amount2
from #finalrates fr
group by ratedesc;
Using PIVOT,
Assuming you have 2 Amt for each RateDesc.
Select RateDesc, [Amount1], [Amount2] From
(
Select RateDesc, Amt
, 'Amount' + cast(row_number() over (partition by RateDesc order by Amt) as varchar(5)) RowVal
from #FinalRates
) x
PIVOT
(
MAX(Amt) For RowVal in ([Amount1], [Amount2])
) p
I have a column in a table which has incremented values like:
AAA0000001
AAA0000002
... and so on
I want to find if the values stored in this column are in proper sequential order or if any value is missing in between or is deleted.
How can i achieve this?
Assuming the pattern is always: AAA[0-9][0-9][0-9][0-9][0-9][0-9][0-9], you can do this with a Tally Table.
Sample Data:
CREATE TABLE Tbl(val VARCHAR(10))
INSERT INTO Tbl VALUES
('AAA0000001'), ('AAA0000002'), ('AAA0000004'), ('AAA0000011');
val
----------
AAA0000001
AAA0000002
AAA0000004
AAA0000011
SQL Fiddle
;WITH Cte AS(
SELECT *,
num = CAST(SUBSTRING(val, 4, LEN(val) - 3) AS INT)
FROM Tbl
),
E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(num) FROM Cte)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
N,
val = 'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7)
FROM Tally
WHERE NOT EXISTS(
SELECT 1 FROM Cte WHERE num = N
)
RESULT
N val
-------------------- ----------
3 AAA0000003
5 AAA0000005
6 AAA0000006
7 AAA0000007
8 AAA0000008
9 AAA0000009
10 AAA0000010
Explanation:
The first CTE, named as Cte, extracts the numeric part of the strings and CASTs them to INT.
The succeeding CTEs, from E1 to Tally(N) generates a table with sequential values from 1 up to the MAX(num) - the INT return from the first CTE.
The final SELECT just checks for the non-existing num from the first CTE.
'AAA' + RIGHT('0000000' + CAST(N AS VARCHAR(7)), 7) transforms N so that it follows the pattern.
This is a Gaps problem. You can look into this article by Dwain Camps for more solutions on Gaps and Islands.
You can use ROW_NUMBER like this.
Sample Data
DECLARE #tab1 TABLE(id VARCHAR(20));
insert into #tab1 VALUES('AAA0000001'),('AAA0000002'),('AAA0000003'),('AAA0000004'),('AAA0000006'),('AAA0000007'),('AAA0000010');
Query
;WITH CTE as
(
SELECT convert(int,STUFF(id,1,3,'')) id,convert(int,STUFF(id,1,3,'')) - ROW_NUMBER()OVER(ORDER BY convert(int,STUFF(id,1,3,''))) rn
FROM #tab1
),CTE2 as
(
SELECT ROW_NUMBER()OVER(ORDER BY rn) as rn, MIN(id) series_start,MAX(id) series_end
FROM CTE
GROUP BY rn
)
SELECT C2.series_end,C1.series_start
FROM CTE2 C1
INNER JOIN CTE2 C2 ON C1.rn = C2.rn + 1;
SQL Fiddle
Explanation
Output of CTE is the difference of gaps between id values.
Output of CTE2 is the start and end of continuous series of numbers
Final Output gives the start and end of gaps within the series
Output
series_end series_start
4 6
7 10
If the schema is fixed then no need for complex queries. This works:
DECLARE #t TABLE ( v VARCHAR(100) );
INSERT INTO #t
VALUES ( 'AAA0000001' ),
( 'AAA0000002' ),
( 'AAA0000007' ),
( 'AAA0000008' ),
( 'AAA0000010' ),
( 'AAA0000011' ),
( 'AAA0000012' );
SELECT * FROM #t t1
CROSS APPLY(SELECT TOP 1 v FROM #t t2 WHERE t2.v > t1.v ORDER BY v) ca
WHERE RIGHT(t1.v, 7) <> RIGHT(ca.v, 7) - 1
Output:
v v
AAA0000002 AAA0000007
AAA0000008 AAA0000010
In sqlserver 2012, you can use LAG and LEAD
DECLARE #t table(col1 varchar(15))
INSERT #t values('AAA0000001'),('AAA0000002'),('AAA0000004')
SELECT
case when
stuff(lag(col1) over (order by col1), 1,3,'') + 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end previous_exists,
case when
stuff(lead(col1) over (order by col1), 1,3,'') - 1
= stuff(col1, 1,3,'') then 'Yes' else 'No' end next_exists,
col1
FROM #t
Result:
previous_exists next_exists col1
No Yes AAA0000001
Yes No AAA0000002
No No AAA0000004
I have a following table:
State LAB GROUP DATE CODE ID
UP A I 1-Jan 1 345
UP R S 1-Feb 1 456
UP A S 1-Jan 2 567
DL R S 1-Feb 3 678
DL T S 1-Jan 1 789
DL A S 1-Jan 2 900
MN T S 1-Jan 3 1011
MN R I 1-Feb 1 1122
MN S I 1-Feb 2 1233
I need a pivot table of following type:
STATE A R T TOTAL
UP 2 1 0 3
DL 1 1 1 3
MN 0 1 1 2
DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE.
I then need the pivot tables filtered for following columns:
GROUP
DATE
CODE
So 1st table will have the pivot table above counting only those records which have GROUP=S
2nd table will have the pivot table above counting only those records which have CODE=1
and so on, I wish to put multiple conditions. and generate several tables one by one and export them.
If this is possible in SQL please let me know! I ruled out excel vba due to the size of table (source table will have 800,000 records approx).
Try this :-
Select [State],[A],[R],[T],Total = [A] + [R]+ [T]
from
(
Select [State],
[A] = Sum(Case when LAB='A' then 1 else 0 END) ,
[R] = Sum(Case when LAB='R' then 1 else 0 END) ,
[T] = Sum(Case when LAB='T' then 1 else 0 END)
from YourTable
group by [State]
)a
SQL FIDDLE
CREATE TABLE #t(States VARCHAR(10),LAB VARCHAR(5),GROUPs VARCHAR(5),DATEs VARCHAR(10),CODE INT,ID INT)
INSERT INTO #t values('UP','A','I','1-Jan',1,345)
INSERT INTO #t values('UP','R','S','1-Feb',1,456)
INSERT INTO #t values('UP','A','S','1-Jan',2,567)
INSERT INTO #t values('DL','R','S','1-Feb',3,678)
INSERT INTO #t values('DL','T','S','1-Jan',1,789)
INSERT INTO #t values('DL','A','S','1-Jan',2,900)
INSERT INTO #t values('MN','T','S','1-Jan',3,1011)
INSERT INTO #t values('MN','R','I','1-Feb',1,1122)
INSERT INTO #t values('MN','S','I','1-Feb',2,1233)
SELECT States,ISNULL(A,0) A,ISNULL(R,0) R,ISNULL(T,0) T,ISNULL(A,0)+ISNULL(R,0)+ISNULL(T,0) total
FROM
(
SELECT States,LAB,Count(ID) AS cnt FROM #t GROUP BY States,LAB /*apply GROUP DATE CODE condition here*/
) AS PVT
PIVOT(MAX(cnt) FOR LAB IN (A,R,T)) pvt
Another solution using PIVOT :
WITH PivotInUse AS (
SELECT state,lab,COUNT(*) AS cnt
FROM YourTable
GROUP BY state,lab
)
SELECT STATE
,COALESCE([A], 0) AS A
,COALESCE([R], 0) AS R
,COALESCE([T], 0) AS T
,COALESCE([A], 0) + COALESCE([R], 0) + COALESCE([T], 0) AS TOTAL
FROM PivotInUse
PIVOT(SUM(cnt) FOR lab IN ([A],[R],[T])) AS p;
Your sample table
SELECT * INTO #TEMP FROM
(
SELECT 'UP' [State],'A' LAB,'I' [GROUP],'1-Jan' [DATE],1 CODE,345 ID
UNION ALL
SELECT 'UP','R','S','1-Feb',1,456
UNION ALL
SELECT 'UP','A','S','1-Jan',2,567
UNION ALL
SELECT 'DL','R','S','1-Feb',3,678
UNION ALL
SELECT 'DL','T','S','1-Jan',1,789
UNION ALL
SELECT 'DL','A','S','1-Jan',2,900
UNION ALL
SELECT 'MN','T','S','1-Jan',3,1011
UNION ALL
SELECT 'MN','R','I','1-Feb',1,1122
UNION ALL
SELECT 'MN','S','I','1-Feb',2,1233
)TAB
Now you need to get the distinct count of each state and get the sum as the result to show Total
in pivoted result.
SELECT DISTINCT [State],LAB,SUM(CNT) CNT
INTO #NEWTABLE
FROM
(
SELECT DISTINCT
[State],LAB,
CASE WHEN [State] IS NULL THEN NULL ELSE COUNT([State]) OVER(PARTITION BY [State],LAB) END CNT
FROM #TEMP
)TAB
GROUP BY [State],LAB
WITH ROLLUP
Now we need to get the distinct columns for pivot(#cols) and columns to identify and replace null with zero in pivot(#NullToZeroCols)
DECLARE #cols NVARCHAR (MAX)
DECLARE #NullToZeroCols NVARCHAR (MAX)
SET #cols = SUBSTRING((SELECT DISTINCT ',['+LAB+']' FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
SET #NullToZeroCols = SUBSTRING((SELECT DISTINCT ',ISNULL(['+LAB+'],0) AS ['+LAB+']'
FROM #NEWTABLE GROUP BY LAB FOR XML PATH('')),2,8000)
Join the pivotted query with the #NEWTABLE to get the Total for each State
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT P.State,' + #NullToZeroCols + ',T2.CNT TOTAL FROM
(
SELECT DISTINCT [State],LAB,CNT FROM #NEWTABLE
) x
PIVOT
(
SUM(CNT)
FOR [LAB] IN (' + #cols + ')
) p
JOIN #NEWTABLE T2 ON P.[STATE]=T2.[STATE]
WHERE P.State IS NOT NULL AND T2.LAB IS NULL AND T2.[STATE] IS NOT NULL;'
EXEC SP_EXECUTESQL #query
Here is your result
Here is the SQLFiddle http://sqlfiddle.com/#!3/c2588/1 (If it shows any error while loading the page, just click RUNSQL, it will work)
Now if you want to get the result as you said DISTINCT COUNT OF ID FOR EACH LAB FOR EACH STATE, just change
OVER(PARTITION BY [State],LAB)
to
OVER(PARTITION BY [State],LAB,Id)
which will show the following result after executing the pivot query