Calculating Dates - sql

I have this problem: List of customers with their next scheduled, reoccurring appointment, that is either yearly, monthly, or quarterly.
The tables\columns I have are:
customer
customer_ID
service
customer_ID
service_RecID
Resource
service_RecID
Recurrence_RecID
Date_Time_Start
Recurrence
Recurrence_RecID
RecurType
RecurInterval
DaysOfWeek
AbsDayNbr
SelectInterval
It is modeled such that when the schedule is setup, the date_start_time is the date of when the first reoccurring appointment took place. Ex.
Recurrence_RecID = 10
RecurType = m (could be y, or d as well for yearly or daily)
RecurInterval = 6 (if recurType = y, this would mean every 6 years)
Given that the system generates these nightly, how would I write a query to calculate the next scheduled appointment, for each customer? I originally thought of using the Resource.Date_Time_Start and just cycling through until a variable nextAppointment >= today(), but is it good practice to run loops in SQL?
If anymore info is needed, let me know. Thank you much!
Edit: I will make a sqlfiddle.

I would suggest using a sub-query as opposed to looping. More efficient that way. This may not be exact but something like...
SELECT
*
FROM
(
SELECT
customer.customer_id,
service.service_RecID,
Resource.Date_Time_Start,
Recurrence.Recurrence_RecID,
RecurType,
RecurInterval,
DaysOfWeek,
AbsDayNbr,
SelectInterval,
NextAppointmentDate=
CASE
WHEN RecurType='m' THEN DATEADD(MONTH,RecurInterval,Resource.Date_Time_Start)
WHEN RecurType='y' THEN DATEADD(YEAR,RecurInterval,Resource.Date_Time_Start)
ELSE
NULL
END
FROM
Recurrence
INNER JOIN Resource ON Resource.Recurrence_RecID=Recurrence.Recurrence_RecID
INNER JOIN service ON service.service_RecID=Resource.service_RecID
INNER JOIN customer ON customer.customer_ID=service.customerID
)AS X
WHERE
NextAppointmentDate>=GETDATE()
ORDER BY Fields...

Related

USING RECURSION IN SQL TO FIND THE LOCATION OF AN EVENT

I am trying to determine the location of an event from 2 tables using sql. I believe that recursion is necessary for this solution, and I'm a bit foggy on that. Any help you can provide would be appreciated.
The first table is the movement table. It shows when and where a person moved. I also included a movement rank, although, I'm not sure it's necessary for a solution. A person may move back and forth between buildings.
The second table is the event table. It shows a person and a time of that person's event.
What I want to be able to do is find, for each event in the event_table, the location of the person. The solution would look something like this:
Thank you so much for your time and consideration.
This does it. This leaves NULL in the column if there is no movement before that time.
SELECT et.*, (
SELECT to_location
FROM movement_table
WHERE person_id=et.person_id
AND movement_time < et.event_time
ORDER BY movement_time desc
LIMIT 1) AS event_location
FROM event_table et;
Output (from sqlite):
10|2021-07-20 11:30:00|
20|2021-06-29 10:29:00|
20|2021-07-02 04:30:00|BUILDING A
20|2021-07-04 15:46:00|BUILDING B
40|2021-07-07 23:59:00|BUILDING C
50|2021-07-13 23:05:00|BUILDING D
50|2021-07-17 09:37:00|BUILDING D
sqlite>
If I understand correctly, location for an event is the TO_LOCATION column of the latest movement before the event in the Movement_table. So you need to join tables and select by max(MOVEMENT_TIME) < (or <=) EVENT_TIME. I cannot give an exact sql but it will be something like:
Select e.event_time, m.to_location
From event_table e, movement_table m
Where m.movement_time =
(Select max(m1.movement_time)
From movement_table m1
Where m1.movement_time < e.event_time)
Basically, a person is in a place for a period of time. You really just need the time period -- to get the end time use lead(). The rest is a join:
select e.*, m.*
from events e left join
(select m.*,
lead(movement_time) over (partition by person_id order by movement_time) as next_movement_time
from movements m
) m
on e.person_id = m.person_id and
e.event_time >= m.movement_time and
(e.event_time < m.next_movement_time or m.next_movement_time is null);

Access 2013 SQL, three tables, two using sum wrong results

Can someone please help me with this issue? I've scoured the Internet looking at dozens of examples, but i just can't find a solution that works.
I am using Access 2013. The problem is that I am trying to make a query that will highlight all part numbers from a supplier that either has customer back orders and/or overdue deliveries.
I am using three tables:
tbl_Inventory_Master which I require the part number, on hand stock value, and the supplier code.
For any back orders I need to join the tbl_Customer_Back_Order table as I need the count of back order lines and the sum of the back order quantity.
If the supplier has a late delivery, then I need to add the tbl_On_Order table showing the count of overdue deliveries and the sum of the overdue quantities.
The query is retrieving the data but the returned quantities are double what they should be.
SELECT
I.Inventory_Part_Num, I.Description, I.On_Hand_Stock,
COUNT (B.Part_Number) AS Back_Order_Count, SUM(B.Back_Order_Qty) as BO_Qty,
COUNT(O.Part_Number) AS Late_Deliveries_Count, SUM(O.Order_Qty) AS Late_Qty
FROM (tbl_Inventory_Master AS I
LEFT OUTER JOIN tbl_Customer_Back_Order AS B
ON I.Inventory_Part_Num = B.Part_Number)
LEFT OUTER tbl_On_Order AS O
ON I.Inventory_Part_Num = O.Part_Number
WHERE
I.Customer_Code = '274' AND
O.Due_Date < [ENTER TODAYS DATE IN FORMAT DD/MM/YYYY]
GROUP BY I.Inventory_Part_Num, I.Description, I.On_Hand_Stock
For example, for the part number 2022940 I should have 10 back order lines and an overdue quantity of 43. Instead, the query is returning 20 back order lines and an overdue quantity sum of 86.
From the on order table I have three orders totaling 144 pieces, instead the query is returning 960.
Can someone please advise, as this is driving me crazy?
You are joining along unrelated dimensions, so you need to aggregate before joining:
SELECT I.Inventory_Part_Num, I.Description, I.On_Hand_Stock,
B.Back_Order_Count, B.BO_Qty,
O.Late_Deliveries_Count, O.Late_Qty
FROM (tbl_Inventory_Master AS I LEFT OUTER JOIN
(SELECT B.Part_Number, COUNT(*) as Back_Order_Count,
SUM(B.Back_Order_Qty) as BO_Qty
FROM tbl_Customer_Back_Order AS B
GROUP BY B.Part_Number
) as B
ON I.Inventory_Part_Num = B.Part_Number
) LEFT JOIN
(SELECT O.Part_Number, COUNT(O.Part_Number) AS Late_Deliveries_Count,
SUM(O.Order_Qty) AS Late_Qty
FROM tbl_On_Order AS O
WHERE O.Due_Date < [ENTER TODAYS DATE IN FORMAT DD/MM/YYYY]
GROUP BY O.Part_Number
) as O
ON I.Inventory_Part_Num = O.Part_Number
WHERE I.Customer_Code = '274';
Notice the outer aggregation is no longer needed.

Refer to another table and return data adjacent to Max() result

I have the following two tables:
Using SQL Server 2012, I want to know the INTERVAL from the Hourly table where the MaxWaitTime and Split match what comes from the Daily table for each day. I am assuming I need to use a window function here, but I can't figure out the right answer.
There may be times where MaxWaitTime is 0 for an entire day, and thus all rows from the hourly table match. In this scenario, I would prefer a Null answer, but the earliest INTERVAL for that day would be fine.
There will also be times where multiple INTERVALs have the same wait time. In this scenario the first INTERVAL where the MaxWaitTime is present that day should be returned.
You can use outer apply if you want at most one match:
Looks like a simple left join should work between the tables. I'm simply going by the data shown above...
The query should look something like this. If the join fails, then a NULL will be returned. Give it a go..
select d.*, h.interval as maxinterval
from daily d outer apply
(select top 1 h.*
from hourly h
where convert(date, h.interval) = d.row_date and
h.split = d.split and
h.maxwaittime = d.maxwaittime
order by h.interval asc
) h;
If you want NULL for multiple matches, you can do something similar:
select d.*, h.interval as maxinterval
from daily d outer apply
(select top 1 h.callsoffered, h.split, max(h.interval) as maxinterval
from hourly h
where convert(date, h.interval) = d.row_date and
h.split = d.split and
h.maxwaittime = d.maxwaittime
group by h.maxwaittime, h.split
having count(*) = 1
) h;
Looks like a simple left join should work between the tables. I'm simply going by the data shown above...
The query should look something like this. If the join fails, then a NULL will be returned. Give it a go..
select daily.* ,hourly.callsoffered, hourly.interval as maxinterval
from daily
left join hourly
on convert(date,hourly.interval) = daily.row_date
and hourly.split = daily.split
and hourly.maxwaittime = daily.maxwaittime

Return data entered in column order by row

I am working on a simple timesheet module for a larger production system and need to display a table of information to the user. I have the following tables to work with:
TimeRecords
ID
WorkerID
AssyLineID
Station
Sequence
NbrHours
DateSubmitted
Workers
ID
Name
AssyLines
Name
During data entry, time is entered by AssyLine for each worker. A given worker may work on 2 or more different stations during the course of the day. The Sequence value is assigned based on the order of names as entered during data entry.
Now I want to return this data for all assembly lines and all workers in the following format:
ResultSet
Worker.ID
Worker.Name
AssyLine.Name - group returned rows by assembly line, in alphabetical order
Sequence - within each assembly line, group by sequence
NbrHours - total hours for worker for this assembly line, all stations
TotalHours - total hours for this worker across all assembly lines and stations
Other caveats:
1) The rows for a given worker should be grouped together, starting with the assembly line where they logged the most hours, in the sequence for that assembly line. I plan to consolidate all entries for a given worker into one row for display to the user and this is much easier if all rows for one user are grouped together. If that can't be done I will have to group and sort the row data in code...
Here is the query I have come up with so far:
SELECT
w.ID
,w.Name
,a.Name
,tr.NbrHours
,tr.Seq
FROM
TimeRecords tr
INNER JOIN
Workers w ON
w.ID = tr.WorkerId
INNER JOIN
AssyLines a ON
a.AssyLineID = tr.AssyLineId
WHERE
tr.DateSubmitted < '2000-01-01'
ORDER BY
w.Name
,a.Name
,tr.Seq
,NbrHours DESC
Obviously this leaves a lot to be desired. The worker entries are not grouped together and there is no overall total for the worker.
Can anyone help me get this right? I'm thinking I will need to do this with a Stored Proc rather than a view...
Thanks,
Dave
Most of this can be done with a simple group by clause; the messy part comes with your requirement for showing all hours, but I believe something like this should work depending on what DB you are using:
SELECT
w.ID
,w.Name
,a.Name
,tr.Seq
,SUM(tr.NbrHours) as nbrHours
(SELECT SUM(tr.NbrHours)
FROM TimeRecords tr2
WHERE tr2.WorkerId = w.id and tr2..DateSubmitted < '2000-01-01') as TotalHours
FROM
TimeRecords tr
INNER JOIN
Workers w ON
w.ID = tr.WorkerId
INNER JOIN
AssyLines a ON
a.AssyLineID = tr.AssyLineId
WHERE
tr.DateSubmitted < '2000-01-01'
GROUP BY
w.ID
,w.Name
,a.Name
,tr.Seq
ORDER BY
ReportName
,ShortName
,tr.Seq

Selecting different condition based on presence of association?

My Discount model has one Period association. I am trying to write a scope that select discounts that starts today, this includes:
when it has a period, select those periods that begins today.
when it does not have a period, select those discounts which were created between yesterday and today
My current query can do the first requirement (actually a bit complex than this):
def self.begins_today
joins(:event).where("begin = ?", today)
end
However how can I achieve requirement 2?
I was thinking of using SQL UNION command, but I think it can't work as a scope.
I'm assuming event contains the period association?
In any case you want a left join between the discounts table and the periods table. This will give you the period data to do the begin = today where clause, and null if there is no period. Thus the SQL to select the data would be
SELECT [columns]
FROM discounts_table
LEFT JOIN periods_table ON periods_table.discount_id = discounts_table.id
WHERE (periods_table.begin = [today]) OR (periods_table.begin IS NULL AND discounts_table.created_at BETWEEN [yesterday] AND [today])
in rails you should be able to achieve this as follows:
Discount
.joins("LEFT JOIN periods_table ON periods_table.discount_id = discounts_table.id")
.where("(periods_table.begin = ?) OR (periods_table.begin IS NULL AND discounts_table.created_at BETWEEN ? AND ?)", today, today, 1.day.ago.to_date)
Unfortunately you need the use SQL statements rather than letting rails create it for you as:
joins with a symbol only creates an INNER JOIN, not a LEFT JOIN
where with symbols, hashes etc will combine conditions using AND, not OR