SQL Server 2014 UDF - Streamlining Function - tsql

I'm a novice who started self-teaching SQL/T-SQL. I've been working with SQL 2014 Express and trying to do everything by writing T-SQL scripts. I'm doing this to help a friend with a database for her live action game she is writing, and can use some help in terms of making the following user defined function more streamlined/write it in a more correct manner.
The function itself I believed necessary because of the need for a persistent calculation to determine an item's economic value. This seemed like the better route than trying to do some sort of update script that updated all the values every time the Items table was updated. Sorry if this all seems rudimentary, trying to learn.
Below is the code. I have it working, I've got it in my development database and it does what it's supposed to. But I'd like to clean it up, and I'm not sure how to do that.
CREATE FUNCTION dbo.ValueCalc (#itemid int)
RETURNS INT
AS
BEGIN
declare #rm1 int, #rm1amount int, #rm1value int,
#rm2 int, #rm2amount int, #rm2value int,
#rm3 int, #rm3amount int, #rm3value int,
#rm4 int, #rm4amount int, #rm4value int,
#cm1 int, #cm1amount int, #cm1value int,
#cm2 int, #cm2amount int, #cm2value int,
#cm3 int, #cm3amount int, #cm3value int,
#cm4 int, #cm4amount int, #cm4value int,
#productionvalue int;
select #rm1 = MatReqs.RM1 FROM MatReqs WHERE MatReqs.ItemId = #itemid;
select #rm1amount = MatReqs.RM1Amount FROM MatReqs WHERE MatReqs.ItemId = #itemid;
select #rm1value = RawMats.BaseValue FROM RawMats WHERE RawMats.RawMatId = #rm1;
if (#rm1 IS NULL) set #rm1=0;
if (#rm1amount IS NULL) set #rm1amount=0;
if (#rm1value IS NULL) set #rm1value=0;
{Repeat the above 3 more times, for rm2, rm3, and rm4}
select #cm1 = MatReqs.CM1 FROM MatReqs WHERE MatReqs.ItemId = #itemid;
select #cm1amount = MatReqs.CM1Amount FROM MatReqs WHERE MatReqs.ItemId = #itemid;
select #cm1value = Items.ProdValue FROM Items WHERE Items.ItemId = #cm1;
if (#cm1 IS NULL) set #cm1=0;
if (#cm1amount IS NULL) set #cm1amount=0;
if (#cm1value IS NULL) set #cm1value=0;
{same here, just removed the repetitions}
set #productionvalue = (#rm1amount * #rm1value)
+ (#rm2amount * #rm2value)
+ (#rm3amount*#rm3value)
+ (#rm4amount*#rm4value)
+ (#cm1amount*#cm1value)
+ (#cm2amount*#cm2value)
+ (#cm3amount*#cm3value)
+ (#cm4amount*#cm4value);
set #productionvalue = #productionvalue + (#productionvalue * .15);
return #productionvalue;
END
GO

If I had some sample data and your desired results and your entire function, I could write a more efficient solution as SQL Server is optimized for set-based code and you don't really use it. As far as simply cleaning up, you could do this, but I'd rather you post what sample data and desired results and for me to get you a good set-based solution instead.
CREATE FUNCTION dbo.ValueCalc (#itemid INT)
RETURNS INT
AS
BEGIN
DECLARE #rm1 INT, #rm1amount INT, #rm1value INT,
#rm2 INT, #rm2amount INT, #rm2value INT,
#rm3 INT, #rm3amount INT, #rm3value INT,
#rm4 INT, #rm4amount INT, #rm4value INT,
#cm1 INT, #cm1amount INT, #cm1value INT,
#cm2 INT, #cm2amount INT, #cm2value INT,
#cm3 INT, #cm3amount INT, #cm3value INT,
#cm4 INT, #cm4amount INT, #cm4value INT,
#productionvalue INT;
--They both come from the same table with the where clause so just combine them
SELECT #rm1 = ISNULL(MatReqs.RM1,0),
#rm1amount = ISNULL(MatReqs.RM1Amount,0)
FROM MatReqs
WHERE MatReqs.ItemId = #itemid;
SELECT #rm1value = ISNULL(RawMats.BaseValue,0) F
FROM RawMats
WHERE RawMats.RawMatId = #rm1;
--The ISNULL() function will take care of this
--if (#rm1 IS NULL) set #rm1=0;
--if (#rm1amount IS NULL) set #rm1amount=0;
--if (#rm1value IS NULL) set #rm1value=0;
--Combine the first two statements again
SELECT #cm1 = ISNULL(MatReqs.CM1,0),
#cm1amount = ISNULL(MatReqs.CM1Amount,0)
FROM MatReqs
WHERE MatReqs.ItemId = #itemid;
SELECT #cm1value = ISNULL(Items.ProdValue,0)
FROM Items
WHERE Items.ItemId = #cm1;
--Again just use ISNULL
--if (#cm1 IS NULL) set #cm1=0;
--if (#cm1amount IS NULL) set #cm1amount=0;
--if (#cm1value IS NULL) set #cm1value=0;

Related

Table not created, even after validating the table existence

class datalog(display_clock):
def con_mysql(self):
cat = mysql.connector.connect(
host="localhost", user="subramanya", passwd="Sureshbabu#4155", database="CFM")
if (cat):
datacursor = cat.cursor()
todaydate = d
check_table = (
"SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME=%s")
datacursor.execute(check_table, (todaydate,))
result = datacursor.fetchone()
if (result):
self.success_login()
else:
datacursor.execute(
"CREATE TABLE {today}(Sl_no INT NOT NULL AUTO_INCREMENT PRIMARY KEY,date DATE,Start_time TIME,End_time TIME,Item CHAR(255),Weight FLOAT, Amount INTEGER(10))".format(today=todaydate))
self.success_login()
else:
datacursor.Terminate
self.error_display.insert(0.0, "Connecting Database failed!!!")
I tried to check whether any table exists for today's date or not.
if not create the same.
no error occurred. But table not created for sysdate.
Welcome to stackoverflow!
I believe there is a small misconception here. You don't need to check if the table exists beforehand and create it afterward. Most of the current database technologies accept the condition IF NOT EXISTS on CREATE TABLE CLAUSE.
CREATE TABLE IF NOT EXISTS sales (
sale_id INT NOT NULL,
);
It means the table sales will be only created IF NOT EXISTS previously.
Also, I strongly recommend refactoring your code a wee bit. Take as a suggestion (please adapt accordingly your project needs):
from datetime import datetime
class Settings:
# please, avoid hard-coded credentials.
DB_HOST = "localhost"
DB_USER = "subramanya"
DB_PASSWD = "Sureshbabu#4155"
DB_SCHEMA = "CFM"
class datalog(display_clock):
def db_connect(self):
conn = mysql.connector.connect(
host=Settings.DB_HOST,
user=Settings.DB_USER,
passwd=Settings.DB_PASSWD,
database=Settings.DB_SCHEMA
)
if not conn:
raise Exception("Connecting Database failed!!!")
return conn
def ensure_table(self):
conn = self.db_connect()
conn.datacursor.execute("""
CREATE TABLE IF NOT EXISTS `{0}`(
Sl_no INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
date DATE,
Start_time TIME,
End_time TIME,
Item CHAR(255),
Weight FLOAT,
Amount INTEGER(10)
);
""".format(datetime.today().strftime('%Y%m%d')) # format 20200915
)
def run(self):
self.ensure_table()
self.success_login()
There are plenty of ways to write this code, but keep in mind that readability matters a lot.

SQL Server : error "Must Declare the Scalar Variable"

Trying to insert into a table from other two tables with a loop
DECLARE #RowCount INT
SET #RowCount = (SELECT Max(FogTopicsID) FROM FSB_FogTopics )
DECLARE #I INT
SET #I = 1
WHILE (#I <= #RowCount)
BEGIN
DECLARE #FogID INT, #StudentID INT, #TopicID INT, #ProcessStudentId INT
SELECT #FogID = FogID, #StudentID = StudentID, #TopicID = TopicsID
FROM FSB_FogTopics
WHERE FogTopicsID = #I
SELECT #ProcessStudentId = ProStudentId
FROM FSB_ProcessStudents
WHERE ProcessId = #FogID AND StudentId = #StudentID
INSERT INTO FSB_ProcessTopics( [ProcessStudentId], [TopicId])
VALUES (#ProcessStudentId, #TopicID)
SET #I = #I + 1
END
but I get an error
Must Declare the Scalar Variable #ProcessStudentId
As pointed out by forklift's comment - You can use proper set based solution instead of horrible loop like so;
INSERT FSB_ProcessTopics( [ProcessStudentId], [TopicId])
SELECT
s.ProStudentId,
f.TopicsId
FROM FSB_FogTopics f
INNER JOIN FSB_ProcessStudents s
ON f.FogId = s.ProcessId
AND f.StudentId = s.StudentId
While I realise this doesn't answer your question per-say, this is a better way to do it and should eliminate the need to solve your problem...
You probably have non-continuous Ids - So you have 1,2,4 as Ids but your code is trying to dind 1,2,3,4
You don't need loops to do this (you should almost never need to use loops in SQL for anything). You can do your INSERT in a single statement:
Insert FSB_ProcessTopics
(ProcessStudentId, TopicId)
Select P.ProStudentId, T.TopicsId
From FSB_FogTopics T
Join FSB_ProcessStudents P On P.ProcessId = T.FogId
And P.StudentId = T.StudentId
Do this as a single statement:
INSERT FSB_ProcessTopics(ProcessStudentId, TopicId)
SELECT ProStudentId, TopicsID
FROM FSB_FogTopics ft JOIN
FSB_ProcessStudents ps
ON ft.StudentID = ps.StudentId AND sps.ProcessId = ft.FogiId;
This should replace the cursor, the loop, everything.

Removing decimal places

I have a set of decimal values, but I need to remove the values after the decimal if they are zero.
17.00
23.50
100.00
512.79
become
17
23.50
100
512.79
Currently, I convert to a string and replace out the trailing .00 - Is there a better method?
REPLACE(CAST(amount as varchar(15)), '.00', '')
This sounds like it is purely a data presentation problem. As such you should let the receiving application or reporting software take care of the formatting.
You could try converting the .00s to datatype int. That would truncate the decimals. However, as all the values appear in one column they will have to have the same type. Making everything an int would ruin your rows with actual decimal places.
As a SQL solution to a presentation problem, I think what you have is OK.
I would advice you to compare the raw decimal value with itself floored. Example code:
declare
#Val1 decimal(9,2) = 17.00,
#Val2 decimal(9,2) = 23.50;
select
case when FLOOR ( #Val1 ) = #Val1
then cast( cast(#Val1 as int) as varchar)
else cast(#Val1 as varchar) end,
case when FLOOR ( #Val2 ) = #Val2
then cast( cast(#Val2 as int) as varchar)
else cast(#Val2 as varchar) end
-------------
17 | 23.50

How to set a bit based on a value existing in a table

I have a table. I have 2 variables, one is a bit, the other is an int.
Table: WorkGroupCollectionDetail
Variables: #WorkgroupID int, #IsFSBP bit
The table has WorkGroupId int PK and WorkGroupCollectionCode varchar PK. That's it.
I can run a query like this:
SELECT WorkGroupId
FROM WorkGroupCollectionDetail
WHERE WorkGroupCollectionCode = 'FSBP'
and it gives me a list of WorkGroupID.
So what I need to do is if the value of #WorkgroupID is inside the results of that query, I need to set the bit variable to true.
select #IsFBSP = case
when exists (
select 42 from WorkGroupDetailCollection
where WorkGroupCollectionCode = 'FSBP' and WorkGroupId = #WorkGroupId ) then 1
else 0 end
which is logically equivalent to:
select #IsFBSP = case
when #WorkGroupId in (
select WorkGroupId from WorkGroupDetailCollection
where WorkGroupCollectionCode = 'FSBP' ) then 1
else 0 end
A query using EXISTS often performs better than a query using IN. You can check the execution plans to see how they compare in your particular case.
Note that these examples include setting the bit value to zero as well as one.
You could modify the SELECT to include the check for the WorkGroupId and update the #IsFSBP accordingly:
IF EXISTS(SELECT WorkGroupId
FROM WorkGroupCollectionDetail
WHERE WorkGroupCollectionCode = 'FSBP'
AND WorkGroupId = #WorkgroupID)
BEGIN
SELECT #IsFSBP = 1;
END
SQL Fiddle example
I'm guessing you're looking for
Set #BitVariable = count(*)
From TestTable
WHERE TestCode = 'TestValue' and TestID = #TestID

TSQL - How to join 1..* from multiple tables in one resultset?

A location table record has two address id's - mailing and business addressID that refer to an address table.
Thus, the address table will contain up to two records for a given addressID.
Given a location ID, I need an sproc to return all tbl_Location fields, and all tbl_Address fields in one resultset:
LocationID INT,
ClientID INT,
LocationName NVARCHAR(50),
LocationDescription NVARCHAR(50),
MailingAddressID INT,
BillingAddressID INT,
MAddress1 NVARCHAR(255),
MAddress2 NVARCHAR(255),
MCity NVARCHAR(50),
MState NVARCHAR(50),
MZip NVARCHAR(10),
MCountry CHAR(3),
BAddress1 NVARCHAR(255),
BAddress2 NVARCHAR(255),
BCity NVARCHAR(50),
BState NVARCHAR(50),
BZip NVARCHAR(10),
BCountry CHAR(3)
I've started by creating a temp table with the required fields, but am a bit stuck on how to accomplish this.
I could do sub-selects for each of the required address fields, but seems a bit messy.
I've already got a table-valued-function that accepts an address ID, and returns all fields for that ID, but not sure how to integrate it into my required result.
Off hand, it looks like 3 selects to create this table - 1: Location, 2: Mailing address, 3: Billing address.
What I'd like to do is just create a view and use that.
Any assistance would be helpful.
Thanks.
something along the lines of the following would work:
select L.*,
a1.Address1 as MAddress1, a1.Address2 as MAddress2,
a2.Address1 as BAddress1, a2.Address2 as BAddress2
from location L
inner join Address a1 on (a1.AddressId = L.MailingAddressId)
inner join Address a2 on (a2.AddressId = L.BillingAddressId)
I didn't put in all of the fields, but you get the idea.
Note that if either of the address ids could be null, the you might use a left join instead.
If I understand your question correctly you want something like:
SELECT
L.*,
MAddress1 = M.Address1,
MAddress2 = M.Address2,
MCity = M.City,
MState = M.State,
MZip = M.Zip,
MCountry = M.Country
BAddress1 = B.Address1,
BAddress2 = B.Address2,
BCity = B.City,
BState = B.State,
BZip = B.Zip,
BCountry = B.Country
FROM
tbl_Location L
INNER JOIN tbl_Address M
ON L.MailingAddressID = M.MailingAddressID
INNER JOIN tbl_Address B
ON L.BillingAddressID = B.BillingAddressID
WHERE
L.LocationID = #LocationID