I'm writing a function that should add each line item quantity multiplied by its unit cost and then iterating through the entire pickticket (PT). I don't get an error when altering the function in SQL Server or when running it, but it gives me a 0 as the output each time.
Here is an example:
[PT 1]
[Line 1 - QTY: 10 Unit Cost: $5.00] total should be = $50.00
[Line 2 - QTY: 5 Unit Cost: $2.50] total should be = $12.50
The function should output - $62.50
Not really sure what I'm missing here, but would appreciate the help.
Alter Function fn_CalculateAllocatedPTPrice
(#psPickTicket TPickTicketNo)
-------------------------------
Returns TInteger
As
Begin
Declare
#iReturn TInteger,
#iTotalLineNumbers TInteger,
#iIndex TInteger,
#fTotalCost TFloat;
set #iIndex = 1;
set #iTotalLineNumbers = (ISNULL((select top 1 PickLineNo
from tblPickTicketDtl
where PickTicketNo = #psPickTicket
order by PickLineNo desc), 0)) /* This returns the highest line number */
while(#iIndex <= #iTotalLineNumbers)
BEGIN
/* This should be adding up the total cost of each line item on the PT */
set #fTotalCost = #fTotalCost + (ISNULL((select SUM(P.RetailUnitPrice*P.UnitsOrdered)
from tblPickTicketDtl P
left outer join tblCase C on (P.PickTicketNo = C.PickTicketNo)
where P.PickTicketNo = #psPickTcket
and P.PickLineNo = #iIndex
and C.CaseStatus in ('A','G','K','E','L','S')), 0))
set #iIndex = #iIndex + 1;
END
set #iReturn = #fTotalCost;
_Return:
Return(#iReturn);
End /* fn_CalculateAllocatedPTPrice */
It seems simple aggregation should suffice
A few points to note:
WHILE loops and cursors are very rarely needed in SQL. You should stick to set-based solutions, and if you find yourself writing a loop you shuold question your code from its beginnings.
Scalar functions are slow and inefficient. Use an inline Table function, which you can correlate with your main query either with an APPLY or a subquery
Your left join becomes an inner join because of the where predicate
User defined types are not normally a good idea (when they are just aliasing system types)
CREATE OR ALTER FUNCTION fn_CalculateAllocatedPTPrice
(#psPickTicket TPickTicketNo)
RETURNS TABLE AS RETURN
SELECT fTotalCost = ISNULL((
SELECT SUM(P.RetailUnitPrice * P.UnitsOrdered)
from tblPickTicketDtl P
join tblCase C on (P.PickTicketNo = C.PickTicketNo)
where P.PickTicketNo = #psPickTcket
and C.CaseStatus in ('A','G','K','E','L','S')
), 0);
GO
Related
I am trying to create a calculated field called efficiency %. This is calculated by taking LaborDtl.LaborHrs minus Calculated_Shouldhavetaken divided by Calculated_Shouldhavetaken. When I run the query I get zero rows returned, but creating the calculated field without the division returns the difference as expected. I believe this is due to how division is treated in the server.
/*
* Disclaimer!!!
* This is not a real query being executed, but a simplified version for general vision.
* Executing it with any other tool may produce a different result.
*/
select
[LaborDtl].[JobNum] as [LaborDtl_JobNum],
[JobHead].[PartNum] as [JobHead_PartNum],
[LaborDtl].[EmployeeNum] as [LaborDtl_EmployeeNum],
[LaborDtl].[OpCode] as [LaborDtl_OpCode],
[JobHead].[ProdQty] as [JobHead_ProdQty],
(LaborDtl.LaborQty + LaborDtl.ScrapQty) as [Calculated_TotalQty],
[LaborDtl].[LaborHrs] as [LaborDtl_LaborHrs],
(TotalQty/ JobHead.ProdQty* LaborDtl.LaborHrs* 1.0) as [Calculated_Shouldhavetaken],
[LaborDtl].[ClockInDate] as [LaborDtl_ClockInDate],
((LaborDtl.LaborHrs- Shouldhavetaken)/ Shouldhavetaken) as [Calculated_effpercent]
from Erp.LaborDtl as LaborDtl
inner join Erp.JobOper as JobOper on
LaborDtl.Company = JobOper.Company
and LaborDtl.JobNum = JobOper.JobNum
and LaborDtl.AssemblySeq = JobOper.AssemblySeq
and LaborDtl.OprSeq = JobOper.OprSeq
inner join Erp.JobOpDtl as JobOpDtl on
JobOper.Company = JobOpDtl.Company
and JobOper.JobNum = JobOpDtl.JobNum
and JobOper.AssemblySeq = JobOpDtl.AssemblySeq
and JobOper.OprSeq = JobOpDtl.OprSeq
inner join Erp.JobHead as JobHead on
JobOpDtl.Company = JobHead.Company
and JobOpDtl.JobNum = JobHead.JobNum
where (LaborDtl.ClockInDate = dateadd (day, -1, Constants.Today))
Any guidance would be appreciated.
Thanks
It could be as you mentioned. Any chance you could encapsulate the query and do your calculation for the division part outside just to test it?
Try casting decimal around Shouldhavetaken
CAST(Shouldhavetaken AS DECIMAL(9,2))
or something like that. See if works.
Another test you can run is to hardcode an int as the divisor and hardcode a decimal and see if you get different results. / 2 or / 2.0
I'm trying to achieve the following. Any help will be highly appreciated!
1.In the first table, do a self-join on a main deal with its corresponding offset deal
2.In the second table, find all the main deals where the prices don't match with the corresponding offset deals.
I don't think the second self-join works. That's supposed to link the main and the offset deals in the prices table and find cases where the prices don't match for certain price dates.
SELECT *
FROM transactions tran1 transactions tran2
,prices pr1
,prices pr1
WHERE tran1.tran_type = 1 --Original deal
AND tran2.tran_type = 2 -- Offset deal
AND tran1.tran_num = tran2.offset_tran_num
AND tran1.ins_num = pr1.ins_num
AND tran2.ins_num = pr2.ins_num
AND pr1.ins_num = pr2.ins_num
AND pr1.profile_num = pr2.profile_num
AND pr1.price_date = pr2.price_date
AND pr1.value != pr2.value
This statement could benefit from using proper join syntax, instead of this older style - immediately on trying to convert it you can see the alias 'pr1' is used twice, you meant to use 'pr2'
Given that being correct, (and assuming you may need some help on converting it to a modern syntax) you have :
SELECT *
FROM transactions tran1
inner join prices pr1 on pr1.ins_num = tran1.ins_num
inner join prices pr2 on pr2.ins_num = pr1.ins_num
and pr2.profile_num = pr1.profile_num
and pr2.price_date = pr1.price_date
and pr2.value != pr1.value
inner join transactions tran2 on tran2.ins_num = pr2.ins_num
and tran2.offset_tran_num = tran1.tran_num
WHERE tran1.tran_type = 1 --Original deal
AND tran2.tran_type = 2 -- Offset deal
It's easier to read but we would need more information, input + expected output to confirm if it is what you intended.
i have the following tables :
dbo.Details
Name Type SubType SerialNumber
D_01 TxA STxA1 4
D_02 TxB STxB2 3
D_03 TxC STxC1 2
D_04 TxD STxD1 7
D_05 TxD STxD1 1
D_06 TxD STxD1 9
dbo.DetailsType
Code Name
TxA A
TxB B
TxC C
...
dbo.DetailsSubType
Code Type Name CustomOR
STxA1 TxA A1 1
STxA2 TxA A2 0
STxB1 TxB B1 1
STxB2 TxB B2 0
STxC1 TxC C1 1
STxC2 TxC C2 0
STxD TxD D1 1
I want to know what query (A or B) is optimal in your opinion, with explanation please:
QUERY A
CREATE PROCEDURE XXX
(
#type nvarchar(10),
#subType nvarchar(10) = null
)
AS
BEGIN
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(
#subType is null or
(#custom = 0 and DTST.CustomOR= 0) or
(#custom = 1 and DT.SubType = #subType)
)
END
QUERY B
declare #custom bit = 0;
if (#subType is not null)
begin
select #custom = CustomOR from dbo.DetailsSubType where SubType = #subType
end
if (#custom = 0)
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
DTST.CustomOR = 0
end
else
begin
select
DTST.SubType,
DT.SerialNumber
from dbo.Details as DT
left join DetailsSubType as DTST
on DT.SubType = DTST.Code
where
DT.Type = #type
and
(DTST.CustomOR = 1 and DT.SubType = #subType)
end
Unfortunately, neither may be optimal. I am guessing that your concern is related to performance and the execution plan of the query. The second method definitely gives SQL Server better opportunities for the optimization plan -- simply because OR is really hard to optimize.
But this doesn't take into account "parameter sniffing". There are lots of articles about this subject (here is a reasonable one).
Parameter sniffing means that SQL Server compiles the query the first time the stored procedure is called. This saves the overhead of recompiling the queries -- a savings that is important if you have lots of "small" queries. But a fool's bargain for larger queries -- because it does not take statistics on the table into account.
I would suggest that you look into articles about this. You may find that the second solution is sufficient. You may find that simply adding option recompile is sufficient. You may find that you want to construct the query as dynamic SQL -- hey, you know it will be recompiled anyway. But you'll be able to make a more informed decision.
You could consider writing three queries that partition your results, where each query handles exactly one of your OR predicates, and UNION ALL the results.
In pseudo code:
SELECT ... FROM ... WHERE #subType is null
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DTST.CustomOR = 0 AND #custom = 0
UNION ALL
SELECT ... FROM ... WHERE #subType is NOT null AND DT.SubType = #subType AND #custom = 1
Having said that, what I actually think is that you should change your data model. It is extremely had (and very slow) to move forward with this setup. You probably haven't normalized your database properly.
I have a new report created in postgres - it should only run if there are 3 completed cases, and then determine an ultimate outcome (pass/fail) based on the individual cases. Depending on if it passes or fails, different information needs to be displayed.
I have the three main queries that I need, but now I'm at a loss of how to combine them into one.
The main query that contains the conditional logic for a case is here:
SELECT CASE
WHEN (SELECT COUNT(*) FROM "Alloc" WHERE "CodeID" = 1 AND "StatusCodeID" IN (2,6)) = 3
THEN
--Determine if case passes based on the 3 individual Assessments
CASE
WHEN (SELECT COUNT(*) FROM "Answer" ans
LEFT JOIN "AnswerOption" ansop ON ansop."AnswerOptionID" = ans."AnswerOptionID"
LEFT JOIN "QuestionTextInstance" qtxti ON qtxti."QuestionTextInstanceID" = ans."QuestionTextInstanceID"
LEFT JOIN "Alloc" aloc ON aloc."AllocID" = qtxti."AllocID"
LEFT JOIN "QuestionText" qtxt ON qtxt."QuestionTextID" = qtxti."QuestionTextID"
LEFT JOIN "Code" bcode ON aloc."CodeID" = bcode."CodeID"
WHERE bcode."CodeID" = 1 AND qtxt."QuestionTextID" = 11 AND ans."Value" = 0) >= 1 --At least 2 must have answered 'Yes'
THEN 'Passed' --Execute 'Pass.sql'
ELSE 'Did NOT Pass' --Execute 'NotPass.sql
END
ELSE 'Report did Not Run'
END
This runs correctly and gives the correct results based on the conditions. However, within the THEN and ELSE blocks on the inner CASE statement, I need to display different information that includes many columns and many joins (which I currently have in different .sql files) instead of Pass/Did NOT Pass, but I can not find a way to implement this because it seems that any query within THEN or ELSE blocks can only return a single value.
How can I accomplish this?
It can be done in plain SQL in various ways. One is with well known composite / row types:
SELECT (x).*
FROM (
SELECT CASE WHEN cond_a_here THEN
(SELECT t FROM t WHERE x = 1)
ELSE (SELECT t FROM t WHERE x = 2) END AS x
) sub
Note the parentheses in (x).*. Those are required for a composite type to make the syntax unambiguous.
It's simpler to understand with PL/pgSQL, but you need to understand how to handle composite types. I have posted many related answers ...
CREATE OR REPLACE FUNCTION foo_before()
RETURNS SETOF t AS
$func$
BEGIN
IF cond_a_here THEN
RETURN QUERY
SELECT * FROM t WHERE x = 1;
ELSE
RETURN QUERY
SELECT * FROM t WHERE x = 2;
END IF;
END
$func$ LANGUAGE plpgsql;
Good morning all,
I am trying to make a revenue view, but I am stuck due to a error I cannot fix.
I've made a function which calcutates total revenue from both Accessories and Bikes. The function combines these to give a result per month, which is the variable. I tried the seperate queries and they seem to work properly. The following occurs, I seem to be missing a step I think:
CREATE FUNCTION fnOmzetPMaand
(
#Maand AS int
)
RETURNS int
AS
BEGIN
DECLARE #AccOmzet AS int
SET #AccOmzet = (
SELECT dbo.fnOmzet(SUM(a.Accessoire_Dagprijs), h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum)
FROM Huurovereenkomst h
INNER JOIN HuurovereenkomstAccessoire ha
ON ha.HuurovereenkomstAccessoire_Huurovereenkomst_id = h.Huurovereenkomst_id
INNER JOIN Accessoire a
ON a.Accessoire_id = ha.HuurovereenkomstAccessoire_Accessoire_id
WHERE MONTH(h.Huurovereenkomst_Betaal_datum) = #Maand
GROUP BY h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum
)
DECLARE #FietsOmzet AS int
SET #FietsOmzet = (
SELECT dbo.fnOmzet(SUM(f.Fiets_Dagprijs), h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum)
FROM Huurovereenkomst h
INNER JOIN HuurovereenkomstFiets hf
ON hf.HuurovereenkomstFiets_Huurovereenkomst_id = h.Huurovereenkomst_id
INNER JOIN Fiets f
ON f.Fiets_id = hf.HuurovereenkomstFiets_Fiets_id
WHERE MONTH(h.Huurovereenkomst_Betaal_datum) = #Maand
GROUP BY h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum
)
RETURN #FietsOmzet + #AccOmzet
END
The view:
CREATE VIEW vOmzetPMaand
AS
SELECT DATENAME(MONTH, Huurovereenkomst_Betaal_datum) Maand,
dbo.fnOmzetPMaand(MONTH(Huurovereenkomst_Betaal_datum)) Omzet
FROM Huurovereenkomst
The error:
Msg 512, Level 16, State 1, Line 43
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as an
expression.
The expected results:
May 100
June 200
July 150
etc etc.
USE [Biker]
GO
/****** Object: UserDefinedFunction [dbo].[fnOmzet] Script Date: 05-Jan-18 12:05:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnOmzet]
(
#Bedrag AS int,
#BeginDatum AS DATETIME,
#EindDatum AS DATETIME
)
RETURNS int
AS
BEGIN
RETURN #Bedrag * DATEDIFF(Y, #BeginDatum, #EindDatum)
END
Yo have two subqueries but both are using the same table and grouped by the same criteria.
Without looking at your dataset, I would said that since the expected result is aggregated by month (based on your question): The impacting data is very likely that you have more than one combination of "h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum" per month. If that is the case, and since those columns values are grouped but not aggregated they would be returning multiple rows.
The following are some of the options that I can think of so far:
Option 1. Use aggregated functions on both of those fields like min and max (if the business logic allows for it in order to obtain a single record.
Option 2. Handling the multiple rows of each result and guarantee a single resultant value. (this can be accomplish with multiple solutions that could be from extra sub query withing your select statement, to even return the values on a table alike structure like table variable and then do an extra processing to return only the final value over the multiple rows)
Option 3. Extract somehow the relevant month from the combination "h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum" and group by the month instead of the you current grouping.
Important: I would prefer the option #3 or some variation of it if the business rules allows it.