Date compression SQL Server - sql-server

I have this function and the query
Function:
ALTER FUNCTION [dbo].[dateholiday]
(#date1 AS DATETIME,
#date2 AS DATETIME )
RETURNS DATETIME
AS
BEGIN
DECLARE #Answer AS DATETIME
IF(#date1 = #date2)
SET #Answer = #date2
RETURN #Answer
END
the Query:
SELECT Posted_By
,Document_Number
,Account_Description
,dbo.dateholiday(CONVERT(NVARCHAR(10), CONVERT(DATETIME, Feiertage, 104), 20), [dbo].[QLIK].[Posted_Date]) AS Holiday
FROM [dbo].[FEIERTAGE_O]
,[dbo].[QLIK]
WHERE dbo.dateholiday(CONVERT(NVARCHAR(10), CONVERT(DATETIME, Feiertage, 104), 20), [dbo].[QLIK].[Posted_Date]) IS NOT NULL
GROUP BY dbo.dateholiday(CONVERT(NVARCHAR(10), CONVERT(DATETIME, Feiertage, 104), 20), [dbo].[QLIK].[Posted_Date])
,Posted_By
,Document_Number
,Account_Description
where FEIERTAGE_O is a list of 115 dates but the Posted_Date is about 1914495. The query works fine but the only problem is that it takes a lot and a lot of time. So I need to make it faster .
Any ideas !

Try if this helps:
SELECT Posted_By
, Document_Number
, Account_Description
, Holiday
FROM (
SELECT DISTINCT Posted_By
, Document_Number
, Account_Description
, dbo.dateholiday(CONVERT(DATETIME, Feiertage, 104)), [dbo].[QLIK].[Posted_Date]) AS Holiday
FROM [dbo].[FEIERTAGE_O]
CROSS JOIN [dbo].[QLIK]
) A
WHERE Holiday IS NOT NULL
Notice the cross join, you didn't specify an on clause for your implicit join (you should always use explicit join syntax), are you sure a cartesian product is what you need? This will explode the number of records (according to google calculator to infinity). Is that why you did a group by (even though you're not aggregating)? I've replaced the group by with a distinct, will give the same result, but is clearer. Have a look at your join whether it is really correct.
Edit
After re-reading your question I think this will give you what you need:
SELECT Posted_By
, Document_Number
, Account_Description
, Holiday
FROM (
SELECT DISTINCT Posted_By
, Document_Number
, Account_Description
, dbo.dateholiday(CONVERT(DATETIME, Feiertage, 104)), [dbo].[QLIK].[Posted_Date]) AS Holiday
FROM [dbo].[FEIERTAGE_O] FO
LEFT JOIN [dbo].[QLIK] Q
ON Q.[Posted_Date] = FO.Feiertage
) A
WHERE Holiday IS NOT NULL
If that's the case, you don't really need the function any more. You can instead use a CASE expression.

Related

Sum values if they are between date range sql

I want to sum values where date is between de creationdate and endDate,, hence ValueEnd.
For instances the second row, the creationDate is the same as the endDate, so I have to sum the ValuePerDay of this day to the previsou value. So in the column ValueEnd it is 3.4+1.17 = 4.57
I started by calculating the sum from the days where de Difference is 1, like this:
SELECT
CONVERT(CHAR(10), CreationDate,103) CreationDate
,CONVERT(CHAR(10), EndDate,103) EndDate
,SUM(Values_an) Values_an
FROM Dat1
WHERE Difference=1
GROUP BY CONVERT(CHAR(10), CreationDate,103), CONVERT(CHAR(10), EndDate,103), Difference
However, I'm having trouble sum the values where the difference if higher than 1. Can someone help me please?
OK, judging by the provided information - and as far as I understood everything right - the following approach might solve your problem:
DECLARE #t TABLE(
CreationDate date,
EndDate date,
Value_An decimal(19,4)
)
INSERT INTO #t VALUES
('2019-03-01', '2019-03-01', 3.4)
,('2019-03-01', '2019-03-03', 3.5)
,('2019-05-01', '2019-05-01', 3.6)
,('2019-06-01', '2019-06-04', 3.7)
;WITH cteMultiRow AS(
SELECT CreationDate, COUNT(*) cntRows
FROM #t
GROUP BY CreationDate
HAVING COUNT(*) > 1
),
cte AS(
SELECT t.*
,ROW_NUMBER() OVER (PARTITION BY t.CreationDate ORDER BY t.EndDate) AS rn
,DATEDIFF(d, t.CreationDate, t.EndDate)+1 AS Difference
,CASE WHEN m.CreationDate IS NOT NULL THEN t.Value_An/(DATEDIFF(d, t.CreationDate, t.EndDate)+1) ELSE t.Value_An END AS ValuePerD
FROM #t t
LEFT JOIN cteMultiRow m ON t.CreationDate = m.CreationDate
),
cteSums AS(
SELECT c.CreationDate, SUM(c.ValuePerD) AS ValuePerD
FROM cte c
GROUP BY c.CreationDate
)
SELECT c.CreationDate, c.EndDate, c.Value_An, c.Difference, c.ValuePerD, ISNULL(s.ValuePerD, c.Value_An) AS ValueEnd
FROM cte c
LEFT JOIN cteSums s ON c.CreationDate = s.CreationDate AND c.rn = 1

SQL Query returning multiple values

I am trying to write a query that returns the time taken by an Order from start to completion.
My table looks like below.
Order No. Action DateTime
111 Start 3/23/2018 8:18
111 Complete 3/23/2018 9:18
112 Start 3/24/2018 6:00
112 Complete 3/24/2018 11:10
Now I am trying to calculate the date difference between start and completion of multiple orders and below is my query:
Declare #StartDate VARCHAR(100), #EndDate VARCHAR(100), #Operation VARCHAR(100)
declare #ORDERTable table
(
order varchar(1000)
)
insert into #ORDERTable values ('111')
insert into #ORDERTable values ('112')
Select #Operation='Boiling'
set #EndDate = (SELECT DATE_TIME from PROCESS WHERE ACTION='COMPLETE' AND ORDER in (select order from #ORDERTable) AND OPERATION=#Operation)
---SELECT #EndDate
set #StartDate = (SELECT DATE_TIME from PROCESS WHERE ACTION='START' AND ORDER in (select order from #ORDERTable) AND OPERATION=#Operation)
---SELECT #StartDate
SELECT DATEDIFF(minute, #StartDate, #EndDate) AS Transaction_Time
So, I am able to input multiple orders but I want to get multiple output as well.
And my second question is if I am able to achieve multiple records as output, how am I gonna make sure which datediff is for which Order?
Awaiting for your answers. Thanks in advance.
I am using MSSQL.
You can aggregate by order number and use MAX or MIN with CASE WHEN to get start or end time:
select
order_no,
max(case when action = 'Start' then date_time end) as start_time,
max(case when action = 'Completed' then date_time end) as end_time,
datediff(
minute,
max(case when action = 'Start' then date_time end),
max(case when action = 'Completed' then date_time end)
) as transaction_time
from process
group by order_no
order by order_no;
You can split up your table into two temp tables, cte's, whatever, and then join them together to find the minutes it took to complete
DECLARE #table1 TABLE (OrderNO INT, Action VARCHAR(100), datetime datetime)
INSERT INTO #table1 (OrderNO, Action, datetime)
VALUES
(111 ,'Start' ,'3/23/2018 8:18'),
(111 ,'Complete' ,'3/23/2018 9:18'),
(112 ,'Start' ,'3/24/2018 6:00'),
(112 ,'Complete' ,'3/24/2018 11:10')
;with cte_start AS (
SELECT orderno, Action, datetime
FROM #table1
WHERE Action = 'Start')
, cte_complete AS (
SELECT orderno, Action, datetime
FROM #table1
WHERE Action = 'Complete')
SELECT
start.OrderNO, DATEDIFF(minute, start.datetime, complete.datetime) AS duration
FROM cte_start start
INNER JOIN cte_complete complete
ON start.OrderNO = complete.OrderNO
Why don't you attempt to approach this problem with a set-based solution? After all, that's what a RDBMS is for. With an assumption that you'd have orders that are of interest to you in a table variable like you described, #ORDERTable(Order), it would go something along the lines of:
SELECT DISTINCT
[Order No.]
, DATEDIFF(
minute,
FIRST_VALUE([DateTime]) OVER (PARTITION BY [Order No.] ORDER BY [DateTime] ASC),
FIRST_VALUE([DateTime]) OVER (PARTITION BY [Order No.] ORDER BY [DateTime] DESC)
) AS Transaction_Time
FROM tableName
WHERE [Order No.] IN (SELECT Order FROM #ORDERTable);
This query works if all the values in the Action attribute are either Start or Complete, but also if there are others in between them.
To read up more on the FIRST_VALUE() window function, check out the documentation.
NOTE: works in SQL Server 2012 or newer versions.

Using Max with CTE and CASE T-SQL

I am trying to use MAX() to select the most recent placement date within our database, and use Table_CTE so I can then select and filter between the dates desired.
BEGIN
DECLARE #Rangetill DATE, #Rangefrom DATE
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
WITH Table_CTE (Name, ID, Rangefrom, Rangetill, StatusID, Statusdate) AS
(
SELECT
PE.Personname + ' ' + PE.Surname [Name],
A.ApplicantId,
#rangefrom [Expiry warning from],
#rangetill [Expiry warning till],
A.Statusid,
selected = CASE
WHEN P.EndDate IS NOT NULL AND P.EndDate > A.StatusDate
THEN CONVERT(DATE, P.EndDate, 103)
ELSE CONVERT(DATE, A.StatusDate, 103)
END
FROM
Applicants AS A
LEFT JOIN
Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN
Placements AS P on A.applicantid = P.Applicantid
)
SELECT *
FROM Table_CTE
WHERE table_cte.Statusdate BETWEEN #Rangetill AND #Rangefrom
AND (Table_CTE.StatusID = 58 OR Table_CTE.statusid = 63)
ORDER BY Name DESC
END
The above selects the right information but also selects duplicate applicants with placement end dates (p.enddate) as they could have been placed more than once. The WHERE clause also limits the most recent enddate to within the range provided by the Variables and as there needs to be a log there will be multiple end dates. so my solution or idea would be to uses a max() within the Case or CTE Select. However I am not sure how to use or work with Max() in this case.
In this case I would like to check and return the Max(p.enddate) if it exists and store that in the statusdate of Table_CTE.
Is this possible and is it the best way to provide this information in a stored procedure?
In the CTE would be more efficient but this is easier
SELECT c.[Name], max(c.Statusdate)
FROM Table_CTE c
WHERE c.Statusdate Between #Rangetill and #Rangefrom
AND c.StatusID in (58, 63)
group by c.[Name]
Add the other columns on your own
Declare
#Rangetill date,
#Rangefrom date
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
With Table_CTE ( ID, Rangefrom, Rangetill, Statusdate)
AS (
Select
A.ApplicantId
, #rangefrom [Expiry warning from]
, #rangetill [Expiry warning till]
, selected = CASE
WHEN max(P.EndDate) IS NOT NULL AND max(P.EndDate) > max(A.StatusDate)
THEN max(CONVERT(DATE, P.EndDate, 103))
ELSE max(CONVERT(DATE, A.StatusDate, 103))
END
FROM Applicants AS A
LEFT JOIN Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN Placements AS P on A.applicantid = P.Applicantid
GROUP BY A.ApplicantId
)
SELECT
PE.PersonName + ' ' + PE.Surname [NAME]
, A.ApplicantId
, Table_CTE.ID
, Table_CTE.Statusdate
, #Rangefrom [Range from]
, #Rangetill [Range till]
FROM Table_CTE
LEFT JOIN Applicants AS A ON A.ApplicantId = Table_CTE.ID
LEFT JOIN Person as PE on PE.PersonID = A.ApplicantId
WHERE table_cte.Statusdate Between #Rangetill and #Rangefrom
AND (A.StatusID = 58 or A.statusid = 63 )
Order by PE.PersonName + ' '+ PE.Surname desc
END
Really messy way around things but I got my solution by removing all but the variables and ID from CTE so I could select Max(DATE) on both A.statusdate and P.EndDate.
By doing this I could group by A.ApplicantID and rejoin specific tables outside of the CTE in order to get Applicant Name and Status ID back into my results set.
Thank you for your help everyone.

Get a list of end of months dates between two months in SQL Server

This is similar to this mySQL question except I only want the end of month dates: Get a list of dates between two dates
I want to be able to enter two dates, something like:
SELECT EndOfMonth('1/1/2015', '1/1/2017');
And the returned results should look like
EndOfMonth
1/31/2015
2/28/2015
3/31/2015
.
.
.
10/31/2016
11/30/2016
12/31/2016
The SQL Server version I'm using is 2008, so I actually don't have access to EOMONTH().
Using a numbers table makes things easier. If you don't already have a numbers table you can use the following sql to create one (taken from this SO post):
SELECT TOP 10000 IDENTITY(int,0,1) AS Number
INTO Tally
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Tally ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (Number)
To learn more about the numbers table and how to use it, read Jeff Moden's The "Numbers" or "Tally" Table: What it is and how it replaces a loop article.
Once you have a numbers table, it's fairly easy with versions 2012 or higher, using the EOMONTH built in function:
DECLARE #Start date = '2015-01-01', #End date = '2017-01-01'
SELECT EOMONTH(DATEADD(MONTH, Number, #Start))
FROM Tally
WHERE Number < DATEDIFF(MONTH, #Start, #End)
For earlier versions, you can use DATEADD with DATEDIFF to get the last day of the previous month, and then simply add one month:
SELECT DATEADD(DAY, -DATEPART(DAY, #Start), (DATEADD(MONTH, Number+1, #Start)))
FROM Tally
WHERE Number < DATEDIFF(MONTH, #Start, #End)
See a live demo on rextester
You can achieve this easily using Common Table expressions(CTE).
--Declaration of start date and end date
declare #StartDate datetime='2017-01-01', #endDate datetime='2017-12-01'
-- Expression
;WITH monthcte
AS (SELECT EOMONTH(#StartDate) AS dates
UNION ALL
SELECT DATEADD(month, 1, dates) AS dates
FROM monthcte
WHERE dates <#endDate
)
-- Select query over expression
SELECT EOMONTH(dates) AS EndOfMonth
FROM monthcte
Order by EndOfMonth
If you are not able to use EOMonth you can generate like this below:
declare #d1 date = '1/1/2015'
declare #d2 date = '1/1/2017'
select top (datediff(MONTH,#d1,#d2)+1) Dates = DateAdd(day,-1, Dateadd(M,Row_number() over(order by (select null)), #d1)) from
master..spt_values n1, master..spt_values n2
If EoMonth is available in your SQL Server Version then you can try as below:
select top (datediff(MONTH,#d1,#d2)+1) Dates = EoMonth(Dateadd(M,Row_number() over(order by (select null))-1, #d1)) from
master..spt_values n1, master..spt_values n2
List of Month:
DECLARE #StartDate datetime
DECLARE #EndDate datetime
set #StartDate = '01/01/2015'
set #EndDate = '01/01/2017'
;WITH cte1 (S) AS (
SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (S)
),
cte2 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte1 AS cte2),
cte3 (S) AS (SELECT 1 FROM cte1 AS cte1 CROSS JOIN cte2 AS cte2)
select distinct cast(result as date) result from
(SELECT TOP (DATEDIFF(day, #StartDate, #EndDate) + 1)
result = DATEADD(day, ROW_NUMBER() OVER(ORDER BY S) - 1, #StartDate)
FROM cte3) as res

How to split date column & sum it up

I have a query where i have a date column (time) which tells about "IN" & "OUT" timing of the people attendance by this single column
My queries are :-
1) How to get the daily attendance of each employee
2) How to come to know if the employee is present less than 5 hours
Please let me know the queries in SQL server.
You'll need to group the query by the user and the items for a particular day then compare the maximum and minimum values, e.g.
declare #users table (
UserId int,
DateColumn datetime
)
insert into #users values (1, '2008-10-31 15:15')
insert into #users values (1, '2008-10-31 10:30')
insert into #users values (1, '2008-10-30 16:15')
insert into #users values (1, '2008-10-30 10:30')
select
UserID
, cast(dt as datetime) dt
, [in]
, [out]
, case when datepart(hour, [out]-[in]) >= 5 then 'yes' else 'no' end [5Hours?],
, cast(datediff(minute, [in], [out]) as float)/60 [hours]
from (
select
UserID
, convert(varchar, DateColumn, 112) dt
, min(DateColumn) [in]
, max(DateColumn) [out]
from #users
group by
UserID, convert(varchar, DateColumn, 112)
) a
To find the difference between two datetimes you can use the following:
SELECT Datepart(hour, dateTimeEnd - dateTimeStart)
The DatePart function returns part of a date time variable, and the dateTimeEnd - dateTimeStart returns the difference between two dates as a new DateTime
select
datediff(minute, TimeFrom, TimeTo) as AttendedTimeInMinutes,
case when datediff(minute, sTimeFrom, sTimeTo) < 5 * 60
then
'less than 5 hours'
else '5 hours or more'
end
from YourTable

Resources