I have the following two tables
CREATE TABLE Ep
([E] varchar(9), [M] varchar(9), [DTE] DATETIME)
;
INSERT INTO Ep
([E], [M], [DTE])
VALUES
('1595861-1', '1595861-1', CONVERT(datetime, '2002-11-26 14:18:00', 20)),
('1595904-1', '1595904-1', CONVERT(datetime, '2002-11-24 15:15:00', 20)),
('1596298-1', '1596298-1', CONVERT(datetime, '2002-12-17 11:12:00', 20)),
('1596357-1', '1596357-1', CONVERT(datetime, '2002-12-09 19:57:00', 20)),
('1596369-1', '1596369-1', CONVERT(datetime, '2002-12-11 06:00:00', 20)),
('1596370-1', '1596370-1', CONVERT(datetime, '2002-12-19 12:31:00', 20)),
('1596473-2', '1596473-1', CONVERT(datetime, '2002-12-15 08:39:00', 20)),
('1596473-3', '1596473-1', CONVERT(datetime, '2002-12-20 08:39:00', 20)),
('1596473-4', '1596473-1', CONVERT(datetime, '2002-12-13 08:39:00', 20)),
('1596473-5', '1596473-1', CONVERT(datetime, '2002-12-16 08:39:00', 20)),
('1596473-1', '1596473-1', CONVERT(datetime, '2002-12-14 08:39:00', 20))
;
CREATE TABLE Mp
([E] varchar(9), [M] varchar(9), [DTE] DATETIME)
;
INSERT INTO Mp
([E], [M], [DTE])
VALUES
('', '1595861-1', CONVERT(datetime, '2002-11-26 14:18:00', 20)),
('', '1595904-1', CONVERT(datetime, '2002-11-24 15:15:00', 20)),
('', '1596298-1', CONVERT(datetime, '2002-12-17 11:12:00', 20)),
('', '1596357-1', CONVERT(datetime, '2002-12-09 19:57:00', 20)),
('', '1596369-1', CONVERT(datetime, '2002-12-11 06:00:00', 20)),
('', '1596370-1', CONVERT(datetime, '2002-12-19 12:31:00', 20)),
('', '1596473-1', CONVERT(datetime, '2002-12-17 08:39:00', 20))
;
Currently I am updating the [E] field in the Mp table via a match on [M] where the DTE field (in Mp) is within a certian range (say +-3 days). The query to do this is currently
UPDATE [Mp]
SET [E] = [Ep].[E]
FROM [Mp] INNER JOIN [Ep]
ON [Mp].[M] = [Ep].[M]
WHERE [Mp].[DTE] BETWEEN [Ep].[DTE] - 3 AND [Ep].[DTE] + 3;
This updates [Mp].[E] for [Mp].[M] = N'1596473-1' to 1596473-2. Essentailly the first entry SQL Server finds that is valid. However, I want to update this query so that SQL Server matches on the [M] field in the required date range (as it does now), but for the [Ep].[DTE] values that is closest to that in the [Mp].[DTE] value of 2002-12-17 08:39:00.
I have looked at adding a DATEDIFF clause, in the following way
UPDATE [Mp]
SET [E] = [Ep].[E]
FROM [Mp] INNER JOIN [Ep]
ON [Mp].[M] = [Ep].[M]
WHERE [Mp].[DTE] BETWEEN [Ep].[DTE] - 3 AND [Ep].[DTE] + 3
ORDER BY DATEDIFF(minutes, [Mp].[DTE], [Ep].[DTE]);
Clearly I can't do this, but I am unsure how to ammend this so that it works. The final data for [Mp] after the update should be
1595861-1 1595861-1 2002-11-26 14:18:00.000
1595904-1 1595904-1 2002-11-24 15:15:00.000
1596298-1 1596298-1 2002-12-17 11:12:00.000
1596357-1 1596357-1 2002-12-09 19:57:00.000
1596369-1 1596369-1 2002-12-11 06:00:00.000
1596370-1 1596370-1 2002-12-19 12:31:00.000
**1596473-5** 1596473-1 2002-12-17 08:39:00.000
Thanks for your time.
Please first check which data following CTE produces as output
For nearest data, I used SQL ROW_NUMBER function with Partition By clause according to the time difference calculation fetched by DATEDIFF() function. To eliminate previous and following records, I used ABS() mathematical function.
select
*,
ROW_NUMBER() over (partition by [Mp].[M] order by abs(datediff(mi, [Mp].[DTE], [Ep].[DTE]))) as rn,
abs(datediff(mi, [Mp].[DTE], [Ep].[DTE])) diff
from Mp
left join Ep
on [Mp].[M] = [Ep].[M]
WHERE [Mp].[DTE] BETWEEN dateadd(dd,-3,[Ep].[DTE]) AND dateadd(dd,3,[Ep].[DTE])
Then using the same CTE expression in an UPDATE command as following, you can populate the desired data into your target database table
;with cte as (
select
[Mp].[M] as M,
[Ep].E as E,
ROW_NUMBER() over (partition by [Mp].[M] order by abs(datediff(mi, [Mp].[DTE], [Ep].[DTE]))) as rn,
abs(datediff(mi, [Mp].[DTE], [Ep].[DTE])) diff
from Mp
left join Ep
on [Mp].[M] = [Ep].[M]
WHERE [Mp].[DTE] BETWEEN dateadd(dd,-3,[Ep].[DTE]) AND dateadd(dd,3,[Ep].[DTE])
)
update [Mp]
set [E] = cte.E
from [Mp]
inner join cte on [Mp].M = cte.M and cte.rn = 1
where
cte.E is not null
After execution of the UPDATE statement, the target table data is as follows
I hope this is what you require
Related
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.
For example I have the following table:
declare #table table(val int, dt datetime)
insert into #table values
(10, '2018-3-20 16:00'),
(12, '2018-3-20 14:00'),
(14, '2018-3-20 12:00'),
(16, '2018-3-20 10:00'),
(10, '2018-3-19 14:00'),
(12, '2018-3-19 12:00'),
(14, '2018-3-19 10:00'),
(10, '2018-3-18 12:00'),
(12, '2018-3-18 10:00')
I try to aggregate using the column in group by, it is okay:
select day, MAX(val) as max_by_value from
(
select DATEPART(DAY, dt) as day, val from #table
) q
group by day
It returns:
day max_by_value
18 12
19 14
20 16
Now I need max value by time of the day, so I need 10 as result for each day.
I try to use over but it say Column '#table.dt' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
select DATEPART(DAY, dt), MAX(val) as max_by_value
,ROW_NUMBER() over (partition by DATEPART(DAY, dt) order by dt desc) as max_by_date
from #table
group by DATEPART(DAY, dt)
I understand why I get this error but don't understand how to fix my issue. Could you please help to find some way to fill [max_by_date] column?
As result I expect the following output:
day max_by_value max_by_time
18 12 10
19 14 10
20 16 10
Starting from 2012 version, you can use the First_value window function:
SELECT DISTINCT DATEPART(DAY, dt),
MAX(val) OVER (partition by DATEPART(DAY, dt)) as max_by_value,
FIRST_VALUE(val) OVER (partition by DATEPART(DAY, dt) order by dt desc) as max_by_date
FROM #table
Note: I've used the OVER clause for the MAX function instead of using group by.
With 2008 version, you can use a subquery instead:
SELECT DISTINCT DATEPART(DAY, dt),
MAX(val) OVER (partition by DATEPART(DAY, dt)) as max_by_value,
(
SELECT TOP 1 val
FROM #table as t1
WHERE DATEPART(DAY, t1.dt) = DATEPART(DAY, t0.dt)
ORDER BY dt DESC
) as max_by_date
FROM #table as t0
I can really use some help. I'm trying to subtract 1 from a VARCHAR report parameter that is within a CTE. I'm working in SQL 2008 R2
This works fine:
AND MONTH(CAST(c.DATETIME AS DATE)) = CONVERT(INT,#Maand)
But this just doesn't seem to work:
AND MONTH(CAST(c.DATETIME AS DATE)) = CONVERT(INT,#Maand) -1
Thanks
Here is some of my code:
ALTER PROCEDURE [rpt].[usp_F_Telefonie_Bereikbaarheid](#Jaar AS VARCHAR(4), #Maand AS VARCHAR(2))
AS
BEGIN
WITH CTE AS(
SELECT
C.DATETIME AS Datum
,d.NAME
,COUNT(Callid) AS Aantal
,'Vorige' AS Gesprekken
,DURATIONSEC
FROM DM.dm.F_CDRS AS c
JOIN DM.dm.D_DEPARTMENTS AS d
ON d.DEPARTMENTID = c.DEPARTMENTID
WHERE Direction = 1
AND YEAR(CAST(c.DATETIME AS DATE)) = #Jaar
AND MONTH(CAST(c.DATETIME AS DATE))= CONVERT(INT,#Maand)
AND WAITTIMESEC <=10
GROUP BY LEFT(D.NAME, 2), D.NAME, C.DATETIME, DURATIONSEC
)
SELECT
,CASE CTE.Gesprekken
WHEN 'Vorige'
THEN SUM(CTE.Aantal)
END AS Vorige
FROM CTE
WHERE MONTH(CAST(CTE.Datum AS DATE)) = #Maand
AND YEAR(CAST(CTE.Datum AS DATE)) = #Jaar
GROUP BY CTE.NAME, CTE.Gesprekken, CTE.DURATIONSEC, CTE.Aantal
You need to convert varchar --> datetime and then use datediff function.
Instead this:
YEAR(CAST(c.DATETIME AS DATE)) = #Jaar
AND MONTH(CAST(c.DATETIME AS DATE))= CONVERT(INT,#Maand)
use:
datediff(month, cast(#Jaar + right('0' + #Maand, 2) + '01' as datetime), c.[datetime]) = 1
I have this error message when I insert my SQL query in Tableau. How to solve this issue?
Is it SQL Server currently does not allow CTE's inside a subquery??
Below is the Tableau output when I insert query in
[Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near the keyword 'WITH'.
[Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near the keyword 'with'. If this statement is a common table
expression, an xmlnamespaces clause or a change tracking context
clause, the previous statement must be terminated with a semicolon.
[Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near ')'.
Below is my current CTE Query (recursive)
WITH shiftHours AS (
SELECT RowID,
y.EMPLOYEENAME AS EMPLOYEENAME,
-- flatten the first hour to remove the minutes and get the initial current hour
DATEADD(hour, DATEDIFF(hour, 0, ShiftA_Start), 0) AS currentHour,
ShiftA_Start,
ShiftA_End,
DATEPART(hour, ShiftA_Start) AS hourOrdinal,
-- determine how much of the first hour is applicable. if it is minute 0 then the whole hour counts
CAST(CASE
WHEN DATEADD(hour, DATEDIFF(hour, 0, ShiftA_Start), 0) = DATEADD(hour, DATEDIFF(hour, 0, ShiftA_End), 0) THEN DATEDIFF(minute, ShiftA_Start, ShiftA_End) / 60.0
WHEN DATEPART(minute, ShiftA_Start) = 0 THEN 1.0
ELSE (60 - DATEPART(minute, ShiftA_Start)) / 60.0
END AS DECIMAL(5,3)) AS hourValue
FROM (
-- use a ROW_NUMBER() to generate row IDs for the shifts to ensure each row is unique once it gets to the pivot
SELECT ROW_NUMBER() OVER(ORDER BY ShiftA_Start, ShiftA_End) AS RowID,
EMPLOYEENAME,
ShiftA_Start,
ShiftA_End
FROM (
-- this is where the data gets pulled from the source table and where the data types are converted from string to DATETIME
SELECT
EMPLOYEENAME,
CONVERT(DATETIME, LEFT(SHIFTA_start, 17), 103) AS ShiftA_Start,
CONVERT(DATETIME, LEFT(SHIFTA_end, 17), 103) AS ShiftA_End
from [TableName].[dbo].[TMS_People]
where
CONVERT(DATETIME, LEFT(SHIFTA_START, 17), 103) IS NOT NULL AND CONVERT(DATETIME, LEFT(SHIFTA_END, 17), 103) IS NOT NULL
AND CONVERT(DATETIME, LEFT(SHIFTA_START, 17), 103) != CONVERT(DATETIME, LEFT(SHIFTA_END, 17), 103)
-- this is also where you would add any filtering from the source table such as date ranges
) x
) AS y
UNION ALL
SELECT RowID,
EMPLOYEENAME,
-- add an hour to the currentHour each time the recursive CTE is called
DATEADD(hour, 1, currentHour) AS currentHour,
ShiftA_Start,
ShiftA_End,
DATEPART(hour, DATEADD(hour, 1, currentHour)) AS hourOrdinal,
CAST(CASE
-- when this is the last time period determine the amount of the hour that is applicable
WHEN DATEADD(hour, 2, currentHour) > ShiftA_End THEN DATEPART(minute, ShiftA_End) / 60.0
ELSE 1
END AS DECIMAL(5,3)) AS hourValue
from shiftHours
-- contine recursion until the next hour is after the ShiftEnd
WHERE DATEADD(hour, 1, currentHour) < ShiftA_End
)
SELECT *
FROM (
SELECT RowID,
EMPLOYEENAME,
ShiftA_Start,
ShiftA_End,
hourValue,
hourOrdinal
from shiftHours
) AS t
PIVOT (
SUM(hourValue)
FOR hourOrdinal IN ([0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12], [13], [14], [15], [16], [17], [18], [19], [20], [21], [22], [23])
) AS pvt
OPTION (MAXRECURSION 0);
Tableau do not support SQL server CTE feature .
You should try to implement same logic using view or sub query or Stored proc/table valued function .
Please go throw below thread CTE (SQL Server) are not sported in Tableau.
https://community.tableau.com/thread/105965 .
View Sample code. for CTE
If we have user table (userId, userName, managerId)
CREATE VIEW vUSER AS
WITH UserCTE AS (
SELECT userId, userName, managerId,0 AS steps
FROM dbo.Users
WHERE userId = null
UNION ALL
SELECT mgr.userId, mgr.userName, mgr.managerId, usr.steps +1 AS steps
FROM UserCTE AS usr
INNER JOIN dbo.Users AS mgr
ON usr.managerId = mgr.userId
)
SELECT * FROM UserCTE ;
Create an sp ie MyProc and put all your query in it
ie
CREATE procedure dbo.MyProc AS
WITH UserCTE AS (
SELECT userId, userName, managerId,0 AS steps
FROM dbo.Users
WHERE userId = null
UNION ALL
SELECT mgr.userId, mgr.userName, mgr.managerId, usr.steps +1 AS steps
FROM UserCTE AS usr
INNER JOIN dbo.Users AS mgr
ON usr.managerId = mgr.userId
)
SELECT * FROM UserCTE ;
2 Create a link server using sp_addlinkedserver ie i am telling is Local
3 Call that sp in your view using openquery.
CREATE VIEW dbo.MyView
AS(
SELECT *
FROM openquery(Local,'exec mydb.dbo.MyProc'))
http://capnjosh.com/blog/how-to-call-a-stored-procedure-from-a-view-in-sql-server/
I have a table checks2:
AllTaskNo int,
CheckQuosimaNo int,
Masroof numeric(18,3),
Maqbood numeric(18,3),
Date1 smalldatetime
I have a query to get balance:
SELECT
AllTaskNo
,CheckQuosimaNo
,Masroof
,Maqbood
,Date
,(SELECT 0 + SUM(Maqbood - Masroof) AS Expr1
FROM Checks2 AS t2
WHERE (BankNo = 6)
AND (CheckQuosimaNo <= Checks2.CheckQuosimaNo)
AND (Date1 BETWEEN CONVERT(DATETIME, '01/01/2014', 103)
AND CONVERT(DATETIME, '31/10/2015', 103)) AND AllTaskNo
IN (SELECT No
FROM AllTasks
WHERE (BankNo = 6)
AND (Date BETWEEN CONVERT(DATETIME, '01/01/2014', 103)
AND CONVERT(DATETIME, '31/10/2015', 103))))) AS NetAmount
FROM
Checks2
WHERE
(BankNo = 6)
AND
(Date1 BETWEEN CONVERT(DATETIME, '01/01/2014', 103)
AND CONVERT(DATETIME, '31/10/2015', 103))
ORDER BY
BankNo
,Date1
,CheckQuosimaNo
the result is:
enter image description here
I need to make a formula (like excel for example NetAmount = NetAmount before + Maqbood - Masroof)
I expect that the selected row in NetAmount = 656360 but then query produce NetAmount=5567675.976
How can I fix this problem
There are many way to do this like Over and Correlated Sub Queries
Over:
SELECT
AllTaskNo ,Maqbood , Masroof,
SUM(-1*Maqbood +Masroof) OVER (ORDER BY AllTaskNo ) AS NetAmount
FROM Checks2 T1
Correlated Sub Queries (Need Sql server 2014+)
SELECT
AllTaskNo ,Maqbood , Masroof,
(
SELECT
SUM(-1*Maqbood +Masroof)
FROM Checks2 T2
WHERE T2.AllTaskNo <=T1.AllTaskNo
) AS NetAmount FROM Checks2 T1
Feel free to comment if you need any further assistance on this item
I have been solve my problem by my own solution ... I think there is a shorter solution .. but any way I get what I want... My solution is:
1- Make a table (Checks3) with a new field named (rank) which is the correct order of the command result.
2- Create a new command mostly like the first one containing the (bank balance formula).
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Checks3]') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table [dbo].[Checks3]
SELECT Checks2.BankNo, Checks2.AllTaskNo, Checks2.CheckQuosimaNo, Checks2.Masroof, Checks2.Maqbood, Checks2.Date1, rank() OVER (ORDER BY checks2.date1, checks2.CheckQuosimaNo) as rank
INTO Checks3
FROM Checks2 INNER JOIN
AllTasks ON Checks2.AllTaskNo = AllTasks.No where Checks2.BankNo =6 and (Checks2.[Date1] BETWEEN CONVERT(DATETIME, '01/10/2015', 103) AND CONVERT(DATETIME,'31/10/2015', 103)) ORDER BY CHECKS2.BankNo, CHECKS2.CheckQuosimaNo
SELECT Checks3.AllTaskNo, Checks3.CheckQuosimaNo, Checks3.Masroof, Checks3.Maqbood, Checks3.Date1,
( SELECT 0+SUM(t2.Maqbood - t2.Masroof) FROM Checks3 t2
WHERE t2.BankNo =6 and t2.rank <= Checks3.rank
and (t2.[Date1] BETWEEN CONVERT(DATETIME, '01/10/2014', 103) AND CONVERT(DATETIME,'31/10/2015', 103))
) as NetAmount
FROM Checks3
where Checks3.BankNo =6 and (Checks3.[Date1] BETWEEN CONVERT(DATETIME, '01/10/2014', 103) AND CONVERT(DATETIME,'31/10/2015', 103)) ORDER BY CHECKS3.RANK