Use cross apply function - sql-server

I have this cross apply query and I want to sum the result of it
CROSS APPLY
(SELECT
SUM(CASE WHEN [day] BETWEEN #FirstDay AND #LastDay
THEN 1 ELSE 0
END) AS UsedDays
FROM
Calendar c
WHERE
([day] >= r.[DateFrom]
AND [day] <= r.[DateTo]
AND [WorkDay] = 1)) calculateUsedDays
I have a request table that contains different requests from different people
and my point is to sum all of the days from the requests from a person.
The cross apply returns the sum of the days by every request from the requests table for every person.
For example:
person
John, usedDays - 5
John, - 7
Peter - 10
Peter - 5
..
And I want to sum these days and group by name of the person so that I can have all of the days by person.
Example:
John - 12
Peter - 15
I tried with sum and group by , but it returns error:
Each GROUP BY expression must contain at least one column that is not an outer reference
Thank you :))

Thank you guys,
I solved this, but now my problem is: Implicit conversion from data type datetime to int is not allowed. Use the CONVERT function to run this query.
This is my code
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [core].[ReportBalanceYearSearch]
#Year int = NULL,
#TypeOfLeaveGid int = NULL,
#IsActive int = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE #Err int
DECLARE #sql nvarchar (max), #paramlist nvarchar(max)
DECLARE #FirstDay datetime
DECLARE #LastDay datetime
DECLARE #typeLeaveGid INT, #typeCreditGid INT, #relLeaveToCreditGid INT
SET #FirstDay = DATEFROMPARTS ( #Year, 1, 1)
SET #LastDay = DATEFROMPARTS ( #Year, 12, 31)
SELECT #typeLeaveGid = gid FROM Nomenclature WHERE type = 'RequestType' and Code = 1
SELECT #typeCreditGid = gid FROM Nomenclature WHERE type = 'RequestType' and Code = 2
SELECT #relLeaveToCreditGid = gid FROM Nomenclature WHERE type = 'Relation' and Code = 6
SELECT #sql = '
SELECT u.[Name],
u.[DepartmentGid],
sum(calculateUsedDays.UsedDays - isnull(calculateCreditDays.CreditDaysInPeriod,0)) as [UsedDays],
ub.[Balance],
sum(calculateUsedDays.UsedDays - isnull(calculateCreditDays.CreditDaysInPeriod,0)) + ub.[Balance] as [TotalDaysInYear],
r.[LeaveTypeGid]
FROM [dbo].[Request] r
inner join [User] u on r.[UserGid] = u.[Gid]
inner join [UserBalance] ub on r.[UserGid] = ub.UserGid and ub.Year = #xYear
LEFT OUTER JOIN dbo.Request CRD
inner join Relations rel ON rel.RelID2 = CRD.Gid AND rel.RelType = #xrelLeaveToCreditGid
inner join Nomenclature nsc ON nsc.Gid = CRD.StatusGid
cross apply (SELECT
sum(case when [day] between COALESCE(#xFirstDay, [day]) AND COALESCE(#xLastDay, [day]) then 1 else 0 end) as CreditDaysInPeriod
FROM Calendar c
WHERE [day] >= crd.[DateFrom] AND [day] <= crd.[DateTo] AND [WorkDay] = 1 ) calculateCreditDays
ON rel.RelID1 = r.Gid
and CRD.TypeGid = #xtypeCreditGid
cross apply (SELECT
sum(case when [day] between #xFirstDay and #xLastDay then 1 else 0 end) as UsedDays
FROM Calendar c
WHERE ([day] >= r.[DateFrom] AND [day] <= r.[DateTo] AND [WorkDay] = 1))calculateUsedDays
where #xYear = DATEPART(year,r.[DateFrom]) and r.TypeGid = #xtypeLeaveGid and #xIsActive IS NULL OR u.[Active] = #xIsActive
group by u.[Name], u.[DepartmentGid],r.[LeaveTypeGid], ub.[Balance]'
SELECT #paramlist ='
#xTypeOfLeaveGid int,
#xFirstDay datetime,
#xYear int,
#xLastDay datetime,
#xtypeLeaveGid int,
#xrelLeaveToCreditGid int,
#xtypeCreditGid int,
#xIsActive bit'
EXEC sys.sp_executesql #sql, #paramlist,
#TypeOfLeaveGid,
#Year,
#IsActive,
#typeLeaveGid,
#relLeaveToCreditGid,
#typeCreditGid,
#FirstDay,
#LastDay
SET #Err = ##Error
RETURN #Err
END

Related

Using DECLARE while creating a VIEW?

The literature says that the declare statement is not compatible with creating a View. How do I get around it?
My declare statement looks like:
DECLARE #risk_5 TABLE (Code VARCHAR(100));
INSERT INTO #risk_5 (Code) VALUES ('AA'),('BB'),('CC');
and is then used within a select statement:
SELECT
id,
CASE
WHEN a.[10_2_1_Country] IN (SELECT Code from #risk_5)
THEN '3'
END AS Risk_Country5
FROM x
The recommendation is to pack the declare into a CTE or a stored procedure.
With both these recommendations though, I do not understand how to connect the two? What am I missing?
If you need to use variable try to use stored procedures, if you write a select query in the stored procedure you can get the data too. And you can use declare inside.
I use this way in my solution e.g.
CREATE PROCEDURE [dbo].[GetLoadSiteMass]( #month INT,
#year int,
#storageId int,
#parent nvarchar(50),
#materialSourceId nvarchar(100),
#complexIds nvarchar(50))
AS
BEGIN
DECLARE #MonthPrev int
DECLARE #YearPrev int
SET #MonthPrev = CASE WHEN #Month = 1 THEN 12 ELSE #Month - 1 END
SET #YearPrev = CASE WHEN #Month = 1 THEN #Year - 1 ELSE #Year END
declare #WagonLoadSiteId int
set #WagonLoadSiteId = (select top 1 CarriageLoadSiteId from CarriageLoadSite where LoadSiteType = 2);
DECLARE #loadSide nvarchar(10), #result decimal(18,3)
SET #loadSide=cast( #storageId as nvarchar(50));
WITH CarriageLoadSiteTreeView (
[CarriageLoadSiteId],RootId,RootName,[Code], Name, ParentID, [LoadSiteType],IsDelete,
CodeSAP,DepartmentId, Capacity, MinLimit, MaxLimit, LoadSitePlaceTypeId) AS
(
SELECT [CarriageLoadSiteId],
[CarriageLoadSiteId] RootId,
Name RootName,
[Code],
Name,
ParentID,
[LoadSiteType],
[IsDelete],
CodeSAP,
DepartmentId,
Capacity,
MinLimit,
MaxLimit,
LoadSitePlaceTypeId
FROM CarriageLoadSite WITH(NOLOCK)
WHERE ISNULL(ParentID,0) =isnull(#storageId,0) AND Isdelete!=1
UNION ALL
SELECT d.[CarriageLoadSiteId],
q.RootId RootId,
RootName RootName,
d.[Code],
d.Name,
d.ParentID,
d.[LoadSiteType],
d.[IsDelete],
d.CodeSAP,
d.DepartmentId,
d.Capacity,
d.MinLimit,
d.MaxLimit,
d.LoadSitePlaceTypeId
FROM CarriageLoadSite AS d WITH(NOLOCK)
INNER JOIN CarriageLoadSiteTreeView AS q ON d.ParentID = q.[CarriageLoadSiteId] WHERE d.IsDelete!=1
)
SELECT
ComplexId,
RootId Id,
cast(RootId as nvarchar(8))+'|Sclad'+IIF(RootId=max(R.CarriageLoadSiteId),'|finish','') [Uid],
RootName CarriageLoadSiteName,
ROUND(SUM(AMOUNT-movement-consumption)/1000,3) Amount,
cast(1 as bit) hasChildren,
T.FullPathId Path,
UparentId=IIF(#parent is null,'',#parent),
[Type]=0,
Petal = IIF(RootId=max(R.CarriageLoadSiteId),'|Sclad|finish','')
FROM (
SELECT
RootId
,RootName
,t.CarriageLoadSiteId
,t.MaterialId
,YEAR(t.Period) [Year]
,MONTH(t.Period) [Month]
,round( case when (t.Amount=0 or t.Amount is null) and (tt.Type=0 or [TypeAmountCarriage]=1 )then carr.[CertifNetto]else t.Amount end ,0 )[Amount]
,t.UnitId
, CarriageId
, tt.TurnoverTypeId
,round(dbo.GetMovementByTurnOverWithTempValue(t.turnoverid),5) movement
,dbo.GetConsumptionByTurnOver(t.turnoverid) consumption
,0 stockBegin
,round(t.Amount,0 ) CommingAmount
,case when (t.Amount=0 or t.Amount is null) and tt.Type=0 then 1 else 0 end [IsNotConfirmed]
,[TypeAmountCarriage]
,M.ComplexId
FROM Turnover t WITH(NOLOCK)
INNER JOIN TurnoverType tt ON tt.TurnoverTypeId = t.TurnoverTypeId
INNER JOIN CarriageLoadSiteTreeView l ON l.CarriageLoadSiteId = t.CarriageLoadSiteId
INNER JOIN [Carriages] carr on carr.[CarriagesID]=t.[CarriageId]
INNER JOIN Material M on M.MaterialID=t.MaterialId
WHERE YEAR(t.Period) = #Year AND
MONTH(t.Period) = #Month AND
l.LoadSiteType = 0 AND
tt.type in (0,5,4) AND
isclear=0 AND
M.MaterialSourceID in (select value from string_split(#materialSourceId, ','))
UNION ALL
SELECT RootId
,RootName
,s.CarriageLoadSiteId
,s.MaterialId
,#Year [Year]
,#Month [Month]
,round(s.Amount,0)
,s.UnitId
,CarriageId
,[TurnoverTypeId]
,round(dbo.GetMovementByStock(s.StockId),5) movement
,dbo.GetConsumptionByStock(s.StockId) consumption
,round(s.Amount,0)-s.spendStock
,0
,0 [IsNotConfirmed]
,[TypeAmountCarriage]
,M.ComplexId
FROM Stock s
INNER JOIN CarriageLoadSiteTreeView l ON l.CarriageLoadSiteId = s.CarriageLoadSiteId
INNER JOIN Material M on M.MaterialID=s.MaterialId
WHERE s.[Year] = #YearPrev AND
s.[Month] = #MonthPrev AND
s.[Type] = 0 AND
l.LoadSiteType = 0 AND
amount >0 AND
isclear=0 AND
M.MaterialSourceID in (select value from string_split(#materialSourceId, ','))
) as R
INNER JOIN CariageLoadSiteTree T on T.CarriageLoadSiteId=RootId
INNER JOIN string_split(#complexIds, ',') MM ON CAST(MM.value AS int) = R.ComplexId
WHERE AMOUNT-movement-consumption>10
GROUP BY RootName,RootId,ComplexId, T.FullPathId
ORDER BY RootName

Subquery returned more than 1 value in temp table stored procedure

When I execute my stored procedure it shows the this error
'Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression'. How to solve this?
Stored procedure:
ALTER PROCEDURE [dbo].[SPEMPIDDETAILS]
AS
BEGIN
DECLARE #INTSTART AS INTEGER;
DECLARE #INTSTOP AS INTEGER;
DECLARE #PREFNO AS VARCHAR(50);
DECLARE #PVAL AS DATE;
DECLARE #VREFNO AS VARCHAR(50);
DECLARE #VVAL AS DATE;
DECLARE #SBREFNO AS VARCHAR(50);
DECLARE #SBVAL AS VARCHAR(50);
DECLARE #UVREFNO AS VARCHAR(50);
DECLARE #UVVAL AS DATE;
DECLARE #TEMPTABLEEMP TABLE (PREFNO VARCHAR(50),
PVAL DATE, VREFNO VARCHAR(50),
VVAL DATE, SBREFNO VARCHAR(50),
SBVAL DATE, UVREFNO VARCHAR(50), UVVAL DATE)
SELECT #INTSTART = 1;
SELECT #INTSTOP = (SELECT COUNT(PK_ID) FROM EMPIDDETAILS);
WHILE #INTSTART <= #INTSTOP
BEGIN
SELECT
#PREFNO = (SELECT REF_NO FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'PASSPORT'),
#PVAL = (SELECT VALIDITY FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'PASSPORT')
--GROUP BY EMPIDDETAILS.EMPLOYE
SELECT
#VREFNO = (SELECT REF_NO FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'VISA'),
#VVAL = (SELECT VALIDITY FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'VISA')
SELECT
#SBREFNO = (SELECT REF_NO FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'SEAMAN BOOK'),
#SBVAL = (SELECT VALIDITY FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'SEAMAN BOOK')
SELECT
#UVREFNO = (SELECT REF_NO FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'USVISA'),
#UVVAL = (SELECT VALIDITY FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'USVISA')
INSERT INTO #TEMPTABLEEMP
VALUES(#PREFNO, #PVAL, #VREFNO, #VVAL, #SBREFNO, #SBVAL, #UVREFNO, #UVVAL)
SET #PREFNO = 0
SET #PVAL = NULL
SET #VREFNO = 0
SET #VVAL = NULL
SET #SBREFNO = 0
SET #SBVAL = NULL
SET #UVREFNO = 0
SET #UVVAL = NULL
SET #INTSTART = #INTSTART + 1;
END
SELECT
PREFNO, PVAL, VREFNO, VVAL, SBREFNO, SBVAL, UVREFNO, UVVAL
FROM #TEMPTABLEEMP
END
Without knowing anything about your data, it's hard to say, but you try assign query results to values. What if query e.g.
SELECT #PREFNO=(SELECT REF_NO FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT')
returns more than one document type passport? This can be applied to all your queries where you wanted to store value in variable.
You are assigning values to variables using select statement. Some time there might be multiple records exist based on your filter. Please check your Document_Type filter one of our filter returning more than one value. In order to avoid this you can use "Top 1"
Refer This,
ALTER PROCEDURE [dbo].[SPEMPIDDETAILS]
AS
BEGIN
DECLARE #INTSTART AS INTEGER;
DECLARE #INTSTOP AS INTEGER;
DECLARE #PREFNO AS VARCHAR(50);
DECLARE #PVAL AS DATE;
DECLARE #VREFNO AS VARCHAR(50);
DECLARE #VVAL AS DATE;
DECLARE #SBREFNO AS VARCHAR(50);
DECLARE #SBVAL AS VARCHAR(50);
DECLARE #UVREFNO AS VARCHAR(50);
DECLARE #UVVAL AS DATE;
DECLARE #TEMPTABLEEMP TABLE (
PREFNO VARCHAR(50)
,PVAL DATE
,VREFNO VARCHAR(50)
,VVAL DATE
,SBREFNO VARCHAR(50)
,SBVAL DATE
,UVREFNO VARCHAR(50)
,UVVAL DATE
)
SELECT #INTSTART = 1;
SELECT #INTSTOP = (
SELECT COUNT(PK_ID)
FROM EMPIDDETAILS
);
WHILE #INTSTART <= #INTSTOP
BEGIN
SELECT #PREFNO = (
SELECT TOP 1 REF_NO
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'PASSPORT'
)
,#PVAL = (
SELECT TOP 1 VALIDITY
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'PASSPORT'
)
--GROUP BY EMPIDDETAILS.EMPLOYE
SELECT #VREFNO = (
SELECT TOP 1 REF_NO
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'VISA'
)
,#VVAL = (
SELECT TOP 1 VALIDITY
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'VISA'
)
SELECT #SBREFNO = (
SELECT TOP 1 REF_NO
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'SEAMAN BOOK'
)
,#SBVAL = (
SELECT TOP 1 VALIDITY
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'SEAMAN BOOK'
)
SELECT #UVREFNO = (
SELECT TOP 1 REF_NO
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'USVISA'
)
,#UVVAL = (
SELECT TOP 1 VALIDITY
FROM EMPIDDETAILS
WHERE DOCUMENT_TYPE = 'USVISA'
)
INSERT INTO #TEMPTABLEEMP
VALUES (
#PREFNO
,#PVAL
,#VREFNO
,#VVAL
,#SBREFNO
,#SBVAL
,#UVREFNO
,#UVVAL
)
SET #PREFNO = 0
SET #PVAL = NULL
SET #VREFNO = 0
SET #VVAL = NULL
SET #SBREFNO = 0
SET #SBVAL = NULL
SET #UVREFNO = 0
SET #UVVAL = NULL
SET #INTSTART = #INTSTART + 1;
END
SELECT PREFNO
,PVAL
,VREFNO
,VVAL
,SBREFNO
,SBVAL
,UVREFNO
,UVVAL
FROM #TEMPTABLEEMP
END
The best thing you can do is to add a TOP(1) clause to all the sub queries used to assign to variable
There are some tables where your data has duplicate values. for example in the line
SELECT #PREFNO=(SELECT REF_NO FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT'),#PVAL=(SELECT VALIDITY FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT')
When you say SELECT REF_NO FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT' there might be some cases where there are two REF_NO that has the same DOCUMENT_TYPE thus giving you two results.
In order to avoid this, either you have to add another condition where you can say that it has no duplicate values (perhaps a primary key) or add TOP 1 in order to ensure that the query returns only one data.
SELECT #PREFNO=(SELECT TOP 1 REF_NO FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT'),#PVAL=(SELECT TOP 1 VALIDITY FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT')
Now there are times that the data returned from the query is not the data you expected. So you might consider adding an ORDER BY clause so that the very first data the is fetched from the query is the desired data you want.
EXAMPLE :
SELECT #PREFNO=(SELECT TOP 1 REF_NO FROM EMPIDDETAILS WHERE DOCUMENT_TYPE='PASSPORT' ORDER BY pk_id DESC)
The example above just retrieves the REF_NO has the highest pk_id considering that pk_id is a primary key with an INT data type.
UPDATE: another thing is just to add DISTINCT if data with the same DOCUMENT_TYPE is entirely the same.
Actually there is no need for cursor. Take a look at two approaches with same output. Your major problem was that you declared #INTSTART as ID of EMP but did not use it in filters.
DECLARE #EMPIDDETAILS TABLE
(
PK_ID INT NOT NULL,
REF_NO VARCHAR(100) NOT NULL,
DOCUMENT_TYPE VARCHAR(100) NOT NULL,
VALIDITY BIT NOT NULL
)
INSERT INTO #EMPIDDETAILS (PK_ID, REF_NO, DOCUMENT_TYPE, VALIDITY)
VALUES
(1, '111-VISA', 'VISA', 1),
(1, '111-PASS', 'PASSPORT', 1),
(2, '222-PASS', 'PASSPORT', 0),
(2, '222-SEEA', 'SEAMAN BOOK', 1),
(2, '222-VISA', 'VISA', 1),
(2, '222-USVS', 'USVISA', 0)
SELECT
ed.PK_ID,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'PASSPORT' THEN ed.REF_NO END) AS PREFNO,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'PASSPORT' THEN CAST(ed.VALIDITY AS INT) END) AS PVAL,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'VISA' THEN ed.REF_NO END) AS VREFNO,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'VISA' THEN CAST(ed.VALIDITY AS INT) END) AS VVAL,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'SEAMAN BOOK' THEN ed.REF_NO END) AS SBREFNO,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'SEAMAN BOOK' THEN CAST(ed.VALIDITY AS INT) END) AS SBVAL,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'USVISA' THEN ed.REF_NO END) AS UVREFNO,
MAX(CASE WHEN ed.DOCUMENT_TYPE = 'USVISA' THEN CAST(ed.VALIDITY AS INT) END) AS UVVAL
FROM #EMPIDDETAILS ed
GROUP BY ed.PK_ID
SELECT
pv.PK_ID,
MAX([PASSPORT]) AS [PASSPORT],
MAX([PASSPORT-VALIDITY]) AS [PASSPORT-VALIDITY],
MAX([VISA]) AS [VISA],
MAX([VISA-VALIDITY]) AS [VISA-VALIDITY],
MAX([SEAMAN BOOK]) AS [SEAMAN BOOK],
MAX([SEAMAN BOOK-VALIDITY]) AS [SEAMAN BOOK-VALIDITY],
MAX([USVISA]) AS [USVISA],
MAX([USVISA-VALIDITY]) AS [USVISA-VALIDITY]
FROM
(
SELECT
ed.PK_ID, ed.REF_NO, ed.DOCUMENT_TYPE,
CAST(ed.VALIDITY AS INT) AS VALIDITY,
ed.DOCUMENT_TYPE+'-VALIDITY' as REF_VAL
FROM #EMPIDDETAILS ed
) ed
PIVOT
(
MAX(ed.REF_NO)
FOR ed.DOCUMENT_TYPE IN ([VISA], [PASSPORT], [SEAMAN BOOK], [USVISA])
) pr
PIVOT
(
MAX(pr.VALIDITY)
FOR pr.REF_VAL IN ([VISA-VALIDITY], [PASSPORT-VALIDITY], [SEAMAN BOOK-VALIDITY], [USVISA-VALIDITY])
) pv
GROUP BY pv.PK_ID
In the end just insert into your temptable or do whatever you want.

How to replace a SQL While Loop with a Select

We have a procedure that uses the below While..Loop to calculate the total number of Days and Weeks paid for absences in our PayRoll System:
EDITED
Declare #Absences Table (slno int identity (1,1),AbsenceId int, ToDate datetime)
INSERT INTO #Absences (AbsenceID,ToDate)
Select AbsenceID, AB.ToDate
from t_Absence AB with (nolock)
Inner Join t_AbsenceCategory AB_CAT with (nolock) ON (AB.AbsenceCategoryID = AB_CAT.AbsenceCategoryID)
where (AB_CAT.IsSSP =1)
and ClientID = #ClientID
and AB.FromDate >= #SSPYearStart --D7830 SJH 21/10/2015
order BY AB.ToDate desc
Declare #AbsenceID INT, #iCtr INT, #maxRows int
Declare #FromDate datetime
SELECT #iCtr = 1, #maxRows = MAX(slno) FROM #Absences
Select #SSPDaysPaid = 0, #SSPweeksPaid = 0, #QualifyingDaysInWeek = 0
If IsNull(#maxRows,0) > 0 select #FromDate = FromDate from t_Absence where AbsenceID = (SELECT AbsenceID FROM #Absences WHERE slno = 1)
WHILE ( #ictr <= #maxRows )
BEGIN
SELECT #AbsenceID = AbsenceID
FROM #Absences
WHERE slno = #iCtr
--Print #AbsenceID
If Exists (Select TOP 1 1 from t_Absence where ToDate > DATEADD(dd,-56, #FromDate))
BEGIN
SELECT #SSPDaysPaid = #SSPDaysPaid + IsNull(A.SSPDays,0),
#FromDate = A.FromDate
from t_Absence A
where A.AbsenceID = #AbsenceID
print '#SSPDaysPaid=' + CAST(#SSPDaysPaid AS Varchar(3)) + ' in Absence ' + cast(#AbsenceID as varchar(6))
DECLARE #Monday int, #Tuesday int, #Wednesday int, #Thursday int, #Friday int, #Saturday int, #Sunday int
SELECT #Monday = QD.Monday, #Tuesday = QD.Tuesday, #Wednesday =QD.Wednesday, #Thursday =QD.Thursday,
#Friday = QD.Friday, #Saturday = QD.Saturday, #Sunday = QD.Sunday
from t_PayrollEmployeeSSPQualifyingDays QD
inner JOIN t_Absence A on A.ClientID = QD.ClientID and A.FromDate = QD.DateFrom AND A.ToDate = QD.DateTo
where A.ClientID = #ClientId
SET #QualifyingDaysInWeek = #Monday + #Tuesday + #Wednesday + #Thursday + #Friday + #Saturday + #Sunday
print '#QualifyingDaysInWeek = ' + cast(#QualifyingDaysInWeek as char(2))
END
SET #iCtr = #iCtr + 1
END
if #QualifyingDaysInWeek <> 0 Set #SSPWeeksPaid = #SSPDaysPaid/#QualifyingDaysInWeek Else Set #SSPWeeksPaid = 0
print '#SSPWeeksPaid=' + cast(#SSPWeeksPaid as varchar(2))
Select
BradfordFactor
, CSPFDEntitlement
, CSPHDEntitlement
, CSPDaysTaken
, HasContract
, CSPFullDaysTaken
, CSPHalfDaysTaken -- DevTask 112703 06/11/2012 SWB Start
, IsNull(#SSPDaysPaid,0) as 'SSPDaysPaid'
, IsNull(#SSPWeeksPaid,0) as 'SSPWeeksPaid'
from
fn_GetEmployeeBradfordFactor(#ClientID,DEFAULT,0, DEFAULT)
END
As I need to find out this information for several different persons I will have to execute this stored proc and loop once for each client Id (#ClientId) identified in the calling procedure ...
Is there an alternative to this loop and would it be worth it in terms of performance?
Based on the OP comment, no other value is needed from the loop, but #QualifyingDaysInWeek
#RicardoC : Well I don't need the prints (they're there for debugging and analysis) but I do need to sum the values #QualifyingDaysInWeek in for each record in the table #Absences
There appears to be no need for the loop at all.
Declare #Absences Table (slno int identity (1,1),AbsenceId int, ToDate datetime)
INSERT INTO #Absences (AbsenceID,ToDate)
Select AbsenceID, AB.ToDate
from t_Absence AB with (nolock)
Inner Join t_AbsenceCategory AB_CAT with (nolock) ON (AB.AbsenceCategoryID = AB_CAT.AbsenceCategoryID)
where (AB_CAT.IsSSP =1)
and ClientID = #ClientID
and AB.FromDate >= #SSPYearStart --D7830 SJH 21/10/2015
order BY AB.ToDate desc
DECLARE #QualifyingDaysInWeek INT
SELECT #QualifyingDaysInWeek = SUM(QD.Monday + QD.Tuesday + QD.Wednesday + QD.Thursday + QD.Friday + QD.Saturday + QD.Sunday)
FROM t_PayrollEmployeeSSPQualifyingDays QD
INNER JOIN t_Absence A ON A.ClientID = QD.ClientID
AND A.FromDate = QD.DateFrom
AND A.ToDate = QD.DateTo
WHERE A.ClientID = #ClientId;

Conversion failed when converting date and/or time from character string

What I am doing is using own function, which is returning string, now I want this column for ordering, and while using it for ordering it is not ordering properly because its a string, when I tried to convert as datetime it causes an error.
Any help ?
Thanks a lot.
SELECT
b.CallId,
CONVERT(VARCHAR(25), b.ETADate, 103) as'ETADate',
dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId) AS 'closingDateTime'
FROM
CallEntry AS a, CallEntrySerialNumbers AS b
WHERE
a.ASPId = 2
AND a.CompanyId = 3
AND a.CallId= b.CallId
AND a.ProdCompanyId = 1
AND b.CallCaseId IS NOT NULL
AND b.CallCaseId NOT LIKE 'NA'
AND ProdCategoryid = 1
AND a.CallDateTime > dateadd(day, -30, getdate())
AND b.StatusId = 2
ORDER BY
dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId) ASC
// what I tried and causes above error is
ORDER BY
CONVERT(DATE, dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId)) asc
EDIT-added function
ALTER FUNCTION [dbo].[ASP_getCallEntrySerialNoLastTranDateTime]
(
#CallEntrySerialNumbersId bigint
)
RETURNS varchar(500)
AS
BEGIN
DECLARE #ReturnVal NVARCHAR(500);
begin
select #ReturnVal = ( select Top 1 CONCAT(CONVERT(VARCHAR(10),CallTranEndDateTime,103),' ',CONVERT(VARCHAR(10),CallTranEndDateTime,108)) from calltransactions
where CallEntrySerialNumbersId = #CallEntrySerialNumbersId AND CallTranTypeId = 3
order by CallTranId desc)
end;
RETURN #ReturnVal;
END
Try this one -
SET DATEFORMAT dmy
SELECT *
FROM (
SELECT
b.CallId
, ETADate = CONVERT(VARCHAR(25), b.ETADate, 103)
, closingDateTime = dbo.getCallEntrySerialNoLastTranDateTime(b.CallEntrySerialNumbersId)
FROM dbo.CallEntry a
JOIN dbo.CallEntrySerialNumbers b ON a.CallId = b.CallId
WHERE a.ASPId = 2
AND a.CompanyId = 3
AND a.ProdCompanyId = 1
AND ISNULL(b.CallCaseId, '') NOT LIKE 'NA'
AND ProdCategoryid = 1
AND a.CallDateTime > DATEADD(day, -30, GETDATE())
AND b.StatusId = 2
) d
ORDER BY CAST(d.closingDateTime AS DATE)
ALTER FUNCTION [dbo].[ASP_getCallEntrySerialNoLastTranDateTime]
(
#CallEntrySerialNumbersId BIGINT
)
RETURNS VARCHAR(30)
AS BEGIN
DECLARE #ReturnVal VARCHAR(30)
SELECT #ReturnVal = (
SELECT TOP 1 CONVERT(VARCHAR(10), CallTranEndDateTime, 103) + ' ' + CONVERT(VARCHAR(10), CallTranEndDateTime, 108)
FROM dbo.CallTransactions
WHERE CallEntrySerialNumbersId = #CallEntrySerialNumbersId
AND CallTranTypeId = 3
ORDER BY CallTranId DESC
)
RETURN #ReturnVal
END

How to make this sql query

I have 2 SQL Server tables with the following structure
Turns-time
cod_turn (PrimaryKey)
time (datetime)
Taken turns
cod_taken_turn (Primary Key)
cod_turn
...
and several other fields which are irrelevant to the problem. I cant alter the table structures because the app was made by someone else.
given a numeric variable parameter, which we will assume to be "3" for this example, and a given time, I need to create a query which looking from that time on, it looks the first 3 consecutive records by time which are not marked as "taken". For example:
For example, for these turns, starting by the time of "8:00" chosen by the user
8:00 (not taken)
9:00 (not taken)
10:00 (taken)
11:00 (not taken)
12:00 (not taken)
13:00 (not taken)
14:00 (taken)
The query it would have to list
11:00
12:00
13:00
I cant figure out how to make the query in pure sql, if possible.
with a cursor
declare #GivenTime datetime,
#GivenSequence int;
select #GivenTime = cast('08:00' as datetime),
#GivenSequence = 3;
declare #sequence int,
#code_turn int,
#time datetime,
#taked int,
#firstTimeInSequence datetime;
set #sequence = 0;
declare turnCursor cursor FAST_FORWARD for
select turn.cod_turn, turn.[time], taken.cod_taken_turn
from [Turns-time] as turn
left join [Taken turns] as taken on turn.cod_turn = taken.cod_turn
where turn.[time] >= #GivenTime
order by turn.[time] asc;
open turnCursor;
fetch next from turnCursor into #code_turn, #time, #taked;
while ##fetch_status = 0 AND #sequence < #GivenSequence
begin
if #taked IS NULL
select #firstTimeInSequence = coalesce(#firstTimeInSequence, #time)
,#sequence = #sequence + 1;
else
select #sequence = 0,
#firstTimeInSequence = null;
fetch next from turnCursor into #code_turn, #time, #taked;
end
close turnCursor;
deallocate turnCursor;
if #sequence = #GivenSequence
select top (#GivenSequence) * from [Turns-time] where [time] >= #firstTimeInSequence
order by [time] asc
WITH Base AS (
SELECT *,
CASE WHEN EXISTS(
SELECT *
FROM Taken_turns taken
WHERE taken.cod_turn = turns.cod_turn) THEN 1 ELSE 0 END AS taken
FROM [Turns-time] turns)
, RecursiveCTE As (
SELECT TOP 1 cod_turn, [time], taken AS run, 0 AS grp
FROM Base
WHERE [time] >= #start_time
ORDER BY [time]
UNION ALL
SELECT R.cod_turn, R.[time], R.run, R.grp
FROM (
SELECT T.*,
CASE WHEN T.taken = 0 THEN 0 ELSE run+1 END AS run,
CASE WHEN T.taken = 0 THEN grp + 1 ELSE grp END AS grp,
rn = ROW_NUMBER() OVER (ORDER BY T.[time])
FROM Base T
JOIN RecursiveCTE R
ON R.[time] < T.[time]
) R
WHERE R.rn = 1 AND run < #run_length
), T AS(
SELECT *,
MAX(grp) OVER () AS FinalGroup,
COUNT(*) OVER (PARTITION BY grp) AS group_size
FROM RecursiveCTE
)
SELECT cod_turn,time
FROM T
WHERE grp=FinalGroup AND group_size=#run_length
I think there is not a simple way to achieve this.
But probably there are many complex ways :). This is an approach that should work in Transact-SQL:
CREATE TABLE #CONSECUTIVE_TURNS (id int identity, time datetime, consecutive int)
INSERT INTO #CONSECUTIVE_TURNS (time, consecutive, 0)
SELECT cod_turn
, time
, 0
FROM Turns-time
ORDER BY time
DECLARE #i int
#n int
SET #i = 0
SET #n = 3 -- Number of consecutive not taken records
while (#i < #n) begin
UPDATE #CONSECUTIVE_TURNS
SET consecutive = consecutive + 1
WHERE not exists (SELECT 1
FROM Taken-turns
WHERE id = cod_turn + #i
)
SET #i = #i + 1
end
DECLARE #firstElement int
SELECT #firstElement = min(id)
FROM #CONSECUTIVE_TURNS
WHERE consecutive >= #n
SELECT *
FROM #CONSECUTIVE_TURNS
WHERE id between #firstElement
and #firstElement + #n - 1
This is untested but I think it will work.
Pure SQL
SELECT TOP 3 time FROM [turns-time] WHERE time >= (
-- get first result of the 3 consecutive results
SELECT TOP 1 time AS first_result
FROM [turns-time] tt
-- start from given time, which is 8:00 in this case
WHERE time >= '08:00'
-- turn is not taken
AND cod_turn NOT IN (SELECT cod_turn FROM taken_turns)
-- 3 consecutive turns from current turn are not taken
AND (
SELECT COUNT(*) FROM
(
SELECT TOP 3 cod_turn AS selected_turn FROM [turns-time] tt2 WHERE tt2.time >= tt.time
GROUP BY cod_turn ORDER BY tt2.time
) AS temp
WHERE selected_turn NOT IN (SELECT cod_turn FROM taken_turns)) = 3
) ORDER BY time
Note: I tested it on Postgresql (with some code modification), but not MS SQL Server. I'm not sure about performance compared to T-SQL.
Another set-based solution (tested):
DECLARE #Results TABLE
(
cod_turn INT NOT NULL
,[status] TINYINT NOT NULL
,RowNumber INT PRIMARY KEY
);
INSERT #Results (cod_turn, [status], RowNumber)
SELECT a.cod_turn
,CASE WHEN b.cod_turn IS NULL THEN 1 ELSE 0 END [status] --1=(not taken), 0=(taken)
,ROW_NUMBER() OVER(ORDER BY a.[time]) AS RowNumber
FROM [Turns-time] a
LEFT JOIN [Taken_turns] b ON a.cod_turn = b.cod_turn
WHERE a.[time] >= #Start;
--SELECT * FROM #Results r ORDER BY r.RowNumber;
SELECT *
FROM
(
SELECT TOP(1) ca.LastRowNumber
FROM #Results a
CROSS APPLY
(
SELECT SUM(c.status) CountNotTaken, MAX(c.RowNumber) LastRowNumber
FROM
(
SELECT TOP(#Len)
b.RowNumber, b.[status]
FROM #Results b
WHERE b.RowNumber <= a.RowNumber
ORDER BY b.RowNumber DESC
) c
) ca
WHERE ca.CountNotTaken = #Len
ORDER BY a.RowNumber ASC
) x INNER JOIN #Results y ON x.LastRowNumber - #Len + 1 <= y.RowNumber AND y.RowNumber <= x.LastRowNumber;

Resources