Update row with condition in SQL Server 2008 - sql-server

This is my table structure:
CREATE table Credit(id integer, organisationid int, availableCredit int)
INSERT INTO Credit VALUES (1, 1, 1000)
INSERT INTO Credit VALUES (2, 1, 100)
INSERT INTO Credit VALUES (3, 2, 600)
INSERT INTO Credit VALUES (4, 2, 400)
I have to reduce the available credit column value, I have amount 1050 with me. I need to reduce 1050 from credit table where organisation id = 1. Here the organisation Id 1 have 1100 available credit in total. The condition is the first inserted credit row should be updated first and then the rest (FIFO model), also the updation should happen only for organisationId = 1.
How do we update this using a single or multiple update statement?
Any suggestions?

Unfortunately, this is a rather messy thing to do in T-SQL - you'll need something like a loop (cursor or WHILE statement).
With this code here, I can get it to run - it's not pretty, but it works.
-- you want to reduce the total credits by this amount
DECLARE #AmountToReduce INT = 1050
-- temporary variables
DECLARE #ID INT, #AvailableCredit INT
-- get the first row from dbo.Credit that still has availableCredit - ordered by id
SELECT TOP 1 #ID = id, #AvailableCredit = availableCredit
FROM dbo.Credit
WHERE availableCredit > 0 AND organisationId = 1
ORDER BY id
-- as long as we still have credit to reduce - loop..
WHILE #AmountToReduce > 0 AND #ID IS NOT NULL
BEGIN
-- if we need to remove the complete availableCredit - do this UPDATE
IF #AmountToReduce > #AvailableCredit
BEGIN
UPDATE dbo.Credit
SET availableCredit = 0
WHERE id = #ID
SET #AmountToReduce = #AmountToReduce - #AvailableCredit
END
ELSE BEGIN
-- if the amount to reduce left is less than the availableCredit - do this UPDATE
UPDATE dbo.Credit
SET availableCredit = availableCredit - #AmountToReduce
WHERE id = #ID
SET #AmountToReduce = 0
END
-- set #ID to NULL to be able to detect that there's no more rows left
SET #ID = NULL
-- select the next "first" row with availableCredit > 0 to process
SELECT TOP 1 #ID = id, #AvailableCredit = availableCredit
FROM dbo.Credit
WHERE availableCredit > 0 AND organisationId = 1
ORDER BY id
END

This script reduces "Id 1" availableCredit by 1000, and reduces "Id 2" availableCredit by 50.
UPDATE Credit
SET availableCredit=(availableCredit-1000)
WHERE id=1
UPDATE Credit
SET availableCredit=(availableCredit-50)
WHERE id=2

Previously I had given only select query,Here is the final UPDATE query which does the task.
DECLARE #balance int=1050
;WITH CTE as (
select id,organisationid,CASE when #balance>availableCredit then 0 else availableCredit-#balance end as availableCredit,
CASE when #balance>availableCredit then #balance-availableCredit else 0 end as balamt from Credit where id=1 and organisationid=1
union all
select t.id,t.organisationid,CASE when c.balamt>t.availableCredit then 0 else t.availableCredit-c.balamt end as availableCredit,
CASE when c.balamt>t.availableCredit then c.balamt-t.availableCredit else 0 end as balamt1
from Credit t inner join CTE c on t.id-1=c.id --and t.organisationid=1
)
Update c SET c.availableCredit = ct.availableCredit
FROM Credit c inner join CTE ct
on c.id=ct.id
SELECT * FROM Credit

Try This query:
CREATE table Credit(id integer, organisationid int, availableCredit int)
INSERT INTO Credit VALUES (1, 1, 1000)
INSERT INTO Credit VALUES (2, 1, 100)
INSERT INTO Credit VALUES (3, 2, 600)
INSERT INTO Credit VALUES (4, 2, 400)
DECLARE #balance int=1050
;WITH CTE as (
select id,organisationid,CASE when #balance>availableCredit then 0 else availableCredit-#balance end as availableCredit,
CASE when #balance>availableCredit then #balance-availableCredit else 0 end as balamt from Credit where id=1 and organisationid=1
union all
select t.id,t.organisationid,CASE when c.balamt>t.availableCredit then 0 else t.availableCredit-c.balamt end as availableCredit,
CASE when c.balamt>t.availableCredit then c.balamt-t.availableCredit else 0 end as balamt1
from Credit t inner join CTE c on t.id-1=c.id and t.organisationid=1
)
SELECT id,organisationid,availableCredit FROM CTE

Related

Make 100 random numbers and add them to table as primary key and catch error if duplicate

I have a problem. I am trying to add 100 random primary keys and catch error if the values is duplicate then add counter that counts the amount of errors and at the end prints out how many values are in ID and how many duplicate number errors there were.
I have done this so far but I am really new to T-SQL and I am not sure if this is even close.
Biggest problem is that I don't know how to get the value number from within the WITH and insert the given number to the table.
DECLARE #TABLE TABLE (ID INT NOT NULL PRIMARY KEY)
DECLARE #I INT = 1
DECLARE #ERROR INT = 0
DECLARE #NUMBER INT
BEGIN TRY
WITH CTE_Numbers(number) AS
(
SELECT 1 AS number
UNION ALL
SELECT number + 1
FROM CTE_Numbers
WHERE number < 100
)
SELECT TOP 1 number
FROM CTE_Numbers
ORDER BY NEWID()
OPTION (MAXRECURSION 0)
BEGIN
WHILE #I <= 100
BEGIN
SET #NUMBER = (SELECT number FROM CTE_Numbers)
INSERT INTO #TABLE VALUES(#NUMBER)
SET #I = #I + 1
END
END
END TRY
BEGIN CATCH
SET #ERROR = #ERROR + 1
END CATCH
SELECT COUNT(ID) AS numbers, #ERROR AS errors
FROM #TABLE
I don't fully get what you're doing but this should get you started. #rows is how many rows you want, #high is the highest ID (between 1 and #high).
--==== Parameters
DECLARE
#rows INT = 10, -- Return this many rows
#high INT = 20; -- Number = 1 to #high
--==== Prep
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t;
--==== Create and Populate #t with random numbers
WITH
e1(x) AS (SELECT 1 FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) AS x(x)),
t(N) AS (SELECT ABS(CHECKSUM(NEWID())%#high)+1 FROM e1 a, e1 b, e1 c, e1 d) -- up to 10K
SELECT TOP(#rows) t.N INTO #t FROM t;
--==== Counts
SELECT
UniqueIds = SUM(IIF(f.T=1, 1, 0)),
Duplicates = SUM(IIF(f.T=1, 0, 1))
FROM
(
SELECT t.N, COUNT(*)
FROM #t AS t
GROUP BY t.N
) AS f(N,T);
--==== Santiy Check
SELECT t.N, ttl = COUNT(*)
FROM #t AS t
GROUP BY t.N
Returns: (random)
UniqueIds Duplicates
----------- -----------
2 4
N ttl
----------- -----------
2 2
5 1
6 2
8 2
12 2
14 1

Multiple select queries execution one after other

I am having six select queries with different where conditions if first select query returns null it should check the next select query and follows. what is the best approach to follow for writing it as stored procedure in SQL server.
You can use ##rowcount
DECLARE #OperatorID INT = 4, #CurrentCalendarView VARCHAR(50) = 'month';
declare #t table (operatorID int, CurrentCalendarView varchar(50));
insert into #t values (2, 'year');
select operatorID - 1, CurrentCalendarView from #t where 1 = 2
if (##ROWCOUNT = 0)
begin
select operatorID + 1, CurrentCalendarView from #t where 1 = 1
end
If I understand your question correctly then you can achieve this like below sample. You can go in this way.
if NOT EXISTS (SELECT TOP(1) 'x' FROM table WHERE id =#myId)
BEGIN
IF NOT EXISTS (SELECT TOP(1) 'x' FROM table2 WHERE id = #myId2)
BEGIN
IF NOT EXISTS (SELECT TOP(1) 'x' FROM table 3 WHERE id = #myID3)
BEGIN
END
END
END

T-SQL Foreach row if column has value 0

I'm new in working with SQL queries and I'm trying to check if for each parent from the Parents table I have the age for all children from Children table. For example:
Parents
Name Id
John Smith 7
Children
Name Age ParentId
Sasha Smith 10 7
Johnny Smith 0 7
This is what I have so far:
create function fnCheckChildren(
#parentId int
)
returns int
begin
declare #allOk int
if(select count(1) from Children where ParentId=#parentId ) = 0
BEGIN
SET #allOk =1 --default all are ok - parent has no children
END
else
BEGIN
--here is my missing part
END
return #allOk
end
go
If I'll call the function for John Smith (#parentId = 7) #allOk should have the value 0 because one of his children (Johnny) has Age 0. If all had Age > 0 then #allOk should have 1.
Can anyone help me with it? As I understand, T-SQL doesn't support For Each...Next, and so on, so what alternatives do I have?
Here is a relative simple and efficient method:
select p.*,
(case when exists (select 1 from children c where c.parentid = p.id and c.age = 0
then 1 else 0
end) as flag
from p;
You can make this a function by simply doing:
create function fnCheckChildren(
#parentId int
)
returns int
begin
if (exists (select 1 from children where parentid = #parentid and age = 0)
)
begin
return(0);
end;
return(1);
end;
There is no need for CURSOR at all:
SELECT *
FROM Parents p
OUTER APPLY (SELECT CASE WHEN
COUNT(*) = COUNT(CASE WHEN c.Age > 0 THEN 1 END) THEN 1
ELSE 0
END AS res
FROM Children c
WHERE p.id = c.parent_id
) sub
-- WHERE ....;
DBFiddle Demo
As I understand, T-SQL doesn't support For Each.
This is not true, For-Each is kind of correlated subquery for instance: using inline syntax like in Gordon's answer or OUTER APPLY.
You can try the following query
CREATE FUNCTION fnCheckChildren(
#parentId INT
)
RETURNS INT
BEGIN
DECLARE #allOk INT
SET #allOk = CASE WHEN (select count(*) from Children where ParentId=#parentId AND Age > 0) > 1 THEN 1
ELSE 0 END
RETURN #allOk
END
Thanks
You could also have a case of no children at all. This will indicate that.
declare #P table (id int identity primary key, name varchar(20));
insert into #P (name) values ('John Smith'), ('Pat Jones'), ('Will Smith');
select * from #P;
declare #C table (id int identity primary key, parID int, name varchar(20), age int);
insert into #C (parID, name, age) values (1, 'jim', 12), (1, 'sally', 0), (2, 'bruce', 10);
select * from #C;
select p.id, P.name
, count(c.id) as [child count]
, case when MIN(c.age) <= 0 then 'childen with zero' else 'no children with zero' end as status
from #P p
left join #C c
on p.id = c.parID
group by p.id, p.name;

How to chunk updates to SQL Server?

I want to update a table in SQL Server by setting a FLAG column to 1 for all values since the beginning of the year:
TABLE
DATE ID FLAG (more columns...)
2016/01/01 1 0 ...
2016/01/01 2 0 ...
2016/01/02 3 0 ...
2016/01/02 4 0 ...
(etc)
Problem is that this table contains hundreds of millions of records and I've been advised to chunk the updates 100,000 rows at a time to avoid blocking other processes.
I need to remember which rows I update because there are background processes which immediately flip the FLAG back to 0 once they're done processing it.
Does anyone have suggestions on how I can do this?
Each day's worth of data has over a million records, so I can't simply loop using the DATE as a counter. I am thinking of using the ID
Assuming the date column and the ID column are sequential you could do a simple loop. By this I mean that if there is a record id=1 and date=2016-1-1 then record id=2 date=2015-12-31 could not exist. If you are worried about locks/exceptions you should add a transaction in the WHILE block and commit or rollback on failure.
Change the #batchSize to whatever you feel is right after some experimentation.
DECLARE #currentId int, #maxId int, #batchSize int = 10000
SELECT #currentId = MIN(ID), #maxId = MAX(ID) FROM YOURTABLE WHERE DATE >= '2016-01-01'
WHILE #currentId < #maxId
BEGIN
UPDATE YOURTABLE SET FLAG = 1 WHERE ID BETWEEN #currentId AND (#currentId + #batchSize)
SET #currentId = #currentId + #batchSize
END
As this as the update will never flag the same record to 1 twice I do not see a need to track which records were touched unless you are going to manually stop the process partway through.
You should also ensure that the ID column has an index on it so the retrieval is fast in each update statement.
Looks like a simple question or maybe I'm missing something.
You can create a temp/permanent table to keep track of updated rows.
create tbl (Id int) -- or temp table based on your case
insert into tbl values (0)
declare #lastId int = (select Id from tbl)
;with cte as (
select top 100000
from YourMainTable
where Id > #lastId
ORDER BY Id
)
update cte
set Flag = 1
update tbl set Id = #lastId + 100000
You can do this process in a loop (except the table creation part)
create table #tmp_table
(
id int ,
row_number int
)
insert into #tmp_table
(
id,
row_number
)
--logic to load records from base table
select
bt.id,
row_number() over(partition by id order by id ) as row_number
from
dbo.bas_table bt
where
--ur logic to limit the records
declare #batch_size int = 100000;
declare #start_row_number int,#end_row_number int;
select
#start_row_number = min(row_number),
#end_row_number = max(row_number)
from
#tmp_table
while(#start_row_number < #end_row_number)
begin
update top #batch_size
bt
set
bt.flag = 1
from
dbo.base_table bt
inner join #tmp_table tt on
tt.Id = bt.Id
where
bt.row_number between #start_row_number and (#start_row_number + #batch_size)
set #start_row_number = #start_row_number + #batch_size
end

Generate a repetitive sequential number using SQL Server 2008

Can anyone help me to generate a repetitive sequential number using SQL Server 2008. Say I have a table of 1000 rows and a new field (int) added to the table. All I need is to auto fill that particular field with sequential numbers 1-100 all the way to the last row.
I have this but doesnt seem that it is working. You help is much appreciated.
DECLARE #id INT
SET #id = 0
while #id < 101
BEGIN
UPDATE Names SET id=#id
set #id=#id+1
END
USE tempdb
GO
DROP TABLE tableof1000rows
GO
CREATE TABLE tableof1000rows (id int identity(1,1), nb int, value varchar(128))
GO
INSERT INTO tableof1000rows (value)
SELECT TOP 1000 o1.name
FROM sys.objects o1
CROSS JOIN sys.objects o2
GO
UPDATE t1
SET nb = t2.nb
FROM tableof1000rows t1
JOIN (SELECT id, (ROW_NUMBER() OVER (ORDER BY id) % 100) + 1 as nb FROM tableof1000rows) t2 ON t1.id = t2.id
GO
SELECT *
FROM tableof1000rows
Use ROW_NUMBER to generate a number. Use modulo maths to get values from 1 to 100.
go
create table dbo.Demo1
(
DID int not null identity primary key,
RepetitiveSequentialNumber int not null
) ;
go
insert into dbo.Demo1 values ( 0 )
go 1000 -- This is just to get 1,000 rows into the table.
-- Get a sequential number.
select DID, row_number() over ( order by DID ) as RSN
into #RowNumbers
from dbo.Demo1 ;
-- Take the last two digits. This gives us values from 0 to 99.
update #RowNumbers
set RSN = RSN % 100 ;
-- Change the 0 values to 100.
update #RowNumbers
set RSN = case when RSN = 0 then 100 else RSN end ;
-- Update the main table.
update dbo.Demo1
set RepetitiveSequentialNumber = r.RSN
from dbo.Demo1 as d inner join #RowNumbers as r on r.DID = d.DID ;
select *
from dbo.Demo1 ;
Not pretty or elegant, but..
while exists(select * from tbl where new_col is null)
update top(1) tbl
set new_col=(select ISNULL(max(new_col),0)+1 from tbl WHERE new_col is null)
Your way isn't working because you are trying to set a value rather than insert one. I'm sure you have found a solution by now but if not then try this instead.
DECLARE #id INT
SET #id = 0
while #id < 101
BEGIN
INSERT Names select #id
set #id=#id+1
END

Resources