Convert ugly T SQL query to CTE - sql

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

Related

SQL query with a loop and two IF

I have a sql request below.
in the table [ServicesConf] there are 2 rows: ID = 1, 2, but after executing sql query to table [ServicesLog] 3 rows are inserted.
How to make only 2 rows be inserted?
DECLARE #cnt int = 1
,#max int = (SELECT COUNT(*) FROM [ServicesConf]);
WHILE #cnt <= #max
BEGIN
DECLARE #DateTimeNow datetime = GETDATE()
DECLARE #DateTimeNowTimeHM varchar(5) = FORMAT(#DateTimeNow, 'HH:mm') --CONVERT(varchar(5), GETDATE(),108)
DECLARE #DateTimeNowTimeM varchar(2) = FORMAT(#DateTimeNow, 'mm')
DECLARE #DateTimeNow_WeekDay varchar = (SELECT DATEPART(WEEKDAY,#DateTimeNow))
DECLARE #DateTimeNow_WeekDay_Check int = (SELECT 1 FROM [ServicesConf] WHERE [SCHEDULE_DAY] LIKE '%' + #DateTimeNow_WeekDay + '%' AND [ID] = #cnt)
DECLARE #DateTimeNow_WeekHour_Check int = (SELECT 1 FROM [ServicesConf] WHERE [SCHEDULE_HOUR] = #DateTimeNowTimeHM AND [ID] = #cnt)
DECLARE #DateTimeNow_Repeat_Check int = (SELECT 1 FROM [ServicesConf] WHERE [REPEAT] LIKE '%' + #DateTimeNowTimeM + '%' AND [ID] = #cnt)
IF (#DateTimeNow_WeekDay_Check = 1
AND #DateTimeNow_Repeat_Check = 1)
BEGIN
INSERT INTO [ServicesLog] ()
SELECT *
FROM [ServicesConf]
WHERE [REPEAT] IS NOT NULL
END
IF (#DateTimeNow_WeekDay_Check = 1
AND #DateTimeNow_WeekHour_Check = 1)
BEGIN
INSERT INTO [ServicesLog] ()
SELECT *
FROM [ServicesConf]
WHERE [REPEAT] IS NULL
END
SET #cnt = #cnt + 1
END

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

Query optimization. Duplicate subqueries

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.

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/

Improve case statement in order clause

I have the store sql
ALTER procedure [dbo].[TNNews_User_SearchBasic]
#Title nvarchar(400),
#CategoryId int,
#IsInterested int,
#IsHot int,
#IsTopCategory int,
#IsPublish int,
#PageSize int,
#PageIndex int,
#OrderBy varchar(20),
#PortalId int,
#LanguageId varchar(6)
as
DECLARE #EndTime DATETIME
DECLARE #StartTime DATETIME
SET #StartTime = GETDATE()
declare #tbCategory table(Id int)
DECLARE #StartRowIndex INT
IF #PageSize=0 SELECT #PageSize=count(*) FROM TNNews
IF(#PageIndex<0) SET #PageIndex=0
SET #StartRowIndex = #PageSize*(#PageIndex-1)+1
;WITH tmpCategory(Id, Name,ParentId,Level)
AS (
SELECT
e.Id,
e.Name,
e.ParentId,
1
FROM dbo.TNCategory AS e
WHERE
Id = #CategoryId or (#CategoryId='' and ParentId<=0)
UNION ALL
SELECT
e.Id,
e.Name,
e.ParentId,
Level + 1
FROM dbo.TNCategory AS e
JOIN tmpCategory AS d ON e.ParentId = d.Id
)
insert #tbCategory select Id from tmpCategory
;WITH tmpNews as
(
SELECT
a.Id,a.Title,a.Subject
,ROW_NUMBER() OVER (ORDER BY (Publisheddate) desc) as ThuTuBanGhi
FROM dbo.TNNews a
where 1 = 1
--and ( Title like '%'+#Title+'%')
and (#CategoryId = -1 or exists (select 0 from #tbCategory b where b.Id = a.CategoryId))
and (#IsInterested = -1 or IsIntrested = #IsInterested )
and (#IsHot = -1 or IsHot = #IsHot )
and (#IsTopCategory = -1 or IsTopCategory = #IsTopCategory )
and (#IsPublish = -1 or IsPublished = #IsPublish)
and PortalId=#PortalId
and LanguageId = #LanguageId
)
select *, (select COUNT(Id) from tmpNews) as 'TongSoBanGhi' from tmpNews
WHERE
ThuTuBanGhi BETWEEN (#StartRowIndex) AND (#StartRowIndex + #PageSize-1)
SET #EndTime = GETDATE()
PRINT 'StartTime = ' + CONVERT(VARCHAR(30),#StartTime,121)
PRINT ' EndTime = ' + CONVERT(VARCHAR(30),#EndTime,121)
PRINT ' Duration = ' + STR(DATEDIFF(MILLISECOND,#StartTime,#EndTime)) + ' millisecond'
select STR(DATEDIFF(MILLISECOND,#StartTime,#EndTime))
After this store excute
EXEC [dbo].[TNNews_User_SearchBasic]
#Title='',
#CategoryId = '',
#IsInterested = -1,
#IsHot = -1,
#IsTopCategory = -1,
#IsPublish = -1,
#PageSize = 20,
#PageIndex = 1,
#OrderBy = '',
#PortalId = 0,
#LanguageId = N'vi-VN'
go
The time excute about "200ms". And I create a new store "TNNews_User_SearchBasic1" with some change.
.....
--,ROW_NUMBER() OVER (ORDER BY (Publisheddate) desc) as ThuTuBanGhi
,ROW_NUMBER() OVER (ORDER BY (case when #OrderBy='VIEW_COUNT' then ViewCount else PublishedDate end) desc) as ThuTuBanGhi
.....
and now the time excute this store
EXEC [dbo].[TNNews_User_SearchBasic1]
#Title='',
#CategoryId = '',
#IsInterested = -1,
#IsHot = -1,
#IsTopCategory = -1,
#IsPublish = -1,
#PageSize = 20,
#PageIndex = 1,
#OrderBy = '',
#PortalId = 0,
#LanguageId = N'vi-VN'
GO
about 900ms.
I don't understand why there is a change. Please help me improve these stores.
PS: I put example db at: http://anhquan22.tk/Portals/0/Videos/web.rar
Finished analysis the structure of your database. The part of the problem is hiding in the table structure.
I have prepared a backup for you. In it, I slightly modified scheme to improve performance and some normalize the table. You can download it from this link.
...to your question, I would do like this -
DECLARE #SQL NVARCHAR(1000)
SELECT #SQL = N'
;WITH tmpCategory (Id, Name, ParentId, [Level]) AS
(
SELECT
e.Id
, e.Name
, e.ParentId
, 1
FROM dbo.TNCategory e
WHERE Id = #CategoryId OR (#CategoryId = '''' AND ParentId <= 0)
UNION ALL
SELECT
e.Id
, e.Name
, e.ParentId
, [Level] + 1
FROM dbo.TNCategory e
JOIN tmpCategory d ON e.ParentId = d.Id
)
SELECT
a.Id
, ROW_NUMBER() OVER (ORDER BY ' +
CASE WHEN #OrderBy = 'VIEW_COUNT'
THEN 'ViewCount'
ELSE 'PublishedDate'
END +' DESC) AS ThuTuBanGhi
FROM dbo.TNNewsMain a
where PortalId = #PortalId
AND LanguageId = #LanguageId'
+ CASE WHEN #IsInterested != -1 THEN ' AND IsInterested = #IsInterested' ELSE '' END
+ CASE WHEN #IsHot != -1 THEN ' AND IsHot = #IsHot' ELSE '' END
+ CASE WHEN #IsTopCategory != -1 THEN ' AND IsTopCategory = #IsTopCategory' ELSE '' END
+ CASE WHEN #IsPublish != -1 THEN ' AND IsPublish = #IsPublish' ELSE '' END
+ CASE WHEN #CategoryId != -1 THEN '' ELSE ' AND EXISTS(SELECT 1 FROM tmpCategory b WHERE b.Id = a.CategoryId)' END
INSERT INTO #temp (Id, ThuTuBanGhi)
EXECUTE sp_executesql
#SQL
, N'#PortalId INT
, #LanguageId VARCHAR(6)
, #CategoryId INT
, #IsInterested INT
, #IsHot INT
, #IsTopCategory INT
, #IsPublish INT'
, #PortalId = #PortalId
, #LanguageId = #LanguageId
, #CategoryId = #CategoryId
, #IsInterested = #IsInterested
, #IsHot = #IsHot
, #IsTopCategory = #IsTopCategory
, #IsPublish = #IsPublish;
SELECT
d.Id
, tm.Title
, tm.[Subject]
, d.ThuTuBanGhi
, c.TongSoBanGhi
FROM (
SELECT t.Id
, t.ThuTuBanGhi
FROM #temp t
WHERE t.ThuTuBanGhi BETWEEN #StartRowIndex AND #StartRowIndex + #PageSize - 1
) d
JOIN TNNewsMain tm ON d.Id = tm.Id
CROSS JOIN (
SELECT TongSoBanGhi = (SELECT COUNT(1) FROM #temp)
) c