SQL Query: Modify Next Row Based On Current Row - sql

Using SQL Server 2012.
The table I'm working with is shown below, along with code for the query. When I find ReasonString_S = 'Fault Reset' I would like to add that rows DurationMinutes_D to the next row and delete the current row.
I think a case when statement would work but I kept getting syntax issues and I'm fairly new to sql queries.
select ROW_NUMBER() OVER (ORDER BY EntryDate_T) AS Row, e.equip_name,
ReasonString_S, DurationMinutes_D, rt.Name_S ProcStateString, EntryDate_T
into #temptable
from AT_PM_PlantStateEvent pse
inner join EQUIPMENT e on pse.OwnerKey_I = e.equip_key
inner join dbo.AT_PM_ReasonTree rt on pse.ReasonKey_64 = rt.atr_key
where EntryDate_T >= #jobstart and EntryDate_T < #jobend
and rt.Name_S <> 'Running'
and e.equip_name = #mach
Thanks for the help!

It seems Row is your identity column if not then don't hesitate to create or use another existing one. It has many benefits as it's very useful in your case you can perform the desired operation with the help of that simply using JOIN as below:
create table #test(rowNum int identity(1,1),
ReasonString_S varchar(50),
DurationMinutes_D float)
insert into #test values
('Model1',0.34),
('Model2',0.35),
('Model3',0.36)
DATA:
rowNum ReasonString_S DurationMinutes_D
-----------------------------------------
1 Model1 0.34
2 Model2 0.35
3 Model3 0.36
update t set t.ReasonString_S = u.DurationMinutes_D
from #test t
left join #test u on u.rowNum = t.rowNum+1
OUTPUT:
rowNum ReasonString_S DurationMinutes_D
-----------------------------------------
1 0.35 0.34
2 0.36 0.35
3 NULL 0.36

Here's a general answer. To update a row based on the value in the next row, you should use the LEAD function. First you need to identify which column you want to sort by in order to determine which row is the next row. LEAD lets you get the value of a particular column from the next row. If you write a subquery using the LEAD function (which can only appear in the SELECT or ORDER BY clause), you can update by joining to the subquery.
Conversely, to update a row based on the value in the previous row, you'd use the LAG function.
Here's an example:
declare #t1 table (id int, val varchar(10))
insert into #t1 values (1, 'val1')
insert into #t1 values (2, 'val2')
insert into #t1 values (3, 'val3')
update #t1 set val = sq.next_row_val
from #t1 t1
inner join (
select t1.id, LEAD(t1.val) over (order by t1.id) as next_row_val
from #t1 t1
)sq on t1.id = sq.id
select * from #t1
id val
1 val2
2 val3
3 NULL

Related

SQL clone/replicate records within same table with a condition

I have a table and i would like to replicate/clone records within the same table. However i would like to do that with a condition. And the condition is i have a column called recordcount with numeric values. For example Row 1 can take on a value of recordcount say 7, then i would like my row 1 to be replicated 7 times. Row 2 could take on a value say 9 then i would like row 2 to be replicated 9 times.
Any help is appreciated. Thank you
What you can do (and I'm pretty sure it's not a best practice),
Is to hold a table with just numbers, which has rowcount that correspond to the numeric value.
Join that with your table, and project your table only.
Example:
create table nums(x int);
insert into nums select 1;
insert into nums select 2;
insert into nums select 2;
insert into nums select 3;
insert into nums select 3;
insert into nums select 3;
create table t (txt varchar(10) , recordcount int);
insert into t select 'A',1;
insert into t select 'B',2;
insert into t select 'C',3;
select t.*
from t
inner join nums
on t.recordcount = nums.x
order by 1
;
Will project:
"A",1
"B",2
"B",2
"C",3
"C",3
"C",3

TSQL: How do i detect and insert missing records

I have T-SQL Table below.
ID Cost MaxCost
-------------------------------
2 200 300
3 400 1000
6 20 100
The above table must have 10 rows with IDs 1 to 10. So its missing 7 rows. How do i insert missing rows with proper ID. The cost & maxcost for missing rows will be zero. Do i need to create a temp table that holds 1 to 10 numbers?
No need for temp table, simple tally derived table and LEFT OUTER JOIN are sufficient:
CREATE TABLE #tab(ID INT, Cost INT, MaxCost INT);
INSERT INTO #tab(ID, Cost, MaxCost)
VALUES (2, 200,300),(3, 400, 1000) ,(6, 20, 100);
DECLARE #range_start INT = 1
,#range_end INT = 10;
;WITH tally AS
(
SELECT TOP 1000 r = ROW_NUMBER() OVER (ORDER BY name)
FROM master..spt_values
)
INSERT INTO #tab(id, Cost, MaxCost)
SELECT t.r, 0, 0
FROM tally t
LEFT JOIN #tab c
ON t.r = c.ID
WHERE t.r BETWEEN #range_start AND #range_end
AND c.ID IS NULL;
SELECT *
FROM #tab
ORDER BY ID;
LiveDemo
EDIT:
Tally table is simply number table. There are many ways to achieve it with subquery:
recursive cte
ROW_NUMBER() from system table that holds many values (used here)
UNION ALL and CROSS JOIN
VALUES(...)
using OPENJSON (SQL Server 2016+)
...
The TOP 1000 will generate only 1000 records if you know that you need more you can use:
SELECT TOP 1000000 r = ROW_NUMBER() OVER (ORDER BY (SELECT 1))
FROM master..spt_values c
CROSS JOIN master..spt_values c2;
Since your number of rows is low you could just define the data explicitly...
CREATE TABLE Data(ID INT, Cost INT, MaxCost INT);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(2, 200, 300);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(3, 400, 1000);
INSERT INTO Data(ID, Cost, MaxCost) VALUES(6, 20, 100);
and the query...
select *
from (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) RowNums (ID)
left outer join Data on RowNums.ID = Data.ID
The first part defines a column ID with rows 1-10, it then left outer joins to your data. The beauty of this is that it is very readable.
I like to google for new and better ways to do things.. so i stumbled over this post and...Well what worked good in SQL7 and works good in SQL2016 is to just use a outer join and look for NULL values(null is missing data) ....
insert into DestTable (keyCol1,col1,col2,col3...)
select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
feel free to test it
wrap your statement in a transaction, delete a few rows and see them come back in the select statement that would normally insert the rows in the destination table...
BEGIN TRAN
--> delete a subset of the data, in this case 5 rows
set rowcount 5;
-->delete and show what is deleted
delete from DestTable;
OUTPUT deleted.*,'DELETD' as [Action]
--> Perform the select to see if the selected rows that are retured match the deleted rows
--insert into DestTable (keyCol1,col1,col2,col3...)
Select keyCol1,col1,col2,col3,...)
from SouceTable as s
left outer join DestTable as d on d.KeyCol1=s.KeyCol1
where d.KeyCol1 is null
and ...
ROLLBACK
another way would be a TSQL merge, google that if you need to also update and optionally delete...

How to update in order and query the updated fields when updating in SQL in a single statement

I need to calculate Dividend Factors in the DB and the basic calculation needed in a general way is row2 field2 = (row2's field1) * (row1's field2) where the field2 is the value I need to both update and query at the same time i.e. when I calculate it for one row, I need the calculated value of the previous row for this row.
Now I have a temp table with has all the values and now I need to calculate the final values, but when I tried this:
UPDATE
#temp
SET
field2 = IsNull(
(SELECT d2.field2 * d.field1 FROM #temp AS d2 WHERE d2.rowNr = d.rowNr - 1)
,d.field1
)
FROM
#temp as d
;
It always saw that the field2 was always NULL and went with the default action, with it should do only for the first row.
Now currently there are only two methods I know for doing this:
Loop through the #temp with a cursor
Use a while statement and loop through the table that way (I opted for this one, because I thought there is no point in using a cursor for a small table of 10-20 rows max)
But I still would like to get this into a single statement, but I have no idea how to do this. I am using MS SQL 2008 R2.
EDIT:
This is the actual data I am working with: (Note, that all field2 values are NULL prior to the calculation and the data type is money)
field1 field2(expected values)
------ ----------------------
1,033 1,033
1,0363 1,0705
1,0558 1,1302
1,0157 1,1479
1,0188 1,1695
1,026 1,1999
1,0286 1,2342
1,0323 1,2741
1,0319 1,3147
Okay if I'm understanding this, you want to find field2 which is based on previous rows of field2 that were just calculated so you need either some form of loop or recursion. Try this recursive solution out:
Setting Up Tables
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp;
DECLARE #yourTable TABLE (ID INT,field1 INT, field2 INT);
INSERT INTO #yourTable(ID,field1,field2)
VALUES (1111,11,11),(2222,22,22),(3333,33,33);
SELECT ROW_NUMBER() OVER (ORDER BY ID) rowNr,
ID,
field1,
field2 INTO #temp
FROM #yourTable;
Calculating values
WITH cte_recursion
AS
(
SELECT TOP 1
rowNR,
ID,
field1,
field2,
field1 AS dividend_factor
FROM #temp A
ORDER BY rowNr
UNION ALL
SELECT B.rowNr,
B.ID,
B.field1,
B.field2,
B.field1 * A.dividend_factor
FROM cte_recursion A
INNER JOIN #temp B
ON A.rowNr = B.rowNr - 1
)
Actual Update
UPDATE #yourTable
SET field2 = B.dividend_factor
FROM #yourTable A
INNER JOIN cte_recursion B
ON A.ID = B.ID
OPTION (MAXRECURSION 0)
SELECT *
FROM #yourTable
Results:
ID field1 field2
----------- ----------- -----------
1111 11 11
2222 22 242
3333 33 7986
Personally I wouldn't use the update because you have to constantly make sure the data is update to date. I'd much rather use the CTE I used to calculate the values and put it in a view so that you know the values are ALWAYS up to date and you don't have to worry about running it. Either that or having a dividend_factor column in your actual table that will be NULL unless the value is updated. Just my two cents
UPDATE d1
SET d1.field2 = IsNull(d2.field2 * d1.field1, d1.field1)
FROM #temp AS d1
left outer join #temp AS d2
on d2.rowNr = d1.rowNr - 1
magic
select d1.field1, EXP(SUM(LOG(d2.field1)))
from #temp AS d1
join #temp AS d2
on d2.rowNr <= d1.rowNr
group by d1.field1
op claims wrong answer
test for youself
drop table #temp;
create table #temp (ID int, val money);
insert into #temp (ID, val) values
(1, 1.033)
, (2, 1.0363)
, (3, 1.0558)
, (4, 1.0157)
, (5, 1.0188)
, (6, 1.026)
, (7, 1.0286)
, (8, 1.0323)
, (9, 1.0319);
SELECT TOP 10 [t1].[ID], EXP(SUM(LOG([t2].[val])))
from #temp AS t1
join #temp AS t2
on t2.[ID] <= t1.[ID]
group by t1.[ID]
order by t1.[ID]

How to make a range series in SQL

I have to improve a Stored Procedure, it uses a select query on a table as follows:
SELECT DISTINCT ProjectId FROM Project where Status ='P' Order by ProjectId
it give an output as follows:
1
2
3
7
8
11
12
13
I need to use these values in insert statement for another table as follow:
insert into Table values (othervalue, 1|1);
insert into Table values (othervalue, 2|2);
....
To decrease the number of inserts, we want to store as follows:
insert into Table values (othervalue, 1|3);
insert into Table values (othervalue, 7|8);
insert into Table values (othervalue, 11|13);
That is in range till the time there is no gap. I tried using CURSOR to loop through the resultset and have some logic to convert it and keep on inserting. But seems some error.
Can we do something in SELECTquery itself?
with t(a,en,bg) as
(
select a,case when [begin] is NULL then NULL else row_number() over(partition by [begin] order by a) end
,case when [end] is NULL then NULL else row_number() over(partition by [end] order by a) end
from (
select t.a, case when t1.a is NULL then 'end' else NULL end [end],
case when t2.a is NULL then 'begin' else NULL end [begin]
from Project as t left join Project as t1 on (t1.a=t.a+1 AND t.Status='P' AND t1.Status='P')
left join Project as t2 on (t2.a=t.a-1 AND t2.Status='P')
) as o )
select cast(t1.a as varchar)+'|'+cast(t.a as varchar) from t inner join t as t1 on t.en=t1.bg
This query will return you values from Project in '1|3' type.
It's not clear from your question whether you use plsql or sql-server. My solution will be work for MS SQL Server

T-SQL Grouping rows from the MAX length columns in different rows (?)

i'm trying to come up with a way to combine rows in a table based on the longest string in any of the rows based on a row key
example
CREATE TABLE test1
(akey int not null ,
text1 varchar(50) NULL,
text2 varchar(50) NULL,
text3 varchar(50) NULL )
INSERT INTO test1 VALUES ( 1,'Winchester Road','crawley',NULL)
INSERT INTO test1 VALUES ( 1,'Winchester Rd','crawley','P21869')
INSERT INTO test1 VALUES ( 1,'Winchester Road','crawley estate','P21869')
INSERT INTO test1 VALUES ( 1,'Winchester Rd','crawley','P21869A')
INSERT INTO test1 VALUES ( 2,'','birmingham','P53342B')
INSERT INTO test1 VALUES ( 2,'Smith Close','birmingham North East','P53342')
INSERT INTO test1 VALUES ( 2,'Smith Cl.',NULL,'P53342B')
INSERT INTO test1 VALUES ( 2,'Smith Close','birmingham North','P53342')
with these rows i would be looking for the result of :
1 Winchester Road, crawley estate, P21869A
2 Smith Close, birmingham North East, P53342B
EDIT: the results above need to be in a table rather than just a comma separated string
as you can see in the result, the output should be the longest text column in the range of the 'akey' field.
i'm trying to come up with a solution that does not involve lots of subqueries on each column, the actual table has 32 columns and over 13 million rows.
the reason i'm doing this is to create a cleaned-up table that has the best results in each column for just one ID per row
this is my first post, so let me know if you need any more info, and i'm happy to hear about any best practices about posting that i've broken!
thanks
Ben.
SELECT A.akey,
(
SELECT TOP 1 T1.text1
FROM test1 T1
WHERE T1.akey=A.akey AND LEN(T1.TEXT1) = MAX(LEN(A.text1))
) AS TEXT1,
(
SELECT TOP 1 T2.text2
FROM test1 T2
WHERE T2.akey=A.akey AND LEN(T2.TEXT2) = MAX(LEN(A.text2))
) AS TEXT2,
(
SELECT TOP 1 T3.text3
FROM test1 T3
WHERE T3.akey=A.akey AND LEN(T3.TEXT3) = MAX(LEN(A.text3))
) AS TEXT3
FROM TEST1 AS A
GROUP BY A.akey
I just realized you said you have 32 columns. I don't see a good way to do that, unless UNPIVOT would allow you to create separate rows (akey, textn) for each text* column.
Edit: I may not have a chance to finish this today, but UNPIVOT looks useful:
;
WITH COLUMNS AS
(
SELECT akey, [Column], ColumnValue
FROM
(
SELECT X.Akey, X.Text1, X.Text2, X.Text3
FROM test1 X
) AS p
UNPIVOT (ColumnValue FOR [Column] IN (Text1, Text2, Text3))
AS UNPVT
)
SELECT *
FROM COLUMNS
ORDER BY akey,[Column], LEN(ColumnValue)
This seems really ugly, but at least works (on SQL2K) and doesn't need subqueries:
select test1.akey, A.text1, B.text2, C.text3
from test1
inner join test1 A on A.akey = test1.akey
inner join test1 B on B.akey = test1.akey
inner join test1 C on C.akey = test1.akey
group by test1.akey, A.text1, B.text2, C.text3
having len(a.text1) = max(len(test1.text1))
and len(B.text2) = max(len(test1.text2))
and len(C.text3) = max(len(test1.text3))
order by test1.akey
I must admit that it needs an inner join for each column and I wonder how this could impact on the 32 columns x 13millions record table... I try both this approach and the one based one subqueries and looked at executions plans: I'ld actually be curious to know