SQL, keep NULL instead of 0 [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
i have a sql stored procedure that for some strange reason return 0 from a user-define-function even if this return a null value.
And more strange, if i moove only this part of stored procedure to a new query, this return correctly NULL, so i cant reproduce it
incriminated column is Abbattimento and the user function is [FN_Abbattimento_Copertura_Processo], linked in this way:
OUTER APPLY dbo.FN_Abbattimento_Copertura_Processo(Kint_TP006_IdAttivita,#AnnoRiferimento, IdLiv4, SPD.Ktyi_TP001_IdSocieta, Ktyi_TP058_IdSocietaService, tyi_TP058_Copertura, 2) [acp]
instead if i add
(Select * FROM dbo.FN_Abbattimento_Copertura_Processo(...)) AS Abbattimento
i get NULL VALUE, so the problem seem the call to function with apply
this is the entire store procedure:
ALTER PROCEDURE [dbo].EXT_Pian_DettaglioProcessi
#AnnoRiferimento SMALLINT = NULL
, #IdSocieta SMALLINT = NULL
, #IDAreaIncaricata SMALLINT = NULL
, #IDAreaCoinvolta SMALLINT = NULL
, #Ufficiale BIT = 0
, #Virtuali BIT = 0
AS
BEGIN
DECLARE #VirtualiIntero AS TINYINT
SET #VirtualiIntero =
CASE #Virtuali
WHEN 0 THEN 2
ELSE 0
END
--IF #IdSocieta = 0
-- SET #IdSocieta = NULL
--print #Virtuali
SELECT
SA.nvc_TP001_Societa AS Societa
, Kint_TP006_IdAttivita AS IdAttivita
, nvc_TP006_Attivita AS Attivita
, AreaIncaricata
, CONVERT(NVARCHAR(10), DataPianificata.InizioValStatus, 103) AS DataPianificazione
, StatusAttuale.DescrizioneStatus AS StatusAttuale
, CONVERT(NVARCHAR(10), StatusAttuale.InizioValStatus, 103) AS Dal
--, IdLiv2 AS IdProcessoLivello2
, Liv2 AS ProcessoLivello2
, IdLiv4 AS IdProcessoLivello4
, Liv4 AS ProcessoLivello4
, SPD.nvc_TP001_Societa AS SocietaProcesso
, SPS.nvc_TP001_SocietaAbbreviata AS SocietaServiceProcesso
, tyi_TP058_Copertura AS Copertura
, ksin_TP058_Anno AS AnnoInizioCopertura
--, CASE Abbattimento
-- WHEN 0 THEN NULL
-- ELSE Abbattimento
-- END AS Abbattimento
, Abbattimento
, TipoCopertura
, ISNULL(CP.CoperturaOverride, 0) + ISNULL(CP.CoperturaAttConRating, 0) AS CoperturaComplessivaProcesso
FROM
-- faccio una distinct per non avere duplicati per area e perimetro
-- non lo faccio nella select iniziale perchè altrimenti dovrei selezionare anche i campi usati solo per ordinamento
(SELECT DISTINCT
Kint_TP013_IdAttivita
, Ksin_TP013_AnnoRifPian
, Ktyi_TP013_ProgressivoPian
FROM Q_TP013_Pianificazione
WHERE
(ISNULL(#IDAreaIncaricata, #IDAreaCoinvolta) IS NULL
OR
Ksin_TP013_IdArea IN (SELECT idarea FROM dbo.FN_StrutturaAree(ISNULL(#IDAreaIncaricata, #IDAreaCoinvolta)))
)) AS Pian
INNER JOIN dbo.[FN_DettaglioPianificazione](#AnnoRiferimento, #Ufficiale) AS FiltroPianificazione
ON IdAttivita = Kint_TP013_IdAttivita
AND Progressivo = Ktyi_TP013_ProgressivoPian
INNER JOIN Q_TP006_Attivita
ON Kint_TP006_IdAttivita = Kint_TP013_IdAttivita
INNER JOIN TP001_Societa AS SA
ON SA.Ktyi_TP001_IdSocieta = tyi_TP006_IdSocieta
INNER JOIN
(SELECT
nvc_TP003_Area AS AreaIncaricata
, Kint_TP024_IdAttivita
FROM
Q_TP024_LinkAttivitaArea
INNER JOIN TP003_Area
ON Ksin_TP003_IdArea = Ksin_TP024_IdArea
WHERE
tyi_TP024_IdTipoLinkAttivitaArea = 1
AND Ksin_TP024_IdArea = COALESCE(#IDAreaIncaricata, Ksin_TP024_IdArea)
) AS AI
ON Kint_TP024_IdAttivita = Kint_TP006_IdAttivita
INNER JOIN dbo.FN_CoperturaProcessiAttivita(#AnnoRiferimento,0, #VirtualiIntero,0)
ON Kint_TP058_IdAttivita = Kint_TP006_IdAttivita
LEFT OUTER JOIN dbo.[FN_StrutturaProcessi](GETDATE())
ON IdLiv4 = Ksin_TP058_IdProcesso
LEFT OUTER JOIN TP001_Societa SPD
ON SPD.Ktyi_TP001_IdSocieta = Ktyi_TP058_IdSocieta
LEFT OUTER JOIN TP001_Societa SPS
ON SPS.Ktyi_TP001_IdSocieta = Ktyi_TP058_IdSocietaService
OUTER APPLY dbo.FN_DataStatusAttivita(Kint_TP006_IdAttivita,1) AS DataPianificata
OUTER APPLY dbo.FN_Status_Attivita_Periodo(GETDATE(), Kint_TP006_IdAttivita, null, null) AS StatusAttuale
OUTER APPLY dbo.FN_Abbattimento_Copertura_Processo(Kint_TP006_IdAttivita,#AnnoRiferimento, IdLiv4, SPD.Ktyi_TP001_IdSocieta, Ktyi_TP058_IdSocietaService, tyi_TP058_Copertura, 2) [acp]
OUTER APPLY dbo.FN_CoperturaProcesso(IdLiv4, SPD.Ktyi_TP001_IdSocieta, SPS.Ktyi_TP001_IdSocieta, #AnnoRiferimento) AS CP
WHERE
Ksin_TP013_AnnoRifPian = #AnnoRiferimento
--and IdAttivita = 12331
ORDER BY
Societa
, IdAttivita
, Ord1
, Ord2
, Ord3
, Ord4
And this is user-function that calculate Abbattimento:
ALTER FUNCTION [dbo].[FN_Abbattimento_Copertura_Processo]
(
-- Add the parameters for the function here
-- Add the parameters for the function here
#idAttivita int,
#anno smallint = 0,
#idProcesso smallint = 0,
#idSocieta tinyint = 0,
#idSocietaService tinyint = 0,
#copertura tinyint = 0,
#tipoAbbattimento TINYINT = 1 --1 Dalla prima presente, 2 dalla precedente
)
RETURNS TABLE
AS
RETURN
(
SELECT
CASE
WHEN #tipoAbbattimento = 1 THEN
(SELECT TOP 1 (CAST(tyi_TP058_Copertura AS SMALLINT) - #copertura)
FROM dbo.Q_TP058_LinkAttivitaLinkProcessoSocieta
WHERE Kint_TP058_IdAttivita = #idAttivita
AND Ksin_TP058_Anno between #anno-5 and #anno
AND Ksin_TP058_IdProcesso = #idProcesso
AND Ktyi_TP058_IdSocieta = #idSocieta
AND Ktyi_TP058_IdSocietaService = #idSocietaService
ORDER BY Ksin_TP058_Anno)
WHEN #tipoAbbattimento = 2 THEN
(SELECT TOP 1 (CAST(tyi_TP058_Copertura AS SMALLINT) - #copertura)
FROM dbo.Q_TP058_LinkAttivitaLinkProcessoSocieta
WHERE Kint_TP058_IdAttivita = #idAttivita
AND Ksin_TP058_Anno between #anno-5 and #anno-1
AND Ksin_TP058_IdProcesso = #idProcesso
AND Ktyi_TP058_IdSocieta = #idSocieta
AND Ktyi_TP058_IdSocietaService = #idSocietaService
ORDER BY Ksin_TP058_Anno DESC)
END AS Abbattimento
)
as you can see in comment now i have a case to convert back 0 to NULL, but honestly i don't like much this solution.
can you help me in finding the cause?

I've not read your query, but maybe will help for you NULLIFin following:
NULLIF(col,0)
It will return NULL if your column equals to 0

Related

Pass multiple values as parameter into a stored procedure

I have the stored procedure to which I pass the parameters. These parameters are indicated by another tool. One of the parameter has a list of entities like C200, C010 etc.
But the requirement is that the person who will run the stored procedure from another tool (Fluence) should be able to call by each entity but also to retrieve the data related to all the entities.
I have the SQL code shown here, which perfectly works if you choose one entity at a time. In the Where clause, I filter it based on the #Entitygroup which is Declared. From another tool to fetch the all the Entity is passed at Total_group parameter name.
ALTER PROCEDURE [DW].[SP_Fetch_Data]
#par_FiscalCalendarYear varchar(10),
#par_Entity AS varchar (10)
AS
BEGIN
/*
BALANCE ACCOUNTS
*/
DECLARE #FiscalCalendarYear int = SUBSTRING(#par_FiscalCalendarYear,1,4) /* 2022 */
, #FiscalCalendarMonth int = SUBSTRING (#par_FiscalCalendarYear,7,10) /* 11 */;
DECLARE #FiscalCalendarPeriod int = #FiscalCalendarYear * 100 + #FiscalCalendarMonth
DECLARE #Entitygroup varchar = #par_Entity
SELECT UPPER([GeneralJournalEntry].SubledgerVoucherDataAreaId) as [Entity]
, CONCAT(#FiscalCalendarYear, ' P', #FiscalCalendarMonth) as [Date]
, ISNULL(ConsolidationMainAccount, '') as [Account]
, [GeneralJournalAccountEntry].TransactionCurrencyCode as [Currency]
, SUM([GeneralJournalAccountEntry].TransactionCurrencyAmount) as [Amount]
, 'Import' as [Audit]
, 'TCUR' as [DataView]
, ISNULL([CostCenter].[GroupDimension], 'No Costcenter') as [CostCenter]
, 'No Group' as [Group]
, ISNULL([Intercompany].[DisplayValue], 'No Intercompany') as [Intercompany]
, 'Closing' as [Movement]
, ISNULL([ProductCategory].[GroupDimension], 'No ProductCategory') as [ProductCategory]
, ISNULL([Region].[GroupDimension], 'No Region') as [Region]
, ISNULL([SalesChannel].[GroupDimension], 'No SalesChannel') as [SalesChannel]
, 'Actual' as [Scenario]
FROM [D365].[GeneralJournalAccountEntry]
LEFT JOIN [D365].[GeneralJournalEntry] ON [GeneralJournalAccountEntry].GENERALJOURNALENTRY = [GeneralJournalEntry].[RECID]
AND [GeneralJournalAccountEntry].[PARTITION] = [GeneralJournalEntry].[PARTITION]
LEFT JOIN [D365].[FiscalCalendarPeriod] ON [GeneralJournalEntry].FiscalCalendarPeriod = FiscalCalendarPeriod.FiscalCalendarPeriod
LEFT JOIN [DW].[MainAccounts] ON [GeneralJournalAccountEntry].MainAccount = [MainAccounts].[RECID]
LEFT JOIN [DW].[Intercompany] ON [GeneralJournalAccountEntry].[RECID] = [Intercompany].[RECID]
LEFT JOIN [DW].[ProductCategory] ON [GeneralJournalAccountEntry].[RECID] = [ProductCategory].[RECID]
LEFT JOIN [DW].[Region] ON [GeneralJournalAccountEntry].[RECID] = [Region].[RECID]
LEFT JOIN [DW].[SalesChannel] ON [GeneralJournalAccountEntry].[RECID] = [SalesChannel].[RECID]
LEFT JOIN [DW].[CostCenter] ON [GeneralJournalAccountEntry].[RECID] = [CostCenter].[RECID]
WHERE [EnumItemName] IN ('Revenue', 'Expense', 'BalanceSheet', 'Asset', 'Liability')
AND [FiscalCalendarPeriod].FiscalCalendarPeriodInt <= #FiscalCalendarPeriod
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup
GROUP BY UPPER([GeneralJournalEntry].SubledgerVoucherDataAreaId)
, ISNULL(ConsolidationMainAccount, '')
, [GeneralJournalAccountEntry].TransactionCurrencyCode
, ISNULL([CostCenter].[GroupDimension], 'No Costcenter')
, ISNULL([Intercompany].[DisplayValue], 'No Intercompany')
, ISNULL([ProductCategory].[GroupDimension], 'No ProductCategory')
, ISNULL([Region].[GroupDimension], 'No Region')
, ISNULL([SalesChannel].[GroupDimension], 'No SalesChannel')
(would be a mess as a comment)
Do you mean if #par_entity is not null and has a value other than '' then use that else go on as if it is not there at all? Then you can change your code:
DECLARE #Entitygroup varchar = #par_Entity
To:
DECLARE #Entitygroup varchar = case
when #par_Entity is not null then #par_Entity
else ''
end;
And:
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup
To:
AND (#Entitygroup = '' OR [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup)
PS: Performance wise it wouldn't be optimal.
EDIT: You might also set it to the possible max value when it is not passed. ie:
DECLARE #Entitygroup varchar = case
when #par_Entity is null or #par_entity = '' then 'zzzzzz'
else #par_Entity
end;
AND [GeneralJournalEntry].SubledgerVoucherDataAreaId <= #Entitygroup

Using multiple nested OR statements in WHERE clause makes query return incorrect results

I have a WHERE clause that has a nested OR statement, as seen here:
-- Declaration of variables
DECLARE
#PageSize INT,
#PageNumber INT,
#SearchPhraseOne VARCHAR(20),
#SearchPhraseTwo VARCHAR(20),
#FilterCategory VARCHAR(30),
#FilterStatus TINYINT,
#NeedsFollowUp TINYINT,
#NeedsTraining TINYINT,
#NeedsInitialVacc TINYINT;
SET #PageNumber = 1;
SET #PageSize = 100;
SET #SearchPhraseOne = null;
SET #SearchPhraseTwo = null;
SET #FilterCategory = 'High Exposure';
SET #FilterStatus = null;
SET #NeedsFollowUp = 1;
SET #NeedsTraining = null;
SET #NeedsInitialVacc = null;
select * from(
select
vel.fullName,
vel.EecEmpNo,
vel.EecLocation,
vel.EecDateOfLastHire,
job.JbcDesc,
vei.eiInitialBBPDate,
vei.eiVCGivenDate,
iif(jv.verTypeName is null, 'Low Risk', jv.verTypeName) as vaccineCategory,
vel.eecEmplStatus,
count(distinct vh.vhID) as vaccCount,
max(isnull(vh.vhNextDateScheduled, null)) as maxNextDateScheduled,
max(cast(vh.vhSeriesComplete as int)) as seriesComplete,
iif(vel.eecEmplStatus = 'T', null,
coalesce(iif(max(cast(vh.vhSeriesComplete as int)) = 1, null, max(isnull(vh.vhNextDateScheduled, null))), -- check if the vaccine items have a SeriesComplete of 1, otherwise use NextDateScheduled
iif(vei.eiInitialBBPDate is not null, null, vel.EecDateOfLastHire), -- check if the InitialBBPDate is not null, if it isn't use DateOfLastHire
iif(vei.eiVCGivenDate is not null, null, vel.EecDateOfLastHire), null)) as actionDate -- check if the OrientationDate is not null, if it isn't use DateOfLastHire
-- if all three of these values are null then there's no ActionDate
-- Terminated employees will not have an action date assigned even if there's a match
from dbo.vaccEmpList vel
left join dbo.vaccEmployeeInfo vei on vei.eiEmployeeNo = vel.EecEmpNo
left join dbo.vaccVaccinationHistory vh on vh.vhEmployeeNo = vel.EecEmpNo
left join dbo.vaccVaccineTypeLookup vt on vh.vhVaccinationTypeID = vt.vtlVaccineTypeID and vt.vtIsActive = 1 -- Only get active vaccination types
join dbo.U_JobCode job on vel.EecJobCode = job.JbcJobCode
left join dbo.JobVerficationXref jv on vel.EecJobCode = jv.JobCode and jv.verName = 'Vaccination Category'
group by vel.fullName, vel.EecEmpNo, job.JbcDesc, jv.verTypeName, vel.EecLocation, vel.eecEmplStatus, vei.eiInitialBBPDate, vei.eiVCGivenDate, vel.EecDateOfLastHire
) as searchResults
where (
(
#SearchPhraseOne is null
or searchResults.fullName like #SearchPhraseOne + '%'
or searchResults.EecEmpNo = #SearchPhraseOne
)
and (
#SearchPhraseTwo is null
or searchResults.fullName like #SearchPhraseTwo + '%'
or searchResults.EecEmpNo = #SearchPhraseTwo
) -- Employee Name/ID
and (
#FilterStatus is null
or (searchResults.eecEmplStatus = 'A' or searchResults.eecEmplStatus = 'L')
) -- Employee Status
and (
#FilterCategory is null
or searchResults.vaccineCategory = #FilterCategory
) -- Employee Vaccination Category
and ( -- ISSUES OCCUR HERE
(#NeedsTraining is null
or (searchResults.actionDate is not null
and (searchResults.eiInitialBBPDate is null or searchResults.eiVCGivenDate is null))
) -- Needs Training if either of these two date values are null
or (#NeedsInitialVacc is null
or (searchResults.actionDate is not null
and (searchResults.vaccCount = 0))
-- Needs Initial Vaccination if there are no vaccine records
)
or (#NeedsFollowUp is null
or (searchResults.actionDate is not null
and ((searchResults.seriesComplete is null or searchResults.seriesComplete = 0) and searchResults.maxNextDateScheduled is not null))
-- Needs a follow-up date if no series complete was detected
)
)
)
The #NeedsFollowUp, #NeedsInitialVacc, and #NeedsTraining variables are all set by the variables above. When one or more of these are set to "1", the query should return employee entries that match the criteria inside their related statements. For example, if the "NeedsFollowUp" and "NeedsTraining" values are set to "1" then the query should return employees that need a follow-up or employees that need training.
Right now, when I set all three to "1" I receive the combined results I'm looking for, but if any of them are set to null, then the query doesn't return the correct results.
EDIT: Here's a reproducible example of what I'm seeing.
I think the way the clauses are set up is causing an issue, but I'm not really sure how to fix this. How can I get the OR statements to work in the way I described above?
I was able to make the OR clauses work correcting by switching from is null to is not null in my where clauses. Using the minimal example, it would look like this:
select * from AGENTS
where (
(#NeedsName is not null and AGENTS.AGENT_NAME is null)
or
(#NeedsCountry is not null and AGENTS.COUNTRY is null)
or
(#NeedsCountry is null and #NeedsName is null)
)
Be sure to include an additional clause for when all options are NULL, so that you can return the appropriate number of rows.
Here's a working version.

SQL MERGE error? [duplicate]

This question already has an answer here:
Msg 8672, Level 16, State 1, Line 1 The MERGE statement attempted to UPDATE or DELETE the same row more than once
(1 answer)
Closed 4 years ago.
Please see my below code - I am new to SQL and haven't seen this error before. Can someone advise as to where it is going wrong? The error I am getting is
(41669 row(s) affected) Msg 8672, Level 16, State 1, Line 41 The MERGE
statement attempted to UPDATE or DELETE the same row more than once.
This happens when a target row matches more than one source row. A
MERGE statement cannot UPDATE/DELETE the same row of the target table
multiple times. Refine the ON clause to ensure a target row matches at
most one source row, or use the GROUP BY clause to group the source
rows.
My code is below
DECLARE #FromDate DateTime = '31 jan 2018'
DECLARE #ToDate DateTime = '28 Feb 2018'
DECLARE #FromDate_In DateTime = #FromDate
DECLARE #ToDate_In DateTime = #ToDate
DECLARE #DirectDebit_In INT = 0
/**************************/
/* Agreement view */
/**************************/
SELECT
sag.[AgreementID]
,sca.CustomerID
,'Lost' AS [Type]
INTO #AgreementSet
FROM Money.dbo.[Fin_SS_TblAgreements] sag WITH (NOLOCK)
INNER JOIN Money.dbo.[Fin_SS_TblCustomersAgreements] [sca] WITH (NOLOCK) ON sca.DealID = sag.DealID AND sca.IsMasterCustomer = 1 AND sca.[SnapShot] = sag.[SnapShot]
INNER JOIN (
SELECT
sps.DealID
FROM Money.dbo.[Fin_SS_TblPaymentSchedule] sps WITH (NOLOCK, INDEX(IX_NC_Snapshot))
WHERE sps.[SnapShot] = #FromDate_In
GROUP BY sps.DealID
) sps ON sag.DealID = sps.DealID
WHERE sag.[SnapShot] = #FromDate_In
AND (sag.AgreementID IS NOT NULL)
AND ((sag.EndDate > sag.[SnapShot]) OR (sag.EndDate = sag.[SnapShot] AND sag.Autorenew = 1))
AND (sag.AgreementStartsOn <= sag.[SnapShot])
AND ( (#DirectDebit_In = 0 AND sag.ContractTypeID <> 3) OR (#DirectDebit_In = 1 AND sag.ContractTypeID = 3) OR (#DirectDebit_In = 3) )
DECLARE #MaxAgIDFrom INT
SELECT #MaxAgIDFrom = MAX(AgreementID) FROM Money.dbo.[Fin_SS_TblAgreements] WHERE [SnapShot] = #FromDate_In
DECLARE #MaxCIDFrom INT
SELECT #MaxCIDFrom = MAX(CustomerID) FROM #AgreementSet
MERGE INTO #AgreementSet L
USING (
SELECT
sag.[AgreementID]
,sca.CustomerID
,'New' AS [Type]
FROM Money.dbo.[Fin_SS_TblAgreements] sag WITH (NOLOCK)
INNER JOIN Money.dbo.[Fin_SS_TblCustomersAgreements] [sca] WITH (NOLOCK) ON sca.DealID = sag.DealID AND sca.IsMasterCustomer = 1 AND sca.[SnapShot] = sag.[SnapShot]
INNER JOIN (
SELECT
sps.DealID
FROM Money.dbo.[Fin_SS_TblPaymentSchedule] sps WITH (NOLOCK, INDEX(IX_NC_Snapshot))
WHERE sps.[SnapShot] = #ToDate_In
GROUP BY sps.DealID
) sps ON sag.DealID = sps.DealID
WHERE sag.[SnapShot] = #ToDate_In
AND (sag.AgreementID IS NOT NULL)
AND ((sag.EndDate > sag.[SnapShot]) OR (sag.EndDate = sag.[SnapShot] AND sag.Autorenew = 1))
AND (sag.AgreementStartsOn <= sag.[SnapShot])
AND ( (#DirectDebit_In = 0 AND sag.ContractTypeID <> 3) OR (#DirectDebit_In = 1 AND sag.ContractTypeID = 3) OR (#DirectDebit_In = 3) )
) N
ON L.AgreementID = N.AgreementID
WHEN MATCHED THEN
UPDATE SET [Type] =
CASE WHEN L.CustomerID = n.CustomerID THEN 'B/F'
ELSE 'Swap'
END
WHEN NOT MATCHED THEN
INSERT (AgreementID, CustomerID, [Type])
VALUES (n.AgreementID, n.CustomerID, n.[Type])
We get this very often.
This happens because of unique column that is being used in more than one row.
In your case this may be because of same agreementid for two or more different customers.
Check the data again and perform the same operation again.

SQL How to count all remains for each date

I have the following SQL function
CREATE FUNCTION [dbo].[GetCardDepartRemains]
(
#CardId INT,
#DepartId INT,
#Date DATETIME = NULL,
#DocumentId INT = NULL
)
RETURNS INT
AS
BEGIN
DECLARE #Res INT
SELECT
#Res = ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = #DepartId THEN 1 ELSE -1 END), 0)
FROM dbo.Operations
WHERE Operations.CardId = #CardId
AND (Operations.[Output] = 0 AND Operations.RecipientId = #DepartId OR Operations.Input = 0 AND Operations.SenderId = #DepartId)
AND (#Date IS NULL OR Operations.[Date] <= #Date)
RETURN #Res
END
GO
It counts remains for certain product on certain department on certain date.
If it is less then zero it means something's wrong with database
Now I need to find all remains for each card, for each department for all dates in database where result is wrong.
Theoretically speaking we can fing this by calling this procedure in a query like this
SELECT DISTINCT Operations.[Date] as [Date],
Departments.Id as Depart,
Cards.Id as [Card],
[dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) as Res
FROM [jewellery].[dbo].[Cards]
CROSS JOIN [jewellery].[dbo].[Departments]
CROSS JOIN [jewellery].[dbo].[Operations]
WHERE [dbo].[GetCardDepartRemains] (Cards.Id, Departments.Id,Operations.[Date],NULL) = -1
But this query executes more than 2 minutes, so we need to write a new query.
My query can find all remains for each card on each department on certain date (ex. '2016-10-04')
SELECT
[Card],
Depart,
Res
FROM
(SELECT
Cards.Id as [Card],
Departments.Id as Depart,
ISNULL(SUM(CASE WHEN Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id THEN 1 ELSE -1 END), 0) as Res
FROM Operations
CROSS JOIN Cards
CROSS JOIN Departments
WHERE Operations.CardId = Cards.Id
AND (Operations.[Output] = 0 AND Operations.RecipientId = Departments.Id OR Operations.Input = 0 AND Operations.SenderId = Departments.Id)
AND (Operations.[Date] <= '2016-10-04')
GROUP BY Cards.Id, Departments.Id
) as X
WHERE Res = -1
Can you help to re-write this query to find remains for all dates?
Assuming SQL Server is 2008 or above:
To find all dates, just comment out the date filter like this:
-- AND (Operations.[Date] <= '2016-10-04')
If you need to filter on a date range:
AND (Operations.[Date] between between getDate()-30 and getDate()
Changing -30 to however many days in the past. So a year ago would be -364.

Plain parameters slower than creating table parameters

I recently discovered the strange behavior, that a function (procedures possibly too) runs significantly slower, when I provide simple NCHAR parameters rather than table value parameters. After trying around a bit I also found, that the function with NCHAR parameters runs as fast as the table value parameter function if I select the NCHAR parameters into a table variable. Here is some code I've created with this.
Function with parameters:
ALTER FUNCTION [rpt].[ReportMaterial]
(
#dateto NCHAR(8),
#mfrnr NVARCHAR(10),
#vkorg NVARCHAR(4)
)
RETURNS #ReturnValue TABLE
(
MANDT NVARCHAR(3),
MATNR NVARCHAR(18),
MFRPN NVARCHAR(40),
MAKTX NVARCHAR(40),
WERKS NVARCHAR(4),
MTART NVARCHAR(4),
KBETR DECIMAL(11, 2),
KONWA NVARCHAR(5)
)
AS
BEGIN
-- FILL UP
SELECT #mfrnr = REPLICATE('0', 10 - LEN(#mfrnr)) + #mfrnr;
-- OUTPUT
WITH mat AS
(
-- direct assignment
SELECT
ma.MANDT,
ma.MATNR,
ma.MFRPN,
mc.WERKS,
ma.MTART
FROM sap.MARA ma
INNER JOIN sap.MARC mc ON ma.MATNR = mc.MATNR AND ma.MANDT = mc.MANDT AND mc.CDCDELETED = 0
INNER JOIN
(
-- better to use in subselect here
SELECT DISTINCT
WERKS
FROM [sap].[Werks] w
WHERE w.WERKSTYPE = 'D' AND w.VKORG = #vkorg
) w ON w.WERKS = mc.WERKS
WHERE ma.MANDT = N'100' AND ma.CDCDELETED = 0 AND ma.MFRNR = #mfrnr
)
INSERT INTO #ReturnValue (MANDT, MATNR, MFRPN, MAKTX, WERKS, MTART, KBETR, KONWA)
SELECT
t.MANDT,
t.MATNR,
t.MFRPN,
mk.MAKTX,
t.WERKS,
t.MTART,
COALESCE(pp.KBETR, 0) KBETR,
pp.KONWA
FROM
(
SELECT
m.MANDT,
m.MATNR,
m.MFRPN,
m.WERKS,
m.MTART
FROM mat m
UNION ALL
-- material selection (part of a hardbundle)
SELECT
ma.MANDT,
ma.MATNR,
ma.MFRPN,
ms.WERKS,
ma.MTART
FROM sap.MARA ma
INNER JOIN sap.MAST ms ON ms.MATNR = ma.MATNR AND ms.STLAN = N'3' AND ms.MANDT = ma.MANDT
INNER JOIN sap.STKO ko ON ko.STLNR = ms.STLNR AND ko.STLAL = ms.STLAL AND ko.MANDT = ms.MANDT
INNER JOIN sap.STPO po ON po.STLNR = ms.STLNR AND po.MANDT = ms.MANDT
INNER JOIN mat mr ON po.IDNRK = mr.MATNR AND mr.MANDT = po.MANDT -- use only relevant items...
INNER JOIN
(
-- better to use as subselect
SELECT DISTINCT
WERKS
FROM [sap].[Werks] w
WHERE w.WERKSTYPE = 'D' AND w.VKORG = #vkorg
) w ON w.WERKS = ms.WERKS
WHERE ma.MTART = N'ZFER' AND ma.MFRNR = N'0080001373' -- we use only dummy hard bundles (sign for mixed manufacturer bundles)
) t
INNER JOIN sap.MAKT mk ON mk.MATNR = t.MATNR AND mk.SPRAS = N'E' AND mk.MANDT = t.MANDT
LEFT JOIN sap.PurchasePrice pp ON pp.MATNR = t.MATNR AND pp.WERKS = t.WERKS AND pp.FLIFN = N'X' AND #dateto BETWEEN pp.VDATU AND pp.BDATU AND #dateto BETWEEN pp.DATAB AND pp.DATBI;
RETURN
END;
Funtion with parameters selected into table variables first:
ALTER FUNCTION [rpt].[ReportMaterial]
(
#dateto NCHAR(8),
#mfrnr NVARCHAR(10),
#vkorg NVARCHAR(4)
)
RETURNS #ReturnValue TABLE
(
MANDT NVARCHAR(3),
MATNR NVARCHAR(18),
MFRPN NVARCHAR(40),
MAKTX NVARCHAR(40),
WERKS NVARCHAR(4),
MTART NVARCHAR(4),
KBETR DECIMAL(11, 2),
KONWA NVARCHAR(5)
)
AS
BEGIN
-- FILL UP
SELECT #mfrnr = REPLICATE('0', 10 - LEN(#mfrnr)) + #mfrnr;
--> Better performance if using a table to join instead of directly using of parameters
-- CREATE TABLE
DECLARE #keyValue TABLE
(
MFRNR NVARCHAR(10),
VKORG NVARCHAR(4)
);
INSERT INTO #keyValue (MFRNR, VKORG) VALUES (#mfrnr, #vkorg);
-- OUTPUT
WITH mat AS
(
-- direct assignment
SELECT
ma.MANDT,
ma.MATNR,
ma.MFRPN,
mc.WERKS,
ma.MTART
FROM sap.MARA ma
INNER JOIN sap.MARC mc ON ma.MATNR = mc.MATNR AND ma.MANDT = mc.MANDT AND mc.CDCDELETED = 0
INNER JOIN
(
-- better to use in subselect here
SELECT DISTINCT
WERKS
FROM [sap].[Werks] w
INNER JOIN #keyValue kv1 ON kv1.VKORG = w.VKORG
WHERE w.WERKSTYPE = 'D'
) w ON w.WERKS = mc.WERKS
INNER JOIN #keyValue kv2 ON kv2.MFRNR = ma.MFRNR
WHERE ma.MANDT = N'100' AND ma.CDCDELETED = 0
)
INSERT INTO #ReturnValue (MANDT, MATNR, MFRPN, MAKTX, WERKS, MTART, KBETR, KONWA)
SELECT
t.MANDT,
t.MATNR,
t.MFRPN,
mk.MAKTX,
t.WERKS,
t.MTART,
COALESCE(pp.KBETR, 0) KBETR,
pp.KONWA
FROM
(
SELECT
m.MANDT,
m.MATNR,
m.MFRPN,
m.WERKS,
m.MTART
FROM mat m
UNION ALL
-- material selection (part of a hardbundle)
SELECT
ma.MANDT,
ma.MATNR,
ma.MFRPN,
ms.WERKS,
ma.MTART
FROM sap.MARA ma
INNER JOIN sap.MAST ms ON ms.MATNR = ma.MATNR AND ms.STLAN = N'3' AND ms.MANDT = ma.MANDT
INNER JOIN sap.STKO ko ON ko.STLNR = ms.STLNR AND ko.STLAL = ms.STLAL AND ko.MANDT = ms.MANDT
INNER JOIN sap.STPO po ON po.STLNR = ms.STLNR AND po.MANDT = ms.MANDT
INNER JOIN mat mr ON po.IDNRK = mr.MATNR AND mr.MANDT = po.MANDT -- use only relevant items...
INNER JOIN
(
-- better to use as subselect
SELECT DISTINCT
WERKS
FROM [sap].[Werks] w
INNER JOIN #keyValue kv1 ON kv1.VKORG = w.VKORG
WHERE w.WERKSTYPE = 'D'
) w ON w.WERKS = ms.WERKS
WHERE ma.MTART = N'ZFER' AND ma.MFRNR = N'00000000'
) t
INNER JOIN sap.MAKT mk ON mk.MATNR = t.MATNR AND mk.SPRAS = N'E' AND mk.MANDT = t.MANDT
LEFT JOIN sap.PurchasePrice pp ON pp.MATNR = t.MATNR AND pp.WERKS = t.WERKS AND pp.FLIFN = N'X' AND #dateto BETWEEN pp.VDATU AND pp.BDATU AND #dateto BETWEEN pp.DATAB AND pp.DATBI;
RETURN
END;
Does anybody have an Idea, why this is the case? Do I need to leverage this effect by allways selecting parameters into temporary tables/table variables?