I have data similar to
ID Position
1 1
2 2
3 3
4 1
5 2
6 3
7 1
8 2
and I want to select the last set of rows that contain all positions, 1 through 3. By last I mean the set of rows with the highest value in the ID column. The desired result is
ID Position
4 1
5 2
6 3
How can i achieve this?
You can use this method, which find the last ID where the Position is 3, and gets the two rows preceding that. This assumes Position is sequential as it is in the sample data.
declare #table table (ID int identity(1,1), Position int)
insert into #table
values
(1),(2),(3),(1),(2),(3),(1),(2)
select top 3
*
from #table
where ID <=(
select max(ID)
from #table
where Position = 3)
order by ID desc
Or you could do this with an AND in your WHERE clause. I would store the ID as a variable so you only have to do the aggregation once though.
declare #id int = ( select max(ID)
from #table
where Position = 3)
select *
from #table
where ID <= #id
and ID >= #id - 2
order by ID
Related
I have a table like below.
DECLARE #Table TABLE (
[Text] varchar(100),
[Order] int,
[RequiredResult] int
);
INSERT INTO #Table
VALUES
('A',1,1),
('B',2,1),
('C',3,1),
('D',1,2),
('A',2,2),
('B',3,2),
('G',4,2),
('H',1,3),
('B',2,3);
I have used dense_rank, but the results are not correct.
select [Text], [Order], RequiredResult
, DENSE_RANK() OVER (ORDER BY [text],[Order]) AS ComputedResult
from #Table;
Results:
Text
Order
RequiredResult
ComputedResult
A
1
1
1
A
2
2
2
B
2
1
3
B
2
3
3
B
3
2
4
C
3
1
5
D
1
2
6
G
4
2
7
H
1
3
8
Please help me to calculate the RequiredResult column.
It looks like the RequiredResult column is simple a running sequence that resets after each broken sequence in the Order column when you process the records in the order they were inserted.
This is a typical Data Island analysis task, except in this case the islands are the rows that are sequential sets, the boundary is when the numbering resets back to 1.
Record the input sequence by adding an IDENTITY column to the table variable.
Calculate an island identifier
Due to the rule about the rows being in sequence based on the Order column, we can calculate a unique number for the Island by subtracting the Order from the IDENTITY column, in this case Id
We can then use DENSE_RANK() ordering by the Island Number
Putting all that together:
DECLARE #Table TABLE (
[Id] int IDENTITY(1,1),
[Text] varchar(100),
[Order] int,
[RequiredResult] int
);
INSERT INTO #Table
VALUES
('A',1,1),
('B',2,1),
('C',3,1),
('D',1,2),
('A',2,2),
('B',3,2),
('G',4,2),
('H',1,3),
('B',2,3);
SELECT [Text],[Order]
, [Id]-[Order] as Island
, RequiredResult
, DENSE_RANK() OVER (ORDER BY [ID]-[ORDER]) AS CalculatedResult
FROM #Table
ORDER BY [ID]
Text
Order
Island
RequiredResult
CalculatedResult
A
1
0
1
1
B
2
0
1
1
C
3
0
1
1
D
1
3
2
2
A
2
3
2
2
B
3
3
2
2
G
4
3
2
2
H
1
7
3
3
B
2
7
3
3
The key here is that we need to record the input sequence so we can us it in the calculation. It doesn't matter what actual numbering value the Id column has, only that it is also in sequence. If that number sequence is broken, then you could use the ROW_NUMER() function result to calculate the Island Number but the specifics on that would depend on the initial query that provides the basic sequential dataset.
You seem to have an ordering in mind for the rows. SQL tables represent unordered (multi)sets. The only column in your data that has the appropriate ordering is text, but your real data might have another column with this information.
Basically, you just want a cumulative sum of the number of 1s up to each row. That would be:
select t.*,
sum(case when ord = 1 then 1 else 0 end) over (order by text)
from t
I am trying to apply ROW_NUMBER() to increment a counter based on particular conditions.
My data looks like this, with the target counter being the Prep column
id DSR PrepIndicator Prep
--------------------------------------
1662835 -1 1 1
1662835 14 2 2
1662835 14 2 3
1662835 20 2 4
1667321 -1 1 1
1667321 30 2 2
1667321 14 2 3
1680648 -1 1 1
1680648 14 2 2
1680648 60 1 1
1680648 14 2 2
1680648 14 2 3
1683870 -1 1 1
1683870 12 2 2
1683870 10 2 3
1683870 60 1 1
1683870 7 2 2
Ignoring the PrepIndicator column for the moment, the business logic I am trying to implement is as follows:
For each of the Id's, starting from 1, increment the Prep counter if the DSR is less than 42.
If it is 42 or greater, reset the Prep counter to 1.
The PrepIndicator, in effect, creates a flag to implement this, in that if PrepIndicator = 1 then Prep = 1. If PrepIndicator = 2, then increment Prep.
I'd prefer to achieve this without the PrepIndicator column if possible.
How would I achieve this conditional increment with ROW_NUMBER()?
I've tried
ROW_NUMBER() OVER (PARTITION BY id, PrepIndicator ORDER BY id)
but it doesn't seem to work when the DSR is >= 42.
Any suggestions or help would be great. Thanks!
First, you will need explicit ordering. "Incrementing the counter" only has meaning if you have a previous value. You can add an IDENTITY column to the table, or use ROW_NUMBER() OVER ORDER BY(/* your logic here */). In your table, you do not even have unique values for the first three columns (see 1680648, 14, 2), so I would think adding an ID is the way to go.
To do what you want to achieve, I believe you must do this in a loop. If you use ROW_NUMBER() you may wish to select into a temporary table. By the nature of your question, the term counter indicates you will have a variable.
UPDATE TableA SET rowId = ROW_NUMBER() OVER(ORDER BY id, DSR, PrepIndicator)
then "conditional" seems to signal a good use of CASE
DECLARE #counter INT = 1
DECLARE #row INT = 1
DECLARE #DSR INT
UPDATE TableA SET Prep = #counter
SET #row = (SELECT rowId FROM TableA WHERE rowId > #row)
WHILE EXISTS( SELECT TOP 1 1 FROM TableA WHERE rowId = #row )
BEGIN
SELECT #DSR = DSR FROM TableA WHERE rowId = #row
SET #counter = CASE WHEN #DSR < 42 THEN #counter + 1 ELSE 1 END
UPDATE TableA SET Prep = #counter WHERE rowId = #row
SET #row = (SELECT rowId FROM TableA WHERE rowId > #row)
END
First, you need to add a primary key because there is no physical order in a SQL table; we can call it IdK. The following code should then give you what you want:
select *, row_number() over (partition by Id, (Select Count (*) from MyTable t2 where t2.idk <= t1.idk and t2.id = t1.id and DSR >= 42) order by idk) prep
from MyTable t1
order by idk
As to why your code doesn't work, this is because the rows are first grouped before the partition/numbering is done. In the case with the two columns id and PrepIndicator for the partition, we get the following intermediary result for the last 5 row before the numbering:
id DSR PrepIndicator Row_Number (Id, PrepIndicator)
1683870 -1 1 1
1683870 60 1 2
1683870 12 2 1
1683870 10 2 2
1683870 7 2 3
Notice that the line with DSR = 60 is now in the second position. This is clearly what you don't want to have. In the case with the Select count(*)..., we have the following result for the last 5 rows after the grouping is done, just before the numbering:
id DSR ...Count() Row_Number (Id, ...Count())
1683870 -1 0 1
1683870 12 0 2
1683870 10 0 3
1683870 60 1 1
1683870 7 1 2
You can notice that in this case, there is no change of position for any row.
Lets say you have the following table:
Id Index
1 3
1 1
2 1
3 3
1 5
what I would like to have is the following:
Id Index
1 0
1 1
2 0
3 0
1 2
As you might notice, the goal is for every row where Id is the same, to incrementally update the Index column, starting from zero.
Now, I know this is fairly simple with using cursors, but out of curiosity is there a way to do this with single UPDATE query, somehow combining with temp tables, common table expressions or something similar?
Yes, assuming that the you don't really care about the order of the values for the new index values. SQL Server offers updatable CTEs and window functions that do exactly what you want:
with toupdate as (
select t.*, row_number() over (partition by id order by (select NULL)) as newindex
from table t
)
update toupdate
set index = newindex;
If you want them in a specific order, then you need another column to specify the ordering. The existing index column doesn't work.
With Row_number() -1 and CTE you can write as:
CREATE TABLE #temp1(
Id int,
[Index] int)
INSERT INTO #temp1 VALUES (1,3),(1,1),(2,1),(3,3),(1,5);
--select * from #temp1;
With CTE as
(
select t.*, row_number() over (partition by id order by (select null))-1 as newindex
from #temp1 t
)
Update CTE
set [Index] = newindex;
select * from #temp1;
Demo
I'm not sure why you would want to do this really, but I had fun figuring it out!
This solution relies on your table having a primary key for the self join... but you could always create an auto inc index if none exists and this is a one off job... This will also have the added benefit of getting you to think about the precise ordering of this you want... as currently there is no way of saying which order [ID] will get [Index] in.
UPDATE dbo.Example
SET [Index] = b.newIndex
FROM dbo.Example a
INNER JOIN (
select
z.ID,
z.[Index],
(row_number() over (partition by ID order by (select NULL))) as newIndex
from Example z
) b ON a.ID = b.ID AND a.[Index]=b.[Index] --Is this a unique self join for your table?.. no PK provided. You might need to make an index first.
Probably, this is what you want
SELECT *,RANK() OVER(PARTITION BY Id ORDER BY [Index])-1 AS NewIndex FROM
(
SELECT 1 AS Id,3 [Index]
UNION
SELECT 1,1
UNION
SELECT 2,1
UNION
SELECT 3,3
UNION
SELECT 1,5
) AS T
& the result will come as
Now if you want to update the table then execute this script
UPDATE tblname SET Index=RANK() OVER(PARTITION BY t.Id ORDER BY t.[Index])-1
FROM tblname AS t
In case I am missing something or any further assistance is required please let me know.
CREATE TABLE #temp1(
Id int,
Value int)
INSERT INTO #temp1 VALUES (1,2),(1,3),(2,3),(4,5)
SELECT
Id
,Value
,ROW_NUMBER() OVER (PARTITION BY Id ORDER BY Id) Id
FROM #temp1
Start with this :)
Gave me results like
Id Value Count
1 2 1
1 3 2
1 2 3
1 3 4
1 2 5
1 3 6
1 2 7
1 3 8
2 3 1
2 4 2
2 5 3
2 3 4
2 4 5
2 5 6
2 4 7
2 5 8
2 3 9
2 3 10
3 4 1
4 5 1
4 5 2
4 5 3
4 5 4
I have a table named myvals with the following fields:
ID number
-- -------
1 7
2 3
3 4
4 0
5 9
Starting on 2nd row, I would like to add the number with the previous row number. So, my end result would look like this
ID number
-- ------
1 7
2 10
3 7
4 4
5 9
You could use the LAG analytic function
SELECT Id, number + LAG(number,1,0) OVER (ORDER BY Id) FROM table
First thing's first. You can't add to null to ID 1 must have a value.
create table #temp
(
month_type datetime,
value int
)
insert into #temp
Select '2015/01/01',1
union
Select '2015/02/01',2
union
Select '2015/03/01',3
union
Select '2015/04/01',4
SELECT t.value,t1.value,(t.value+t1.value)/2 FROM #temp t1
left join #temp t on t.month_type=Dateadd(MONTH,-1,t1.month_type)
I have a table MOUVEMENTS which has 3 columns :
ID IDREF NUMBER
1 1 5
2 1 3
3 1 4
4 1 2
5 2 1
I'd like to fetch the rows of this table with that constraints :
IDREF = 1
Ordered by ID ASC
and the X first SUM of NUMBER (by IDREF)
I imagine that we will first calculate the SUM. And then we will restrict with that column
ID IDREF NUMBER SUM
1 1 5 5
2 1 3 8
3 1 4 12
4 1 2 2
5 2 1 1
In this case, if we want to have 11, we will take the two first column + the third and we will change the number to have a coherent value.
So the result awaited :
ID IDREF NUMBER SUM
1 1 5 5
2 1 3 8
3 1 3 11
Please note the change in the third line on the NUMBER and SUM column.
Do you know how to achieve that ?
This query should work from sql 2000 to 2008 R2
I've created a solution here which uses a view: http://www.sqlfiddle.com/#!3/ebb01/15
The view contains a running total column for each IDRef:
CREATE VIEW MouvementsRunningTotals
AS
SELECT
A.ID,
A.IDRef,
MAX(A.Number) Number,
SUM (B.Number) RunningTotal
FROM
Mouvements A
LEFT JOIN Mouvements B ON A.ID >= B.ID AND A.IDRef = B.IDRef
GROUP BY
A.ID,
A.IDRef
If you can't create a view then you could create this as a temporary table in tsql.
Then the query is a self join on that view, in order to determine which is the last row to be include based on the Number you pass in. Then a CASE statement ensures the correct value for the last row:
DECLARE #total int
DECLARE #idRef int
SELECT #total = 4
SELECT #idRef = 1
SELECT
A.ID,
A.IDRef,
CASE
WHEN A.RunningTotal <= #total THEN A.Number
ELSE #total - B.RunningTotal
END Number
FROM
MouvementsRunningTotals A
LEFT JOIN MouvementsRunningTotals B ON
A.IDRef = B.IDRef
AND A.RunningTotal - A.Number = B.RunningTotal
WHERE
A.IDRef = #IDRef
AND (A.RunningTotal <= #total
OR (A.RunningTotal > #total AND B.RunningTotal < #total))
You can add more data in the Build Schema box and change the Number in the #total parameter in the Query box to test it.
select id, (select top 1 number from mouvements) as number, idref
from mouvements where idref=1 order by id asc