2 T-SQL queries that should be the same are not - tsql

I have the 2 below queries that should produce the same result as far as I can tell but they are actually producing vastly different numbers. Why is "Between" dates not the same as specifying the month and year of those dates?
What could be causing this?
SELECT [Account]
, SUM([Amount]) AS [Amount]
FROM [Table]
WHERE [Account] = 'Specific Account'
AND Month([Date]) = 5
AND Year([Date]) = 2015
GROUP BY [Account]
Sum Result: -1,500,000
SELECT [Account]
, SUM([Amount]) AS [Amount]
FROM [Table]
WHERE [Account] = 'Specific Account'
AND [Date] BETWEEN '2015-05-01' AND '2015-05-31'
GROUP BY [Account]
Sum Result: 350,000
I need the first one to be correct because I need to group the results by Month and Year, which would be cumbersome using the second query.
Query that I need ultimately:
SELECT [Account]
, Month([Date]) AS [Month]
, Year([Date]) AS [Year]
, SUM([Amount]) AS [Amount]
FROM [Table]
GROUP BY [Account]
, Month([Date])
, Year([Date])

[Date] BETWEEN '2015-05-01' AND '2015-05-31'
will only include rows on the 31st where the time component is midnight and omit the rest of the day.
You should forget about BETWEEN as there is no valid string literal that you can put on the right that will work correctly for datetime,smalldatetime,datetime2(0)..datetime2(7) and use
WHERE [Date] >= '2015-05-01' AND [Date] < '2015-06-01'

Try below for your first case, where you are getting more rows.
AND (Month([Date]) = 5 AND Year([Date]) = 2015)
instead of
AND Month([Date]) = 5 AND Year([Date]) = 2015
==Update==
I would suggest to use CONVERT function. And you should revise your query like below
CONVERT(varchar(10),DATE_COLUMN,112) between '20150501' and '20150531'

Related

extract days of daterange grouped by month postresql

I have a pickupDate and returnDate in my OrderHistory table. I want to extract the sum of rental days of all OrderHistory entries, grouped/ordered by month. A cte seems to be the solution but I don´t get how to implement it in my query since the cte´s i saw were refering to themselves where it says "FROM cte".
I tried something like this:
SELECT
SUM((EXTRACT (DAY FROM("OrderHistory"."returnDate")-("OrderHistory"."pickupDate")))) as traveltime
, to_char("OrderHistory"."pickupDate"::date, 'YYYY-MM') as M
FROM
"OrderHistory"
GROUP BY
M
ORDER BY
M
But the outcome doesn´t split bookings btw two months (e.g. pickupDate=27th march 2022 and returnDate=03rd of april 2022) but will assign the whole 7 days to the month of march, since the returndate is in it. It should show 4 days in march and 3 in april.
Sorry for the probably very stupid question but I am a beginner. (my code is written in postgresql btw)
PostgreSQL naming conventions
Are PostgreSQL column names case-sensitive?
use legal, lower-case names exclusively so double-quoting is not
needed.
Final result in db fiddle
Add daterange column.
alter table order_history add column date_ranges daterange;
update order_history
with a(m_begin, m_end, pickup_date) as
(select date_trunc('month', pickup_date)::date,
(date_trunc('month', pickup_date) + interval '1 month - 1 day')::date,
pickup_date from order_history)
update order_history set date_ranges =
daterange(a.m_begin, a.m_end,'[]') from a
where a.pickup_date = order_history.pickup_date;
then final query:
WITH A AS(
select
pickup_date,
return_date,
return_date - pickup_date as total,
case when return_date <# date_ranges then (return_date - pickup_date)
else ( date_trunc('month', pickup_date) + interval '1 month - 1 day')::date - pickup_date
end partial_mth
from order_history),
b as (SELECT *, a.total - partial_mth parital_not_mth FROM a)
select *,
case when to_char(pickup_date,'YYYY-MM') = to_char(return_date,'YYYY-MM')
then
sum(partial_mth) over(partition by to_char(pickup_date,'YYYY-MM')) +
sum(parital_not_mth) over (partition by to_char(return_date,'YYYY-MM'))
else sum(partial_mth) over(partition by to_char(pickup_date,'YYYY-MM'))
end
from b;
After trying different things I think I found the best answer to my question, that I want to share with the community:
WITH hier as (
SELECT
"OrderHistory"."pickupDate" as start_date
, "OrderHistory"."returnDate" as end_date
, to_char("OrderHistory"."pickupDate"::date, 'YYYY-MM') as M
FROM
"OrderHistory"
GROUP BY
1, 2, 3
ORDER BY
3
), calendar as (
select date '2022-01-01' + (n || ' days')::interval calendar_date
from generate_series(0, 365) n
)
select
to_char(calendar_date::date, 'YYYY-MM')
, count(*) as tage_gebucht
from calendar
inner join hier on calendar.calendar_date between start_date and end_date
where calendar_date between '2022-01-01' and '2022-12-31'
group by 1
order by 1;
I think this is the simplest solution I came up with.

postgresql - data for 1st day of each month, or next day if no data for that day

i need to get the balance for the 1st of each month from a table ordered by date, if the 1st is missing from the dataset for a certain month then for that month i want the next available dates data.
I have tried many things but I tried the following to place a case in the where statement which just gives me the first and the second any ideas, maybe an over statement
select date_
, balance
from mytable
where case when extract(day from date_) = 1 then extract(day from date_) = 1 else (extract (day from date_) = 2 )end
group by date_
order by date_ desc
you can use window function row_number:
select * from
(
select * , row_number() over (partition by date_trunc('month',date_) order by date_ ) rn
) t
where rn = 1
DISTINCT ON should do the trick:
SELECT DISTINCT ON (EXTRACT (day FROM date_))
date_, balance
FROM mytable
ORDER BY EXTRACT (day FROM date_), date_;
That will get the first date for each month.

Order by Week Descending based on current week

Is there way to order this query by week # but starting with the current week then descending order ? So if the current week # was 2 it would order by 2,1,52,51 etc...
SELECT
SalesPerson
, CAST(SUM(hours) AS DECIMAL(18 , 2))
, DATEPART(wk , DATEADD(wk , DATEDIFF(wk , 0 , OrderDate) , 0)) AS Wk#
FROM Orders
WHERE OrderDate >= DATEADD(month , -12 , GETDATE())
GROUP BY DATEADD(wk , DATEDIFF(wk , 0 , OrderDate) , 0)
Thanks!
E
In your example are weeks 2 and 1 from a different year than the 52 etc? You could then ORDER BY YEAR(OrderDate) DESC, DATEPART(week,OrderDate) DESC
But you would have to add YEAR(OrderDate) to your GROUP BY clause.
Just to order by current week first and then other weeks in descending order you can try below order logic. You can add your SELECT and other logic on top of it.
ORDER BY
CASE WHEN
DATEPART(week,OrderDate) - DATEPART(WEEK, GETDATE()) = 0 THEN 100 ELSE
DATEPART(week,OrderDate)
END DESC
Be careful with weeks from different years with the same number…
Anyway: Since you group by weeks, you could simply ORDER BY MIN(OrderDate) DESC.

How does this Time Difference Calculation work?

I wanted to display the difference in HH:MM:SS between two datetime fields in SQL Server 2014.
I found a solution in this Stack Overflow post. And it works perfectly. But I want to understand the "why" of how this arrives at the correct answer.
T-SQL:
SELECT y.CustomerID ,
y.createDate ,
y.HarvestDate ,
y.DateDif ,
DATEDIFF ( DAY, 0, y.DateDif ) AS [Days] ,
DATEPART ( HOUR, y.DateDif ) AS [Hours] ,
DATEPART ( MINUTE, y.DateDif ) AS [Minutes]
FROM (
SELECT x.createDate - x.HarvestDate AS [DateDif] ,
x.createDate ,
x.HarvestDate ,
x.CustomerID
FROM (
SELECT CustomerID ,
HarvestDate ,
createDate
FROM dbo.CustomerHarvestReports
WHERE HarvestDate >= DATEADD ( MONTH, -6, GETDATE ())
) AS [x]
) AS [y]
ORDER BY DATEDIFF ( DAY, 0, y.DateDif ) DESC;
Results:
1239090 2017-11-07 08:51:03.870 2017-10-14 11:39:49.540 1900-01-24 21:11:14.330 23 21 11
1239090 2017-11-07 08:51:04.823 2017-10-19 11:17:48.320 1900-01-19 21:33:16.503 18 21 33
1843212 2017-10-27 19:14:02.070 2017-10-21 10:49:57.733 1900-01-07 08:24:04.337 6 8 24
1843212 2017-10-27 19:14:03.057 2017-10-21 10:49:57.733 1900-01-07 08:24:05.323 6 8 24
The first column in Customer ID - the second and third columns are the columns I wanted to calculate the time difference between. The third column is the difference between the two columns - and one of the points in the code in which I do not understand.
If you subtract two datetime fields like this create date - harvestdate, why does it default to the year 1900?
And regarding DATEDIFF ( DAY, 0 , y.DateDiff) - what does the 0 mean? Does the 0 set the date as '01-01-1900'?
It works - for that I am grateful. I was hoping I could get an explanation as to why this behavior works?
I've added some comments that should explain it:
SELECT y.CustomerID ,
y.createDate ,
y.HarvestDate ,
y.DateDif ,
DATEDIFF ( DAY, 0, y.DateDif ) AS [Days] , -- calculates the number of whole days between 0 and the difference
DATEPART ( HOUR, y.DateDif ) AS [Hours] , -- the number of hours between the two dates has already been cleverly
-- calculated in [DateDif], therefore, all that is required is to extract
-- that figure using DATEPART
DATEPART ( MINUTE, y.DateDif ) AS [Minutes] -- same explanation as [Hours]
FROM (
SELECT x.createDate - x.HarvestDate AS [DateDif] , -- calculates the difference expressed as a datetime;
-- 0 is '1900-01-01 00:00:00.000' as a datetime, so the
-- resulting datetime will be that plus the difference
x.createDate ,
x.HarvestDate ,
x.CustomerID
FROM (
SELECT CustomerID ,
HarvestDate ,
createDate
FROM dbo.CustomerHarvestReports
WHERE HarvestDate >= DATEADD ( MONTH, -6, GETDATE ())
) AS [x]
) AS [y]
ORDER BY DATEDIFF ( DAY, 0, y.DateDif ) DESC;

Get First and Last Day of Any Year

I'm currently trying to get the first and last day of any year. I have data from 1950 and I want to get the first day of the year in the dataset to the last day of the year in the dataset (note that the last day of the year might not be December 31rst and same with the first day of the year).
Initially I thought I could use a CTE and call DATEPART with the day of the year selection, but this wouldn't partition appropriately. I also tried a CTE self-join, but since the last day or first day of the year might be different, this also yields inaccurate results.
For instance, using the below actually generates some MINs in the MAX and vice versa, though in theory it should only grab the MAX date for the year and the MIN date for the year:
;WITH CT AS(
SELECT Points
, Date
, DATEPART(DY,Date) DA
FROM Table
WHERE DATEPART(DY,Date) BETWEEN 363 AND 366
OR DATEPART(DY,Date) BETWEEN 1 AND 3
)
SELECT MIN(c.Date) MinYear
, MAX(c.Date) MaxYear
FROM CT c
GROUP BY YEAR(c.Date)
You want something like this for the first day of the year:
dateadd(year, datediff(year,0, c.Date), 0)
and this for the last day of the year:
--first day of next year -1
dateadd(day, -1, dateadd(year, datediff(year,0, c.Date) + 1, 0)
try this
for getting first day ,last day of the year && firstofthe next_year
SELECT
DATEADD(yy, DATEDIFF(yy,0,getdate()), 0) AS Start_Of_Year,
dateadd(yy, datediff(yy,-1, getdate()), -1) AS Last_Day_Of_Year,
DATEADD(yy, DATEDIFF(yy,0,getdate()) + 1, 0) AS FirstOf_the_NextYear
so putting this in your query
;WITH CT AS(
SELECT Points
, Date
, DATEPART(DY,Date) DA
FROM Table
WHERE DATEPART(DY,Date) BETWEEN
DATEPART(day,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) AND
DATEPART(day,dateadd(yy, datediff(yy,-1, getdate()), -1))
)
SELECT MIN(c.Date) MinYear
, MAX(c.Date) MaxYear
FROM CT c
GROUP BY YEAR(c.Date)
I should refrain from developing in the evenings because I solved it, and it's actually quite simple:
SELECT MIN(Date)
, MAX(Date)
FROM Table
GROUP BY YEAR(Date)
I can put these values into a CTE and then JOIN on the dates and get what I need:
;WITH CT AS(
SELECT MIN(Date) Mi
, MAX(Date) Ma
FROM Table
GROUP BY YEAR(Date)
)
SELECT c.Mi
, m.Points
, c.Ma
, f.Points
FROM CT c
INNER JOIN Table m ON c.Mi = m.Date
INNER JOIN Table f ON c.Ma = f.Date