A way to parameterize VIEWs using WITH? - clickhouse

I find that I write a lot of very long queries, where I copy paste subqueries. I've been looking for an idiomatic way to increase code reuse. I am aware of CREATE VIEW. However, I do use the WITH statement a lot. I am aware that WHERE clauses are pushed down into the view. However, I am not aware of a solution that would allow me to parameterize GROUP BY and ORDER BY statements. Let me give you an example:
WITH
'America/New_York' AS tz,
toDateTime('2000-10-02 00:00:00', tz) AS start_dt,
toDateTime('2001-03-10 00:00:00', tz) AS end_dt,
INTERVAL 1 DAY AS interval_param,
'1hour` AS frequency_param
SELECT
ts,
argMinMerge(src.price) AS first,
argMaxMerge(src.price) AS last,
toTimeZone(min(src.ts), tz) AS first_ts,
toTimeZone(max(src.ts), tz) AS last_ts
FROM some_db.some_table AS src
WHERE src.frequency = frequency_param
AND toTimeZone(src.ts, tz) >= start_dt
AND toTimeZone(src.ts, tz) < end_dt
GROUP BY toStartOfInterval(src.ts, interval_param, tz) AS ts
ORDER BY ts
In this example I define some static parameters within the WITH clause which are then used in SELECT, WHERE, and GROUP BY clauses. It's very convenient to have all "input parameters" at the top in the WITH clause. As far as I know I will not be able to create a VIEW that would encapsulate this entire query.
Is the below the best I could do?
CREATE VIEW my_query_as_view AS
SELECT
ts,
argMinMerge(src.price) AS first,
argMaxMerge(src.price) AS last,
toTimeZone(min(src.ts), tz) AS first_ts,
toTimeZone(max(src.ts), tz) AS last_ts
FROM some_db.some_table AS src
WITH
'America/New_York' AS tz,
toDateTime('2000-10-02 00:00:00', tz) AS start_dt,
toDateTime('2001-03-10 00:00:00', tz) AS end_dt,
INTERVAL 1 DAY AS interval_param,
'1hour` AS frequency_param
SELECT * FROM my_query_as_view
WHERE src.frequency = frequency_param
AND toTimeZone(src.ts, tz) >= start_dt
AND toTimeZone(src.ts, tz) < end_dt
GROUP BY toStartOfInterval(src.ts, interval_param, tz) AS ts
ORDER BY ts
I am not sure this is any better. The problem becomes worse when I start nesting 3, 4, or even more queries.

Related

How do I separate the time and date in SQL navigator?

I am trying to separate the time and date in one column to be independent off each other. I am new at writing scripts
this is my query:
select
*
from
[tablename]
where
to_date([column_name]) in ( '15-Jun-2021', '16-Jun-2021' )
and
to_char([column_name],'dd-Mon-yyyy HH:MM:ss') < '15-Jun-2021 19:54:30'
The way you put it, it would be
select *
from your_table
where date_column >= date '2021-06-15'
and date_column < to_date('15.06.2021 19:54:30', 'dd.mm.yyyy hh24:mi:ss')
because
date_column should be of date datatype. If it isn't, you'll have problems of many kinds in the future. Therefore,
don't to_date it, it is already a date
don't to_char it either, because you'd be comparing strings and get unexpected result. Use that function when you want to nicely display the result
the second condition you wrote makes the first one questionable. If date_column is less than value you wrote, then you can omit date '2021-06-16' from the first condition because you won't get any rows for that date anyway
date literal (date '2021-06-15') sets time to midnight, so condition I wrote should return rows you want
SQL> select date '2021-06-15' first,
2 to_date('15.06.2021 19:54:30', 'dd.mm.yyyy hh24:mi:ss') second
3 from dual;
FIRST SECOND
------------------- -------------------
15.06.2021 00:00:00 15.06.2021 19:54:30
SQL>

Query to bring all the data for the last day in oracle

Please advise from review perspective is that below query will fetch up all the data with context to last date or still any modification is required as i am worried from the point of last day data , i want that it should bring also all the data with context to last day
SELECT PFT.*, PA.* FROM PFT_DATA PFT, CONTACT_TOKEN CT, PRODUCT_ATTRIBUTE PA, PERSON P WHERE PFT.PERSON_ID = ? AND PFT.TO_CONTACT_TOKEN_ID = CT.CONTACT_TOKEN_ID AND PFT.PRODUCT_ATTRIBUTE_ID = PA.PRODUCT_ATTRIBUTE_ID AND PFT.PERSON_ID = P.PERSON_ID AND PFT.CREATED_ON between to_date('05/24/2018', 'MM/dd/yyyy HH24:MI:SS') AND to_date('06/02/2018', 'MM/dd/yyyy HH24:MI:SS');
If pft.created_on has value for the time portion of the date, then no. Your to_date will give you 2018-06-02 00:00:00 and so a pft.created_on of 2018-06-02 00:00:01 (thru 23:59:59) would not be included.
Better to use 2018-06-02 23:59:59 as you already have the 'HH24:MI:SS' in your date mask.
And if pft.created_on happens to be a TIMESTAMP, you would be better off using TO_TIMESTAMP instead of TO_DATE.

How to generate diff between TIMESTAMP and DATE in SELECT in oracle 10

I need to query 2 tables, one contains a TIMESTAMP(6) column, other contains a DATE column. I want to write a select statement that prints both values and diff between these two in third column.
SB_BATCH.B_CREATE_DT - timestamp
SB_MESSAGE.M_START_TIME - date
SELECT SB_BATCH.B_UID, SB_BATCH.B_CREATE_DT, SB_MESSAGE.M_START_TIME,
to_date(to_char(SB_BATCH.B_CREATE_DT), 'DD-MON-RR HH24:MI:SS') as time_in_minutes
FROM SB_BATCH, SB_MESSAGE
WHERE
SB_BATCH.B_UID = SB_MESSAGE.M_B_UID;
Result:
Error report -
SQL Error: ORA-01830: date format picture ends before converting entire input string
01830. 00000 - "date format picture ends before converting entire input string"
You can subtract two timestamps to get an INTERVAL DAY TO SECOND, from which you calculate how many minutes elapsed between the two timestamps. In order to convert SB_MESSAGE.M_START_TIME to a timestamp you can use CAST.
Note that I have also removed your implicit table join with an explicit INNER JOIN, moving the join condition to the ON clause.
SELECT t.B_UID,
t.B_CREATE_DT,
t.M_START_TIME,
EXTRACT(DAY FROM t.diff)*24*60 +
EXTRACT(HOUR FROM t.diff)*60 +
EXTRACT(MINUTE FROM t.diff) +
ROUND(EXTRACT(SECOND FROM t.diff) / 60.0) AS diff_in_minutes
FROM
(
SELECT SB_BATCH.B_UID,
SB_BATCH.B_CREATE_DT,
SB_MESSAGE.M_START_TIME,
SB_BATCH.B_CREATE_DT - CAST(SB_MESSAGE.M_START_TIME AS TIMESTAMP) AS diff
FROM SB_BATCH
INNER JOIN SB_MESSAGE
ON SB_BATCH.B_UID = SB_MESSAGE.M_B_UID
) t
Convert the timestamp to a date using cast(... as date). Then take the difference between the dates, which is a number - expressed in days, so if you want it in minutes, multiply by 24*60. Then round the result as needed. I made up a small example below to isolate just the steps needed to answer your question. (Note that your query has many other problems, for example you didn't actually take a difference of anything anywhere. If you need help with your query in general, please post it as a separate question.)
select ts, dt, round( (sysdate - cast(ts as date))*24*60, 2) as time_diff_in_minutes
from (select to_timestamp('2016-08-23 03:22:44.734000', 'yyyy-mm-dd hh24:mi:ss.ff') as ts,
sysdate as dt from dual )
;
TS DT TIME_DIFF_IN_MINUTES
-------------------------------- ------------------- --------------------
2016-08-23 03:22:44.734000000 2016-08-23 08:09:15 286.52

expression in join clause?

Oracle tables
The following query works as START_TIME_ and DT.LABEL_DATE_TIME_15MIN_INT output is like for like. I have to admit it's the first time I've written a join like this, is it acceptable or is there a more efficient/proper way of accomplishing this?
select
A.ID,
trunc(A.START_TIME,'dd') + (round(to_char(A.START_TIME,'sssss') / 900) / 96) as START_TIME_,
DT.LABEL_DATE_TIME_15MIN_INT
from TBL_A A
inner join DATE_TIME DT
on ((trunc(A.IN_TIME_START,'dd') + (round(to_char(A.IN_TIME_START,'sssss') / 900) / 96)) = DT.DATE_TIME_15MIN_INT)
order by A.ID;
Reason for approach: A.START_TIME is TIMESTAMP(6) GMT-5 DT table has EPOCH time Keys in GMT with corresponding "Formatted Labels" adjusted to GMT-5. Instead of converting A.START_TIME to EPOCH, adjusting for timezone, and joining on the key I performed the arithmetic to A to the correct 15min interval then joined to the label. Am I missing a really simple solution here?
I would go for a more concise approach:
SELECT
a.id,
a.start_time_,
dt.label_date_time_15min_int
FROM
date_time dt
JOIN (
SELECT
id,
TRUNC(in_time_start,'dd') + (ROUND(TO_CHAR(in_time_start,'sssss') / 900) / 96) AS in_time_start_,
TRUNC(start_time,'dd') + (ROUND(TO_CHAR(start_time,'sssss') / 900) / 96) AS start_time_
FROM
tbl_a
) a ON a.in_time_start_ = dt.date_time_15min_int
ORDER BY
a.id
;
But fundamentally there's not much difference. Your join is valid, and you could also put it in the where clause if you wanted to. At this point there's not much you can do to optimize it without structural and applicative changes.

Oracle Recursive Query - Dates

I've got two sets of dates being passed into a query and I would like to find all the months/years between both sets of dates.
When I try this:
WITH CTE_Dates (cte_date) AS (
SELECT cast(date '2014-01-27' as date) from dual
UNION ALL
SELECT cast(ADD_MONTHS(TRUNC(cte_date, 'MONTH'),1) as date)
FROM CTE_Dates
WHERE ( TO_DATE(ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)) BETWEEN TO_DATE ('27-01-2014','DD-MM-YYYY') AND TO_DATE ('27-04-2014','DD-MM-YYYY'))
OR
( TO_DATE(ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)) BETWEEN TRUNC(TO_DATE('27-11-2014','DD-MM-YYYY'), 'MONTH') AND TO_DATE ('27-01-2015','DD-MM-YYYY'))
)
SELECT * from CTE_Dates
I get:
27-JAN-14
01-FEB-14
01-MAR-14
01-APR-14
I would also want to get:
01-NOV-14
01-DEC-14
01-JAN-15
It looks like the OR portion of the WHERE clause gets ignored.
Suggestions on how to create this query?
Thanks
Cory
The problem with what you have now (aside from extra cast() and to_date() calls) is that on the fourth iteration both the conditions are false so the recursion stops; there's nothing to make it skip a bit and pick up again, otherwise it would continue forever. I don't think you can achieve both ranges within the recursion.
You can put the latest date you want inside the recursive part, and then filter the two ranges you want afterwards:
WITH CTE_Dates (cte_date) AS (
SELECT date '2014-01-27' from dual
UNION ALL
SELECT ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)
FROM CTE_Dates
WHERE ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1) <= date '2015-01-27'
)
SELECT * from CTE_Dates
WHERE cte_date BETWEEN date '2014-01-27' AND date '2014-04-27'
OR cte_date BETWEEN date '2014-11-27' AND date '2015-01-27';
CTE_DATE
---------
27-JAN-14
01-FEB-14
01-MAR-14
01-APR-14
01-DEC-14
01-JAN-15
6 rows selected
You can replace the hard-coded values with your pairs of start and end dates. If the ranges might overlap or the second range could be (or end) before the first one, you could pick the higher date:
WHERE ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)
<= greatest(date '2015-01-27', date '2014-04-27')
... though that only makes sense with variables, not fixed values.

Resources