Modify T-SQL query - sql-server

I have trouble with this query:
SELECT DISTINCT
r.uuid AS id,
r.uuid,
r.customerId
FROM
IF_reminders r
LEFT JOIN
IF_reminders_sent rs ON rs.reminderUuid = r.uuid
AND rs.event = 'eventName'
WHERE
r.eventNameEnabled = 1
AND (rs.sentAt IS NULL
OR rs.sentAt NOT BETWEEN (DATEADD(DAY, -14, '2022-05-01')) AND (DATEADD(DAY, 1, '2022-05-01')))
The date in DATEADD function is filled programmatically.
Table IF_reminders contain defined reminders of different types for the users.
Table IF_reminders_sent contain records then the reminder for particular event was sent to the user.
The query must return a list of user reminders for the event to which the reminder should be sent. If a reminder has already been sent, this user should be ignored.
The query shown above works as expected if table IF_reminders_sent does not contain any rows from the past years. If table does contain rows from past years, then user will get reminder every day in the specified date range.
How to update the query in a way that if for current year remainders for particular event not yet sent then full list will be returned but if current year has sent reminders for particular event then past years records will be ignored.
Update
Tables structures. Three ... represent additional events columns but birthday and mothersday describe possible structures for all of them.
IF_reminders columns
IF_reminders_sent
uuidcustomerIdsortByfirstNamelastNameemailphoneaddressrelationshipbirthdayEnabledbirthdayDate....mothersDayEnabledcreatedAtupdatedAt....
idcustomerIdreminderUuideventsentAt
The idea of query is to filter out user reminders what the program should sent out. Program will fire function for to send out Mothersday reminders 7 days before event and send it once. While the IF_remiders_sent was empty all works OK. But then it contains records from the past year then query returns always list of reminders to be sent because sentAt for previous year is NOT BETWEEN dates specified in the query and starts spam users. If for the mothersDay event reminders for current year are not sent yet the query has output full list of users who have this reminder active. If for the current year the reminder is sent it should ignore current year records (NOT BETWEEN part of query) and now it has to ignore previous years too. How to add this condition to the query?
Sample IF_reminders_sent:
id
customerId
Uuid
event
sentAt
2
124724
4871a550-0d85-4391-83e0-2fff63e412ae
mothersDay
2021-04-26 16:36:59.877
9
124724
4871a550-0d85-4391-83e0-2fff63e412ae
mothersDay
2022-04-26 16:36:59.877

You can define in your where clause to check the current year:
SELECT DISTINCT
r.uuid AS id,
r.uuid,
r.customerId
FROM
IF_reminders r
LEFT JOIN
IF_reminders_sent rs ON rs.reminderUuid = r.uuid
AND rs.event = 'eventName'
WHERE
DATEPART(year,rs.sentAt)=DATEPART(year,getdate()) and
r.eventNameEnabled = 1
AND (rs.sentAt IS NULL
OR rs.sentAt NOT BETWEEN (DATEADD(DAY, -14, '2022-05-01')) AND
(DATEADD(DAY, 1, '2022-05-01')))

You need to flip the logic around. You are looking for all reminders which do not have a sent reminder since the beginning of the year.. So you need NOT EXISTS
SELECT
r.uuid AS id,
r.uuid,
r.customerId
FROM
IF_reminders r
WHERE NOT EXISTS (SELECT 1
FROM
IF_reminders_sent rs
WHERE rs.reminderUuid = r.uuid
AND rs.event = 'eventName'
AND rs.sentAt >= DATEFROMPARTS(YEAR(GETDATE()), 1, 1)
AND rs.sentAt NOT BETWEEN DATEADD(DAY, -14, '2022-05-01') AND DATEADD(DAY, 1, '2022-05-01')
)
AND r.eventNameEnabled = 1;
Note that if you want to give a date range you should always use rs.sentAt >= SomeDateCalculation AND rs.sentAt < OtherDateCalculation rather than using functions such as YEAR(rs.sentAt) = YEAR(GETDATE())

Related

Calculate the duration between two dates based on holiday from calendar table

I have two tables : Calendar and Request_Stages:
I want to calculate the duration between DATE)FROM and DATE_TO for each request stage.
The desired result:
What I have tried:
SELECT Req.requestID, Req.STAGE_ID COUNT(DAT.holiday) as duration
FROM REQUEST Req
JOIN [dbo].[STG_ACCR_DATE] DAT
ON DAT.DATE >= Req.DATE_FROM
AND DAT.FDATE <= Req.DATE_TO
WHERE DAT.OFF_DAY = 0 --TO CALCULATE ONLY WORKING DAYS
GROUP BY Req.request_ID, Req.STAGE_ID
ORDER BY Req.request_ID, Req.STAGE_ID
The problem with my current result:
it doesn't SHOW the stages with zero working day, for example if a stage start date and end date are equal, the desired result is one '1' working day, but my query is returning zero '0' and doesn't show it in the results with this issue, stages records are lost.
Any suggestion to fix my query or new solution idea are appreciate it, probably I am thinking wrong, so any solution is welcome.
The posted query doesn't really match up with the problem as presented.
select *,
datediff(day, DATE_FROM, DATE_TO) + 1 - (
select count(*) from CALENDAR as c
where c.DATE between rs.DATE_FROM and rs.DATE_TO and c.IS_HOLIDAY = 1
) as DURATION
from REQUEST_STAGES as rs

Count by days, with all days

I need to count records by days, even if in the day were no records.
Count by days, sure, easy.
But how i can make it to print information, that 'in day 2018-01-10 was 0 records)
Should I use connect by level? Please, any help would be good. Can't use plsql, just oracle sql
First you generate every date that you want in an inline view. I chose every date for the current year because you didn't specify. Then you left outer join on date using whichever date field you have in that table. If you count on a non-null field from the source table then it will count 0 rows on days where there is no join.
select Dates.r, count(tablename.id)
from (select trunc(sysdate,'YYYY') + level - 1 R
from dual
connect by level <= trunc(add_months(sysdate,12),'YYYY') - trunc(sysdate,'YYYY')) Dates
left join tablename
on trunc(tablename.datefield) = Dates.r
group by Dates.r

How to do WHERE <before> an aggregate function (Postgres)

It's hard to explain from the title, but this is my SQL:
SELECT
SUM("payments"."amount"),
"invoices"."property_id"
FROM "payments"
JOIN "invoices"
ON "payments"."invoice_id" = "invoices"."id"
GROUP BY "property_id"
It returns the sum of all Payment records (amount column) for a particular Property (which is connected through it's invoices).
In other words:
Property has_many: :invoices
Invoice has_one: :payment
I'm trying to select payments between a particular date range though, but it has to happen "before" the aggregate function (so do the exact query above, but only for 2017-01-01 through 2017-02-01). The field would be generated_at on Payment
You are looking for a WHERE clause. (WHERE is executed before aggregation; HAVING is executed after.) Suggested date literals in PostgreSQL are ANSI standard DATE 'YYYY-MM-DD'. Date ranges are usually checked with >= start day and < end day + 1 (in order to deal properly with the time part if any).
SELECT
SUM(p.amount),
i.property_id
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
WHERE p.generated_at >= DATE '2017-01-01'
AND p.generated_at < DATE '2017-02-02'
GROUP BY i.property_id;

Factoring public holidays in to a SQL code

Apologies if this is a simple one. I'm looking for some help with the following:
SELECT *
FROM (
SELECT TOP 7
RIGHT (CONVERT (VARCHAR, CompletedDate, 108), 8) AS Time,
WorkType
FROM Table
WHERE WorkType = 'WorkType1'
OR DATEPART (DW, CompletedDate) IN ('7','1')
AND WorkType = 'WorkType2'
ORDER BY CompletedDate DESC) Table
ORDER BY CompletedDate ASC
Multiple events run every day, and the above searches for the last one scheduled to run each day, and pulls the time from it for the past 7 days. This time marks the end of the day's events, and is the value I'm after.
Events run at a different order on weekends, so I search for a different WorkType. WorkType 1 is unique to weekdays. WorkType2 is run both at weekdays and weekends, however it is not the final event on a weekday, so I don't search for it then.
However, this kind of falls apart when public holidays such as bank holidays come into play, as they use the weekend timings. I still need to capture these times, but the above skips over them. If I were to remove or expand the DATEPART, I would end up with duplicate values for each day that don't mark the final job of the day.
What changes can I make to this to capture these lost holiday timings, without manually going through and checking every holiday? Is there a way that I can return a value for JobType2, if JobType1 does not appear on a day?
I suggest a materialized calendar table with one row per date along with the desired WorkType for that day. That will allow you to simply join on to the calendar table to determine the proper WorkType value without embedding the logic in the query itself.
With this table loaded with all dates for your reporting domain:
CREATE TABLE dbo.WorkTypeCalendar(
CalendarDate date NOT NULL
CONSTRAINT PK_Calendar PRIMARY KEY CLUSTERED
, WorkType varchar(10) NOT NULL
);
GO
The query can be refactored as below:
SELECT *
FROM ( SELECT TOP 7
RIGHT(CONVERT (varchar, CompletedDate, 108), 8) AS Time
, WorkType
FROM Table1 AS t
JOIN WorkTypeCalendar AS c ON t.WorkType = c.WorkType
AND t.CompletedDate >= c.CalendarDate
AND t.CompletedDate < DATEADD(DAY,
1,
c.CalendarDate)
ORDER BY CompletedDate DESC
) Table1
ORDER BY CompletedDate ASC
You also might consider making this a generalized utility calendar table. See http://www.dbdelta.com/calendar-table-and-datetime-functions/ for an complete example of such a table and script to load US holidays you can adjust for your needs and locale.

SQL Server Retrieving Recurring Appointments By Date

I'm working on a system to store appointments and recurring appointments. My schema looks like this
Appointment
-----------
ID
Start
End
Title
RecurringType
RecurringEnd
RecurringTypes
---------------
Id
Name
I've keeped the Recurring Types simple and only support
Week Days,
Weekly,
4 Weekly,
52 Weekly
If RecurringType is null then that appointment does not recur, RecurringEnd is also nullable and if its null but RecurringType is a value then it will recur indefinatly. I'm trying to write a stored procedure to return all appointments and their dates for a given date range.
I've got the stored procedure working for non recurring meetings but am struggling to work out the best way to return the recurrences this is what I have so far
ALTER PROCEDURE GetAppointments
(
#StartDate DATETIME,
#EndDate DATETIME
)
AS
SELECT
appointment.id,
appointment.title,
appointment.recurringType,
appointment.recurringEnd,
appointment.start,
appointment.[end]
FROM
mrm_booking
WHERE
(
Start >= #StartDate AND
[End] <= #EndDate
)
I now need to add in the where clauses to also pick up the recurrences and alter what is returned in the select to return the Start and End Dates for normal meetings and the calculated start/end dates for the recurrences.
Any pointers on the best way to handle this would be great. I'm using SQL Server 2005
you need to store the recurring dates as each individual row in the schedule. that is, you need to expand the recurring dates on the initial save. Without doing this it is impossible to (or extremely difficult) to expand them on the fly when you need to see them, check for conflicts, etc. this will make all appointments work the same, since they will all actually have a row in the table to load, etc. I would suggest that when a user specifies their recurring date, you make them pick an actual number of recurring occurrences. When you go to save that recurring appointment, expand them all out as individual rows in the table. You could use a FK to a parent appointment row and link them like a linked list:
Appointment
-----------
ID
Start
End
Title
RecurringParentID FK to ID
sample data:
ID .... RecurringParentID
1 .... null
2 .... 1
3 .... 2
4 .... 3
5 .... 4
if in the middle of the recurring appointments schedule run, say ID=3, they decide to cancel them, you can follow the chain and delete the remaining ID=3,4,5.
as for expanding the dates, you could use a CTE, numbers table, while loop, etc. if you need help doing that, just ask. the key is to save them as regular rows in the table so you don't need to expand them on the fly every time you need to display or evaluate them.
I ended up doing this by creating a temp table of everyday between the start and end date along with their respective day of the week. I limited the recurrence intervals to weekdays and a set amount of weeks and added where clauses like this
--Check Week Days Reoccurrence
(
mrm_booking.repeat_type_id = 1 AND
#ValidWeeklyDayOfWeeks.dow IN (1,2,3,4,5)
) OR
--Check Weekly Reoccurrence
(
mrm_booking.repeat_type_id = 2 AND
DATEPART(WEEKDAY, mrm_booking.start_date) = #ValidWeeklyDayOfWeeks.dow
) OR
--Check 4 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 3 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*4) = 0
) OR
--Check 52 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 4 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*52) = 0
)
In case your interested I built up a table of the days between the start and end date using this
INSERT INTO #ValidWeeklyDayOfWeeks
--Get Valid Reoccurence Dates For Week Day Reoccurences
SELECT
DATEADD(d, offset - 1, #StartDate) AS [Date],
DATEPART(WEEKDAY,DATEADD(d, offset - 1, #StartDate)) AS Dow
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY s1.id) AS offset
FROM syscolumns s1, syscolumns s2
) a WHERE offset <= DATEDIFF(d, #StartDate, DATEADD(d,1,#EndDate))
Its not very elegant and probably very specific to my needs but it does the job I needed it to do.

Resources