Postgres : using computed variable in a SELECT statement (part 2/2) - sql

I'm having a SELECT statement as follow (doesn't work):
SELECT foo,
extract(day from CAST (date as TIMESTAMP) - CAST (birth_date as TIMESTAMP)) / 365.25 as age_norm,
CASE
WHEN age_norm >= 0 AND age_norm <1 THEN '00'
WHEN age_norm >= 1 AND age_norm <5 THEN '01-4'
--etc
END as age_group
FROM bar
Is there a way to "inject" here the "variable" age_normin the query ?
NOTE
Asked a similar question here, but without the parameter foopresent in this question
EDIT
Tried:
SELECT foo,
(
SELECT t.age_norm,
CASE
WHEN t.age_norm >= 0 AND t.age_norm <1 THEN '00'
WHEN t.age_norm >= 1 AND t.age_norm <5 THEN '01-4'
--etc
END
FROM (SELECT extract(day from CAST (date as TIMESTAMP) - CAST (birth_date as TIMESTAMP)) / 365.25 as age_norm FROM bar) t
)
as age_group
FROM bar
But getting:
ERROR: subquery must return only one column
LINE 10: ( SELECT t.age_norm,
^
SQL state: 42601
Character: 212
CONCLUSION
Tried some of proposed solutions but either the resulting query is too slow (compared to hard coding the extract function in each where clause) or the resulting query become too complicate (it is in fact more complicate than expressed in this post with group by clauses and others things).
So will implements each when clause as follow:
WHEN (extract(day from CAST (date as TIMESTAMP) - CAST (birth_date as TIMESTAMP)) / 365.25) >=0 AND (extract(day from CAST (date as TIMESTAMP) - CAST (birth_date as TIMESTAMP)) / 365.25) <1 THEN '00'
etc.
Thank you for your replies!

Use a derived table (a query in the FROM clause):
SELECT
foo,
age_norm,
CASE
WHEN age_norm >= 0 AND age_norm <1 THEN '00'
WHEN age_norm >= 1 AND age_norm <5 THEN '01-4'
END as age_group
FROM (
SELECT
foo,
extract(day from CAST (date as TIMESTAMP) - CAST (birth_date as TIMESTAMP)) / 365.25 as age_norm
FROM bar
) s

perhaps a lateral join like this....
SELECT foo,age_group.*
FROM
FROM bar
, LATERAL (
SELECT t.age_norm,
CASE
WHEN t.age_norm >= 0 AND t.age_norm <1 THEN '00'
WHEN t.age_norm >= 1 AND t.age_norm <5 THEN '01-4'
--etc
END
FROM (SELECT extract(day from CAST (bar.date as TIMESTAMP) - CAST bar.birth_date as TIMESTAMP)) / 365.25 as age_norm ) t
) as age_group;

Related

Calculating age in where clause

In the users table I have birth_date column. I want to select only users under the age 18.
I tried using alias
select
*,
age = case
when datediff(year, getdate(), birth_date) > 0
then year(getdate()) - year(birth_date) - 1
else year(getdate()) - year(birth_date)
end
from
users
where
age < 18
But apparently I cannot use alias in where.
So I tried using case but it's also not gonna work
select *
from users
where
case
when datediff(year, getdate(), birth_date) > 0
then year(getdate()) - year(birth_date) - 1 < 18
else year(getdate()) - year(birth_date) < 18
What shall I do in this case? I don't want to use a stored procedure.
Use cte
WITH cte AS
(
SELECT *,
CASE
WHEN DATEDIFF(year, getdate(), birth_date) > 0
THEN year(getdate()) - year(birth_date) - 1
ELSE year(getdate()) - year(birth_date)
END AS age
FROM users
)
SELECT *
FROM cte
WHERE age < 18
demo in db<>fiddle
The correct way to do this is not to use DATEDIFF at all. It will be less accurate (as it uses date boundaries) and slower (it can't use indexes).
Instead use DATEADD against the current date, do not use functions against the column
SELECT *
FROM dbo.users
WHERE birth_date > DATEADD(year, -18, GETUTCDATE()) -- maybe cast right-side to DATE?
db<>fiddle
simple use this !
SELECT * FROM dbo.users WHERE DATEDIFF(day,birth_date,GETDATE()) < 6570
I think you're just looking for direction on how to use an alias/CASE statement in a column?
If that's correct, then you'll just need to surround your alias with brackets.
select *
,case
when datediff(year, getdate(), birth_date) > 0
then year(getdate()) - year(birth_date) - 1
else year(getdate()) - year(birth_date)
end as age
from users
where age < 18
Below is one way to calculate age which accounts for leap days and other complexities. This calculates the difference of the yyyymmdd integer values and then divides by 10000 to evaluate only the year difference.
SELECT *
FROM users
WHERE
(CAST(FORMAT(GETDATE(), 'yyyyMMdd') AS int) -
CAST(FORMAT(birth_date, 'yyyyMMdd') AS int)) / 10000 < 18;

SUM the value based on the time and date

I need help for adding different time interval reads value for date.
Suppose in day they are 24 hours, and I want the sum of 4-8pm in one row and another i.e. 0-4pm and 9pm 12 am in one row.
I was using below query :
SELECT
ennt_date,
CASE
WHEN to_number(TO_CHAR(dta.end_time,'HH24:MI:SS'),'sssss')/60 >= 960
AND to_number(TO_CHAR(dta.end_time,'HH24:MI:SS'),'sssss') /60 <=
1200
THEN (reads)
ELSE (reads)
END
from MD_data
group by ennt_date
getting error saying:
case is not group by function
This should work. You can adjust the hhGroup time ranges per your needs. I was unclear as to whether the 4pm-8pm went thru 7:59 (8pm) or thru 8:59(9pm)
SELECT ennt_date
,hhGroup
,Count(1) as cnt
FROM
(Select
ennt_date
,CAST( dta.end_time as time) as tm
,DATEPART(HH, dta.end_time ) as hh
,CASE When DATEPART(HH, dta.end_time ) < 16 Then '0am-4pm'
When DATEPART(HH, dta.end_time ) < 20 Then '4pm-8pm'
When DATEPART(HH, dta.end_time ) < 21 Then '8pm-9pm'
When DATEPART(HH, dta.end_time ) < 24 Then '9pm-mid'
END as hhGroup
From md_data
) as mm
GROUP BY ennt_date, hhGroup
You can divide the day in 4-hour segments and sum each one separately:
with
x as (
select
(extract(hour from end_time) div 4) * 4 as fragment,
reads
from md_data
)
select
fragment,
sum(reads)
from x
group fragment
Sample results would help, but I think you want:
SELECT trunc(ennt_date),
( ceil( extract(hour from ennt_date) / 4.0) * 4 - 4 ) as hour,
SUM(reads)
from MD_data
group by trunc(ennt_date),
ceil( extract(hour from ennt_date) / 4.0);

How to get DATE_DIFF in decimal

My friends are migrating from Netezza to BigQuery. In Netezza "month_between" function gives them back a decimal result. Meanwhile in BQ date_diff is always an integer. Is there a way to get fractional output in BQ?
(their logic)
You could write an UDF:
CREATE TEMP FUNCTION months_between_impl(date_1 DATE, date_2 DATE) AS (
CASE
WHEN date_1 = date_2
THEN 0
WHEN EXTRACT(DAY FROM DATE_ADD(date_1, INTERVAL 1 DAY)) = 1
AND EXTRACT(DAY FROM DATE_ADD(date_2, INTERVAL 1 DAY)) = 1
THEN DATE_DIFF(date_1,date_2, MONTH)
WHEN EXTRACT(DAY FROM date_1) = 1
AND EXTRACT(DAY FROM DATE_ADD(date_2, INTERVAL 1 DAY)) = 1
THEN DATE_DIFF(DATE_ADD(date_1, INTERVAL -1 DAY), date_2, MONTH) + 1/31
ELSE DATE_DIFF(DATE_ADD(date_1, INTERVAL -1 DAY), date_2, MONTH) - 1 + EXTRACT(DAY FROM DATE_ADD(date_1, INTERVAL -1 DAY)) / 31 + (31 - EXTRACT(DAY FROM date_2) + 1) / 31
END
);
CREATE TEMP FUNCTION months_between(date_1 DATE, date_2 DATE) AS (
TRUNC(months_between_impl(date_1, date_2),9)
);
WITH
t AS (
SELECT DATE("2005-02-02") AS from_date, DATE("2005-01-01") AS to_date, "1.032258064516129" AS Expected
UNION ALL
SELECT DATE("2007-03-15"), DATE("2007-02-20"), "0.838709677419354"
UNION ALL
SELECT DATE("2008-03-29"), DATE("2008-02-29"), "1.0"
UNION ALL
SELECT DATE("2008-03-31"), DATE("2008-02-29"), "1.0"
UNION ALL
SELECT DATE("2005-11-29"), DATE("2006-03-01"), "-3.096774194"
UNION ALL
SELECT DATE("1993-07-01"), DATE("1993-03-31"), "3.03225806"
UNION ALL
SELECT DATE("2005-03-31"), DATE("2005-01-01"), "2.967741935"
UNION ALL
SELECT DATE("2008-03-30"), DATE("2008-02-29"), "1.032258064516129"
)
SELECT
from_date, to_date, expected, months_between(from_date, to_date) months_Between
FROM t;
added by Mikhail
Below is real run on Netezza showing that above UDF actually returns totally correct result (as for some reason the numbers in expected column are not what really Netezza returns - rather correct numbers are under result column - which as I mentioned exactly what Felipe's UDF produces)

Converting query from Microsoft SQL to Oracle

The following Microsoft SQL query compares two date fields of a table and returns those records for which the difference in minutes is greater than 5.
SELECT t.Id, t.date1, t.date2,
DATEDIFF(MINUTE, t.date1 , t.date2) AS Mtime
FROM table1 t
WHERE
DATEDIFF(MINUTE,t.date1, t.date2) > 5
I have no idea how to write this with ORACLE. I've searched for solution and the closest I came to was :
SELECT t.date1, t.date2,
(t.date1 - t.date2) * 1440 AS Mtime
FROM table1 t
WHERE
(t.date1 -t.date2) * 1440 > 5
which gives me the error inconsistent datatypes: expected INTERVAL DAY TO SECOND got NUMBER
Does anyone know how to write this query with ORACLE ?
Don't use the difference. Just add the interval:
WHERE t.DeliveryDate >= t.Deadline + interval '5' minute
Or:
WHERE t.DeliveryDate >= t.Deadline + 5 / (24 * 60)
The equivalent in SQL Server is:
WHERE t.DeliveryDate >= DATEADD(minute, 5, t.Deadline)
This is a good habit. If one of the values is a constant, then the use of a function (- or datediff()) prevents the use of an index.
This should work -
SELECT t.Id, t.date1, t.date2,
(CAST(t.date1 AS DATE)-CAST(t.date2 AS DATE)) * 1440 AS Mtime FROM table1 t where (CAST(t.date1 AS DATE)-CAST(t.date2 AS DATE)) * 1440 > 5

Using case when to insert values

Below shows a executable statement:
Successful attempt:
INSERT INTO Personnel_Assignment (DATE, testno, Hours)
SELECT '21-OCT-2011', '12345',
CASE
WHEN Extract(day From(S.ENDTIME-S.STARTTIME) ) >= 1
THEN (Extract(Day From(S.ENDTIME-S.STARTTIME) ) * 24
+ Extract(Hour From(S.ENDTIME-S.STARTTIME) ) )
WHEN S.endtime IS NULL
THEN NULL
ELSE
Extract(Hour From(S.ENDTIME-S.STARTTIME) ) )
||'hrs' End ||
Extract(Minute From(S.ENDTIME-S.STARTTIME) ) || 'Min' As Hours
FROM Schedule S`
Please note that the data type for endtime and start time is timestamp with timezone in this format:
Nls_Timestamp_Tz_Format='HH24:MI TZR'
Just a question that i would like to ask:
My datatype for hours is varchar2
And if i wish to sum my hours in the end from the results above, would it be tedious in converting it into number?
Thanks
First of all, || Else doesn't make sense. The part after || has to be another expression to concatenate.
Secondly, you certainly can nest case expressions, but in your case you don't need to. A single case expression can have multiple when/then branches, in the form case when [condition_A] then [expression_if_A_is_true] when [condition_B] then [expression_if_A_is_false_and_B_is_true] else [expression_if_A_and_B_are_both_false] end.