Stored Procedure/Function Logic for Summing of account balances - sql

Version B C D output Account number Balance
----------------------------------------------------------------
2 1283 1303 0 4071 1 10
2 1283 1304 0 4072 2 20
3 1283 4068 1303 4071 1 30
3 1283 4069 1304 4072 4 40
4 1283 4071 4068 4071 5 -50
4 1283 4072 4069 4072 2 90
Version,B,C,D are columns present in all_details table.Column "Output" is the desired output i wish to achieve and i wish to store all the above columns in a table until output
How in the first line of Output i placed 4071 is
1) i took 1303 in column C and then looked into column D
2) then it is again referring to 4068 in column C
3) Then i took 4068 and it is refering to 4071 in column C its like a linkage
I'm using column B as it is related information to other columns.
i need a another column output so that i can identify related links and sum up balances.For example
I'll sum up related links 1303,4068,4071 balances Group by Output i'll get 10+40 = 50 for account 1 and -50 for account 5 corresponding to 4071

So from what I am understanding, you need to have something that recursively finds the last linked number, for a given number. These links exists between your Column C and Column D.
I am assuming that Column B is a grouping type number, but I am certain that you might be able to figure out how to adjust the function to return what you need it to return.
What you will need to do is build a SQL function that will iterate through your table and follow the links until it can find no more links. Details and example follow below as to how one can do that.
So firstly the building up of the sample data, as you presented them (NOTE this creates a table on your database, be careful!)
-- Building testing data
if exists (select 1
from sys.objects
where name = 'versionhistory'
and type = 'U')
begin
drop table versionhistory
end
create table versionhistory
( versionno int,
colB int,
colC int, -- Current Value
colD int ) -- Previous value
insert versionhistory
( versionno,
colB,
colC,
colD )
values -- B C D
( 2, 1283, 1303, 0),
( 2, 1283, 1304, 0),
( 3, 1283, 4068, 1303),
( 3, 1283, 4069, 1304),
( 4, 1283, 4071, 4068),
( 4, 1283, 4072, 4069)
go
Now we need to create the function that will iterate through the tables records, follow the links between the two columns until it can find no more links, then return the last linked value.
-- Create the function that will get the last entry for a give number
if exists (select 1 from sys.objects where name = 'f_get_last_ver' and type = 'FN')
begin
drop function f_get_last_ver
end
go
create function f_get_last_ver
( #colB int,
#colC int )
returns int
as
begin
declare #nextColC int,
#lastColC int
-- Initial check if there is a 'next' version
select #nextColC = isnull((select vh.colC
from versionhistory vh
where vh.colB = #colB
and vh.colD = #colC), 0)
-- This will handle the loop until there are no more entries linked
while isnull(#nextColC, 0) <> 0
begin
-- Store our last value for return purposes
select #lastColC = #nextColC
-- Get our next version number that is linked
select #nextColC = isnull((select vh.colC
from versionhistory vh
where vh.colB = #colB
and vh.colD = #nextColC), 0)
end
-- Return our last value, otherwise if no linkage was found, return the input
return isnull(#lastColC, #colC)
end
go
And finally here you have the usage of the function.
-- Example usage
select dbo.f_get_last_ver(1283, 1303), -- returns 4071
dbo.f_get_last_ver(1283, 1304) -- returns 4072
Remember to clean up your database when done testing/experimenting with this
I hope that the comments inside the function will explain enough of what is happening, but if something is unclear, ask away.
P.S. Please rename your columns and variables in your actual code into more meaningful column names and variables, since B, C, D doesn't really explain what they are used for.

Related

Accessing specific row value by id in a scalar SQL function

How can I use a specific value of a field 'ranking' from table 'course' by 'course_id' in a function? I need to return 1 if the value given by a parameter is higher and 0 if the value is lower. So, I need to get the data from the table somehow based on course_id as a parameter.
CREATE OR ALTER FUNCTION dbo.f_rank (#course_id INT, #user_ranking INT)
RETURNS INT
AS
BEGIN
RETURN
CASE
WHEN #course_id.ranking > #user_ranking THEN 1
ELSE 0
END
END
After the function returns 0 or 1 I need to display:
If on call function returns 1 then display ‘Ranking of <course_name> is above score’, else ‘Ranking of <course_name> is below score’.
Sample data:
course_id | course_name | ranking
1 | GIT | 10
2 | CSS | 2
3 | C++ | 6
I need to compare a ranking of course_id = 1 for example which is 10 with the random number given as a parameter. course_id is also given as a parameter.
For example:
If the user chooses as input params (course_id = 1 and user_ranking = 5)
Expected result:
'Ranking of GIT is above score' - if function returns 1
'Ranking of GIT is below score' - if function returns 0
I assume that you probably want something like this (no expected results were given on request):
--Note you will have to DROP your old function if already exists as a scalar function first.
--You cannot ALTER a scalar function to be a table function.
CREATE OR ALTER FUNCTION dbo.f_rank (#course_id INT, #user_ranking INT)
RETURNS table
AS RETURN
SELECT CONVERT(bit, CASE WHEN C.ranking > #user_ranking THEN 1 ELSE 0 END) AS SomeColumnAlias --Obviusly give this a proper name
FROM dbo.Course C
WHERE C.Course_id = #course_id;
GO
As I mentioned, I use an inline table value function, as this will likely be more performant, and you don't mention your version of SQL Server (so don't know if you are on 2019, and so could use an inlineable scalar function).

Given a table of numbers, can I get all the rows which add up to less than or equal to a number?

Say I have a table with an incrementing id column and a random positive non zero number.
id
rand
1
12
2
5
3
99
4
87
Write a query to return the rows which add up to a given number.
A couple rules:
Rows must be "consumed" in order, even if a later row makes it a a perfect match. For example, querying for 104 would be a perfect match for rows 1, 2, and 4 but rows 1-3 would still be returned.
You can use a row partially if there is more available than is necessary to add up to whatever is leftover on the number E.g. rows 1, 2, and 3 would be returned if your max number is 50 because 12 + 5 + 33 equals 50 and 90 is a partial result.
If there are not enough rows to satisfy the amount, then return ALL the rows. E.g. in the above example a query for 1,000 would return rows 1-4. In other words, the sum of the rows should be less than or equal to the queried number.
It's possible for the answer to be "no this is not possible with SQL alone" and that's fine but I was just curious. This would be a trivial problem with a programming language but I was wondering what SQL provides out of the box to do something as a thought experiment and learning exercise.
You didn't mention which RDBMS, but assuming SQL Server:
DROP TABLE #t;
CREATE TABLE #t (id int, rand int);
INSERT INTO #t (id,rand)
VALUES (1,12),(2,5),(3,99),(4,87);
DECLARE #target int = 104;
WITH dat
AS
(
SELECT id, rand, SUM(rand) OVER (ORDER BY id) as runsum
FROM #t
),
dat2
as
(
SELECT id, rand
, runsum
, COALESCE(LAG(runsum,1) OVER (ORDER BY id),0) as prev_runsum
from dat
)
SELECT id, rand
FROM dat2
WHERE #target >= runsum
OR #target BETWEEN prev_runsum AND runsum;

Auto Generated Serial Number using Stored Procedure

I want to create a procedure which would create a serial number using a stored procedure.
I have three tables:
Table 1:
create table ItemTypes
(
ItemTypeID int not null primary key,
ItemType varchar(30)
)
Table 2:
create table ItemBatchNumber
(
ItemBatchNumberID int not null primary key,
ItemBatchNumber varchar(20),
ItemType varchar(30),
)
Table 3:
create table ItemMfg
(
ManufactureID int not null primary key,
ItemBatchNumberID int foreign key references ItemBatchNumber(ItemBatchNumberID),
SerialNumber varchar(10),
MfgDate datetime
)
For each Item Type there are several Item batch number.
Now, first 3 digit of serial no is xyz. 4 digit of serial no should be Item Type(e.g if Item type is 'b' then serial no should be xyzb).
5 digit of serial no should be like this:
In a day, for first Item batch number of a Item type- 5th digit should be 1 and it will remain 1 for that day.For the next Item batch number it should be 2 and it will remain 2 for that day.
For next day same rule applied.
e.g suppose 'b' Item Type has 3 Item batch number WB1,WB2,WB3. If today someone select WB2(Item batch number) of 'b' Item Type first then Serial No should be xyzb1 and it will remain xyzb1 for today for WB2. Now if someone select WR1 next then Serial No should be xyzb2 for today. Tomorrow which Item batch number of 'b' Item type will be selected first, for that batch number and that type serial no should be xyzb1. Same rule applied for other item type.
I have tried till now:
create procedure Gen_SerialNumber
(
#ManufactureID int,
#IitemType varchar(30),
#ItemBatchNumberID int,
#Date datetime,
#SerialNumber out,
#fifthDigit int out
)
AS
Begin
set #IitemType=(Select ItemType from ItemBatchNumber where ItemBatchNumber=#ItemBatchNumber)
Declare #SerialNumber1 varchar(20)
Set #SerialNumber1= 'xyz'+''+#IitemType+''+CAST( (Select COUNT(distinct ItemBatchNumber)from ItemBatchNumber
where ItemType=#IitemType) as varchar (10) )
Set #fifthDigit=SUBSTRING(#SerialNumber1,5,1)
IF EXISTS(SELECT SerialNumber FORM ItemMfg WHERE SerialNumber=null or SerialNumber!=#SerialNumber)
SET #fifthDigit=1
IF EXISTS(SELECT mfgDate,ItemBatchNumberID FROM ItemMfg WHERE mfgDate=#Date and ItemBatchNumberID=#ItemBatchNumberID)
SET #fifthDigit=1
ELSE
SET #fifthDigit=#fifthDigit+1
SET #SerialNumber=('xyz'+''+#ItemType+''+cast(#fifthdigit as varchar(2)))
INSERT INTO ItemMfg VALUES(#ItemType,#ItemBatchNumberID,#SerialNumber,#Date)
END
I am new to SQL. 4rth digit of SN is generated correctly from my code. I am facing problem on how to increment the value of fifth digit checking with dates when next different item batch number of a same item type or different item type is used. Please let me know if you have any doubt. Thanks in advance.
A couple of concerns with your question: I am confused by the appearance of the WireBatchNumber column in your stored procedure, as that is not included in the table definition you provided. Secondly, the last #ItemType (right before the end command) is misspelled.
I think the challenge here is that you need your stored procedure increment a variable across batches and to "start over" each day. That would suggest to me that you need the procedure to
1. Track the last time it was run and see if it was today or not.
2. Track how many times it has been run today.
It is not a very beginner level knowledge type of task, but there is a way, apparently: how to declare global variable in SQL Server..?. Using the type of variable mentioned in this link, you could set up some conditional structures that compare a date variable to today using the DateDiff() function, changing the date variable and resetting your counters if they the two dates are on different days, then incrementing a counter for the item batch number and using this counter to provide the fifth digit.
Let's populate some data:
ItemTypes:
ItemTypeID ItemType
1 a
2 b
3 c
ItemBatchNumber:
ItemBatchNumberID ItemBatchNumber ItemType
...
11 WB1 b
22 WB2 b !
33 WB3 b !!
44 WB1 c
55 WB2 c !
66 WB3 c !!
77 WB3 c !!
...
ItemMfg:
ManufactureID ItemBatchNumberID MfgDate
111 22 2015-03-01 7:00 -> xyzb1
222 11 2015-03-01 8:00 -> xyzb1
333 22 2015-03-01 9:00 -> xyzb2
444 33 2015-03-02 5:00 -> xyzb1
555 33 2015-03-02 6:00 -> xyzb2
666 11 2015-03-02 7:00 -> xyzb1
777 33 2015-03-02 8:00 -> xyzb3
888 11 2015-03-02 9:00 -> xyzb2
999 22 2015-03-02 9:35 -> xyzb1
I see some inappropriate things - it does not necessary means there are mistakes.
Sorry, I do not know real business rules - they may explain things differently.
I assume the simplest and most usable logic.
ItemTypes - is looks like lookup table. But you do not use ItemTypeID, instead you use it's value (a,b,c) as unique code.
So table should looks like this:
ItemType(PK) ItemTypeDescr
a Type A
b Most Usable Type B
c Free text for type C
ItemBatchNumber - match table - define matches between batches and ItemTypes.
It may have data as marked with "!", and this is valid.
To avoid 77 need add some unique index or set PK on ItemBatchNumber+ItemType.
But in any case sets as 22,55 and 11,44.. are normaly expected. So your SP will fails.
Here you will get an error.
Here query may return multipple rows (22, 55):
set #IitemType=(Select ItemType from ItemBatchNumber where ItemBatchNumber=#ItemBatchNumber)
alternatively:
Select #IitemType = ItemType from ItemBatchNumber where ItemBatchNumber=#ItemBatchNumber
in case of multipple rows it will return last one.
But anyway it is not correct - if #ItemBatchNumber = 'WB2' which value is expected 'b' or 'c'?
errors:
...
... and ItemTypeID=#IitemType ...
ItemTypeID is int;
#IitemType is char ('b') - what do you expect?
...
MfgDate=#Date
Dates '2015-03-02 5:00' and '2015-03-02 7:00' are not the same, but in the same day.
...
...from ItemBatchNumber,ItemMfg
where MfgDate=#Date
and ItemBatchNumber=#ItemBatchNumber
and ItemTypeID=#IitemType
even if ItemBatchNumber will return one row, and date will not count time, it will return ALL batches from ItemMfg from one day.
you need to do proper join.
...
Select COUNT(distinct ItemBatchNumber)
you always will need (count() + 1), and not a distinct
I am not sure, when you need to generate SN:
a. at the moment when (before) you ADD new ItemMfg, then need to check for current day.
b. for any existed ItemMfg row (for 555), then you need to exclude from count() all rqws after 555.
That's how query may looks:
a. (on adding new ItemMfg-row - pass values used for creation ItemMfg-row)
create procedure AddNewItemMfg -- ...and generate SN
(
#ItemBatchNumberID int,
#MfgDate datetime -- pass getdate()
) AS Begin
-- calc 5th digit:
declare #fifthDigit int;
select
#fifthDigit = count(*) + 1
from ItemBatchNumber AS bb
inner join ItemMfg ii ON ii.ItemBatchNumberID = bb.ItemBatchNumberID
where bb.ItemBatchNumberID = #ItemBatchNumberID -- single ItemBatchNumber-row to get ItemType
and ii.MfgDate <= #MfgDate -- all previous datetimes
and cast(ii.MfgDate as date) = cast(#MfgDate as date) -- same day
-- ManufactureID is Identity (i.e. autoincremented)
INSERT INTO ItemMfg (ItemBatchNumberID, MfgDate, SerialNumber)
SELECT #ItemBatchNumberID
, #MfgDate
, 'xyz' + bb.ItemType + cast(#fifthDigit as varchar(5))
FROM ItemBatchNumber bb
WHERE bb.ItemBatchNumber = #ItemBatchNumber
;
end
b. for any already existed ItemMfg-row
create procedure GenerateSNforExistedItemMfg
(
#ManufactureID int
) AS Begin
-- generate SN - same as previous but in single query
declare #SN varchar(10);
Select #SN = 'xyz'
+ bb.ItemType
+ cast(
select count(*) + 1
from ItemMfg mm -- single row from mm
inner join ItemBatchNumber AS bb -- single row from bb
ON bb.ItemBatchNumberID = mm.ItemBatchNumberID
inner join ItemMfg ii -- many matched rows
ON ii.ItemBatchNumberID = bb.ItemBatchNumberID
where mm.ManufactureID = #ManufactureID -- single row from mm
and ii.MfgDate <= mm.MfgDate -- all previous datetimes
and cast(ii.MfgDate as date) = cast(mm.MfgDate as date) -- same day
as varchar(5))
FROM ItemBatchNumber AS bb
INNER JOIN ItemMfg ii ON ii.ItemBatchNumberID = bb.ItemBatchNumberID
WHERE ii.ManufactureID = #ManufactureID
-- update SN
UPDATE ItemMfg SET
SerialNumber = #SN
WHERE ii.ManufactureID = #ManufactureID;
end

List category/subcategory tree and display its sub-categories in the same row

I have a hierarchical table of Regions and sub-regions, and I need to list a tree of regions and sub-regions (which is easy), but also, I need a column that displays, for each region, all the ids of it's sub regions.
For example:
id name superiorId
-------------------------------
1 RJ NULL
2 Tijuca 1
3 Leblon 1
4 Gavea 2
5 Humaita 2
6 Barra 4
I need the result to be something like:
id name superiorId sub-regions
-----------------------------------------
1 RJ NULL 2,3,4,5,6
2 Tijuca 1 4,5,6
3 Leblon 1 null
4 Gavea 2 4
5 Humaita 2 null
6 Barra 4 null
I have done that by creating a function that retrieves a STUFF() of a region row,
but when I'm selecting all regions from a country, for example, the query becomes really, really slow, since I execute the function to get the region sons for each region.
Does anybody know how to get that in an optimized way?
The function that "retrieves all the ids as a row" is:
I meant that the function returns all the sub-region's ids as a string, separated by a comma.
The function is:
CREATE FUNCTION getSubRegions (#RegionId int)
RETURNS TABLE
AS
RETURN(
select stuff((SELECT CAST( wine_reg.wine_reg_id as varchar)+','
from (select wine_reg_id
, wine_reg_name
, wine_region_superior
from wine_region as t1
where wine_region_superior = #RegionId
or exists
( select *
from wine_region as t2
where wine_reg_id = t1.wine_region_superior
and (
wine_region_superior = #RegionId
)
) ) wine_reg
ORDER BY wine_reg.wine_reg_name ASC for XML path('')),1,0,'')as Sons)
GO
When we used to make these concatenated lists in the database we took a similar approach to what you are doing at first
then when we looked for speed
we made them into CLR functions
http://msdn.microsoft.com/en-US/library/a8s4s5dz(v=VS.90).aspx
and now our database is only responsible for storing and retrieving data
this sort of thing will be in our data layer in the application

Comparing two tables for each column of each row and result as log variable

I need to compare two tables in each row. the tables are as follows:-
Table a:
ID First_Name Last_name Birthdate
1 Shradha Deshkmukh 1981-12-25 00:00:00
2 Shradha Verma 1981-05-11 00:00:00
3 Divya Dutta 1982-07-21 00:00:00
4 Matthew Palmer 1983-12-28 00:00:00
table d:-
id fn ln dob
1 Shradha Tiwari 1981-12-25 00:00:00
2 Divya Dutta 1983-07-21 00:00:00
3 Sulabh Manesar 1975-09-11 00:00:00
4 Matthew Palmer 1983-12-28 00:00:00
5 Samuel Maxwell 1984-05-22 00:00:00
Now my original table is having about 17 columns and this is just a simpler version of it. The tables 'A' and 'D' are generated by a query. Table 'A' will be populated and Table D will be like a temporary table that gets its values from the query, compares all the First Names of each table and if it encounters any change , it needs to update the log table By the first name and also mentions all the columns that are different.
For this I have created a temporary table, viz. '#TMP_COMPARE' which takes all the columns of table 'a'. and then compares those columns against that of table 'd' and it has the columns PLN, PDOB, Pmatch which have values 0 by default and are set to one in case all columns match for that row(Pmatch=1), Last name matches (PLN=1), Dob matches (Pdob=1).
Once this '#TMP_COMPARE' compares the two tables I will then update the log table with the columns that dont match for a first name.
USE Testing
GO
IF OBJECT_ID('TEMPDB..#TMP_COMPARE') is not null
BEGIN
DROP TABLE #TMP_COMPARE
END
CREATE TABLE #TMP_COMPARE(
FN varchar(20),
LN varchar(20),
dob smalldatetime,
PLN int default 0,
Pdob int default 0,
Pmatch int default 0)
BEGIN
INSERT INTO #TMP_COMPARE
SELECT a.fn, a.ln, a.dob,
case when a.ln = d.Last_name AND a.dob = d.Birthdate
THEN 1
END AS #TMP_COMPARE.PMATCH,
CASE WHEN a.dob <> d.Birthdate
THEN 0
WHEN a.dob = d.Birthdate then 1
END AS #TMP_COMPARE.Pdob,
CASE WHEN a.ln <> d.Last_name
THEN 0
WHEN a.ln = d.Last_name
then 1
END AS #TMP_COMPARE.PLN
FROM dbo.a2 as a
JoIN d ON a.fn = d.First_Name
END
SELECT * FROM #TMP_COMPARE
Error I am getting is :-
Msg 102, Level 15, State 1, Line 24
Incorrect syntax near '.'
What is wrong in my query, and should I do this any other way please advice.
Now this is something very basic that is wrong in my query but I am a newbie and any help is very much appreciated.
Thanks in advance,
DCS
#log is a single scalar variable value and can only hold a single value at a given time. So you are unable to reference previous values of that variable, just what is currently there.
That means it's only going to display the last value (match) for every record in your select statement.
You may want to move the select inside the loop and change it to an insert into a logging table of some sort. That way you can just select * from loggingtable after the loop is over to retrieve all the data.
There is likely a way better way to handle this also, but I wanted to give a quick response (no time to refactor right now).
You can use a two character data field to store a result.
Position 1 = LN match state
Position 2 = Birthdate match state
11 = LN Match True ; Birthday Match True
10 = LN Match True ; Birthday Match False
01 = LN Match False; Birthday Match True
00 = LN Match False; Birthday Match True