Due to performance perspective I just need to remove loop and using some joins or other solution to update the data in #Result table and get the same result which return by loop.
Scalar function:
CREATE FUNCTION [MultiplyerScl]
(#a INT, #b INT)
RETURNS INT
AS
BEGIN
DECLARE #i AS INT = 2
DECLARE #Value AS INT
IF (#b % #a = 0)
BEGIN
SET #Value = #b
END
ELSE
BEGIN
WHILE (1=1)
BEGIN
IF((#b * #i) % #a = 0)
BEGIN
SET #Value = #b * #i
BREAK;
END
ELSE
BEGIN
SET #i = #i + 1
END
END
END
RETURN #Value
END
Table design and its value.
CREATE TABLE #NUM (Groupid INT, GroupValue INT)
INSERT INTO #NUM
VALUES (1, 8), (1, 9), (1, 23), (2, 5), (2, 5), (2, 10)
Main for loop logic.
SELECT
Groupid,
GroupValue,
MaxValue = MAX(GroupValue) OVER (PARTITION BY Groupid),
MaxCount = COUNT(1) OVER(),
RID = ROW_NUMBER() OVER (ORDER BY groupid)
INTO
#Result
FROM
#NUM
DECLARE #i AS INT = 1
DECLARE #RawCnt AS INT = (SELECT MAX(MaxCount) FROM #Result)
DECLARE #iGroupid INT
DECLARE #iGroupvalue INT
DECLARE #iMaxValue INT
WHILE(#i <= #RawCnt)
BEGIN
SELECT
#iGroupid = Groupid,
#iGroupvalue = Groupvalue,
#iMaxValue = MaxValue
FROM
#Result
WHERE
RID = #i
UPDATE #Result
SET MaxValue = dbo.[MultiplyerScl](#iGroupvalue, #iMaxValue)
WHERE Groupid = #iGroupid
SET #i = #i + 1
END
SELECT * FROM #Result
Try this out
SELECT
Groupid,
GroupValue,
MaxValue = MAX(GroupValue) OVER (PARTITION BY Groupid),
MaxCount = COUNT(1) OVER(),
RID = ROW_NUMBER() OVER (ORDER BY groupid)
INTO
#Result
FROM
#NUM
;WITH Res AS
(
SELECT Groupid, e.Groupvalue, dbo.[MultiplyerScl](Groupvalue, e.MaxValue) AS
MaxValue, 1 AS i
FROM #Result e
UNION ALL
--recursive execution
SELECT e.Groupid, m.Groupvalue, dbo.[MultiplyerScl](e.Groupvalue, m.MaxValue) AS MaxValue, m.i + 1 AS i
FROM #Result e
INNER JOIN Res m ON e.Groupid = m.Groupid
WHERE dbo.[MultiplyerScl](e.Groupvalue, m.MaxValue) > m.MaxValue
)
SELECT Groupid, MAX(MaxValue) AS MaxValue
INTO #FinalResult
FROM Res
GROUP BY Groupid
UPDATE re
SET re.MaxValue = ire.MaxValue
FROM #FinalResult ire
INNER JOIN #Result re ON re.Groupid = ire.Groupid
SELECT * FROM #Result
Related
How to fetch the text written in square brackets[] through a sql server query
Input:
test1/test2/test3[ab]/test4[c]
Output:
ab
c
Example
Declare #YourTable table (SomeCol varchar(500))
Insert Into #YourTable values
('test1/test2/test3[ab]/test4[c]')
Select A.*
,NewValue = left(value,charindex(']',Value)-1)
From #YourTable A
Cross Apply string_split(SomeCol,'[') B
Where B.value like '%]%'
Returns
SomeCol NewValue
test1/test2/test3[ab]/test4[c] ab
test1/test2/test3[ab]/test4[c] c
(Over-thunk the original answer)
You use this following logic-
DECLARE #Test VARCHAR(MAX) = 'test1/test2/test3[ab]/test4[c]'
DECLARE #TempTab TABLE (
Val VARCHAR(100)
)
DECLARE #Loop INT = 1
DECLARE #St INT = 0
DECLARE #End INT = 0
WHILE #Loop <= (SELECT LEN(#Test))
BEGIN
IF SUBSTRING(#Test, #Loop, 1) = CASE WHEN #St = 0 THEN '[' ELSE ']' END
BEGIN
IF #St = 0
SET #St = #Loop + 1
ELSE
SET #End = #Loop
END
IF #St <> 0 AND #End <> 0
BEGIN
INSERT INTO #TempTab(Val)
SELECT SUBSTRING (#Test,#St,#End-#St)
SET #St = 0
SET #End = 0
END
SET #Loop = #Loop +1
END
SELECT * FROM #TempTab
I need to split a string by delimiters |, then for every value obtained, I need to insert same in the name field like so:
INSERT INTO Monitoring (UserId, Name, DateCreated)
VALUES (#UserId, 'abc', getdate())
VALUES (#UserId, 'def', getdate()) etc...
Below is my code to split the string.
DECLARE #SplitStringTable TABLE (Value nvarchar(MAX) NOT NULL)
DECLARE #StringToSplit nvarchar(MAX) = 'abc|def|gh|ijj'
DECLARE #SplitEndPos int
DECLARE #SplitValue nvarchar(MAX)
DECLARE #SplitDelim nvarchar(1) = '|'
DECLARE #SplitStartPos int = 1
SET #SplitEndPos = CHARINDEX(#SplitDelim, #StringToSplit, #SplitStartPos)
WHILE #SplitEndPos > 0
BEGIN
SET #SplitValue = SUBSTRING(#StringToSplit, #SplitStartPos, (#SplitEndPos - #SplitStartPos))
INSERT #SplitStringTable (Value) VALUES (#SplitValue)
SET #SplitStartPos = #SplitEndPos + 1
SET #SplitEndPos = CHARINDEX(#SplitDelim, #StringToSplit, #SplitStartPos)
END
BEGIN TRANSACTION T1
DECLARE #i int = 0
WHILE #i < #SplitEndPos
BEGIN
INSERT INTO Monitoring (UserId, Name, DateCreated)
VALUES (#UserId, #Name, getdate())
SET #i = #i + 1
END
COMMIT TRANSACTION T1
Any help please, how shall I proceed?
There are many split/parsing functions out there.
Assume variables:
Declare #UserID int = 1
Declare #String varchar(max)='abc|def'
Insert Into Monitoring (UserId,Name,DateCreated)
Select UserID = #UserID
,Name = Key_Value
,DateCreated = GetDate()
From [dbo].[udf-Str-Parse](#String,'|')
The following would be inserted
UserID Name DateCreated
1 abc 2016-09-26 17:31:24.107
1 def 2016-09-26 17:31:24.107
The UDF if needed
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return
(
Select Key_PS = Row_Number() over (Order By (Select null))
,Key_Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#String,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
EDIT
By the way, if you just run the parse function alone, you would get the following:
Declare #String varchar(max)='abc|def'
Select * From [dbo].[udf-Str-Parse](#String,'|')
-- Returns
Key_PS Key_Value
1 abc
2 def
I want to remove the #temp1 from my query and use it as another table varible that work same as it is working using #temp1 table.
Here is my query
DROP TABLE #temp1
;WITH cteSales AS(
SELECT BillDate,
BillNo,
'-' AS NarrationNumber,
TotalAmount,
0 AS ReceivedAmount
FROM SalesMaster
WHERE SalesMaster.ClientID = #ClientID
AND SalesMaster.BillDate BETWEEN #FromDate AND #ToDate
UNION ALL
SELECT [Date] AS BillDate,
SalesAccount.BillNo,
SalesAccount.NarrationNumber,
0 AS TotalAmount,
Amount AS ReceivedAmount
FROM SalesAccount
LEFT JOIN SalesMaster sm
ON sm.BillNo = SalesAccount.BillNO
WHERE sm.ClientID = #ClientID
AND sm.BillDate BETWEEN #FromDate AND #ToDate
), cteFormattedSales AS(
SELECT ROW_NUMBER() OVER(ORDER BY BillNo ASC, BillDate ASC) AS RowNum,
*
FROM cteSales
)
SELECT cfs1.RowNum,
cfs1.BillDate,
cfs1.BillNo,
cfs1.NarrationNumber,
cfs1.TotalAmount,
cfs1.ReceivedAmount,
(cfs1.TotalAmount - cfs1.ReceivedAmount) AS DueAmount INTO #temp1
FROM cteFormattedSales cfs1
DECLARE #BillDate date,
#PrevBillNo INT,
#NextBillNo INT,
#NarrationNumber VARCHAR(15),
#TotalAmount DECIMAL(18, 2),
#ReceivedAmount DECIMAL(18, 2),
#NextDue DECIMAL(18, 2),
#PrevDue DECIMAL(18, 2)
DECLARE #finalTable TABLE(
BillDate date,
BillNo INT,
NarrationNumber NVARCHAR(15),
TotalAmount DECIMAL(18, 2),
ReceivedAmount DECIMAL(18, 2),
DueAmount DECIMAL(18, 2)
)
DECLARE #TotalRecords INT,
#Counter INT = 1,
#CarryOverAmount DECIMAL(18, 2)
SELECT #TotalRecords = COUNT(*)
FROM #temp1
SELECT #BillDate = t.BillDate,
#PrevBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#PrevDue = t.DueAmount
FROM #temp1 t
WHERE t.RowNum = #Counter
WHILE #Counter <= #TotalRecords
BEGIN
SELECT #BillDate = t.BillDate,
#NextBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#NextDue = t.DueAmount
FROM #temp1 AS t
WHERE t.RowNum = #Counter
IF (#Counter = 1)
SET #CarryOverAmount = #TotalAmount
SET #PrevDue = #CarryOverAmount - #ReceivedAmount
IF #PrevBillNo <> #NextBillNo
BEGIN
SET #PrevBillNo = #NextBillNo
SET #CarryOverAmount = #TotalAmount + #PrevDue
SET #PrevDue = 0
END
INSERT INTO #finalTable
(
BillDate,
BillNo,
NarrationNumber,
TotalAmount,
ReceivedAmount,
DueAmount
)
VALUES
(
#BillDate,
#PrevBillNo,
#NarrationNumber,
#TotalAmount,
#ReceivedAmount,
CASE
WHEN #NarrationNumber = '-' THEN #CarryOverAmount
ELSE #PrevDue
END
)
IF #PrevBillNo = #NextBillNo
BEGIN
IF #Counter > 1
BEGIN
SET #CarryOverAmount = CASE
WHEN #NarrationNumber = '-' THEN #CarryOverAmount
+ #PrevDue
ELSE #CarryOverAmount - #ReceivedAmount
END
END
END
SET #Counter = #Counter + 1
END
SELECT *
FROM #finalTable
Here the code that i get error on invalid ReceivedAmount
SELECT #BillDate = t.BillDate,
#PrevBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#PrevDue = t.DueAmount
FROM #Temp1 t
WHERE t.RowNum = #Counter
WHILE #Counter <= #TotalRecords
BEGIN
SELECT #BillDate = t.BillDate,
#NextBillNo = t.BillNo,
#NarrationNumber = t.NarrationNumber,
#TotalAmount = t.TotalAmount,
#ReceivedAmount = t.ReceivedAmount,
#NextDue = t.DueAmount
FROM #Temp1 AS t
WHERE t.RowNum = #Counter
You need to do just 3 steps:
A. DECLARE table variable
-- correct types of columns
DECLARE #temp1 TABLE(
RowNum int
,BillDate datetime
,BillNo int
,NarrationNumber int
,TotalAmount money
,RecievedAmount money
,DueAmount money
)
B. Rewrite SELECT ... INTO -> INSERT INTO .. SELECT
INSERT INTO #temp1
SELECT cfs1.RowNum,
cfs1.BillDate,
cfs1.BillNo,
cfs1.NarrationNumber,
cfs1.TotalAmount,
cfs1.ReceivedAmount,
(cfs1.TotalAmount - cfs1.ReceivedAmount) AS DueAmount
FROM cteFormattedSales cfs1
... etc ...
C. Replace all #temp1 to #temp1
I have a varchar field in SQL Server 2008 like
colName_vch = 'field1;field2;field3;field4;field5;field6;field7'
I want the value of field4.
Now one long way is to do use RIGHT,CHARINDEX for ; and SUBSTRING in recursion, but that becomes very complex and increases query time.
Is there any other quick/less complex way for achieving this?
I know this is a bad DB design, but I am stuck with this for a while now.
Any help is appreciated!!
I'm not sure if i've really understood your question, but here is my guess:
You could write a custom Split function which splits by a delimiter(in this case ;). Then you can use ROW_NUMBER to get the desired part with a given index(4 here).
For example:
DECLARE #string VARCHAR(100);
SET #string='field1;field2;field3;field4;field5;field6;field7';
DECLARE #index INT;
SET #index = 4;
WITH cte
AS (SELECT item,
rn=Row_number()
OVER(
ORDER BY item)
FROM dbo.Split(#string, ';'))
SELECT TOP 1 item
FROM cte
WHERE rn = #index
Here a DEMO on sql-fiddle.
This is my split-function:
CREATE FUNCTION [dbo].[Split]
(
#ItemList NVARCHAR(MAX),
#delimiter CHAR(1)
)
RETURNS #IDTable TABLE (Item VARCHAR(50))
AS
BEGIN
DECLARE #tempItemList NVARCHAR(MAX)
SET #tempItemList = #ItemList
DECLARE #i INT
DECLARE #Item NVARCHAR(4000)
SET #tempItemList = REPLACE (#tempItemList, ' ', '')
SET #i = CHARINDEX(#delimiter, #tempItemList)
WHILE (LEN(#tempItemList) > 0)
BEGIN
IF #i = 0
SET #Item = #tempItemList
ELSE
SET #Item = LEFT(#tempItemList, #i - 1)
INSERT INTO #IDTable(Item) VALUES(#Item)
IF #i = 0
SET #tempItemList = ''
ELSE
SET #tempItemList = RIGHT(#tempItemList, LEN(#tempItemList) - #i)
SET #i = CHARINDEX(#delimiter, #tempItemList)
END
RETURN
END
CREATE FUNCTION dbo.SplitStrings
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS #t TABLE([Index] INT IDENTITY(1,1), Item NVARCHAR(255))
AS
BEGIN
INSERT #t(Item) SELECT SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)
FROM (SELECT ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, 1) = #Delimiter
ORDER BY Number OPTION (MAXDOP 1);
RETURN;
END
GO
DECLARE #x TABLE(i INT, string NVARCHAR(4000));
INSERT #x SELECT 1, N'field1;field2;field3;field4;field5;'
UNION ALL SELECT 2, N'x;y;6;r;3;2;w;'
UNION ALL SELECT 3, N'ttt;444;rrr;333;111;444;777;888;';
SELECT x.i, s1.Item
FROM #x AS x
CROSS APPLY dbo.SplitStrings(x.string, ';') AS s1
WHERE s1.[Index] = 4;
I have a table with username and password fields. Now i dont want the password to be stored exactly as a string the user inputted. I want this field to be encrypted or converted into a GUID so no one including people working on SQL can see it.
In case the user loses his password, he has to come up with a new one and it shall get updated in the table.
Any ideas how i can achieve this?
OWASP guidelines say to use a one-way hash for storing passwords.
This article shows how in ASP.NET: http://www.15seconds.com/issue/000217.htm
(You didn't mention the technology you're using to connect to the server, so I took a guess on ASP.NET.)
You can use hashbytes to do so. Like this: assuming password = admin
DECLARE #dummy nvarchar(4000);
select #dummy = CONVERT(nvarchar(4000),'admin');
SELECT HashBytes('SHA1', #dummy);
CREATE FUNCTION dbo.fnInitRc4
(
#Pwd VARCHAR(256)
)
RETURNS #Box TABLE (i TINYINT, v TINYINT)
AS
BEGIN
DECLARE #Key TABLE (i TINYINT, v TINYINT)
DECLARE #Index SMALLINT,
#PwdLen TINYINT
SELECT #Index = 0,
#PwdLen = LEN(#Pwd)
WHILE #Index <= 255
BEGIN
INSERT #Key
(
i,
v
)
VALUES (
#Index,
ASCII(SUBSTRING(#Pwd, #Index % #PwdLen + 1, 1))
)
INSERT #Box
(
i,
v
)
VALUES (
#Index,
#Index
)
SELECT #Index = #Index + 1
END
DECLARE #t TINYINT,
#b SMALLINT
SELECT #Index = 0,
#b = 0
WHILE #Index <= 255
BEGIN
SELECT #b = (#b + b.v + k.v) % 256
FROM #Box AS b
INNER JOIN #Key AS k ON k.i = b.i
WHERE b.i = #Index
SELECT #t = v
FROM #Box
WHERE i = #Index
UPDATE b1
SET b1.v = (SELECT b2.v FROM #Box b2 WHERE b2.i = #b)
FROM #Box b1
WHERE b1.i = #Index
UPDATE #Box
SET v = #t
WHERE i = #b
SELECT #Index = #Index + 1
END
RETURN
END
ANd this function does the encrypt/decrypt part
CREATE FUNCTION dbo.fnEncDecRc4
(
#Pwd VARCHAR(256),
#Text VARCHAR(8000)
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #Box TABLE (i TINYINT, v TINYINT)
INSERT #Box
(
i,
v
)
SELECT i,
v
FROM dbo.fnInitRc4(#Pwd)
DECLARE #Index SMALLINT,
#i SMALLINT,
#j SMALLINT,
#t TINYINT,
#k SMALLINT,
#CipherBy TINYINT,
#Cipher VARCHAR(8000)
SELECT #Index = 1,
#i = 0,
#j = 0,
#Cipher = ''
WHILE #Index <= DATALENGTH(#Text)
BEGIN
SELECT #i = (#i + 1) % 256
SELECT #j = (#j + b.v) % 256
FROM #Box b
WHERE b.i = #i
SELECT #t = v
FROM #Box
WHERE i = #i
UPDATE b
SET b.v = (SELECT w.v FROM #Box w WHERE w.i = #j)
FROM #Box b
WHERE b.i = #i
UPDATE #Box
SET v = #t
WHERE i = #j
SELECT #k = v
FROM #Box
WHERE i = #i
SELECT #k = (#k + v) % 256
FROM #Box
WHERE i = #j
SELECT #k = v
FROM #Box
WHERE i = #k
SELECT #CipherBy = ASCII(SUBSTRING(#Text, #Index, 1)) ^ #k,
#Cipher = #Cipher + CHAR(#CipherBy)
SELECT #Index = #Index +1
END
RETURN #Cipher
END
This is implemented by Peter but it helps u................