sql accumulating decimals at the end - sql

I need help with a simple query for dividing a value to equal numbers. But I also want to avoid decimals values to accumulate them all to the final value.This is achievable by query below, however if I choose different datatype such as MONEY or NUMERIC for #amt, it gets tricky. Can anyone do what I did below with MONEY or NUMERIC datatype?
DECLARE #tbl TABLE (id TINYINT, princ NUMERIC (14,2))
DECLARE #counter INT = 1
DECLARE #term TINYINT = 12
DECLARE #amt INT = 50000
DECLARE #decimals INT = #amt-((#amt/#term)*#term)
WHILE #counter <= #term
BEGIN
INSERT INTO #tbl (id,princ)
SELECT #counter, #amt/#term
SET #counter = #counter + 1
UPDATE #tbl SET princ = princ+#decimals WHERE id = #term
END
SELECT * FROM #tbl

<Do what you need to do you populate the table, excluding the last row>
INSERT INTO
#tbl (
id,
princ
)
SELECT
MAX(id) + 1,
#amt - SUM(princ)
FROM
#tbl
Or, perhaps, with a CTE...
WITH
recursive_distribution AS
(
SELECT 0 AS id, CAST(#amt / #term AS INT) AS princ
UNION ALL
SELECT id + 1, princ
FROM recursive_distribution
WHERE id < #term - 1
UNION ALL
SELECT 1, #amt - (princ * (#term-1))
FROM recursive_distribution
WHERE id = #term - 1
)
INSERT INTO
#tbl (id, princ)
SELECT
id, princ
FROM
recursive_distribution
WHERE
id > 0
OPTION
(MAXRECURSION 0) -- Only needed if #term >= 100
(Starting at 0, then excluding row 0 means this can cope with #term = 1)

Related

SQL Loop based calculation

I have following data:
Calculation is the column consists of following formula:
For Exmaple H12 = =(C12-D12+C12-E12+C12-F12+C12-G12)
+(D12-C12+D12-E12+D12-F12+D12-G12)
+(E12-C12+E12-D12+E12-F12+E12-G12)
+(F12-C12+F12-D12+F12-E12+F12-G12)
+(G12-C12+G12-D12+G12-E12+G12-F12)
In short for category 1, (col1 value i.e. 2 minus col1 value) into (col1 value minus value for rest columns) by keeping col1 as fix then Plus same for col2.
Additional conditions are there can be any number of columns (cols) and any number of categories.
So I have attempted to write a code. But how to write a calculation part?:
DECLARE #i int = 0
DECLARE #j int = 0
DECLARE #k int = 1
DECLARE #Category_Count int = (select count(distinct Category) from Table) --This I am getting some other table
DECLARE #columns int = (select count(distinct columns) from Table) --This I am getting some other table
WHILE #i < #Category_Count
BEGIN
WHILE #j < #columns
BEGIN
WHILE #k < #Category_Count
BEGIN
/* Calculation=
How to write this part */
SET #k = #k + 1
END
SET #j = #j + 1
END
SET #i = #i + 1
END
create table #tab1
(
Category varchar(1),
col1 int,
col2 int,
col3 int,
col4 int,
col5 int,
Calculation int
)
insert into #tab1 values('a',2,3,1,1,3,0),('b',1,3,3,3,2,0),('c',12,5,4,2,6,0),('d',2,3,4,3,2,0),('e',2,1,2,1,1,0),('f',2,3,1,2,2,0),('g',2,3,1,1,3,0),('h',2,3,1,1,3,0),('i',2,3,1,1,3,0),
('j',2,3,1,1,3,0)
DECLARE #count int = 0
DECLARE #j int = 0
DECLARE #k int = 1
select row_number() over(order by category) as RN,* into #tab2 from #tab1
select #count=count(*) from #tab1
WHILE (#k <= #count)
BEGIN
update #tab2 set Calculation=
((col1-col2+col1-col3+col1-col4+col1-col5)
+(col2-col2+col2-col3+col2-col4+col2-col5)
+(col3-col2+col3-col2+col3-col4+col3-col5)
+(col4-col2+col4-col2+col4-col3+col4-col5)
+(col5-col2+col5-col2+col5-col3+col5-col4))
from #tab2 where RN= #k
set #k=#k+1
END

How to insert a comma in between a string

I having a string and i need to insert a comma in between a given string. Please help me with some ideas
declare #a varchar(15) = 'asdfghj'
select #a
I expect the output as 'a,s,d,f,g,h,j'
This will work in sql server 2008
DECLARE #a varchar(max)='23'
DECLARE #b INT=len(#a)
WHILE #b>1
SELECT #a=stuff(#a,#b,0,','),#b-=1
SELECT #a
If you ever upgrade to sql server 2017, you can use this version, assuming the string is no longer than 2047 characters(2047 is the max number in spt_values with type=P, otherwise you can use a number table or something similar):
DECLARE #a varchar(max)='abcd'
SELECT string_agg(substring(#a,number+1,1),',')
FROM master..spt_values
WHERE number < len(#a)
and type='P'
SQL Server isn't designed to do this, but here's a simple way to do this in a while loop;
DECLARE #a varchar(15) = 'asdfghj'
DECLARE #i int = 1
DECLARE #output varchar(30)
WHILE (#i <= len(#a))
BEGIN
SET #output = ISNULL(#output,'') + ',' + SUBSTRING(#a, #i, 1)
SET #i = #i + 1
END
SET #output = STUFF(#output,1,1,'')
SELECT #output
Output: a,s,d,f,g,h,j
It takes each separate character of input and inserts a comma before it. The STUFF function removes the very first comma in the string, you could do this with a SUBSTRING or RIGHT if you preferred.
This would work on any length string you pass in if you tweak the length of #a and #output
Consider carefully Panagiotis Kanavos's comment, but if you want to do this with T-SQL, one possible approach is using recursive CTE and FOR XML (supported from SQL Server 2008):
DECLARE #a varchar(15) = 'asdfghj'
;WITH cte AS (
SELECT SUBSTRING(#a, 1, 1) AS Symbol, 1 AS Position
UNION ALL
SELECT SUBSTRING(#a, Position + 1, 1), Position + 1
FROM cte
WHERE Position < LEN(#a)
)
SELECT STUFF((
SELECT CONCAT(N',', Symbol)
FROM cte
ORDER BY Position
FOR XML PATH('')
), 1, 1, N'') AS Result
OPTION (MAXRECURSION 0)
Output:
Result
a,s,d,f,g,h,j
Notes:
Just for information, starting with SQL Server 2017 you may use STRING_AGG():
DECLARE #a varchar(15) = 'asdfghj'
;WITH cte AS (
SELECT SUBSTRING(#a, 1, 1) AS Symbol, 1 AS Position
UNION ALL
SELECT SUBSTRING(#a, Position + 1, 1), Position + 1
FROM cte
WHERE Position < LEN(#a)
)
SELECT STRING_AGG(Symbol, ',')
FROM cte
OPTION (MAXRECURSION 0)
As has already been mentioned, this is a terrible idea.
Do not do this in SQL Server if you can avoid it.
That said, if for some reason you can't avoid it, a set based solution to this would involve a numbers table. If you don't have one of these you can generate one with little overhead using a cte. To work with just one value you can do this:
declare #a varchar(15) = 'asdfghj';
with t(t)as(select t from(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))as t(t))
,n(n)as(select top(len(#a)) row_number()over(order by(select null)) from t,t t2,t t3,t t4)
select #a = stuff((select ',' + substring(#a,n,1)
from n
order by n
for xml path('')
)
,1,1,''
);
select #a;
Which outputs: a,s,d,f,g,h,j
Or to apply this logic to a table of values you can do the following:
declare #t table (a varchar(100));
insert into #t values('asdfghj'),('qwerty'),('qwertyuiopasdfghjklzxcvbnm[];#?|');
with t(t)as(select t from(values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))as t(t))
,n(n)as(select top(select max(len(a)) from #t) row_number()over(order by(select null)) from t,t t2,t t3,t t4)
select stuff((select ',' + substring(a,n,1)
from n
where n.n <= len(t.a)
order by n.n
for xml path('')
)
,1,1,''
) as CommaDelimited
from #t as t;
Which outputs:
+---------------------------------------------------------------------------+
| CommaDelimited |
+---------------------------------------------------------------------------+
| a,s,d,f,g,h,j |
| q,w,e,r,t,y |
| q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m,[,],;,#,?,| |
+---------------------------------------------------------------------------+
Hope this works, i used the STUFF() function to insert the ,
declare #a varchar(15) = 'asdfghj'
declare #totalchar int = len(#a)
declare #counter int = #totalchar
while #counter >=1
begin
if #counter + 1 <= #totalchar
begin
set #a = stuff(#a,#counter + 1, 0 , ',')
end
set #counter = #counter -1
end
select #a
I agree with Panagiotis Kanavos comment strongly. Below snippet will work for you.
Declare #a varchar(15) = 'asdfghj',#OUTPUT VARCHAR(MAX)= ''
SELECT #OUTPUT = #OUTPUT+AA+',' FROM (
SELECT number ,SUBSTRING(#a, number, 1) AA
FROM master.DBO.spt_values WHERE TYPE = 'P'
AND number BETWEEN 1 AND LEN(#a)
)A
Order by number
SELECT SUBSTRING(#OUTPUT,1,LEN(#OUTPUT)-1)
I used spt_values which is undocumented by Microsoft, which is not preferred. You can try the same with Numbers table instead.
Try This easy method...
DECLARE #INPUT VARCHAR(10) = 'ABCD'
DECLARE #i INT = 1
DECLARE #OUTPUT VARCHAR(50) = ''
WHILE #I < = LEN(#INPUT)
BEGIN
SET #OUTPUT = #OUTPUT + SUBSTRING(#INPUT,#i,1) + ','
SET #i = #i + 1
END
SET #OUTPUT = SUBSTRING(#OUTPUT,1,LEN(#OUTPUT) - 1)
PRINT #OUTPUT

How select row from where condition passing MemberID ? Column contains List of memberID

I have column called MemberID which contains list of memberid like
( 1|2|3|12|23|12 ) all Id separated by | I want can i pass one ID so get all row which contain provided ID.
Please try this
Supported Starting from SQL 2016
CREATE TABLE #Test
(
RowID INT IDENTITY(1,1),
EID INT,
Sampl VARCHAR(50)
)
INSERT INTO #Test VALUES (1,'1|2|3|4')
INSERT INTO #Test VALUES (2,'1|2|3|5')
INSERT INTO #Test VALUES (3,'1|2|3|4')
INSERT INTO #Test VALUES (4,'1|2|3|6')
GO
SELECT * FROM #Test
CROSS APPLY STRING_SPLIT ( Sampl , '|' )
WHERE Value IN ('4')
RowID EID Sampl value
----------- ----------- -------------------------------------------------- --------------------------------------------------
1 1 1|2|3|4 4
3 3 1|2|3|4 4
For lower versions of SQL you may need to create a function to do the process
CREATE FUNCTION dbo.SplitString(#String NVARCHAR(MAX), #Delimiter CHAR(1))
RETURNS #Results TABLE (Result NVARCHAR(MAX))
AS
BEGIN
DECLARE #Index INT
DECLARE #Data NVARCHAR(MAX)
SELECT #Index = 1
IF #String IS NULL RETURN
WHILE #Index != 0
BEGIN
SELECT #Index = CHARINDEX(#Delimiter, #String)
IF #Index != 0
SELECT #Data = LEFT(#String, #Index - 1)
ELSE
SELECT #Data = #String
INSERT INTO #Results(Result) VALUES (#Data)
SELECT #String = RIGHT(#String, LEN(#String) - #Index)
IF LEN(#String) = 0 BREAK
END
RETURN
END
SELECT * FROM #Test
CROSS APPLY Gemini.dbo.SplitString ( Sampl , '|' )
WHERE Result IN ('4')
haven't tested it but this should work:
declare #Id varchar(10)='1'
select * from content_membeparticipation where charindex('|' + #Id + '|','|' + memberId + '|' )>0
but I agree with the comments, if at all possible, the best option is normalise the tables

Sql Query To Find Relevant Match

I am trying to write a query that should return the following output:
val1 = 'GREAT'
val2 = 'TAGER'
See above have the same characters and it should return 'Exist' if the match is found. Otherwise, if there is a character missing, then 'Not exist'. I am using CASE to get the required output and was wondering if it could be done using SUBSTRING, CHARINDEX etc. Hoping to get a solution or idea to do so. Thanks.
You can make it as a function / procedure according to your needs.
DECLARE #S1 VARCHAR(100)
DECLARE #S2 VARCHAR(100)
SELECT #S1 = val1
,#S2 = val2
FROM <TABLE_NAME>
DECLARE #c CHAR(1)
DECLARE #i TINYINT
DECLARE #o1 VARCHAR(100) = ''
DECLARE #o2 VARCHAR(100) = ''
WHILE DataLength(#s1) > 0
BEGIN
SET #c = Left(#s1, 1)
SET #s1 = Substring(#s1, 2, len(#s1))
SET #i = 1
WHILE #i <= Len(#o1)
AND #c > substring(#o1, #i, 1)
SET #i += 1
SET #o1 = left(#o1, #i - 1) + #c + substring(#o1, #i, len(#o1))
END
WHILE DataLength(#s2) > 0
BEGIN
SET #c = Left(#s2, 1)
SET #s2 = Substring(#s2, 2, len(#s2))
SET #i = 1
WHILE #i <= Len(#o2)
AND #c > substring(#o2, #i, 1)
SET #i += 1
SET #o2 = left(#o2, #i - 1) + #c + substring(#o2, #i, len(#o2))
END
SELECT CASE
WHEN #o1 = #o2
THEN 'Exist'
ELSE 'Not Exist'
END
This is a custom script for you
Run this SP first
IF(OBJECT_ID('CharSplit')) IS NOT NULL
DROP PROCEDURE CharSplit;
GO
CREATE PROC CharSplit
#Words VARCHAR(MAX)
AS
BEGIN
IF OBJECT_ID('tempdb..#temp1') IS NOT NULL
DROP TABLE #temp1;
CREATE TABLE #temp1
(
letter CHAR(1), freq INT
);
IF OBJECT_ID('tempdb..#temp2') IS NOT NULL
DROP TABLE #temp2;
CREATE TABLE #temp2
(
letter CHAR(1), freq INT
);
DECLARE #t VARCHAR(MAX);
DECLARE #I INT;
--SET #Words = 'sanuantony';
SELECT #I = 0;
WHILE(#I < LEN(#Words) + 1)
BEGIN
SELECT #t = SUBSTRING(#words, #I, 1);
INSERT INTO #temp1
(letter, freq
)
VALUES
(#t, 0
);
SET #I = #I + 1;
END;
TRUNCATE TABLE #temp2;
INSERT INTO #temp2
(letter, freq
)
SELECT letter, COUNT(freq)
FROM #temp1
GROUP BY letter;
SELECT *
FROM #temp2
ORDER BY letter;
END;
Now Just try your business logic
DECLARE #t1 AS TABLE
(
letter CHAR(1), freq INT
);
DECLARE #t2 AS TABLE
(
letter CHAR(1), freq INT
);
INSERT INTO #t1
EXEC charSplit 'alammalay';
INSERT INTO #t2
EXEC charSplit 'malayalam';
IF(
(
SELECT COUNT(1)
FROM #t1
) =
(
SELECT COUNT(1)
FROM #t2
)
AND
(
SELECT COUNT(1)
FROM #t2
) =
(
(
SELECT COUNT(1)
FROM #t1
)
UNION
(
SELECT COUNT(1)
FROM #t2
)
)
)
SELECT 'Both are matching' AS result;
ELSE
SELECT 'Both are not matching' AS result;

How to insert split-ed amount into a table without using loop?

I need to split an amount into multiple part and insert into an table called installment, how can i implement it without using loop?
declare #installment as table (installment_index int identity(1,1),
amount money,
due_date datetime)
declare #total_amount money
declare #number_of_installment int
declare #amount money
declare #i int
declare #date datetime
set #date = getdate()
set #number_of_installment = 20
set #total_amount = 5001.00
set #amount = #total_amount / #number_of_installment
set #i= 1
while #i <= #number_of_installment
begin
insert into #installment
(amount,due_date) values (#amount, dateadd(month,#i,#date))
set #i = #i + 1
end
This would replace while loop:
;with numbers as (
select 1 number
union all
select number + 1
from numbers
where number < #number_of_installment
)
insert into #installment (amount,due_date)
select #amount, dateadd(month,number,#date)
from numbers
option (maxrecursion 0)
CTE numbers returns table of numbers from 1 to #number_of_installment
insert uses this table to insert #number_of_installment records to #installment.
EDIT:
I must mention that, according to this article, nothing beats auxiliary table of numbers/dates for similar purposes.