Why is the result of datediff year in Firebird too high? - sql

I have question about function datediff in firebird. When I try to diff two dates like 15.12.1999 and 30.06.2000 in sql like this
SELECT
SUM(datediff (YEAR, W.FROM, W.TO)),
SUM(datediff (MONTH, W.FROM, W.TO)),
SUM(datediff (DAY, W.FROM, W.TO))
FROM WORKERS W
WHEN W.ID=1
I get in result 1 year, 6 month and 198 days but it is not true with value years (of course result should be 0) How I have to write my query to get correct result in parameter year? In that link https://firebirdsql.org/refdocs/langrefupd21-intfunc-datediff.html in documentation there is information about this case but there is not how to solve this problem.

The documentation is not very clear, but I'm pretty sure that datediff() is counting the number of boundaries between two dates. (This is how the very similar function in SQL Server works.) Hence, for year, it is counting the number of "Dec 31st/Jan 1st" boundaries. This is explicitly explained in the documentation.
If you want a more accurate count, you can use a smaller increment. The following is pretty close:
(datediff(day, w.from, t.to) / 365.25) as years_diff

Related

SQL 13 months transaction and quantity data extract broken down per month

I was reading through a couple of older posts and tried to apply the same logic to my question, I need to extract 13 months of data broken down per month, I would also like to apply the data to relevant headers... any suggestions. Please see code below and error received.
SELECT ST.TXDATE, ST.CODE, ST.QUANTITY
FROM StocTran ST
WHERE ST.TXDATE >= DATEADD(MONTH, -13, CAST(GETDATE() AS DATE))
ORDER BY ST.TXDATE
ERROR: [Elevate Software][DBISAM] DBISAM Engine Error # 11949 SQL
parsing error - Expected end of statement but instead found ( in
SELECT SQL statement at line 3, column 27 Error Code: 11949
DATEADD is a function in MS's TransactSQL for Sql Server. I do not know that DBIsam supports it, and it is not listed in DBIsam's list of supported functions here:
https://www.elevatesoft.com/manual?action=viewtopic&id=dbisam4&product=delphi&version=7&topic=functions
Generally, date functions are not portable across different SQL engines, and from that list, one possibility might be to use the EXTRACT function instead:
The EXTRACT function returns a specific value from a date, time, or timestamp value. The syntax is as follows:
EXTRACT(extract_value
FROM column_reference or expression)
EXTRACT(extract_value,
column_reference or expression)
Use EXTRACT to return the year, month, week, day of week, day, hours, minutes, seconds, or milliseconds from a date, time, or timestamp column. EXTRACT returns the value for the specified element as an integer.
The extract_value parameter may contain any one of the specifiers:
YEAR
MONTH
WEEK
DAYOFWEEK
DAYOFYEAR
DAY
HOUR
MINUTE
SECOND
MSECOND
Even if you are in a hurry, I strngly recommend that you study that page carefully.
UPDATE: From googling dbisam dateadd it looks like Elevate don't have a good answer for an equivalent to DATEADD. One of the hits is this thread:
https://www.sqlservercentral.com/Forums/Topic173627-169-1.aspx
which suggested an alternative way to do it using Delphi's built-in date functions (like IncMonth which I suggested you use in an answer to another q. Basically, you would calculate the start- and end-dates of a range of dates, then convert them to strings to construct a WHERE clause with a column date (from your db) which is equal to or greater than the start date and less or equal to the end date.

SQL Server DATEDIFF results in overflow on day comparison

Got a slight issue, I've got a view with several hundred thousand rows (and only going to get exponentially bigger) with a column with a datetime like so: 2017-07-10 12:13:46.000.
I'm trying to only select items with a timestamp in the last 7 days. I've got this:
SELECT Top(100) * FROM vw_dataList
WHERE DATEDIFF( DAY, vw_dataList.startTime, CURRENT_TIMESTAMP ) < 7;
But this results in an error:
The datediff function resulted in an overflow. The number of dateparts separating two date/time instances is too large. Try to use datediff with a less precise datepart.
I'm not really sure why this is, as even if DATEDIFF creates an integer from the timestamp, it shouldn't be such a big integer as to cause an overflow should it? Not really sure where to go from here so any advice is appreciated!
Cheers!
It looks like you have a date in your table that is significantly far in the past or future that is causing the DATEDIFF function to overflow. That function returns a signed integer so any date that is 2 billion (give or take) days in the future or past will overflow.
One option is to not use DATEDIFF at all and instead use DATEADD subtract 7 days from the current time and use that to compare:
SELECT TOP(100) *
FROM vw_dataList
WHERE vw_dataList.startTime >= DATEADD(DAY, -7, CURRENT_TIMESTAMP)
A possible alternative, though I wouldn't recomment it in this situation is to use DATEDIFF_BIG as this returns a BIGINT.
Seems that the plan for this query will be better if you use:
WHERE w_dataList.startTime > GETDATE() - 7
As you commented that the size of your table is too big, you can also insert a index in this column that probably won't be used with DATEDIFF() function.
Index sample:
CREATE INDEX ix_dataList ON vw_dataList (startTime DESC);
PS: Seems that it is a view, so you should replace the view to insert it in your table.
PS2: Check if you really need this index, you can check it in the execution plan.

SQL Average of total days in DATA per month

I have a SQL question.
I am trying to find the average injection volume per month. Currently my code takes the sum of all days of injection, and divides them by the TOTAL DAYS in the month.
Sum(W1."INJECTION_VOLUME" /
EXTRACT(DAY FROM LAST_DAY(W1."INJECTION_DATE"))) AS "AVGINJ"
This is not what I wanted.
I need to take the injection_volume and divide by the total days in the DATA .
ie. right now the data only 8 days of injection volume, lets say it is 3000.
So right now the sql is 3000/31.
I need to have it be 3000/8 (the total days in the data for the current month.)
Also, this should only be for the current month. All other completed months should be divided by the total days in the month.
Use
SELECT
SUM(W1.INJECTION_VOLUME) / COUNT(DISTINCT MyDateField)
FROM MyTable
WHERE X=Value
This gives you what you're after
SUM(W1.INJECTION_VOLUME) is the total volume for the dataset
Gives you the number of days, no matter how many records you have
COUNT(DISTINCT MyDateField)
So if you have 100 records but only 5 actual unique days in this time, this expression gives you 5
Note that this kind of calc is normally worked out with
SUM(A) / SUM(B)
not
SUM(A/B)
They give you completely different answers.
In order to get the average of the data for the current month you will need to divide by the count in the month:
SUM(`W1`.`INJECTION_VOLUME` / COUNT(EXTRACT(YEAR_MONTH FROM `W1`.`INJECTION_DATE`)))
To get all other data as the full month you'll need to combine your code:
SUM(`W1`.`INJECTION_VOLUME` / EXTRACT(DAY FROM LAST_DAY(`W1`.`INJECTION_DATE`)))
With an IF. So something like this:
SUM(
IF(
EXTRACT(YEAR_MONTH FROM `W1`.`INJECTION_DATE`) = EXTRACT(YEAR_MONTH FROM NOW()),
`W1`.`INJECTION_VOLUME` / COUNT(EXTRACT(YEAR_MONTH FROM `W1`.`INJECTION_DATE`)),
`W1`.`INJECTION_VOLUME` / EXTRACT(DAY FROM LAST_DAY(`W1`.`INJECTION_DATE`)
)
)
Note: this is untested and I'm not sure about the RDBMS you are using so you may need to change the code slightly to make it work.

Calculating the AVG value per GROUP in the GROUP BY Clause

I'm working on a query in SQL Server 2005 that looks at a table of recorded phone calls, groups them by the hour of the day, and computes the average wait time for each hour in the day.
I have a query that I think works, but I'm having trouble convincing myself it's right.
SELECT
DATEPART(HOUR, CallTime) AS Hour,
(AVG(calls.WaitDuration) / 60) AS WaitingTimesInMinutes
FROM (
SELECT
CallTime,
WaitDuration
FROM Calls
WHERE DATEADD(day, DATEDIFF(Day, 0, CallTime), 0) = DATEADD(day, DATEDIFF(Day, 0, GETDATE()), 0)
AND DATEPART(HOUR, CallTime) BETWEEN 6 AND 18
) AS calls
GROUP BY DATEPART(HOUR, CallTime)
ORDER BY DATEPART(HOUR, CallTime);
To clarify what I think is happening, this query looks at all calls made on the same day as today, and where the hour of the call is between 6 and 18 -- the times are recorded and SELECTed in 24-hour time, so this between hours is to get calls between 6am and 6pm.
Then, the outer query computes the average of the WaitDuration column (and converts seconds to minutes) and then groups each average by the hour.
What I'm uncertain of is this: Are the reported BY HOUR averages only for the calls made in that hour's timeframe? Or does it compute each reported average using all the calls made on the day and between the hours? I know the AVG function has a optional OVER/PARTITION clause, and it's been a while since I used the AVG group function. What I would like is that each result grouped by an hour shows ONLY the average wait time for that specific hour of the day.
Thanks for your time in this.
The grouping happens on the values that get spit out of datepart(hour, ...). You're already filtering on that value so you know they're going to range between 6 and 18. That's all that the grouping is going to see.
Now of course the datepart() function does what you're looking for in that it looks at the clock and gives the hour component of the time. If you want your group to coincide with HH:00:00 to HH:59:59.997 then you're in luck.
I've already noted in comments that you probably meant to filter your range from 6 to 17 and that your query will probably perform better if you change that and compare your raw CallTime value against a static range instead. Your reasoning looks correct to me. And because your reasoning is correct, you don't need the inner query (derived table) at all.
Also if WaitDuration is an integer then you're going to be doing decimal division in your output. You'd need to cast to decimal in that case or change the divisor a decimal value like 60.00.
Yes if you use the AVG function with a GROUP BY only the items in that group are averaged. Just like if you use the COUNT function with a GROUP BY only the items in that group are counted.
You can use windowing functions (OVER/PARTITION) to conceptually perform GROUP BYs on different criteria for a single function.
eg
AVG(zed) OVER (PARTITION BY DATEPART(YEAR, CallTime)) as YEAR_AVG
Are the reported BY HOUR averages only for the calls made in that hour's timeframe?
Yes. The WHERE clause is applied before the grouping and aggregation, so the aggregation will apply to all records that fit the WHERE clause and within each group.

Query to find a weekly average

I have an SQLite database with the following fields for example:
date (yyyymmdd fomrat)
total (0.00 format)
There is typically 2 months of records in the database. Does anyone know a SQL query to find a weekly average?
I could easily just execute:
SELECT COUNT(1) as total_records, SUM(total) as total FROM stats_adsense
Then just divide total by 7 but unless there is exactly x days that are divisible by 7 in the db I don't think it will be very accurate, especially if there is less than 7 days of records.
To get a daily summary it's obviously just total / total_records.
Can anyone help me out with this?
You could try something like this:
SELECT strftime('%W', thedate) theweek, avg(total) theaverage
FROM table GROUP BY strftime('%W', thedate)
I'm not sure how the syntax would work in SQLite, but one way would be to parse out the date parts of each [date] field, and then specifying which WEEK and DAY boundaries in your WHERE clause and then GROUP by the week. This will give you a true average regardless of whether there are rows or not.
Something like this (using T-SQL):
SELECT DATEPART(w, theDate), Avg(theAmount) as Average
FROM Table
GROUP BY DATEPART(w, theDate)
This will return a row for every week. You could filter it in your WHERE clause to restrict it to a given date range.
Hope this helps.
Your weekly average is
daily * 7
Obviously this doesn't take in to account specific weeks, but you can get that by narrowing the result set in a date range.
You'll have to omit those records in the addition which don't belong to a full week. So, prior to summing up, you'll have to find the min and max of the dates, manipulate them such that they form "whole" weeks, and then run your original query with a WHERE that limits the date values according to the new range. Maybe you can even put all this into one query. I'll leave that up to you. ;-)
Those values which are "truncated" are not used then, obviously. If there's not enough values for a week at all, there's no result at all. But there's no solution to that, apparently.