Query optimization. Duplicate subqueries - sql

We found a slow query in our legacy system. What I see in the query is a duplicate fragment. Here's the full query:
DECLARE #SellerId INT;
DECLARE #DateFrom DATETIME;
DECLARE #DateTo DATETIME;
SET #SellerId = 5396884;
SET #DateFrom = '2016-01-05';
SET #DateTo = '2016-10-08';
DECLARE #CurrentDate DATETIME;
SET #CurrentDate = GETDATE();
CREATE TABLE #ReportDate (codes INT, dates DATETIME);
DECLARE #dif as INT;
DECLARE #cont as INT;
DECLARE #currdate as DATETIME;
SET #dif = DATEDIFF(day, #DateFrom, #DateTo);
SET #cont = 1;
SET #currdate = #DateFrom - 1;
WHILE (#cont <= #dif + 1)
BEGIN
SET #currdate = DATEADD(DAY, 1, #currdate);
INSERT INTO #ReportDate VALUES (#cont, #currdate);
SET #cont = #cont + 1;
END
/* HOW TO OPTIMIZE THIS ONE? */
SELECT
#ReportDate.dates as valid_date,
(
SELECT
COUNT(DISTINCT(nonCancelledSales.num_remito)) as actives
FROM
(
SELECT *
FROM salesView
WHERE
salesView.sell_id NOT IN
(
SELECT sell_id
FROM salesStates
WHERE
salesStates.aborted = 1
)
) nonCancelledSales
WHERE
nonCancelledSales.seller_id = #SellerId AND
nonCancelledSales.cancelled = 0 AND
nonCancelledSales.void = 0 AND
nonCancelledSales.hasDiscount = 0 AND
nonCancelledSales.dateOfSale <= #ReportDate.dates AND
nonCancelledSales.currentState = (SELECT MAX(hveest.date)
FROM salesStates hveest
WHERE
hveest.sell_id = nonCancelledSales.sell_id AND
hveest.date <= #ReportDate.dates) AND
nonCancelledSales.lastProductDate = (SELECT MAX(hvepro.date)
FROM productHistory hvepro
WHERE
hvepro.sell_id = nonCancelledSales.sell_id AND
hvepro.date <= #ReportDate.dates)
) total_actives,
(
SELECT
ISNULL(SUM(nonCancelledSales.paymentValue),0) as active
FROM
(
SELECT *
FROM salesView
WHERE
salesView.sell_id NOT IN
(
SELECT sell_id
FROM salesStates
WHERE
salesStates.aborted = 1
)
) nonCancelledSales
WHERE
nonCancelledSales.seller_id = #SellerId AND
nonCancelledSales.cancelled = 0 AND
nonCancelledSales.void = 0 AND
nonCancelledSales.hasDiscount = 0 AND
nonCancelledSales.dateOfSale <= #ReportDate.dates AND
nonCancelledSales.currentState = (SELECT MAX(hveest.date)
FROM salesStates hveest
WHERE
hveest.sell_id = nonCancelledSales.sell_id AND
hveest.date <= #ReportDate.dates) AND
nonCancelledSales.lastProductDate = (SELECT MAX(hvepro.date)
FROM productHistory hvepro
WHERE
hvepro.sell_id = nonCancelledSales.sell_id AND
hvepro.date <= #ReportDate.dates)
) active
FROM
#ReportDate
GROUP BY
#ReportDate.dates
DROP TABLE #ReportDate
Here are the two duplicated fragments I see:
(
SELECT
COUNT(DISTINCT(nonCancelledSales.num_remito)) as actives
FROM
(
SELECT *
FROM salesView
WHERE
salesView.sell_id NOT IN
(
SELECT sell_id
FROM salesStates
WHERE
salesStates.aborted = 1
)
) nonCancelledSales
WHERE
nonCancelledSales.seller_id = #SellerId AND
nonCancelledSales.cancelled = 0 AND
nonCancelledSales.void = 0 AND
nonCancelledSales.hasDiscount = 0 AND
nonCancelledSales.dateOfSale <= #ReportDate.dates AND
nonCancelledSales.currentState = (SELECT MAX(hveest.date)
FROM salesStates hveest
WHERE
hveest.sell_id = nonCancelledSales.sell_id AND
hveest.date <= #ReportDate.dates) AND
nonCancelledSales.lastProductDate = (SELECT MAX(hvepro.date)
FROM productHistory hvepro
WHERE
hvepro.sell_id = nonCancelledSales.sell_id AND
hvepro.date <= #ReportDate.dates)
) total_actives,
(
SELECT
ISNULL(SUM(nonCancelledSales.paymentValue),0) as active
FROM
(
SELECT *
FROM salesView
WHERE
salesView.sell_id NOT IN
(
SELECT sell_id
FROM salesStates
WHERE
salesStates.aborted = 1
)
) nonCancelledSales
WHERE
nonCancelledSales.seller_id = #SellerId AND
nonCancelledSales.cancelled = 0 AND
nonCancelledSales.void = 0 AND
nonCancelledSales.hasDiscount = 0 AND
nonCancelledSales.dateOfSale <= #ReportDate.dates AND
nonCancelledSales.currentState = (SELECT MAX(hveest.date)
FROM salesStates hveest
WHERE
hveest.sell_id = nonCancelledSales.sell_id AND
hveest.date <= #ReportDate.dates) AND
nonCancelledSales.lastProductDate = (SELECT MAX(hvepro.date)
FROM productHistory hvepro
WHERE
hvepro.sell_id = nonCancelledSales.sell_id AND
hvepro.date <= #ReportDate.dates)
) active
Is it fully necessary to duplicate the query ? In the first one he's getting:
COUNT(DISTINCT(nonCancelledSales.num_remito)) as actives
on the second one:
ISNULL(SUM(nonCancelledSales.paymentValue),0) as active
I suppose there has to be some way to rewrite the query but I'm not sure how.

You can combine these, if you use OUTER APPLY.
The idea is:
SELECT . . ., x.actives, x.active
FROM #ReportDate OUTER APPLY
(SELECT COUNT(DISTINCT(nonCancelledSales.num_remito)) as actives,
COALESCE(SUM(nonCancelledSales.paymentValue), 0) as active
. . . -- rest of query here
) x;
In this case, OUTER APPLY is a lot like a correlated subquery in the FROM clause that can return multiple rows.

Related

How to replace where clause with a function and use it in stored procedures?

I have two stored procedures that they have the same where clause, one of them is use for pagination:
ALTER PROCEDURE [dbo].[ret_PayrollCalculations_GetPagedFilteredPersonnels]
(
#SortField varchar(512),
#PageNo int,
#PageSize int,
#CalculationCommandType int,
#StartWorkingPeriodId int,
#StartYear int,
#EndWorkingPeriodId int,
#EndYear int,
#Status int,
#SalariedGuid uniqueidentifier,
#SalariedType int,
#OfficeNumber varchar(64),
#SalariedResultSetId int,
#Keyword nvarchar(2024),
#OperationalUnitIds [dbo].[ListOfID] READONLY
)
AS
DECLARE #AccessibleSalariedGuids [dbo].[ListOfGuid]
IF EXISTS (SELECT * FROM #OperationalUnitIDs)
BEGIN
INSERT INTO #AccessibleSalariedGuids
SELECT FeatureGuid FROM prs_OperationalUnitFeatures
WHERE
OperationalUnitID in (SELECT * FROM #OperationalUnitIDs) AND
FeatureFlag IN (2,4)
END
ELSE BEGIN
INSERT INTO #AccessibleSalariedGuids
SELECT [Guid] FROM ret_vwSalaried
END
DECLARE #OffsetRows INT = CASE WHEN #PageNo = 1 THEN 0 ELSE (#PageNo - 1) * #PageSize END;
DECLARE #TotalCount INT;
WITH Result AS(
SELECT
CASE
WHEN #SortField = N'[FullName]' THEN ROW_NUMBER() OVER (ORDER BY salaried.[FullName])
WHEN #SortField = N'[FullName] DESC' THEN ROW_NUMBER() OVER (ORDER BY salaried.[FullName] DESC)
WHEN #SortField = N'[WorkingPeriodTitle]' THEN ROW_NUMBER() OVER (ORDER BY calcs.[Year],workingPeriods.[Index])
WHEN #SortField = N'[WorkingPeriodTitle] DESC' THEN ROW_NUMBER() OVER (ORDER BY calcs.[Year] DESC,workingPeriods.[Index] DESC)
WHEN #SortField = N'[PersonnelNo]' THEN ROW_NUMBER() OVER (ORDER BY salaried.[PersonnelNo])
WHEN #SortField = N'[PersonnelNo] DESC' THEN ROW_NUMBER() OVER (ORDER BY salaried.[PersonnelNo] DESC)
END AS [RowNumber],
calcs.[Guid],
calcs.[CalculationCommandGuid],
calcs.[SalariedGuid],
salaried.[PersonnelNo],
salaried.[FullName] AS [PersonnelFullName],
command.[Type] [CommandType],
salaried.[SalariedType],
workingPeriods.Title AS [WorkingPeriodTitle],
command.[MainYear] AS [Year],
command.[Approved],
command.[FinalApproved]
FROM
ret_PayrollCalculationCommands command INNER JOIN
ret_PayrollCalculations calcs ON calcs.[CalculationCommandGuid] = command.[Guid] INNER JOIN
ret_vwSalaried salaried ON calcs.[SalariedGuid] = salaried.[Guid] INNER JOIN
prs_workingPeriods workingPeriods ON workingPeriods.[Id] = command.[MainWorkingPeriodID]
WHERE
ISNULL(calcs.[MainCalculation],0) = 1 AND
ISNULL(command.[Deleted],0)=0 AND
(#Keyword = '' OR salaried.PersonnelNo = #Keyword OR salaried.FullName LIKE N'%' + #Keyword + '%' OR salaried.FullNameReversed LIKE N'%' + #Keyword + '%') AND
(ISNULL(#calculationCommandType, 0) = 0 OR command.[Type] = #calculationCommandType) AND
(ISNULL(#StartYear, 0) = 0 OR command.[MainYear] >= #StartYear) AND
(ISNULL(#StartWorkingPeriodId, 0) = 0 OR command.[MainWorkingPeriodID] >= #StartWorkingPeriodId) AND
(ISNULL(#EndYear, 0) = 0 OR command.[MainYear] <= #EndYear) AND
(ISNULL(#EndWorkingPeriodId, 0) = 0 OR command.[MainWorkingPeriodID] <= #EndWorkingPeriodId) AND
(ISNULL(#Status, -1) = -1 OR command.[Approved] = #Status) AND
(ISNULL(#SalariedType, -1) = -1 OR salaried.[SalariedType] = #SalariedType) AND
(ISNULL(#SalariedGuid,'00000000-0000-0000-0000-000000000000') = '00000000-0000-0000-0000-000000000000' OR calcs.[SalariedGuid] = #SalariedGuid) AND
(#OfficeNumber IS NULL OR salaried.[OfficeNumber] LIKE '%'+#OfficeNumber+'%') AND
(ISNULL(#SalariedResultSetId, -1) = -1 OR calcs.[SalariedGuid] IN (SELECT [SalariedGuid] FROM ret_SalariedResultSetItems WHERE SalariedResultSetID = #SalariedResultSetId)) AND
(calcs.[SalariedGuid] IN (SELECT * FROM #AccessibleSalariedGuids))
), TableForTotalCount AS (SELECT COUNT(*) As TotalCount FROM Result)
SELECT
(SELECT TOP 1 TotalCount FROM TableForTotalCount) AS TotalCount,
*
FROM Result
ORDER BY
[RowNumber]
OFFSET #OffsetRows ROWS
FETCH NEXT #PageSize ROWS ONLY
and another one supposed to return some Guids
ALTER PROCEDURE [dbo].[ret_PayrollCalculations_GetFilteredPersonnels]
(
#CalculationCommandType int,
#StartWorkingPeriodId int,
#StartYear int,
#EndWorkingPeriodId int,
#EndYear int,
#Status int,
#SalariedGuid uniqueidentifier,
#SalariedType int,
#OfficeNumber varchar(64),
#SalariedResultSetId int,
#Keyword nvarchar(2024),
#OperationalUnitIds [dbo].[ListOfID] READONLY
)
AS
DECLARE #AccessibleSalariedGuids [dbo].[ListOfGuid]
IF EXISTS (SELECT * FROM #OperationalUnitIDs)
BEGIN
INSERT INTO #AccessibleSalariedGuids
SELECT FeatureGuid FROM prs_OperationalUnitFeatures
WHERE
OperationalUnitID in (SELECT * FROM #OperationalUnitIDs) AND
FeatureFlag IN (2,4)
END
ELSE BEGIN
INSERT INTO #AccessibleSalariedGuids
SELECT [Guid] FROM ret_vwSalaried
END
SELECT
calcs.[Guid]
FROM
ret_PayrollCalculationCommands command INNER JOIN
ret_PayrollCalculations calcs ON calcs.[CalculationCommandGuid] = command.[Guid] INNER JOIN
ret_vwSalaried salaried ON calcs.[SalariedGuid] = salaried.[Guid]
WHERE
ISNULL(calcs.[MainCalculation],0) = 1 AND
ISNULL(command.[Deleted],0)=0 AND
(#Keyword = '' OR salaried.PersonnelNo = #Keyword OR salaried.FullName LIKE N'%' + #Keyword + '%' OR salaried.FullNameReversed LIKE N'%' + #Keyword + '%') AND
(ISNULL(#calculationCommandType, 0) = 0 OR command.[Type] = #calculationCommandType) AND
(ISNULL(#StartYear, 0) = 0 OR command.[MainYear] >= #StartYear) AND
(ISNULL(#StartWorkingPeriodId, 0) = 0 OR command.[MainWorkingPeriodID] >= #StartWorkingPeriodId) AND
(ISNULL(#EndYear, 0) = 0 OR command.[MainYear] <= #EndYear) AND
(ISNULL(#EndWorkingPeriodId, 0) = 0 OR command.[MainWorkingPeriodID] <= #EndWorkingPeriodId) AND
(ISNULL(#Status, -1) = -1 OR command.[Approved] = #Status) AND
(ISNULL(#SalariedType, -1) = -1 OR salaried.[SalariedType] = #SalariedType) AND
(ISNULL(#SalariedGuid,'00000000-0000-0000-0000-000000000000') = '00000000-0000-0000-0000-000000000000' OR calcs.[SalariedGuid] = #SalariedGuid) AND
(#OfficeNumber IS NULL OR salaried.[OfficeNumber] LIKE '%'+#OfficeNumber+'%') AND
(ISNULL(#SalariedResultSetId, -1) = -1 OR calcs.[SalariedGuid] IN (SELECT [SalariedGuid] FROM ret_SalariedResultSetItems WHERE SalariedResultSetID = #SalariedResultSetId)) AND
(calcs.[SalariedGuid] IN (SELECT * FROM #AccessibleSalariedGuids))
When a bug appears I have to fix the problem in both stored procedures, to avoid duplication I wanted Where clauses in a function and call the function in stored procedures,
But I don't know how?
This is how I would approach this:
If you're using Microsoft sql-server, you can make transact-sql code.
Convert your sql procedure into a string and make the Where clause a text variable that you declare elsewhere.
So it's creating a meta- procedure.
For eg.
DECLARE #whereClause LONGTEXT;
DECLARE #SQLString LONGTEXT;
SET #whereClause = 'i=1'
SET #SQLString = 'SELECT * FROM table WHERE' & #whereClause
sp_executesql SQLString

adding two numeric values results null value in stored procedure

In a stored procedure I am trying to add two numeric values as shown
DECLARE #tradeamt1 NUMERIC(23,2)
DECLARE #tradeamt3 NUMERIC(23,2)
DECLARE #value NUMERIC(23,2)
if (#retVal1 = 0)
BEGIN
SELECT #row_count = count(1),
#tradeamt1=Sum(trade_amt) ,
#units= Sum(curr_shrs_num)
FROM [csr_staging].[dbo].[fi_impact_source] WITH(NOLOCK)
Where acct_id = 'BANKFEES'
and SD_ID >= EFF_DT
print #tradeamt1
END
if(#retVal3 > 0)
BEGIN
select #row_count = count(1) - #retVal3 + #row_count,#tradeamt3=Sum(trade_amt),#units= #units +Sum(curr_shrs_num) - #currshares
FROM [CSR_Staging].[dbo].[fi_impact_source] WITH(NOLOCK)
where (clearing_code = 'MBS'or clearing_code = 'CNS') and (SD_ID >= EFF_DT)
print #tradeamt3
END
set #value = #tradeamt1 +#tradeamt3
this value gives null instead of adding tradeamount1 =1.00 and tradeamount3 = 191432650.13
Maybe this could work for your SP:
declare #tradeamt1 numeric(23, 2) = 0
, #tradeamt3 numeric(23, 2) = 0
, #value numeric(23, 2) = 0
select #row_count = 0, #units = 0
if #retVal1 = 0
select #row_count = count(1)
, #tradeamt1 = isnull(sum(trade_amt), 0)
, #units = isnull(sum(curr_shrs_num), 0)
from [CSR_Staging].[dbo].[fi_impact_source] with (nolock)
where acct_id = 'BANKFEES'
and SD_ID >= EFF_DT
if #retVal3 > 0
select #row_count = #row_count + count(1) - #retVal3
, #tradeamt3 = isnull(sum(trade_amt), 0)
, #units = #units + isnull(sum(curr_shrs_num), 0) - #currshares
from [CSR_Staging].[dbo].[fi_impact_source] with (nolock)
where clearing_code in ('MBS', 'CNS')
and SD_ID >= EFF_DT
set #value = #tradeamt1 + #tradeamt3
Using isNull( ensures that both #tradeamt1 and #tradeamt3 are not null
This is most probably because when tradeamt1 and tradeamt3 are not initialised and for some reason also not assigned values.
You could initialise them with:
DECLARE #tradeamt1 NUMERIC(23,2) = 0
DECLARE #tradeamt3 NUMERIC(23,2) = 0
DECLARE #value NUMERIC(23,2) = 0
Let me know if that helps.
:)

The MIN function requires 1 argument(s)

Below is the code snippet in which MIN function using. When execute below code it is giving an error.
CREATE FUNCTION [dbo].[FN_TempCalcTransportExemp]
(
#EmployeeID varchar(20),
#PayElement varchar(20),
#Month varchar(10),
#FinYear varchar(10)
)
RETURNS decimal
AS
BEGIN
DECLARE #TarnsportExemption decimal(18,2) = 0
DECLARE #TDSIsFullExemption bit
DECLARE #PermanentPhysicalDisability decimal(18,2) = 0
DECLARE #UsingComapnyCar bit
DECLARE #Conveyance decimal(18,2) = 0
DECLARE #TransYes decimal(18,2) = 0
DECLARE #TransNo decimal(18,2) = 0
DECLARE #MinConveyance decimal(18,2) = 0
DECLARE #MinTransport decimal(18,2) = 0
SELECT
#TDSIsFullExemption = TDSDetailsFullExemption,
#TransYes = TDSDetailsYes,
#TransNo = TDSDetailsNo
FROM
tbl_TDSSettingDetails
WHERE
TDSSettingsDetailID = 2
SELECT
#Conveyance = #Month
FROM
tbl_Income
WHERE
EmployeeID = #EmployeeID
AND Element = #PayElement
AND FinancialYear = #FinYear
SELECT
#UsingComapnyCar = UsingCompanyCar,
#PermanentPhysicalDisability = PermanentPhysicalDisability
FROM
tbl_Employee_TDS
WHERE
EmployeeID = #EmployeeID
AND TDSFinancialYear = #FinYear
IF (#TDSIsFullExemption = 1)
BEGIN
SET #TarnsportExemption = #Conveyance
END
ELSE
BEGIN
IF (#UsingComapnyCar = 1)
BEGIN
IF (#Conveyance = 0)
BEGIN
SET #MinConveyance = 0
END
ELSE
BEGIN
SET #MinConveyance = #Conveyance
END
IF (#PermanentPhysicalDisability = 1)
BEGIN
SET #MinTransport = #TransYes
END
ELSE
BEGIN
SET #MinTransport = #TransNo
END
SET #TarnsportExemption = MIN(#MinConveyance, #MinTransport)
END
ELSE
BEGIN
SET #TarnsportExemption = 0
END
END
RETURN #TarnsportExemption
END
Error Message:
Msg 174, Level 15, State 1, Procedure FN_TempCalcTransportExemp, Line
66
The MIN function requires 1 argument(s).
set #TarnsportExemption = MIN(#MinConveyance,#MinTransport) - The MIN function is not what you think it is.
You probably want to do something like this:
set #TarnsportExemption = CASE WHEN #MinConveyance < #MinTransport THEN
#MinConveyance
ELSE
#MinTransport
END
Another option is this:
SELECT #TarnsportExemption = MIN(val)
FROM
(
SELECT #MinConveyance as val
UNION ALL
SELECT #MinTransport as val
)
And one more option is to use the values clause:
SELECT #TarnsportExemption = MIN(val)
FROM (VALUES ( #MinConveyance), (#MinTransport)) AS value(val)
Change your min statement like below MIN. Please refer MIN (Transact-SQL)
--fROM
set #TarnsportExemption = MIN(#MinConveyance,#MinTransport)
--To
SELECT #TarnsportExemption = MIN(A) FROM (
SELECT #MinConveyance A
UNION ALL
SELECT #MinTransport
)AS AA
In SQL, MIN function will return you minimum value of a column from selected list; so you can use MIN only in inline queries.
e.g. Select min(Salary) from Tbl_Employee
So, in your case either you can use case when then or union all to get minimum value from two variables as:-
SET #TarnsportExemption = CASE WHEN #MinConveyance < #MinTransport THEN #MinConveyance ELSE #MinTransport END
OR
SELECT #TarnsportExemption = MIN(TEMPS.[VALUE])
FROM (
SELECT #MinConveyance AS VALUE
UNION ALL
SELECT #MinTransport AS VALUE
) AS TEMPS

Convert ugly T SQL query to CTE

I'm having trouble making my TSQL query into a cte. I'm pretty sure it's a candidate for that. I've used cte's in other queries, but this one has to get data from multiple sources and that's causing me some grief. Here's what I've got that works:
declare #projid int = 0
declare #proj varchar = (select projectid from Projects where projectid = #projid)
declare #maxid int = (select max(projectid) from Projects)
declare #projsize bigint = (select projectsizeexpitem from Projects where projectid = #projid)
declare #numofdays int = (select DATEDIFF(DAY, MIN(starttime), GETDATE()) from transactions where projectid = #projid)
declare #dailyrate bigint = (select (SUM(transactionitemsmigrated)/#numofdays) from Transactions where projectid = #projid)
declare #complete bigint = (select SUM(transactionitemsmigrated) from transactions where projectid = #projid)
declare #daysremaining int = (select (CAST(GETDATE() AS int) + ((#projsize-#complete)/#dailyrate)))
WHILE #projid < #maxid
BEGIN
IF #projid IN (select projectid from projects)
BEGIN
set #proj = (select projectname from Projects where projectid = #projid)
set #maxid = (select max(projectid) from Projects)
set #projsize = (select projectsizeexpitem from Projects where projectid = #projid)
set #numofdays = (select DATEDIFF(DAY, MIN(starttime), GETDATE()) from transactions where projectid = #projid)
set #dailyrate = (select (SUM(transactionitemsmigrated)/#numofdays) from Transactions where projectid = #projid)
set #complete = (select SUM(transactionitemsmigrated) from transactions where projectid = #projid)
set #daysremaining = (select (CAST(GETDATE() AS int) + ((#projsize-#complete)/#dailyrate)))
select
[Project] = (select projectname from projects where projectid = #projid),
[TotalItems] = #projsize,
[DaysActive] = #numofdays,
[DailyRate] = #dailyrate,
[TotalComplete] = #complete,
[ItemsRemaining] = #projsize - #complete,
[DaysRemaining] = ((#projsize-#complete)/#dailyrate),
[CompDate] = CAST(CAST(#daysremaining AS datetime) AS date)
SET #projid = #projid + 1
END
ELSE
BEGIN
SET #projid = #projid + 1
END
END
The problem with this one is that it returns each iteration of the SELECT statement as a separate results table. I'd like to have them all together for aggregation. Here's what I've tried:
declare #projid int = 0
declare #proj varchar = (select projectid from Projects where projectid = #projid)
declare #maxid int = (select max(projectid) from Projects)
declare #projsize bigint = (select projectsizeexpitem from Projects where projectid = #projid)
declare #numofdays int = (select DATEDIFF(DAY, MIN(starttime), GETDATE()) from transactions where projectid = #projid)
declare #dailyrate bigint = (select (SUM(transactionitemsmigrated)/#numofdays) from Transactions where projectid = #projid)
declare #complete bigint = (select SUM(transactionitemsmigrated) from transactions where projectid = #projid)
declare #daysremaining int = (select (CAST(GETDATE() AS int) + ((#projsize-#complete)/#dailyrate)))
;WITH cte AS (
select
[ID] = 1,
[Project] = (select projectname from projects where projectid = #projid),
[TotalItems] = #projsize,
[DaysActive] = #numofdays,
[DailyRate] = #dailyrate,
[TotalComplete] = #complete,
[ItemsRemaining] = #projsize - #complete,
[DaysRemaining] = ((#projsize-#complete)/#dailyrate),
[CompDate] = CAST(CAST(#daysremaining AS datetime) AS date)
UNION ALL
select
[ID] + 1,
[Project] = (select projectname from projects where projectid = #projid),
[TotalItems] = #projsize,
[DaysActive] = #numofdays,
[DailyRate] = #dailyrate,
[TotalComplete] = #complete,
[ItemsRemaining]= #projsize - #complete,
[DaysRemaining] = ((#projsize-#complete)/#dailyrate),
[CompDate] = CAST(CAST(#daysremaining AS datetime) AS date)
from cte
where [ID] < #maxid
)
select *
from cte
where #projid <= #maxid
OPTION (MAXRECURSION 100)
EDIT: Got it. Here's what I used. Big thanks to #Amit-Sukralia for his comment that got me to the solution!
SELECT
[Project] = projectName,
[TotalItems] = projectsizeexpitem,
[DaysActive] = (select DATEDIFF(DAY, MIN(starttime), GETDATE()) from transactions where projectid = p.projectid),
[DailyRate] = (select (SUM(transactionitemsmigrated)/(DATEDIFF(DAY, MIN(starttime), GETDATE()) )) from Transactions where projectid = p.projectid),
[TotalComplete] = (select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid),
[ItemsRemaining] = projectsizeexpitem - (select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid),
[ExpectedCompDate] = CAST((GETDATE() + ((p.projectsizeexpitem-(select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid))/
(select (SUM(transactionitemsmigrated)/(DATEDIFF(DAY, MIN(starttime), GETDATE()) )) from Transactions where projectid = p.projectid))) as int) - CAST(GETDATE() as int)
FROM Projects p
You can try something like below. It doesn't require a CTE:
SELECT
ROW_NUMBER() OVER (PARTITION BY projectid) AS ID,
projectName AS [Project],
(select max(projectid) from Projects) AS MaxId,
projectsizeexpitem AS [TotalItems],
(select DATEDIFF(DAY, MIN(starttime), GETDATE()) from transactions where projectid = p.projectid) AS [DaysActive],
(select (SUM(transactionitemsmigrated)/(DATEDIFF(DAY, MIN(starttime), GETDATE()) )) from Transactions where projectid = p.projectid) AS [DailyRate],
(select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid) AS [TotalComplete],
projectsizeexpitem - (select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid) AS [ItemsRemaining],
(select (CAST(GETDATE() AS int) + ((projectsizeexpitem - (select SUM(transactionitemsmigrated) from transactions where projectid = p.projectid))/
(select (SUM(transactionitemsmigrated)/(DATEDIFF(DAY, MIN(starttime), GETDATE()) )) from Transactions where projectid = p.projectid)))) AS [DaysRemaining]
from Projects p

SELECT COUNT(foreign ID) for each row in datatable

I have datable where I store my dailyContent selection.
This is different for each day.
When I select more then one day, then I need to return items, which are the same for all days.
I have tried something like this:
SELECT * FROM
(
SELECT
( -- For count
SELECT COUNT(recipeId) AS [Count]
FROM DailyContentDish
WHERE
(
(weekDayId=#mon OR #mon IS NULL) OR
(weekDayId=#tue OR #tue IS NULL) OR
(weekDayId=#wed OR #wed IS NULL) OR
(weekDayId=#thu OR #thu IS NULL) OR
(weekDayId=#fri OR #fri IS NULL) OR
(weekDayId=#sat OR #sat IS NULL) OR
(weekDayId=#sun OR #sun IS NULL)
)
GROUP BY DailyContentDish.recipeId
) AS cnt,
-- End for count
DailyContentDish.dailyContentDishId,
DailyContentDish.recipeId AS Rec,
title,
150 AS calories,
defaultSmallImagePath,
activePreparationTime + passivePreparationTime AS overallPreparationTime,
CAST(
CASE WHEN EXISTS
(
SELECT DailyContentDishFavourite.dailyContentDishFavouriteId
FROM DailyContentDishFavourite
WHERE DailyContentDishFavourite.dailyContentDishId = DailyContentDish.dailyContentDishId
)
THEN 1
ELSE 0
END
AS BIT) AS isFavouriteWhenGeneratingMenu
FROM DailyContentDish
LEFT OUTER JOIN
RecipesTranslations ON RecipesTranslations.recipeId = DailyContentDish.recipeId
LEFT OUTER JOIN
RecipeAdditionalInformation ON RecipeAdditionalInformation.recipeId = DailyContentDish.recipeId
WHERE
isEnabled = 1 AND
mealId=#mealId AND
(
weekDayId=#mon OR
weekDayId=#tue OR
weekDayId=#wed OR
weekDayId=#thu OR
weekDayId=#fri OR
weekDayId=#sat OR
weekDayId=#sun
)
) p
WHERE p.cnt = #daysCount
The problem is, that nested select which should return count returns it for all rows, not just one entry (each row, that is).
Since entries with recipeId are entered more then once I would like to know how many times are they entered.
SELECT
( -- For count
SELECT COUNT(recipeId) AS [Count]
FROM DailyContentDish
WHERE
(
(weekDayId=#mon OR #mon IS NULL) OR
(weekDayId=#tue OR #tue IS NULL) OR
(weekDayId=#wed OR #wed IS NULL) OR
(weekDayId=#thu OR #thu IS NULL) OR
(weekDayId=#fri OR #fri IS NULL) OR
(weekDayId=#sat OR #sat IS NULL) OR
(weekDayId=#sun OR #sun IS NULL)
) AND Something should be here (I guess)
GROUP BY DailyContentDish.recipeId
) AS cnt,
-- End for count
This part should return COUNT of entries for each row I select - but it retuns me COUNT of all entries.
Or should I take a different path with this.
Any hint is greatly appreciated.
I am using MS SQL server 2008
EDIT:
this is whole stored procedure:
#userId int,
#languageId int,
#mealId int,
#mon int,
#tue int,
#wed int,
#thu int,
#fri int,
#sat int,
#sun int,
#orderBy nvarchar(2),
#pageSize int,
#startRowIndex int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #daysCount int
SET #daysCount = 0
IF (#mon IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#tue IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#wed IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#thu IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#fri IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#sat IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
IF (#sun IS NOT NULL)
BEGIN
SET #daysCount = #daysCount+1
END
-- Insert statements for procedure here
SELECT *
FROM (
SELECT
(
SELECT [Count] = COUNT(recipeId)
FROM dbo.DailyContentDish d
WHERE
(
ISNULL(#mon, weekDayId) = weekDayId OR
ISNULL(#tue, weekDayId) = weekDayId OR
ISNULL(#wed, weekDayId) = weekDayId OR
ISNULL(#thu, weekDayId) = weekDayId OR
ISNULL(#fri, weekDayId) = weekDayId OR
ISNULL(#sat, weekDayId) = weekDayId OR
ISNULL(#sun, weekDayId) = weekDayId
)
GROUP BY d.recipeId
) AS cnt,
d.dailyContentDishId,
d.recipeId AS Rec,
title,
150 AS calories,
defaultSmallImagePath,
activePreparationTime + passivePreparationTime AS overallPreparationTime,
CASE WHEN d2.dailyContentDishId IS NULL THEN 1 ELSE 0 END AS isFavouriteWhenGeneratingMenu
FROM dbo.DailyContentDish d
LEFT JOIN dbo.RecipesTranslations r ON r.recipeId = d.recipeId
LEFT JOIN dbo.RecipeAdditionalInformation t ON t.recipeId = d.recipeId
LEFT JOIN dbo.DailyContentDishFavourite d2 ON d.dailyContentDishId = d2.dailyContentDishId
WHERE isEnabled = 1
AND mealId = #mealId
AND (
weekDayId = #mon OR
weekDayId = #tue OR
weekDayId = #wed OR
weekDayId = #thu OR
weekDayId = #fri OR
weekDayId = #sat OR
weekDayId = #sun
)
) p
WHERE p.cnt = #daysCount
Edit1: I have uploaded diagram:
Here is sample data (for meal Id = 1, this is also selected on form), explanation:
- recipe with ID 125 will be present on Monday (weekDayId = 1) and Saturday (weekDayId = 7). So, this recipe must be returned if I select only Monday OR if I select only Saturday OR if I select Monday and Saturday. If I also select any other day then ithis record is not returned.
- recipe with ID 105 must be returned when weekDays 1, 6 and 7 (Monday, Saturday, Sunday) are selected. Same as above, if any other day is selected then this record is not returned.
Possible this help you -
ALTER PROCEDURE dbo.usp_GetDailyContentDish
#userId INT,
#languageId INT,
#mealId INT,
#mon INT,
#tue INT,
#wed INT,
#thu INT,
#fri INT,
#sat INT,
#sun INT,
#orderBy NVARCHAR(2),
#pageSize INT,
#startRowIndex INT
AS BEGIN
SET NOCOUNT ON;
DECLARE #daysCount INT
SELECT #daysCount =
CASE WHEN #mon IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #tue IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #wed IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #thu IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #fri IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #sat IS NOT NULL THEN 1 ELSE 0 END +
CASE WHEN #sun IS NOT NULL THEN 1 ELSE 0 END
SELECT
Rec
, calories
, overallPreparationTime
, isFavouriteWhenGeneratingMenu
FROM (
SELECT
(
SELECT [Count] = COUNT(d3.recipeId)
FROM dbo.DailyContentDish d3
WHERE
(
ISNULL(#mon, weekDayId) = weekDayId OR
ISNULL(#tue, weekDayId) = weekDayId OR
ISNULL(#wed, weekDayId) = weekDayId OR
ISNULL(#thu, weekDayId) = weekDayId OR
ISNULL(#fri, weekDayId) = weekDayId OR
ISNULL(#sat, weekDayId) = weekDayId OR
ISNULL(#sun, weekDayId) = weekDayId
) AND d3.recipeId = d.recipeId
GROUP BY d3.recipeId
) AS cnt,
d.dailyContentDishId,
d.recipeId AS Rec,
title,
150 AS calories,
defaultSmallImagePath,
activePreparationTime + passivePreparationTime AS overallPreparationTime,
CASE WHEN d2.dailyContentDishId IS NULL THEN 1 ELSE 0 END AS isFavouriteWhenGeneratingMenu
FROM dbo.DailyContentDish d
LEFT JOIN dbo.RecipesTranslations r ON r.recipeId = d.recipeId
LEFT JOIN dbo.RecipeAdditionalInformation t ON t.recipeId = d.recipeId
LEFT JOIN dbo.DailyContentDishFavourite d2 ON d.dailyContentDishId = d2.dailyContentDishId
WHERE isEnabled = 1
AND mealId = #mealId
AND (
weekDayId = #mon OR
weekDayId = #tue OR
weekDayId = #wed OR
weekDayId = #thu OR
weekDayId = #fri OR
weekDayId = #sat OR
weekDayId = #sun
)
) p
WHERE p.cnt = #daysCount
END
Small off-top:
This:
AND (
weekDayId = #mon OR
weekDayId = #tue OR
weekDayId = #wed OR
weekDayId = #thu OR
weekDayId = #fri OR
weekDayId = #sat OR
weekDayId = #sun
)
Possible optimize to this:
weekDayId % #weekDay = 0
If #mon, #tue is not null and contains 1, 2, ...
SELECT *
FROM (
SELECT
( -- For count
SELECT COUNT(d.recipeId) AS [Count]
FROM DailyContentDish d
WHERE (
ISNULL(#amon, d.weekDayId) = d.weekDayId OR
ISNULL(#tue, d.weekDayId) = d.weekDayId OR
ISNULL(#wed, d.weekDayId) = d.weekDayId OR
ISNULL(#thu, d.weekDayId) = d.weekDayId OR
ISNULL(#fri, d.weekDayId) = d.weekDayId OR
ISNULL(#sat, d.weekDayId) = d.weekDayId OR
ISNULL(#sun, d.weekDayId) = d.weekDayId
) AND d.recipeId = DailyContentDish.recipeId
GROUP BY d.recipeId
) AS cnt,
-- End for count
DailyContentDish.dailyContentDishId,
DailyContentDish.recipeId AS Rec,
title,
150 AS calories,
defaultSmallImagePath,
activePreparationTime + passivePreparationTime AS overallPreparationTime,
CAST(
CASE WHEN EXISTS
(
SELECT DailyContentDishFavourite.dailyContentDishFavouriteId
FROM DailyContentDishFavourite
WHERE DailyContentDishFavourite.dailyContentDishId = DailyContentDish.dailyContentDishId
)
THEN 1
ELSE 0
END
AS BIT) AS isFavouriteWhenGeneratingMenu
FROM DailyContentDish
LEFT OUTER JOIN
RecipesTranslations ON RecipesTranslations.recipeId = DailyContentDish.recipeId
LEFT OUTER JOIN
RecipeAdditionalInformation ON RecipeAdditionalInformation.recipeId = DailyContentDish.recipeId
WHERE
isEnabled = 1 AND
mealId=#mealId AND
(
weekDayId=#mon OR
weekDayId=#tue OR
weekDayId=#wed OR
weekDayId=#thu OR
weekDayId=#fri OR
weekDayId=#sat OR
weekDayId=#sun
)
) p
WHERE p.cnt = #daysCount
UPDATE 21.05.2013(added Demo)
IF OBJECT_ID('tempdb.dbo.#weekDays') IS NOT NULL DROP TABLE dbo.#weekDays
SELECT weekDayId
INTO dbo.#weekDays
FROM(VALUES(#mon),
(#tue),
(#wed),
(#thu),
(#fri),
(#sat),
(#sun))x(weekDayId)
WHERE weekDayId IS NOT NULL
CREATE UNIQUE CLUSTERED INDEX x ON dbo.#weekDays(weekDayId)
SELECT d.dailyContentDishId,
d.recipeId AS Rec,
title,
150 AS calories,
defaultSmallImagePath,
activePreparationTime + passivePreparationTime AS overallPreparationTime,
CASE WHEN f.dailyContentDishId IS NULL THEN 1 ELSE 0 END AS isFavouriteWhenGeneratingMen
FROM DailyContentDish d
LEFT JOIN RecipesTranslations ON RecipesTranslations.recipeId = d.recipeId
LEFT JOIN RecipeAdditionalInformation ON RecipeAdditionalInformation.recipeId = d.recipeId
LEFT JOIN dbo.DailyContentDishFavourite f ON d.dailyContentDishId = f.dailyContentDishId
WHERE d.isEnabled = 1 AND d.mealId = #mealId
AND NOT EXISTS(
SELECT d3.weekDayId
FROM dbo.#weekDays d3
EXCEPT
SELECT d2.WeekDayId
FROM DailyContentDish d2
WHERE d.recipeId = d2.recipeId
AND d2.isEnabled = 1 AND d2.mealId = #mealId
)
Simple demo on SQLFiddle
only meals which are the same for that days (and meals) should be
returned
What is missing is the correlation of the outer query to the count calculation in your inner query. I.e. the following:
( DC1.WeekDayId In( #Mon, #Tue, #Wed, #Thu, #Fri, #Sat, #Sun )
Or Coalesce( #Mon, #Tue, #Wed, #Thu, #Fri, #Sat, #Sun ) Is Null )
And DC1.MealId = DC.MealId
You need to alias at least one of the tables to make this happen. Typically, it is easier to read if you alias the tables on both the inner and outer query. The other item missing is that the seven variables for each day represent ticks ("select this day") instead of the day itself. If we change that, it makes the query simpler.
Select #Mon = Case When #Mon Is Not Null Then 1 End
, #Tue = Case When #Tue Is Not Null Then 2 End
, #Wed = Case When #Wed Is Not Null Then 3 End
, #Thu = Case When #Thu Is Not Null Then 4 End
, #Fri = Case When #Fri Is Not Null Then 5 End
, #Sat = Case When #Sat Is Not Null Then 6 End
, #Sun = Case When #Sun Is Not Null Then 7 End;
Select Count(*) Over() As CountOfResults
, (
Select Count(*)
From DailyContentDish As DC1
Where DC1.weekDayId = DC.weekDayId
) As CountOfDishesOnDay
, (
Select Count(Distinct mealId)
From DailyContentDish As DC1
Where DC1.weekDayId = DC.weekDayId
) As CountOfMeals
, DC.DailyContentDishId
, DC.RecipeId As Rec
, Title
, 150 As Calories
, DefaultsMallImagePath
, ActivePreparationTime
+ PassivePreparationTime As OverallPreparationTime
, Cast (
Case
When Exists (
Select 1
From DailyContentDishFavourite As DF1
Where DF1.DailyContentDishId = DC.DailyContentDishId
) Then 1
Else 0
End
As Bit) As IsFavouriteWhenGeneratingMenu
From DailyContentDish As DC
Left Join RecipesTranslations As RT
On RT.RecipeId = DC.RecipeId
Left Join RecipeAdditionalInformation As RA
On RA.RecipeId = DC.RecipeId
Where DC.IsEnabled = 1
And DC.MealId=#MealId
And DC.WeekDayId In( #Mon, #Tue, #Wed, #Thu, #Fri, #Sat, #Sun )
Addition
If the daily variables are supposed to represent a count of results per day returned (e.g., if #Mon = 2, then we should get back two rows for Monday), then you could do something like so:
Declare #DailyParameters Table
(
DayCount int not null
, DayOfWeek int not null
)
Insert #DailyParameters( DayCount, DayOfWeek )
Select Z.Cnt, Z.DayOfWeek
From (
Select #Mon As Cnt, 1 As DayOfWeek
Union All Select #Tue, 2
Union All Select #Wed, 3
Union All Select #Thu, 4
Union All Select #Fri, 5
Union All Select #Sat, 6
Union All Select #Sun, 7
) As Z
Where Z.Cnt Is Not Null
The Where clause would then change to something like so:
Where DC.IsEnabled = 1
And DC.MealId = #MealId
And DC.WeekDayId In( Select DayOfWeek From #DailyParameters )
And (
Select Count(*)
From DailyContentDish As DC1
Where DC1.weekDayId = DC.weekDayId
) = (
Select D1.DayCount
From #DailyParameters As D1
Where D1.DayOfWeek = DC.weekDayId
)
"Since entries with recipeId are entered more then once I would like to know how many times are they entered."
Base on this statement I would just use the OVER clause with COUNT.
Something like:
SELECT
COUNT(*) OVER (PARTITION BY recipeId) AS 'cnt'
FROM DailyContentDish
I would need more details about the tables and what the output should be in order to provide more details.
Here is more info on the OVER clause in 2008: http://msdn.microsoft.com/en-us/library/ms189461(v=sql.105).aspx
Example using COUNT with OVER: http://blog.sqlauthority.com/2011/08/11/sql-server-tips-from-the-sql-joes-2-pros-development-series-advanced-aggregates-with-the-over-clause-day-11-of-35/