Evaluate expression stored in Varchar Column directly in the "select" statement - sql

I have a table named PriceDetails with three columns
"price","discount formula" and "finalprice". I have to calculate final price based on "Price" and "discount formula" columns.
My table looks like this,
Price DiscountFormulae
100 100*(3/100)
200 200*(1.1/100)+200*(5/100)
300 300*(1/100)+300*(3/100)+300*(2/100)
400 400*(7/100)+400*(6.6/100)+400*(5.5/100)+400*(4/100)
I want to calculate final price. The formula would be "Price"-"DiscountFormulae"
for example
100 - (100*(3/100)) =97
My Expected Output would be
Price DiscountFormulae FinalPrice
100 100*(3/100) 97
Note: My DiscountFormulae Column is Varchar(1000)
How do I get this. Please help me.

Not very good solution, but it shows, how difficult is it:
CREATE TABLE #temp (Price int, DiscountFormula Varchar(1000))
INSERT INTO #temp
(#temp.Price, #temp.DiscountFormula)
VALUES
(100,'100.0*(3.0/100)'),
(200,'200.0*(1.1/100)+200*(5.0/100)'),
(300,'300.0*(1.0/100)+300*(3.0/100)+300*(2.0/100)'),
(400,'400.0*(7.0/100)+400*(6.6/100)+400*(5.5/100)+400*(4.0/100)')
DECLARE #price int
DECLARE cur CURSOR FOR SELECT t.Price FROM #temp AS t
OPEN cur
FETCH NEXT FROM cur INTO #price
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #sql nvarchar(MAX) = N'
SELECT
t.Price,
t.DiscountFormula,
' + (SELECT TOP 1 DiscountFormula FROM #Temp WHERE Price = #price) + N' as Value
FROM #temp AS t
WHERE Price = ' +CONVERT(nvarchar(max),#price)
EXEC sp_executesql #sql
FETCH NEXT FROM cur INTO #price
END
CLOSE cur
DROP TABLE #temp
By the way, use float values in formula.

You could do it in a stored procedure, like:
But dynamic SQL, like exec or sp_executesql, is not allowed in user defined functions.
DECLARE #calc varchar(max)
SET #calc = '100*(3/100)'
DECLARE #sql nvarchar(max)
DECLARE #result float
SET #sql = N'SET #result = ' + #calc
exec sp_executesql #sql, N'#result float output', #result out
SELECT #result

Related

Using cursor to loop through a table variable in SQL Server

I have a parameter of a stored procedure which gets some data in the format
1/1/2018-2/1/2018,2/1/2018-3/1/2018,3/1/2018-4/1/2018,4/1/2018-5/1/2018,5/1/2018-6/1/2018,6/1/2018-7/1/2018,7/1/2018-8/1/2018,8/1/2018-9/1/2018,9/1/2018-10/1/2018,
10/1/2018-11/1/2018,11/1/2018-12/1/2018,12/1/2018-12/31/2018
I have a function which splits the data based on the , character and stores the results into a table variable as shown here:
declare #SPlitDates table(ItemNumber int, Item nvarchar(max))
insert into #SPlitDates
select *
from dbo.SPlitFunction(#RequestData, ',')
After this I have to perform certain operations on the data range so I use cursors to loop through the temp table as shown below
DECLARE cur CURSOR FOR
SELECT Item
FROM #SPlitDates
ORDER BY ItemNumber
OPEN cur
FETCH NEXT FROM cur INTO #monthStart
WHILE ##FETCH_STATUS = 0
BEGIN
-- Some operation
END
The max data points that I will get in the temp table is the date range for 12 months.
My question is that could I be using something else apart from cursors to improve performance or it doesn't matter when the dataset is really this small.
Thanks
Edit - To show operation inside the cursor
declare #SPlitDates table(ItemNumber int, Item nvarchar(max))
insert into #SPlitDates
select *
from dbo.SPlitFunction(#RequestData, ',')
declare #SPlitDatesData table (ItemNumber varchar(100), Item nvarchar(max))
declare #SPlitDatesAvgData table(Code nvarchar(100), Val decimal(18,2))
declare #dataFilter as nvarchar(max),
#SQL as nvarchar(max);
declare #monthStart nvarchar(100)
declare #count int
set #count = 0
--Declaring a cursor to loop through all the dates as defined in the requested quarter
DECLARE cur CURSOR FOR
SELECT Item
FROM #SPlitDates
ORDER BY ItemNumber
OPEN cur
FETCH NEXT FROM cur INTO #monthStart
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #Period NVARCHAR(100)
SET #Period = #monthStart
INSERT INTO #SPlitDatesData
--split the dates to get the start and the end dates
SELECT *
FROM dbo.SPlitFunction(#Period, '-')
DECLARE #PeriodStart NVARCHAR(100)
DECLARE #PeriodEnd NVARCHAR(100)
SET #PeriodStart = (SELECT Item FROM #SPlitDatesData WHERE ItemNumber = 1)
SET #PeriodEnd = (SELECT Item FROM #SPlitDatesData WHERE ItemNumber = 2)
DELETE FROM #SPlitDatesData
--add the start and end dates to the filter
SET #dataFilter = 'StatusDate between convert(datetime,('''+#PeriodStart+'''))
and DATEADD(dy, 1, convert(datetime,('''+#PeriodEnd+''')))'
SET #count = #count +1;
SET #SQL = 'INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
VALUES (#count,
''SL Payroll'',(select dbo.GetAverageCycleBetweenBids('''+#PeriodStart+''',
'''+#PeriodEnd+''',''SL''))
)'
EXEC SP_ExecuteSQL #SQL, N'#count int', #count;
SET #count = #count +1;
SET #SQL = 'INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
VALUES (#count,
''GV Payroll'',(select dbo.GetAverageCycleBetweenBids('''+#PeriodStart+''',
'''+#PeriodEnd+''',''GV''))
)'
EXEC SP_ExecuteSQL #SQL , N'#count int', #count;
SET #count = #count +1;
SET #SQL = 'Insert into #BidAverageCycleCalculation (SortOrder,Code,Data)
Values (#count,
''Global Payroll'',(select dbo.GetAverageCycleBetweenBids('''+#PeriodStart+''',
'''+#PeriodEnd+''',''GVS''))
)'
EXEC SP_ExecuteSQL #SQL, N'#count int', #count;
SET #count = #count +1;
SET #SQL = 'Insert into #BidAverageCycleCalculation (SortOrder,Code,Data)
Values (#count,
''TimeHCM'',(select dbo.GetAverageCycleBetweenBids('''+#PeriodStart+''',
'''+#PeriodEnd+''',''Time''))
)'
EXEC SP_ExecuteSQL #SQL, N'#count int', #count;
delete from #SPlitDatesAVgData
FETCH NEXT FROM cur INTO #monthStart
END
CLOSE cur
DEALLOCATE cur
This uses two parts - first convert your string into a table, then do bulk inserts into your destination. No need for cursors.
** Please excuse any syntax errors as I'm doing it without access to your actual tables or functions so cant test it, but you get the idea
declare #in varchar(max)
set #in= '1/1/2018-2/1/2018,2/1/2018-3/1/2018,3/1/2018-4/1/2018,4/1/2018-5/1/2018,5/1/2018-6/1/2018,6/1/2018-7/1/2018,7/1/2018-8/1/2018,8/1/2018-9/1/2018,9/1/2018-10/1/2018,10/1/2018-11/1/2018,11/1/2018-12/1/2018,12/1/2018-12/31/2018'
declare #xml xml;
set #xml= convert(xml,'<r><f>'+replace(replace(#in,',','</t></r><r><f>'),'-','</f><t>') +'</t></r>')
declare #t table(id int identity, f date, t date)
insert #t
select
Tbl.Col.value('f[1]', 'date') f,
Tbl.Col.value('t[1]', 'date') t
FROM #xml.nodes('//r') Tbl(Col)
select * from #t
declare #count int;
select #count=count(*) from #t
INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
select id, 'SL Payroll',(select dbo.GetAverageCycleBetweenBids(f,t,'SL')) from #t
INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
select id+#count,'GV Payroll',(select dbo.GetAverageCycleBetweenBids(f,t,'GV')) from #t
INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
select id+#count*2,'Global Payroll',(select dbo.GetAverageCycleBetweenBids(f,t,'GVS')) from #t
INSERT INTO #BidAverageCycleCalculation (SortOrder, Code, Data)
select id+#count*3,'TimeHCM',(select dbo.GetAverageCycleBetweenBids(f,t,'Time')) from #t

Execute dynamic query only to get affected row count

I want to execute a dynamic query to get the affected row count. But SQL Result pane returns me the result after executing it. How to avoid returning the columns. I tried the below way.
DECLARE #Command NVARCHAR(MAX)= 'SELECT * FROM Product WHERE ID = 12'
DECLARE #Count AS INT
EXEC sp_executesql #Command, N'#C INT OUTPUT', #C=#Count OUTPUT
IF (#Count > 0)
BEGIN
EXECUTE (#Command)
END
ELSE
BEGIN
DECLARE #CatalogProduct VARCHAR(MAX) = 'SELECT p.ManufactureCode,p.PartNo,p.Size,p.ID AS ProductID,p.Name ,p.ParentProductID,p.BasePrice FROM Product.Product p WHERE p.ThruDate > GETDATE() '+#Where
EXECUTE (#CatalogProduct)
END
END
I want to avoid returning the null column set from the above attached image.
You can turn off the display, but I think a better approach is to get the count you want directly:
DECLARE #Command NVARCHAR(MAX)= 'SELECT * FROM Product WHERE ID = 12';
DECLARE #count AS INT;
DECLARE #CntCommand NVARCHAR(MAX);
SET #CntCommand = 'SELECT #count = COUNT(*) FROM (' + #Command + ') x)';
EXEC sp_executesql #CntCommand, N'#count INT OUTPUT', #count=#count OUTPUT;
Why not simply?
IF (SELECT COUNT(*) FROM Product = 12) > 0 BEGIN...
I can't see why the COUNT statement needs to be dynamic; there's nothing dynamic about it.
Also, having the SQL '... WHERE p.ThruDate > GETDATE() '+#Where is a terrible idea. If #where is a parameter it'll be wide open to SQL injection.
Try this one. Returns number of rows affected by the last query:
select ##Rowcount
DECLARE #Command NVARCHAR(MAX)= 'SELECT * FROM Product WHERE ID = 12'
DECLARE #CountCommand NVARCHAR(MAX)= 'SELECT #Count=count(1) FROM Product WHERE ID = 12'
DECLARE #Count AS INT
EXEC sp_executesql #CountCommand , N'#Count INT OUTPUT', #Count=#Count OUTPUT
IF (#Count > 0)
BEGIN
EXECUTE (#Command)
END
ELSE
BEGIN
DECLARE #CatalogProduct VARCHAR(MAX) = 'SELECT p.ManufactureCode,p.PartNo,p.Size,p.ID AS ProductID,p.Name ,p.ParentProductID,p.BasePrice FROM Product.Product p WHERE p.ThruDate > GETDATE() '+#Where
EXECUTE (#CatalogProduct)
END
END

Values not passed to dynamic query in sql server

Is it possible to print the Dynamic select statement after passing the parameters values.When i print the SELECT #SQL.It is giving only select statement without parameter values.In my below procedure the dynamic select statement not giving correct output after passing the parameters.But when i directly passing the the parameter values into the select statement it is giving correct output.In my below procedure splitting function is working fine.Else part in
if statement is not working properly.
CREATE TYPE TableVariable AS TABLE
(
id int identity(1,1),
field_ids INT,
value VARCHAR(MAX)
)
Alter PROCEDURE Testing
(
#TableVar TableVariable READONLY,
#Catalog_id INT
)
AS
Declare #maxPK INT
Declare #pk INT
Declare #fid INT
Declare #is_List SMALLINT
Declare #val VARCHAR(MAX)
Declare #field_Type VARCHAR(50)
Declare #Where VARCHAR(MAX)
Declare #SQL NVARCHAR(MAX);
Set #pk = 1
BEGIN
BEGIN TRY
SET NOCOUNT ON;
Select #maxPK = count(*) From #TableVar
SELECT #Catalog_id
Set #SQL = 'SELECT DISTINCT v1.entity_id from values v1 inner join listings l ON v1.entity_id = l.entity_id WHERE l.c_id=#Catalog_id'
While #pk <= #maxPK
BEGIN
SELECT #fid= field_ids FROM #TableVar where id=#pk;
SELECT #val= value FROM #TableVar where id=#pk;
SELECT #field_Type=type,#is_List=is_list FROM FIELD WHERE ID=#fid
IF (#is_List = 0)
BEGIN
SET #SQL += ' and exists (select 1 from values v'+convert(varchar(15),#pk+1)+' where v1.entity_id = v'+convert(varchar(15),#pk+1)+'.entity_id and v'+convert(varchar(15),#pk+1)+'.field_id=#fid and(value IN(SELECT val FROM spliting(#val,'',''))))'
SELECT #fid
END
else IF (#is_List = 1 OR #field_Type = 'xy')
BEGIN
SET #SQL += ' and exists (select 1 from values v'+convert(varchar(15),#pk+1)+' where v1.entity_id = v'+convert(varchar(15),#pk+1)+'.entity_id and v'+convert(varchar(15),#pk+1)+'.field_id=#fid and(value in(#val)))'
SELECT #fid
END
Select #pk = #pk + 1
END
EXECUTE SP_EXECUTESQL #SQL, N'#Catalog_id int,#fid int,#val varchar(max)',#Catalog_id=#Catalog_id,#fid=#fid,#val=#val
SELECT #SQL
END TRY
BEGIN CATCH
END CATCH
END
DECLARE #DepartmentTVP AS TableVariable;
insert into #DepartmentTVP values(1780,'Smooth As Silk Deep Moisture Shampoo,Smooth As Silk Deeper Moisture Conditioner')
--insert into #DepartmentTVP values(1780,'Smooth As Silk Deeper Moisture Conditioner')
insert into #DepartmentTVP values(1782,'037-05-1129')
insert into #DepartmentTVP values(2320,'["fairtrade","usda_organic","non_gmo_verified"]')
SELECT * FROM #DepartmentTVP
EXEC Testing #DepartmentTVP,583
Yes right before the statment:
EXECUTE SP_EXECUTESQL #SQL, N'#Catalog_id int,#fid int,#val varchar(max)',#Catalog_id=#Catalog_id,#fid=#fid,#val=#val
type:
print #SQL

Sum up values from databases and parametrize it. [SQL Server]

I want to sum up values from several databases. At this moment I have three databases: SPA_PROD, SPB_PROD and SPC_PROD.
My SQL query:
IF EXISTS (SELECT *
FROM sys.objects
WHERE object_id = OBJECT_ID(N'[dbo].[TESTSUM]')
AND TYPE IN (N'P',N'PC'))
DROP PROCEDURE [dbo].[TESTSUM]
GO
CREATE PROC TESTSUM
AS
BEGIN
DECLARE #dbName SYSNAME,
#ObjectSUM INT,
#d datetime
SET #d = '20141113'
DECLARE #SQL NVARCHAR(MAX)
DECLARE #DBObjectStats TABLE (
--DBName SYSNAME,
DBObjects INT)
DECLARE curAllDBs CURSOR FOR
SELECT name
FROM MASTER.dbo.sysdatabases
WHERE name like '%PROD'
ORDER BY name
OPEN curAllDBs
FETCH curAllDBs INTO #dbName
WHILE (##FETCH_STATUS = 0) -- db loop
BEGIN
--SQL QUERY
SET #SQL = 'select #dbObjects = sum(doctotal) from ' +
QuoteName(#dbName) + '..Invoice
where DocDate = ''' + cast(#d as varchar(25)) + ''''
PRINT #SQL -- Debugging
EXEC sp_executesql #SQL, N'#dbObjects int output',
#dbObjects = #ObjectSUM output
INSERT #DBObjectStats
SELECT #ObjecSUM
FETCH curAllDBs INTO #dbName
END
CLOSE curAllDBs
DEALLOCATE curAllDBs
-- Return results
SELECT sum(DBObjects) [InvoiceSUM] FROM #DBObjectStats
END
GO
-- Execute stored procedure
EXEC TESTSUM
GO
And this work perfect and giving me right sum from all my DBs: 120 000$ ( 25 000 from SPA_PROD , 95 000 SPC_PROD and 0 (NULL) from SPB_PROD.
What I want to do:
I would like to parametrize, which allows me to choose date and databases. For example I want to choose SPA_PROD and SPB_PROD with date 2014-01-01 in another case I want all databases (SPA + SPB + SPC with another date.
Is this even possible? Any ideas?
I can use everything what gives me SQL Server 2012 and T-SQL. Maybe this technology offers me easiest way to do this.
I am also using SAP Crystal Reports to convert SQL output into a beautiful report.
Sorry for my English and I tried to describe to you my problem as far as I could. If you want any additional information which helps u to help me -> ask me :).
You can create a User-Defined Table Type:
CREATE TYPE DBTable AS TABLE
(
DBName VARCHAR(128)
);
You can use it as an input parameter of your stored procedure. As well as the date parameter.
CREATE PROCEDURE TESTSUM
#Databases DBTable READONLY
,#Date DATETIME
AS
BEGIN
...
...
...
You call it like this:
DECLARE #T AS DBTable;
DECLARE #D AS DATETIME = GETDATE();
INSERT INTO #T VALUES ('DB1', 'DB2', 'DB3')
EXEC TESTSUM #T, #D
maybe instead of
SELECT name
FROM MASTER.dbo.sysdatabases
use
SELECT name
FROM #temptable
and insert into #temptable specific db you want
Using your example I modified it to accept a string of database names (generated through you crystal reports select action). Then passing this string with the date in question to first validate the database exist and if online add the required union clause to the generated SQL code.
CREATE PROCEDURE TESTSUM
#DbNameS NVARCHAR(max)
,#Date DATETIME
AS
BEGIN
DECLARE #SQL NVARCHAR(MAX) = ''
/* ADD EXTRA ',' RO STRING ARRAY OF DATABASES */
SET #DbNameS = #DbNameS + ',';
DECLARE #L INT = LEN(#DbNameS);
DECLARE #D INT = 0;
DECLARE #LD INT = 1;
DECLARE #DBF VARCHAR(50);
DECLARE #ACTIVE INT = 0;
/* START SQL QUERY */
SET #SQL = 'SELECT SUM([InvoiceSUM]) AS [InvoiceSUM] FROM ( SELECT '''' AS DB, 0.00 AS [InvoiceSUM]' + CHAR(13)
/* LOOP THROUGH EACH DBF NAME PASSED CHECKING IF VALID AND ONLINE */
WHILE #D < #L
BEGIN
SET #D = CHARINDEX(',', #DbNameS,#LD);
IF #LD != #D
BEGIN
SET #DBF = SUBSTRING(#DbNameS,#LD,#D-#LD)
/* VALIDATE DBF IS VALID AND ACTIVE */
SELECT #ACTIVE = COUNT(*) FROM SYS.databases WHERE name = #DBF AND [state] = 0
IF #ACTIVE = 1
BEGIN
/*
BEGIN CODE TO UNION THE SUM RESULTS FOR EACH ACTIVE AND VALID DBF
TO MAKE IT WORK WITH SOME EXISTING DBF's ON MY SYSTEM I CHANGED THE SUMMARY CODE FOR TESTING
*/
SET #SQL = #SQL + 'UNION SELECT '''+ #DBF +''' AS DB, ISNULL(SUM( CAST(DVE AS DECIMAL(18,10)) ),0) AS [InvoiceSUM] FROM '+ #DBF + '.DBO.SO_MSTR WHERE CAST(RecordCreated AS DATE) = '''+ CAST(#Date AS VARCHAR(20)) + '''' + CHAR(13)
END;
END;
SET #LD = #D + 1;
END;
/* CLOSE OUT UNION SUMMARY QUERY */
SET #SQL = #SQL + ') AS DATA'
/* OUTPUT RESULTS */
EXEC SP_EXECUTESQL #SQL
END;
Crystal reports would effective be generating this code: EXEC TESTSUM 'SPA_PROD,SPB_PROD,SPC_PROD','12/09/2014'

Pivoting SQL Server 2005 for unknown number of rows

My table is a dynamic one. E.g.:
id SUBJECT
1 his
2 math
3 sci
4 opt
5 ENG
6 SOC
The number of rows is not limited. There could be a hundred. I want output like this:
ID 1 2 3 4 5 6
HIS MATH SCI OPT ENG SOC
I could use a pivot query, but I would have to know the number of columns. How can I do this without knowing the number of columns in advance?
i got an answer but it's very tricky
create a table for all your records
count the records
create a table with that much number of columns
create a comma separated variable for the table which has records
then split the comma separated variables into multiple columns
here is the code
DECLARE #HEADDESC NVARCHAR(150)
DROP TABLE #HEADS
CREATE TABLE #HEADS
(
ID INT IDENTITY
,HEADS NVARCHAR(150)
,NU INT
)
DECLARE #NO INT;
SET #NO = 0
DECLARE C1 CURSOR FOR (
SELECT HEADDESC
FROM GMC.FEEHEAD_MASTER
WHERE CODE = 'GF' AND HEADDESC <> '')
OPEN C1
FETCH NEXT FROM C1 INTO #HEADDESC
WHILE ##FETCH_STATUS = 0
BEGIN
PRINT #HEADDESC
SET #NO = #NO+1
INSERT INTO #HEADS(HEADS,NU)
VALUES(#HEADDESC,#NO)
FETCH NEXT FROM C1 INTO #HEADDESC
END
--SELECT * FROM #HEADS
CLOSE C1
DEALLOCATE C1
DECLARE #COLNO INT
SET #COLNO = (SELECT COUNT(*) FROM #HEADS)
DECLARE #COLUMNS VARCHAR(8000)
SELECT #COLUMNS = COALESCE(#COLUMNS +','+ CAST(HEADS AS VARCHAR) ,
CAST(HEADS AS VARCHAR))
FROM #HEADS
--GROUP BY HEADS
DECLARE #value NVARCHAR(100)
SET #value = ',1,STUDENTIDNO,STUDENTNAME,'
SET #COLUMNS = #VALUE+#COLUMNS
SET #COLNO = #COLNO+4
--SELECT #COLUMNS
DROP TABLE #HEADSCOMMA
CREATE TABLE #HEADSCOMMA(HEADS NVARCHAR(3000))
INSERT INTO #HEADSCOMMA VALUES (#COLUMNS)
DROP TABLE #TEMP
CREATE TABLE #TEMP(COL1 NVARCHAR(1000))
DECLARE #SQL NVARCHAR(MAX)
DECLARE #COL NVARCHAR(1000)
DECLARE #COL1 INT
DECLARE #COLNAME NVARCHAR(1000)
SET #COL1 = 2
SET #COL = 'COL'
PRINT #COL1
--SET #COLNAME = #COL +CAST(#COL1 AS NVARCHAR(10))
WHILE #COL1 < =#COLNO
BEGIN
SET #COLNAME = #COL +CAST(#COL1 AS NVARCHAR(100))
PRINT #COLNAME
SET #SQL = 'ALTER TABLE #TEMP ADD '+#COLNAME+' NVARCHAR(100)'
EXEC(#SQL)
SET #COL1= #COL1+1
END
--SELECT * FROM #HEADSCOMMA -- COMMA SEPERATED VALUES
DECLARE #S VARCHAR(8000), #DATA VARCHAR(8000)
--DROP TABLE #NORMALISEDTABLE
--CREATE TABLE #NORMALISEDTABLE (HEADS NVARCHAR(200))
SELECT #S=''
WHILE EXISTS (SELECT * FROM #HEADSCOMMA WHERE HEADS>#S)
BEGIN
SELECT #S=HEADS FROM #HEADSCOMMA WHERE HEADS>#S
PRINT #S
SELECT #DATA=''''+REPLACE(#S,',',''',''')+''''
PRINT #DATA
INSERT INTO #TEMP
EXEC('SELECT '+#DATA)
END
SELECT * FROM #temp
will give the records
Dynamic SQL is an option.