Write a cursor to search and insert - sql

I have tables like that:
Main
id name isedit
1 kyle 0
2 jhon 1
3 dave 0
EditHistory
id idmain name isedit Begin end
1 2 jhon 0 28.05.2020 18:30 28.05.2020 18:35
2 2 jhon 0 28.05.2020 18:35 NULL
3 1 kyle 0 27.05.2020 12:03 NULL
I currently use trigger:
(…) if update(isedit) and exists (
select 1
from Inserted I
where design = 0
) begin
Insert into dbo.HistoryEdit
([idmain][name][isedit][Begin][end]) SELECT id, name, iedit, GETDATE(), null
from Inserted
end;
I need to create cursor that will check through EditHistory for previous rows with same idmain and if there is such row edit its end date to GETDATE() and insert into HistoryEdit as in my current insert.
I know it can be easily done with IF's and thats how I would do it. But I have to use cursor for that, and I never used cursor before.

I never used cursor before.
Well don't start now. Just update the older rows before you insert the newer ones:
declare #d datetime = GetDate()
update EditHistory set end = #d
where id in (select id from inserted)
and end is null;
Insert into dbo.HistoryEdit
([idmain][name][isedit][Begin][end])
SELECT id, name, iedit, #d, null
from Inserted

Related

SQL Server: how to update a column with a value that is in that column when another number in another column is >1

I have a table with the following data:
Part Comp level item_nbr
-------------------------------
abc ab 1 1
null cd 2 2
null ef 3 3
cde gh 1 4
null ij 2 5
null kl 3 6
null mn 4 7
I would like to update the nulls to the value in each level 1, so every level that is >1 is updated with the level one value.
Part Comp level
---------------------
abc ab 1
abc cd 2
abc ef 3
cde gh 1
cde ij 2
cde kl 3
cde mn 4
I am at a loss as to how to achieve this on a very large dataset. Any help would be greatly appreciated!
To explain another way,
part level
abc 1
2
3
Then the next row is populated with another part
efg 1
2
2
etc.
Further clarification:
I need the string"abc" to be filled down with the string "abc" while the column fields below are null. The next row has a string of efg and the following column fields below are null, again, those fields should be filled down with the value "efg" and so on.
The level field = 1 will always have a part number, but all the other levels report up to the level 1 part, so should be populated identically. And repeat.
Hope this makes sense.
Use an updatable CTE with window functions:
with toupdate as (
select t.*,
max(part) over (partition by itm_nbr_not_null) as new_part
from (select t.*,
max(case when part is not null then item_nbr end) over (order by item_nbr) as itm_nbr_not_null
from t
) t
)
update toupdate
set part = new_part
where part is null;
You can run the CTE to see what is happening.
well, from your question what I understand is, you need to update the null column's value until you get a not null value. and you want to continue it up to the last row of the table.
for that scenario, I created a stored procedure, where I read the value of every n-th cell if it is null I changing it with the prev. cell's value, when the cell was not null.
Approach:
create a temporary table/ table variable.
add an extra column, which is basically identity, which will help to rank the column.
iterate a loop until the maximum row is reached.
in each iteration, read the cell value for the i-th row
4.1 if it is not null put it in a temporary variable.
4.2 else, replace/update the i-th cell's value with the temporary variable
continue it, until you reached up to the last row of the table/table variable.
look at my following snippets:
create proc DemoPost
as
begin
declare #table table(serial_no int identity(1,1), name varchar(30), text varchar(30), level int)
insert #table
select Name, Text, Level from Demo
declare #max as int = (select max(serial_no) from #table)
--select #max
declare #i as int =0
declare #temp as varchar(30)
declare #text as varchar(30)
while #i < #max
begin
set #i = #i +1
set #temp = (select name from #table where serial_no = #i)
-- if #temp is not null, fetch its value, otherwise, update/replace it with
-- previously gotten not-null cell's value.
if #temp is not null
begin
set #text = (select name from #table where serial_no = #i)
end
else
begin
update #table
set name = #text where serial_no = #i
end
end
select name, text, level from #table
end
You can update it using temporary table according to the given scenario i thought item_nbr is unique in row Hope this will help
SELECT *
INTO #TEMP
FROM URTablehere
DECLARE #PRev VARCHAR(MAX)
WHILE ( SELECT COUNT(*)
FROM URTablehere
) > 0
BEGIN
DECLARE #ID INT
DECLARE #Part VARCHAR(MAX)
DECLARE #Num INT
SELECT TOP ( 1 )
#ID = level ,
#Part = Part ,
#Num = item_nbr
FROM #TEMP
IF ( #ID = 1 )
BEGIN
SELECT #PRev = #Part
END
IF ( #ID > 1
AND #Part IS NULL
)
BEGIN
UPDATE URTablehere
SET Part = #PRev
WHERE item_nbr = #Num
END
DELETE
FROM #TEMP WHERE item_nbr=#Num
END

MS SQL - Aging Records

Please help as I am stuck with this!! :/
I have 5 columns that I select in a query and last 2 are derived/calculated
Date | Account | Symbol | Type | User | AgeKEY | Age |
where KEY is concatenated (Account+Symbol+Type+User)
How do I look back 1 year into history and calculate the Age of the record? Age is the continuous # of business days that the AgeKey appears in history
Aging Logic Example -
11/3 KeyExists hence Age = 1
11/4 KeyExists hence Age = 2
11/7 KeyExists hence Age = 3 (note over weekend ages only by 1 day)
11/8 KeyDoesntExist
11/9 KeyExists hence Age = 1 (counter restarts from 1 if this happens)
with T-SQL loop (it reads data from tab table and inserts to tab_result):
create table tab
(dt date, id int);
insert into tab values(DATEADD(day,-12,GETDATE()),1);
insert into tab values(DATEADD(day,-10,GETDATE()),1);
insert into tab values(DATEADD(day,-9,GETDATE()),1);
insert into tab values(DATEADD(day,-8,GETDATE()),1);
insert into tab values(DATEADD(day,-7,GETDATE()),3);
insert into tab values(DATEADD(day,-6,GETDATE()),3);
insert into tab values(DATEADD(day,-5,GETDATE()),1);
insert into tab values(DATEADD(day,-4,GETDATE()),1);
insert into tab values(DATEADD(day,-3,GETDATE()),1);
create table tab_result
(dt date, id int, age int);
DECLARE #id INT, #dt date, #prevId INT=NULL, #prevDt date=NULL, #age int
DECLARE CurName CURSOR FAST_FORWARD READ_ONLY
FOR
SELECT id,dt
FROM tab
ORDER BY id,dt
OPEN CurName
FETCH NEXT FROM CurName INTO #id, #dt
set #age=0;
WHILE ##FETCH_STATUS = 0
BEGIN
if (#prevId<>#id or #prevDt <> DATEADD(day,-1, #dt))
set #age=1;
else
set #age=#age+1;
insert into tab_result values (#dt, #id, #age )
set #prevId=#id
set #prevDt=#dt
FETCH NEXT FROM CurName INTO #id, #dt
END
CLOSE CurName
DEALLOCATE CurName
select * from tab_result order by id, dt;
with plain sql it would be something like below (id in the example is your key):
create table tab
(dt date, id int);
insert into tab values(DATEADD(day,-12,GETDATE()),1);
insert into tab values(DATEADD(day,-10,GETDATE()),1);
insert into tab values(DATEADD(day,-9,GETDATE()),1);
insert into tab values(DATEADD(day,-8,GETDATE()),1);
insert into tab values(DATEADD(day,-7,GETDATE()),3);
insert into tab values(DATEADD(day,-6,GETDATE()),3);
insert into tab values(DATEADD(day,-5,GETDATE()),1);
insert into tab values(DATEADD(day,-4,GETDATE()),1);
insert into tab values(DATEADD(day,-3,GETDATE()),1);
with x as (
select tab.dt,
tab.id,
case when prev.dt is not null then 1 else 0 end as exists_on_prev_day
from
tab left outer join tab prev on (tab.id=prev.id and DATEADD(day,-1 , tab.dt)= prev.dt)
)
select id,dt,
(select
-- count all records with the same id and date less or equal date of the given record
count(*) from x x2 where x2.id=x.id and x2.dt<=x.dt
-- (tricky part) we want to count only records between current record and last record without "previous" record (that is with exists_on_prev_day flag = 0)
and not exists (select 1 from x x3 where x3.id=x2.id and x3.dt>x2.dt and x3.dt<=x.dt and x3.exists_on_prev_day=0 )) age
from x
order by id, dt;
result:
id dt age
1 1 19.11.2016 00:00:00 1
2 1 21.11.2016 00:00:00 1
3 1 22.11.2016 00:00:00 2
4 1 23.11.2016 00:00:00 3
5 1 26.11.2016 00:00:00 1
6 1 27.11.2016 00:00:00 2
7 1 28.11.2016 00:00:00 3
8 3 24.11.2016 00:00:00 1
9 3 25.11.2016 00:00:00 2

Need Query - Row count with loop

I have a Primary table like
========================================
ID NAME logtime (date time colum)
========================================
1 cat dd/mm/yyyy 10.30
2 cat dd/mm/yyyy 9.20
3 cat dd/mm/yyyy 9.30
4 cat dd/mm/yyyy 7.20
Secondary Table like
---------------------
Name improvement
---------------------
cat 1
Now I want to create a loop
To calculate difference between first 2 rows, if the difference is >= 1 hr then update the secondary table as existing value +1(that is 2) else rest it.
Now calculate 2'nd and 3'rd rows,
To calculate difference between first 2 rows, if the difference is >= 1 hr then update the secondary table as existing value +1, here the answer is 2.
Then 3'rd and 4'th.
Once all row are calculated then exit from the loop.
can anyone give me a query for this?
declare #timediff table(id int identity(1,1),Name varchar(50),logtime datetime)
insert into #timediff Select Name,logtime from timediff
declare #datetimeDiff int
declare #i int=1
while(#i<=(Select count(*) from #timediff))
Begin
Select #datetimeDiff=datediff(hour,logtime,
(Select logtime from #timediff where id=#i+1))
from #timediff where id=#i
if(#datetimeDiff>=1)
BEGIN
Select #datetimeDiff
--You can write your update code here
END
Set #i=#i+2
END

Sorting results of SQL query just like IN parameter list

I'm doing a query which looks something like
SELECT id,name FROM table WHERE id IN (2,1,4,3)
I'd like to get
id name
2 B
1 A
4 D
3 C
but I'm getting
1 A
2 B
3 C
4 D
Is there any way to sort the query results in the same way as the list I'm including after IN?
Believe me, I have a practical reason that I would need it for ;)
SELECT id,name FROM table WHERE id IN (2,1,4,3)
ORDER BY CASE id
WHEN 2 THEN 1
WHEN 1 THEN 2
WHEN 4 THEN 3
WHEN 3 THEN 4
ELSE 5
END
This might solve your problem.
Solution 2, insert your list into a temp table and get them a running sequence
id, seq(+1 every new row added)
-----------------
2 1
1 2
4 3
3 4
then join 2 table together and order by this seq.
Okay, I did it myself. It's a bit mad but it works ;)
DECLARE #IDs varchar(max)
DECLARE #nr int
DECLARE #znak varchar(1)
DECLARE #index int
DECLARE #ID varchar(max)
SET #IDs='7002,7001,7004,7003'
SET #nr=1
SET #index=1
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #temp (nr int, id int)
--fill temp table with Ids
WHILE #index<=LEN(#Ids)
BEGIN
set #znak=''
set #ID=''
WHILE #znak<>',' AND #index<=LEN(#Ids)
BEGIN
SET #znak= SUBSTRING(#IDs,#index,1)
IF #znak<>',' SET #ID=#ID+#znak
SET #index=#index+1
END
INSERT INTO #temp(nr,id) VALUES (#nr,CAST(#ID as int))
SET #nr=#nr+1
END
-- select proper data in wanted order
SELECT MyTable.* FROM MyTable
INNER JOIN #temp ON MyTable.id=#temp.id
ORDER BY #temp.nr

How to select as much records as indicated by the value from database

I have one table in relational database Sybase ASE, with few columns. Three of them looks like this example:
_____________
| Product |
---------------
| ProductId |
| Name |
| Quantity |
_____________
So we have some records:
__________________________________
| ProductId | Name | Quantity |
----------------------------------
| 1 | pants | 2 |
| 2 | shirt | 1 |
| 3 | sweater | 3 |
----------------------------------
I need to get every name as many times as 'Quantity' of this product.
So the result should looks like:
pants
pants
shirt
sweater
sweater
sweater
If somebody have any idea how can I do this, please help me.
EDIT
2014-01-24 14:17 UTC+1
I'd like to thanks everybody. Gordon's solution is realy nice, but for my situation (bigger Quantity) I can't use that sql. I try do somethnig like 333kenshin's and simon's solutions but without cursor. I do somthnig like this:
IF OBJECT_ID('#TEMP') is not null
DROP TABLE #TEMP
create TABLE #TEMP (Name varchar(255))
DECLARE #Name varchar(255)
DECLARE #Quant INT
DECLARE #prodId INT
SET #prodId = 1
WHILE (EXISTS(SELECT 1 FROM product WHERE productID = #prodId))
BEGIN
SELECT
#Name = Name
#Quant = Quantity
FROM Product
DECLARE #i INT
SET #i = 1
WHILE #i <= #Quant
BEGIN
insert into #TEMP
values(#Name)
SELECT #i=#i+1
END
SELECT #prodId = #prodId + 1
END
select * from #TEMP
drop table #TEMP
For me, and my DB it was fastest solution. So thanks a lot for every answers.
To do this, you need a series of integers. You can generate one manually:
select p.name
from product p join
(select 1 as n union all select 2 union all select 3 union all select 4
) n
on n.n <= p.quantity;
This will work if quantity is not too big and you can put in the values in n.
The correct way to do this is temp table + cursors:
create a temp table
create cursor to iterate through Product table
within the cursor, create an inner WHILE loop
exit the loop and finally select the temp table
The following isn't 100% correct Sybase syntax, but it's pretty close.
-- 1: temp table
select productName into #TEMP
-- 2: cursor
declare
#productName char(10),
#quantity int
declare ProductRead CURSOR for
select
productName,
quantity
from
Product
OPEN ProductRead
FETCH ProductRead
INTO
#productName,
#quantity
WHILE (##sqlstatus=0)
BEGIN
-- 3: inner for loop
DECLARE #i INT
SET #i = 1
WHILE #i <= #quantity
BEGIN
insert #productName into #TEMP
END
END
-- 4: final result set
select productName from #TEMP
You could use a temporary table. Select all your product rows, then loop through each row returned. For each product, loop Quantity times and in the loop insert the product into the temp table. I'm not a Sybase user, so I can't give you the syntax but you would have a stored procedure which would do something like:
select all rows from product into a cursor
for each row
for i = 1 to row.Quantity
insert into temp (Name) values (row.Name)
next
end loop
select * from temp and return it
It's a pretty eccentric requirement, though.
EDIT: Gordon's solution is neat though! If n never gets too big I'd go with that!