JOIN tables ON DATE = NUMBER? - sql

I am trying to join two tables in Oracle SQL. One table has a DATE data type which represents a date(go figure) the other has an NUMBER data type which represents a month. I need to join the tables on the DATE's month and the NUMBER. I tried TO_CHAR() but it didn't work. Any suggestions?

Oracle's EXTRACT() function may do the trick ( https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions050.htm ). Suppose we have 2 tables, populated with test data, like so:
create table numbers_ (num_ number);
create table dates_ (date_ date);
begin
for i in 1 .. 12
loop
insert into numbers_ values (i);
end loop;
insert into dates_ values ('15-JUL-2017');
insert into dates_ values ('16-AUG-2017');
insert into dates_ values ('17-SEP-2017');
end;
/
We can use EXTRACT to get the "months" from the dates_ table:
select extract (month from date_) from dates_;
EXTRACT(MONTHFROMDATE_)
7
8
9
Use the "extracted" months for joining the tables:
select *
from
numbers_ N,
( select extract( month from date_ ) month from dates_ ) D
where N.num_ = D.month;
-- output
NUM_ MONTH
7 7
8 8
9 9
If you need more columns from the dates_ table, add them into the subquery (and to the main SELECT clause). Example:
select
N.num_
, D.date_
, D.month
from
numbers_ N,
( select
extract( month from date_ ) month
, date_
from dates_ ) D
where N.num_ = D.month;
(See also: dbfiddle)
Or - better (as #Wernfried Domscheit suggested):
select
N.num_
, D.date_
from
numbers_ N join dates_ D
on extract(month from D.date_) = N.num_ ;

Related

SQL:Insert rows with sum of DLY rows less than the WKLY rows

Requirement: Is to insert rows ONLY FOR those rows whose difference b/w SUM of DLY rows are less than WKLY value and the DATES of DLY are within the range of DATES of WKLY
DDL:
create or replace table table_a
(
ID number,
qty number,
date_from date,
date_to date,
grain String
);
insert into tempdw.table_a values (1,102,'2020-07-04','2020-07-04','DLY');
insert into tempdw.table_a values (1,1028,'2020-07-05','2020-07-05','DLY');
insert into tempdw.table_a values (1,2828,'2020-07-06','2020-07-06','DLY');
insert into tempdw.table_a values (1,3870,'2020-07-05','2020-07-11','WKLY');
I need to insert a new row (yellow) with the difference of SUM of DLY(Orange) and WKLY(Green)
Tried :
select ID , sum(impression) over(partition by id , time_grain),date_from,date_to,time_grain
from tempdw.test_impress;
I don't have access to Snowflake but here's an example worked out (and tested with your sample data) using PostgreSQL. Hopefully you can tweak it for your own flavour of SQL.
INSERT INTO table_a
SELECT id,
missing_qty,
missing_date,
missing_date,
'DLY' AS grain
FROM ( /* NOTE: Use Average of b.qty because the value repeats on each row selected */
SELECT a.id,
Cast(Avg(b.qty) - Sum(a.qty) AS INTEGER) AS missing_qty,
( /* NOTE: Find an unused date in the week */
SELECT date(date_from + interval '1 day')
FROM table_a
WHERE grain = 'DLY'
AND date_from + interval '1 day' NOT IN
(
SELECT date_from
FROM table_a
WHERE id = a.id
AND grain <> 'WKLY') ) AS missing_date
FROM table_a a
JOIN table_a b
ON a.id = b.id
AND a.date_from BETWEEN b.date_from AND b.date_to
AND a.grain = 'DLY'
AND b.grain = 'WKLY'
GROUP BY a.id ) x
WHERE missing_qty > 0
This seems to work based on the data you've provided:
alter session set week_start = 7; -- Sets start of week to Sunday
insert into table_a (ID, qty, date_from, date_to, grain)
with t1 as (
select *
, concat(year(date_from),'-',week(date_from)) as year_week -- Week used to group records
, max(date_to) over (partition by grain, year_week) as max_dly_date -- Max date already used within week
,dateadd(day,1,max_dly_date) as new_dly_date -- Next date after the max date
,sum(qty) over (partition by grain, year_week) as sum_dly_qty -- Total qty by week and grain
from table_a
)
select dly.ID, (wkly.qty - dly.sum_dly_qty), dly.new_dly_date, dly.new_dly_date, 'DLY'
from t1 dly
inner join t1 wkly on dly.year_week = wkly.year_week and wkly.grain = 'WKLY'
where dly.grain = 'DLY' and dly.date_to = dly.max_dly_date; -- We only need one DLY record in each week

cross join to get all dates and hours and avoid duplicate values

We have 2 tables:
sales
hourt (only 1 field (hourt) of numbers: 0 to 23)
The goal is to list all dates and all 24 hours for each day and group hours that have sales. For hours that do not have sales, zero will be shown.
This query cross joins the sales table with the hourt table and does list all dates and 24 hours. However, there are also many duplicate rows. How can we avoid the duplicates?
We're using Amazon Redshift (based on Postgres 8.0).
with h as (
SELECT
a.purchase_date,
CAST(DATE_PART("HOUR", AT_TIME_ZONE(AT_TIME_ZONE(CAST(a.purchase_date AS
DATETIME), "0:00"), "PST")) as INTEGER) AS Hour,
COUNT(a.quantity) AS QtyCount,
SUM(a.quantity) AS QtyTotal,
SUM((a.price) AS Price
FROM sales a
GROUP BY CAST(DATE_PART("HOUR",
AT_TIME_ZONE(AT_TIME_ZONE(CAST(a.purchase_date AS DATETIME), "0:00"),
"PST")) as INTEGER),
DATE_FORMAT(AT_TIME_ZONE(AT_TIME_ZONE(CAST(a.purchase_date AS DATETIME),
"0:00"), "PST"), "yyyy-MM-dd")
ORDER by a.purchase_date
),
hr as (
SELECT
CAST(hourt AS INTEGER) AS hourt
FROM hourt
),
joined as (
SELECT
purchase_date,
hourt,
QtyCount,
QtyTotal,
Price
FROM h
cross JOIN hr
)
SELECT *
FROM joined
Order by purchase_date,hourt
Sample Tables:
Before the cross join, query returned correct sales and grouped hours, as seen in the below table.
Desired results table:
Need to create a series of all the hour values and left join your data back to that. Comments inline explain the logic.
WITH data AS (-- Do the basic aggregation first
SELECT DATE_TRUNC('hour',a.purchase_date) purchase_hour --Truncate timestamp to the hour is simpler
,COUNT(a.quantity) AS QtyCount
,SUM(a.quantity) AS QtyTotal
,SUM((a.price) AS Price
FROM sales a
GROUP BY DATE_TRUNC('hour',a.purchase_date)
ORDER BY DATE_TRUNC('hour',a.purchase_date)
-- SELECT '2017-01-13 12:00:00'::TIMESTAMP purchase_hour, 1 qty_count, 1 qty_total, 119 price
-- UNION ALL SELECT '2017-01-13 15:00:00'::TIMESTAMP purchase_hour, 1 qty_count, 1 qty_total, 119 price
-- UNION ALL SELECT '2017-01-14 21:00:00'::TIMESTAMP purchase_hour, 1 qty_count, 1 qty_total, 119 price
)
,time_range AS (--Calculate the start and end **date** values
SELECT DATE_TRUNC('day',MIN(purchase_hour)) start_date
, DATE_TRUNC('day',MAX(purchase_hour))+1 end_date
FROM data
)
,hr AS (--Generate all hours between start and end
SELECT (SELECT start_date
FROM time_range
LIMIT 1) --Limit 1 so the optimizer knows it's not a correlated subquery
+ ((n-1) --Make the series start at zero so we don't miss the starting value
* INTERVAL '1 hour') AS "hour"
FROM (SELECT ROW_NUMBER() OVER () n
FROM stl_query --Can use any table here as long as it enough rows
LIMIT 100) series
WHERE "hour" < (SELECT end_date FROM time_range LIMIT 1)
)
--Use NVL to replace missing values with zeroes
SELECT hr.hour AS purchase_hour --Timestamp like `2017-01-13 12:00:00`
, NVL(data.qty_count, 0) AS qty_count
, NVL(data.qty_total, 0) AS qty_total
, NVL(data.price, 0) AS price
FROM hr
LEFT JOIN data
ON hr.hour = data.purchase_hour
ORDER BY hr.hour
;
I achieved the desired results by using Left Join (table A with table B) instead of Cross Join of these two tables:
Table A has all the dates and hours
Table B is the first part of the original query

How to convert Varchar into Date

I'm trying to create a Trial Balance Report ,
and i have a field named : 'PERIOD_NAME' which stores the accounting period name,
and my question is : is it possible to convert the 'PERIOD_NAME' which is a "VARCHAR2" into "DATE"
in order to sort the months ASC , To give me this Result
jan- 16
jan- 17
Feb- 16
Feb- 17
Use TO_DATE( string, format_model, nls_settings ) assuming your PERIOD_NAME is like jan-17 (but it is unclear from your question what the exact format is):
SELECT TO_DATE( period_name, 'MON-YY', 'NLS_DATE_LANGUAGE=English' )
FROM your_table;
If you want to sort by month then year then you can use EXTRACT to get the month or year and sort on that:
SELECT *
FROM (
SELECT TO_DATE( period_name, 'MON-YY', 'NLS_DATE_LANGUAGE=English' ) AS period_date
FROM your_table
)
ORDER BY EXTRACT( MONTH FROM period_date ),
EXTRACT( YEAR FROM period_date );
I think this is what you are after? Basically, I have used to_date to convert the period name to a date and then extracted the month and year separately to use in the sort. Hope this helps!
Data
CREATE TABLE my_table
(
col1 VARCHAR2(10)
) ;
INSERT INTO my_table VALUES ('jan- 16');
INSERT INTO my_table VALUES ('jan- 17');
INSERT INTO my_table VALUES ('Feb- 16');
INSERT INTO my_table VALUES ('Feb- 17');
Solution
SELECT t.*
FROM my_table t
ORDER BY to_char(to_date(col1, 'mon- yy'), 'mm'),
to_char(to_date(col1, 'mon- yy'), 'yy')
Result
COL1
jan- 16
jan- 17
Feb- 16
Feb- 17

Get date nearest to sysdate

I would like to get per N the max(date) <= sysdate. If no dates in the past/today exist I want the min(date). So I do not want the absolute date nearest to sysdate.
Testdata
CREATE TABLE DATTEST (N NUMBER, D DATE);
INSERT INTO DATTEST (N,D) VALUES (1,TRUNC(SYSDATE-2000));
INSERT INTO DATTEST (N,D) VALUES (1,TRUNC(SYSDATE-1000));
INSERT INTO DATTEST (N,D) VALUES (1,TRUNC(SYSDATE+100));
INSERT INTO DATTEST (N,D)VALUES (2,TRUNC(SYSDATE));
INSERT INTO DATTEST (N,D)VALUES (2,TRUNC(SYSDATE+1000));
INSERT INTO DATTEST (N,D)VALUES (3,TRUNC(SYSDATE+1000));
So far I've got this. It gives the right results bus does two tablescans. I'm working on large tables and this query is called many times. I've been breaking my head converting this to a single table scan.
with nums as
(SELECT LEVEL num
FROM DUAL
CONNECT BY LEVEL <= 3
)
select
num
,(nvl((select max(d)
from dattest
where d <= trunc(sysdate)
and n = num),
(select min(d)
from dattest
where d > trunc(sysdate)
and n = num))
)
from nums;
Expected output
1 26-06-12
2 23-03-15
3 17-12-17
How about using an aggregation, with some conditional logic?
select id,
coalesce(max(case when d <= trunc(sysdate) then d end),
min(d)
)
from table t
group by id;

SQL Count for a Date Column

I have a table that containts a set of columns one of it is a Date column.
I need to count how many occurrences of the values of that column refer to the same month. And return if for one month, that count sums more than 3.
For example:
____________________
| DATE | .... |
---------------------
1998-09-02
1998-09-03
1998-10-03
1998-10-04
This must return no value. Because it doesn't have the necessary number of repetitions.
But this it does:
____________________
| DATE | .... |
---------------------
1998-09-02
1998-09-03
1998-09-12
1998-09-14
1998-10-02
1998-11-21
For the november month.
Is for an Oracle DB.
SELECT
COUNT(date)
, TRUNC(DATE,'MON')
FROM TABLE
GROUP BY TRUNC(DATE,'MON')
HAVING COUNT(DATE) > 3
create table x (date_col date);
insert into x values (date '1998-09-02');
insert into x values (date '1998-09-03');
insert into x values (date '1998-09-12');
insert into x values (date '1998-09-14');
insert into x values (date '1998-10-02');
insert into x values (date '1998-11-21');
SELECT TRUNC(date_col,'MM'), count(*)
FROM x
GROUP BY TRUNC(date_col,'MM')
HAVING count(*) > 3;
So if 3 coloums contain 1999-01-xx you want to get that fetched ?
SELECT YEAR(date), MONTH(date)
FROM table GROUP BY YEAR(date), MONTH(date)
HAVING COUNT(*) > 3
If you need all the rows that contain the upper result it should look something like that
SELECT * FROM table
INNER JOIN (
SELECT YEAR(date) as y, MONTH(date) as m
FROM table GROUP BY YEAR(date), MONTH(date)
HAVING COUNT(*) > 3
) as virtualTable
ON virtualTable.y = YEAR(date) AND virtualTable.m = MONTH(date)
This example will help :
create table d1
( event_date date, event_description varchar2(100));
insert into d1 values (sysdate,'Phone Call');
insert into d1 values (sysdate,'Letter');
insert into d1 values (sysdate-50,'Interview');
insert into d1 values (sysdate-50,'Dinner with parents');
insert into d1 values (sysdate-100,'Birthday');
insert into d1 values (sysdate-100,'Holiday');
insert into d1 values (sysdate-100,'Interview');
insert into d1 values (sysdate-100,'Phone Call');
commit;
select * from d1;
EVENT_DATE EVENT_DESCRIPTION
------------------------- -----------------------------------------------
04-MAR-10 14.47.58 Phone Call
04-MAR-10 14.47.58 Letter
13-JAN-10 14.47.58 Interview
13-JAN-10 14.47.58 Dinner with parents
24-NOV-09 14.47.58 Birthday
24-NOV-09 14.47.58 Holiday
24-NOV-09 14.47.58 Interview
24-NOV-09 14.47.58 Phone Call
8 rows selected
You can see that Nov-09 is the only month which more than 3 events.
Referring back to your original question, which was And return if for one month, that count sums more than 3. The following SQL aggregate will work.
select trunc(event_date,'MONTH'),count('x') from d1
having count('x') > 3 group by trunc(event_date,'MONTH')
Alternatively, use to_char to convert the Date type to a Char with a MON-YYYY picture as follows :
select to_char(trunc(event_date,'MONTH'),'MON-YYYY') month,
count('x') no_of_occurances from d1 having count('x') > 3 group trunc(event_date,'MONTH')
Ideally you should create a stored procedure that accepts the two criteria you need, Month(integer) and limit(integer)
In a parameterized procedure that executes the following
SELECT MONTH(Date) AS TheMonth, COUNT(MONTH(Date)) AS TheMonthCount
FROM MyTable
GROUP BY MONTH(Date)
HAVING (COUNT(MONTH(Date)) > #limit) AND (MONTH(Date) = #month)
To also output the relevant month you could use the following
SELECT CAST(YEAR(Date) AS NVARCHAR) + '.' +
CAST(MONTH(Date) AS NVARCHAR) AS 'The ',
MONTH(Date ) AS TheMonth, COUNT(MONTH(Date)) AS TheMonthCount
FROM Audit_Entry
GROUP BY MONTH(Date),
CAST(YEAR(Date) AS NVARCHAR) + '.' +
CAST(MONTH(Date) AS NVARCHAR)
HAVING (COUNT(MONTH(Date)) > #limit) AND (MONTH(Date) = #month)
This should work for mysql and mssql:
SELECT MONTH(date), Sum(MONTH(date))
FROM table
GROUP BY date
HAVING Sum(MONTH(date)) > 3
I am not sure which database you are using.
In MySQL query will be similar to the method proposed by #THEn
On SQL server you have other interesting possibilities.
Read the this article for more details.
You could use Oracle's EXTRACT method :
select theMonth, sum(monthCount)
from (
select
extract(MONTH FROM t.theDateColumn) as theMonth,
1 as monthCount
)
group by theMonth
having sum(monthCount) >= 3
I don't have an Oracle database at hand at the moment, so this code may not work as is - I apologize for this.
Could be wrong but a guess:
SELECT SUM(date) FROM table
GROUP BY date where SUM(date) > 3