I'm building a function to return the net value of a sales invoice once any purchase invoices marked against it have been taken off - part of these costs are contractor costs of which we also want to calculate either NI or a set increase to the contractor pay to reflect "true" value.
So on one sales invoice, we may have several contractors where we have purchase invoices against it, some may be at NI calculated at 13.8%, others may be at a standard "uplift" of 10% and others may be at 0% added.
The bit that I'm trying to figure out is if I need to make the whole function as a cursor as there are going to be different figures for a variable for each purchase invoice
e.g.
(SELECT SUM(th.hoursworked * (th.payrate * (1 + (#NIAMOUNT / 100))) )
but the #NIAMOUNT will have to change for each purchase invoice.
Is the best way of doing this via a cursor, or is there a better way of doing this?
Sorry if this is a bit verbose!
You should avoid cursors wherever possible - cursors operate 'serially' which will limit SQL's performance.
If the calculation is simple, one way to do this would be do do the NI lookup inline, e.g.
(SELECT SUM(th.hoursworked * (th.payrate * (1 +
((CASE somejoinedtable.UpliftType
WHEN 1 THEN 10
WHEN 2 THEN 13.8
WHEN 3 THEN 0
END
/ 100))))
If this becomes too messy to do inline, you could abstract a user defined function
CREATE FUNCTION dbo.fnGetNIAmount(int #someLookupParam)
...
And then do like so
(SELECT SUM(th.hoursworked * (th.payrate * (1 +
((dbo.fnGetNIAmount(somejoinedtable.UpliftType )
/ 100))))
You almost certainly don't need a cursor
Join your hours worked to the contract they are based on, and take the NI value from the contract.
Related
I am working on an oracle PL/SQL process which divides a single monetary amount across multiple involved parties in a particular group. Assuming 'pGroupRef' is an input parameter, the current implementation first designates a 'primary' involved party, and then it splits the amount across all the secondaries as follows:
INSERT INTO ActualValue
SELECT
...
pGroupRef AS GroupRef,
ROUND(Am.Amount * P.SplitPercentage / 100, 2) AS Amount,
...
FROM
Amount Am,
Party P
WHERE
Am.GroupRef = pGroupRef
AND P.GroupRef = Am.GroupRef
...
P.PrimaryInd = 0;
Finally, it runs a second procedure to insert whatever amount is left over to the primary party, i.e.:
INSERT INTO ActualValue
SELECT
...
pGroupRef AS GroupRef,
Am.Amount - S.SecondaryAmounts,
FROM
Amount Am,
Party P,
(SELECT SUM(Amount) AS SecondaryAmounts FROM ActualValue WHERE GroupRef = pGroupRef) S
WHERE
Am.GroupRef = pGroupRef
AND P.GroupRef = Am.GroupRef
...
P.PrimaryInd = 1;
However, the full query here is very large and I am making this area more complex by adding subgroups, each of which will have their own primary member, and the possibility of overrides - hence if I continued to use this implementation then it would mean a lot of duplicated SQL.
I suppose I could always calculate the correct amounts into an array before running a single unified insert - but I feel like there has to be an elegant mathematical way to capture this logic in a single SQL Query.
So you can use analytical functions to get what you are looking for. As I didn't know your exact structure, this is only an example:
SELECT s.party_id, s.member_id,
s.portion + DECODE(s.prime, 1, s.total - SUM(s.portion) OVER (PARTITION BY s.party_id),0)
FROM (SELECT p.party_id, p.member_id,
ROUND(a.amt*(p.split/100), 2) AS PORTION,
a.amt AS TOTAL, p.prime
FROM party p
INNER JOIN amount a ON p.party_id = a.party_id) s
So in the query you have a subquery that gathers the required information, then the outer query puts everything together, only applying the remainder to the record marked as prime.
Here is a DBFiddle showing how this works (LINK)
N.B.: Interestingly in the example in the DBFiddle, there is a 0.01 overpayment, so the primary actually pays less.
I have a database in which there are 13 different products, sold in 6 different countries.
Prices increase once a year.
Prices need to be calculated using a linear interpolation method. I have 21 different price and quantity increments for each product for each country for each year.
The user needs to be able to see how much an order would cost for any given value (as you would expect).
What the database needs to do (in English!) is to:
If there is a matching quantity from TblOrderDetail in the TblPrices,
use the price for the current product, country and year
if there isn't a matching quantity but the quantity required is greater than 1000 for one product (GT) and greater than 100 for every other product:
Find the highest quantity for the product, country and year (so, 1000 or 100, depending on the product), and calculate a pro-rated price. eg. If someone wanted 1500 of product GT for the UK for 2015, we'd look at the price for 1000 GT in the UK for 2015 and multiply it by 1.5. If 1800 were required, we'd multiply it by 1.8. I haven't been able to get this working yet as I'm looking at it alongside the formula for the next possibility...
If there isn't a matching quantity and the quantity required is less than 1000 for the product GT but 100 for the other products (this is the norm)...
Find the quantity and price for the increment directly below the quantity required by the user for the required product, country and year (let's call these quantitybelow and pricebelow)
Find the quantity and price for the increment directly above the quantity required by the user for the required product, country and year (let's call these quantityabove and priceabove)
Calculate the price for the required number of products for an account holder in a particular country for a given year using this formula.
ActualPrice: PriceBelow + ((PriceAbove - PriceBelow) * (The quantity required in the order detail - QuantityBelow) / (QuantityAbove - QuantityBelow))
I have spent days on this and have sought advice about this before but I am still getting very stuck.
The tables I've been working with to try and make this work are as follows:
TblAccount (primary key is AccountID, it also has a Country field which joins to the TblCountry.Code (primary key)
TblOrders (primary key is Order ID) which joins to TblAccount via the AccountID field; TblOrderDetail via the OrderID. This table also holds the OrderDate and Recipient ID which links to a person in TblContact - I don't need that here but will need it later to generate an invoice
TblOrderDetail (primary key is DetailID) which joins to TblOrders via OrderID field; TblProducts via ProductID field, and holds the Quantity required as well as the product
TblProducts (primary key is ProductCode) which as well as joining to TblOrderDetail, also joins to TblPrice via the Product field
TblPrices links to the TblProducts (as you have just read). I've also created an Alias for the TblCountry (CountryAliasForProductCode) so I can link it to the TblPrices to show the country link. I'm not sure if I needed to do this - it doesn't work if I do or I don't do it, so I seek guidance again here.
This is the code I've been trying to use (and failing) to get my price and quantity steps above and I hope to replicate it, making a couple of tweaks to get the steps below:
SELECT MIN(TblPrices.stepquantity) AS QuantityAbove, MIN(TblPrices.StepPrice) AS PriceAbove, TblOrders.OrderID, TblOrders.OldOrderID, TblOrders.AccountID, TblOrders.OrderDate, TblOrders.RecipientID, TblOrders.OrderStatus, TblOrderDetail.DetailID, TblOrderDetail.Product, TblOrderDetail.Quantity
FROM (TblCountry INNER JOIN ((TblAccount INNER JOIN TblOrders ON TblAccount.AccountID = TblOrders.AccountID) INNER JOIN (TblOrderDetail INNER JOIN TblProducts ON TblOrderDetail.Product = TblProducts.ProductCode) ON TblOrders.OrderID = TblOrderDetail.OrderID) ON TblCountry.Code = TblAccount.Country) INNER JOIN (TblCountry AS CountryAliasForProduct INNER JOIN TblPrices ON CountryAliasForProduct.Code = TblPrices.CountryCode) ON TblProducts.ProductCode = TblPrices.Product
WHERE (StepQuantity >= TblOrderDetails.Quantity)
AND (TblPrices.CountryCode = TblAccount.Country)
AND (TblOrderDetail.Product = TblPrices.Product)
AND (DATEPART('yyyy', TblPrices.DateEffective) = DATEPART('yyyy', TblOrders.OrderDate));
I've also tried...
I've even tried going back to basics and trying again to generate the steps below in 1 query, then try the steps above in another and finally, create the final calculation in another query.
This is what I have been trying to get my prices and quantities below:
SELECT Max(StepQuantity) AS quantity_below, Max(StepPrice) AS price_below, TblOrderDetails.Quantity, TblAccounts.Country
FROM
(TblProducts INNER JOIN TblPrices ON TblProducts.ProductCode = TblPrices.Product)
(TblOrderDetail INNER JOIN TblProducts ON TblOrderDetail.Product = TblProducts.ProductCode)
(TblOrders INNER JOIN TblOrderDetail ON TblOrders.OrderID = TblOrderDetail.OrderID)
(TblAccount INNER JOIN TblOrders ON TblAccount.AccountID = TblOrders.AccountID),
WHERE (((TblPrices.StepQuantity)<=(TblOrderDetail.Quantity)) AND ((TblPrices.CountryCode)=([TblAccounts].[country])) AND ((TblPrices.Product)=([TblOrderDetail].[product])) AND ((DatePart('yyyy',[TblPrices].[DateApplicable]))=(DatePart('yyyy',[TblOrders].[OrderDate]))));
You may be able to see glaring errors in this but I'm afraid I can't. I've tried re-jigging it and I'm getting nowhere.
I need to be able to tie the information in to the OrderDetail records as the price generated will need to be added to a financial transactions table as a debit amount and will show as an amount owing on statements.
I'm really not very good at SQL. I've read and worked though several self-study books and I have asked part of this question before; but I really am struggling with it. If anyone has any ideas on how to proceed, or even where I've gone wrong with my code, I'd be delighted, even if you tell me I shouldn't be using SQL. For the record, I originally posted this question on a different forum under Visual Basic. Responses from that forum brought me to SQL - however, anything that works would be good!
I've even tried, using Excel, concatenating the Year&Product&Country&Quantity to get a unique product code, interpolating the prices for every quantity between 1 and 1000 for each product, country and year and bringing them into a TblProductsAndPrices table. In Access, I created a query to concatenate the Year(of order date from tblOrders)&Product(of tblorderdetails)&Country(of tblAccount) in order to get the required product code for the order. Another query would find a price for me. However, any product code that doesn't appear on the list (such as where a quantity isn't listed in the tblProductsAndPrices as it is larger than the highest price increment) doesn't have a price.
If there was a workable solution to what I've just described that would generate a price for everything, then I'd be so pleased.
I'd really like to be able to generate an order for any quantity of any product for any account based in any country on any date and retrieve a price which will be used to "debit" a financial account in the database, who in a transaction history for an account and appear on statements. I'd also like to be able to do an ad-hoc price check on the spot.
Thank you very much for taking the time to read this. I really appreciate it. If you could offer any help or words of encouragement, I'd be very grateful.
Many thanks
Karen
Maybe no one thinks on an easy solution to the problem, since not all minds work in database thinking.
Easy solution: Create one view that gives all calculated values, not only the final one you need, each one as a column. Then you can use such view in a relation view and use on some rows one of the values and on other rows other values, etc.
How to think is simple, think in reverse order, instead of thinking "if that then I need to calculate such else I need this other", think as "I need "such" and I need "this other", both are columns of an intermediate view, then think on top level "if" that would be another view, such view will select the correct value ignoring the rest.
Never ever try to solve all in one step, that can be a really big headache.
Pros: You can isolate calculated values (needed or not), sql is much more easy to write and maintain.
Cons: Resources use is bigger than minimal, but most of times that extra calculated values does not represent a really big impact.
In terms of tutorial out there: Instead of a Top-Down method, use a Down-Top method.
Sometimes it is better (with your example) to calculate all three values (you write sentences on bold) ignoring the if part, and have all three possible values for your order and after that discard the ones not wanted, than trying to only calculate one.
Trying to calculate only one is thinking as a procedural programming, when working with databases most times one must get rid of such thinking and think as reverse, first do the most internal part of such procedural programming to have all data collected, then do the external selection of the procedural programing.
Note: If one of the values can not be calculated, just generate a Null.
I know it is hard to think on First in, last out (Down-Top) model, but it is great for things as the one you want.
Step1 (on specific view, or a join from one view per calculation):
Calculate column 1 as price for the current product, country and
year
Calculate column 2 as calculate a pro-rated price as if 1000
Calculate column 3 as calculate a pro-rated price as if 100
Calculate column 4 as etc
Calculate column N as etc
Step 2 (Another view, the one you want):
Calculate the if part, so you can choose adequate column from previous view (you can use immediately if or a calculated auxiliary field).
Hope you can follow theese way of thinking, I have solved a lot of things like that one (and more complex) thinking in that way, but it is not easy to think as that, needs an extra effort.
I’m in the process of creating a report that will tell end users what percentage of a gridview (the total number of records is a finite number) has been completed in a given month. I have a gridview with records that I’ve imported and users have to go into each record and update a couple of fields. I’m attempting to create a report tell me what percentage of the grand total of records was completed in a given month. All I need is the percentage. The grand total is (for this example) is 2000.
I’m not sure if the actual gridview information/code is needed here but if it does, let me know and I’ll add it.
The problem is that I have been able to calculate the percentage total but when its displayed the percentage total is repeated for every single line in the table. I’m scratching my head on how to make this result appear only once.
Right now here’s what I have for my SQL code (I use nvarchar because we import from many non windows systems and get all sorts of extra characters and added spaces to our information):
Declare #DateCount nvarchar(max);
Declare #DivNumber decimal(5,1);
SET #DivNumber = (.01 * 2541);
SET #DateCount = (SELECT (Count(date_record_entered) FROM dbo.tablename WHERE date_record_entered IS NOT NULL and date_record_entered >= 20131201 AND date_record_entered <= 20131231);
SELECT CAST(ROUND(#DivNumber / #DateCount, 1) AS decimal(5,1) FROM dbo.tablename
Let’s say for this example the total number of records in the date_record_entered for the month of December is 500.
I’ve tried the smaller pieces of code separately with no success. This is the most recent thing I’ve tried.
I know I'm missing something simple here but I'm not sure what.
::edit::
What I'm looking for as the expected result of my query is to have a percentage represented of records modified in a given month. If 500 records were done that would be 25%. I just want to have the 25 (and trainling decimal(s) when it applies) showing once and not 25 showing for every row in this table.
The following query should provide what you are looking for:
Declare #DivNumber decimal(5,1);
SET #DivNumber = (.01 * 2541);
SELECT
CAST(ROUND(#DivNumber / Count(date_record_entered), 1) AS decimal(5,1))
FROM dbo.tablename
WHERE date_record_entered IS NOT NULL
and date_record_entered >= 20131201
AND date_record_entered <= 20131231
Why do you select the constant value cast(round(#divNumber / #DateCount, 1) as decimal(5,1)from the table? That's the cause of your problem.
I'm not too familiar with sql server, but you might try to just select without a from clause.
select emp.Natinality_Id,mstn.Title as Nationality,count(emp.Employee_Id) as Employee_Count,
count(emp.Employee_Id)* 100.0 /nullif(sum(count(*)) over(),0) as Nationality_Percentage
FROM Employee as emp
left join Mst_Natinality as mstn on mstn.Natinality_Id = emp.Natinality_Id
where
emp.Is_Deleted='false'
group by emp.Natinality_Id,mstn.Title
I'm currently developing an app in Delphi which uses SQL to tap into the back end of a 3rd party invoicing system so we can extend the reporting capabilities of it. I consider myself to be reasonably proficient in the delphi side of programming, however SQL is new to me so with the immense help of this forum, and other resources, i have managed to teach myself more than I thought I would be able to.
Most of the data is pulled out of several tables (i don't have an issue with that side, so I won't clog up the post with those details), however I have an issue getting the cost price. It's stored in a table that tracks the historical cost price so for each product (16'000+) there are potentially hundreds of records, however I only need the cost for each product that is closest (<=) to the date of the invoice.
Here is the function:
CREATE FUNCTION dbo.CostAtDate ( #costdate AS datetime , #product AS int )
RETURNS decimal(18,2)
AS
BEGIN
DECLARE #result decimal(18,2)
SET #result = (
Select Top 1
BASE_InventoryCostLogDetail.AverageCostAfter
From
BASE_InventoryCostLogDetail
Where
CreatedDttm < #costdate And CreatedDttm > DATEADD(month,-1,#costDate) And
ProdId = #product
Order By
CreatedDttm Desc)
RETURN #result
END
And here is one of the queries (there are several different ones, but all based around the same structure):
Select
BASE_Customer.Name,
SO_SalesOrder.OrderNumber,
SO_SalesOrderInvoice_Line.Description,
SO_SalesOrderInvoice_Line.UnitPrice,
Case SO_SalesOrderInvoice_Line.ItemTaxCodeId
When '100' Then (SO_SalesOrderInvoice_Line.UnitPrice / 11) * 10
Else SO_SalesOrderInvoice_Line.UnitPrice End As exgst,
SO_SalesOrderInvoice_Line.QuantityUom,
SO_SalesOrderInvoice_Line.QuantityDisplay,
Case SO_SalesOrderInvoice_Line.QuantityUom
When 'cases.' Then dbo.CostAtDate(SO_SalesOrder.OrderDate,
SO_SalesOrderInvoice_Line.ProdId) * BASE_Product.SoUomRatioStd
Else dbo.CostAtDate(SO_SalesOrder.OrderDate,
SO_SalesOrderInvoice_Line.ProdId) End As cost,
Case SO_SalesOrderInvoice_Line.QuantityUom
When 'cases.' Then ((dbo.CostAtDate(SO_SalesOrder.OrderDate,
SO_SalesOrderInvoice_Line.ProdId) * BASE_Product.SoUomRatioStd) / 11) * 10
Else (dbo.CostAtDate(SO_SalesOrder.OrderDate,
SO_SalesOrderInvoice_Line.ProdId) / 11) * 10 End As exgstcost,
BASE_Product.SoUomRatioStd,
BASE_Product.Name As Name1,
SO_SalesOrder.OrderDate
From
BASE_Customer Inner Join
SO_SalesOrder On SO_SalesOrder.CustomerId = BASE_Customer.CustomerId
Inner Join
SO_SalesOrderInvoice_Line On SO_SalesOrderInvoice_Line.SalesOrderId =
SO_SalesOrder.SalesOrderId Inner Join
BASE_Product On SO_SalesOrderInvoice_Line.ProdId = BASE_Product.ProdId
Where
SO_SalesOrder.OrderDate Between '20131028' And '20131029'
Now this works fine when I only have a few invoices in the selected range, but given that it calls the function a minimum of three times per record, the performance really degrades when I go to produce the report over a time period of more than a day (we often need reports covering a two week period).
Unfortunately, given that it is a third party product (inFlow Inventory for anyone who is curious) I can't change the table structures.
Is there any way whether it be using more efficient joins, a derived table (I understand the concept, but have never done it) or even rewriting the whole query that will improve the performance greatly?
It appears I have managed to solve my own problem, it just took a little bit more research, lateral thinking, a lot of failed attempts and curse words (oh so many curse words!)
I toyed with the idea of adding extra steps in the delphi side of this program that would select from the cost prices table based upon the date range i needed and then re-write my original query to incorporate the new table joined in. However it's not as fun to solve a problem if you don't learn any new skills along the way ;-).
The answer: TVF- Table Valued Function.
After a lot of research on alternative ways i stumbled across these TVF's. Further investigation seemed to reveal that because of the way the optimizer handles scalar functions as opposed to TVF's, they were tremendously quicker in certain applications so i decided to re-write my function as such:
CREATE FUNCTION dbo.CostAtDate ( #costdate AS datetime , #product AS int )
RETURNS table
AS
Return (
Select Top 1
BASE_InventoryCostLogDetail.AverageCostAfter
From
BASE_InventoryCostLogDetail
Where
CreatedDttm < #costdate And CreatedDttm > DATEADD(month,-1,#costDate) And
ProdId = #product
Order By
CreatedDttm Desc)
And instead of calling it the traditional way
dbo.CostAtDate(SO_SalesOrder.OrderDate, SO_SalesOrderInvoice_Line.ProdId)
I re-jigged all references of it in my query to:
(Select * from dbo.CostAtDate(SO_SalesOrder.OrderDate,
SO_SalesOrderInvoice_Line.ProdId))
Testing it i found a significant increase in performance (4sec for 65k+ records, as opposed to the previous function which would usually time out after a few minutes even though the expected resultset was ~10k records.)
I'm sure plenty of you know an even better way, but for the moment this works well....and i found it all by myself: kudos to me!
This is vaguely related to:
How to Update all Dates in a Table
But only if you squint real hard.
Part 1 - I know that SQL can update one column on a table based on a formula that involves other columns on that same table. And it can be done with a single, simple line of SQL as follows:
UPDATE thieving_prices SET current_price = (1 + usury_rate) * current_price;
Now all prices have just been hiked by their various associated villainous rates.
Part 2 - I also know that Rails 3 offers a wonderful bit of magic called update_all which creates an UPDATE query using a single line of SQL:
ThievingPrice.update_all(:current_price = 35000.00)
generates:
UPDATE thieving_prices SET current_price = 35000.00;
Now all of the prices are identical for all products. Very useless in this sneaky store.
Part 3 - All of these prices have their own steady rates of increase and there aught to be a way to write an elegant line of code in Rails 3 to accomplish it.
And the line:
ThievingPrice.update_all(:current_price => (1 + :usury_rate) * :current_price)
doesn't work. Nor does any syntactic variation involving parenthesis, braces or brackets--so far as my experiments have shown. Surely others have come across a need for such a construction. I don't want to fall back on the incredibly slow and resource-wasting each block:
ThievingPrice.each do |tp|
new_price = tp.current_price * (1 + tp.usury_rate)
tp.update_attribute(:current_price => new_price)
end
By the time that finishes my victims patrons have left the store. What are my options?
Try ThievingPrice.update_all("current_price = (1 + ?) * current_price" , :usury_rate). it should take the parameter and multiply the current price by that amount based on the object being updated at the moment.