JOIN two SELECT statement results from two different tables - sql

I am trying to create a code to join two statements each from different table and conditions, as follows:
the first statement:
select TO_CHAR(Entry_date, 'MON.YYYY') AS Months, count(Customer_id) "Count Customer"
from table1
where entry_date >= TO_DATE('01.01.1900', 'DD.MM.YYYY')
AND entry_date <= TO_DATE('31.12.2017', 'DD.MM.YYYY')
and Customer_status = 'Active'
group by TO_CHAR(entry_date,'MON.YYYY')
order by to_date(TO_CHAR(entry_date, 'MON.YYYY'),'MON.YYYY')
The second statement:
select count (order_id) "Order"
from table2
where leave_date >= TO_DATE('01.01.1900', 'DD.MM.YYYY')
AND leave_date <= TO_DATE('31.12.2017', 'DD.MM.YYYY')
group by TO_CHAR(leave_date,'MON.YYYY')
order by to_date(TO_CHAR(leave_date, 'MON.YYYY'),'MON.YYYY')
the result should look like this
Months Count Customer Order
Jan. 2017 15 0
Feb. 2017 1 8
Mar. 2017 30 10
The order should be dependent on the Months that were stated in the first statement.
Thanks for your help in advance.

I would write this as:
select yyyymm, sum(cust_count) as cust_count, sum(num_orders) as num_orders
from ((select to_char(entry_date, 'YYYY-MM') as yyyymm, count(*) as cust_count, 0 as num_orders
from table1
where entry_date >= date '1900-01-01' and
entry_date < date '2018-01-01' and
Customer_status = 'Active'
group by to_char(entry_date, 'YYYY-MM')
) union all
(select to_char(leave_date, 'YYYY-MM') as yyyymm, 0,
count(*) as num_orders
from table2
where leave_date >= date '1900-01-01' and
leave_date < date '2018-01-31'
group by to_char(leave_date, 'YYYY-MM')
)
) tt
group by yyyymm
order by yyyymm;
Notes on some changes:
The use of date rather than to_char() with date constants.
The use of the format "YYYY-MM", which orders correctly. (You don't have to use it but it recommended.)
The union all brings all the data together. In Oracle, you can also use a full outer join, but that requires more use of coalesce().

Related

SQL query to find Old clients that have transactions before 2014 but nothing afterwards?

Self-taught beginner here.
I want to sort out (old) clients that have last transactions in 2014 but nothing else till date.
I tried this:
select distinct transact_clntab.chn, transact_clntab.TRADE_DATE, transact_clntab.acct_code from transact_clntab
WHERE Trade_date not between '2019-01-01' and '2019-12-31'
and TRADE_DATE not between '2018-01-01' and '2018-12-31'
and TRADE_DATE not between '2017-01-01' and '2017-12-31'
and TRADE_DATE not between '2016-01-01' and '2016-12-31'
and TRADE_DATE not between '2015-01-01' and '2015-12-31'
and not exists
(SELECT chn, TRADE_DATE, acct_code FROM transact_clntab
WHERE Trade_date between '2008-01-01' and '2019-12-31'
and TRADE_DATE between '2009-01-01' and '2018-12-31'
and TRADE_DATE between '2010-01-01' and '2017-12-31'
and TRADE_DATE between '2011-01-01' and '2016-12-31'
and TRADE_DATE between '2012-01-01' and '2015-12-31'
and TRADE_DATE between '2013-01-01' and '2017-12-31'
and TRADE_DATE between '2014-01-01' and '2016-12-31')
but get no result
THEN
I tried sorting with excel but even more difficult
SELECT chn, TRANTYPE, ACCT_CODE, TRADE_DATE FROM transact_clntab
where TRADE_DATE < '2016-01-01'
order by TRADE_DATE
I expect the output to be
chn Transactiontype Account code Trade date
7683592 SALES BABA-2688 2008-06-02 00:00:00.000
58987897 PURCHASE FASH-1492 2008-06-02 00:00:00.000
If you want clients whose last trade is in 2014, then use aggregation:
SELECT t.acct_code
FROM transact_clntab t
GROUP BY t.acct_code
HAVING MAX(t.trade_date) < '2015-01-01' AND
MAX(t.trade_date) >= '2014-01-01';
If you want information about the last transaction for such accounts, use window functions:
SELECT t.*
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY t.acct_code ORDER BY t.trade_date DESC) as seqnum
FROM transact_clntab t
) t
WHERE seqnum = 1 AND
t.trade_date < '2015-01-01' AND
t.trade_date >= '2014-01-01';
Try grouping your data over the client id (I'm thinking it is chn) and fetch maximum date and put a condition that date is less than 2015.
SELECT transact_clntab.chn, MAX(transact_clntab.TRADE_DATE) last_transaction, transact_clntab.acct_code
FROM transact_clntab
GROUP BY transact_clntab.chn,transact_clntab.acct_code
HAVING last_transaction < '2015-01-01';
Hope this helps!
You're close. But you're not correlating by the customer, that's why the subquery to NOT EXISTS always finds some rows I guess. Your multiple BETWEENs can also be simplified and are probably better replaced by a check of an right open interval.
Assuming acct_code identifies the customer, you can try:
SELECT DISTINCT
tc1.chn,
tc1.trade_date,
tc1.acct_code
FROM transact_clntab tc1
WHERE tc1.trade_date >= '2014-01-01'
AND tc1.trade_date < '2015-01-01'
AND NOT EXISTS (SELECT *
FROM transact_clntab tc2
WHERE tc2.acct_code = tc1.acct_code
AND tc2.trade_date >= '2015-01-01');

SQL - How to find missing activity days using start_date and end_date

I have a few fields in a database that look like this:
trip_id
start_date
end_date
start_station_name
end_station_name
I need to write a query that shows all the stations with no activity on a particular day in the year 2015. I wrote the following query but it's not giving the right output:
select
start_station_name,
extract(date from start_date) as dt,
count(*)
from
trips_table
where
(
start_date >= timestamp('2015-01-01')
and
start_date < timestamp('2016-01-01')
)
group by
start_station_name,
dt
order by
count(*)
Can someone help come up with the right query? Thanks in advance!
Below is for BigQuery Standard SQL
It assumes start_date and end_date are of DATE type
It also assumes that all days in between start_date and end_date are "dedicated" to station in start_station_name field, which most likely not what is expected but question is missing details here thus such an assumption
#standardSQL
WITH days AS (
SELECT day
FROM UNNEST(GENERATE_DATE_ARRAY('2015-01-01', '2015-12-31')) AS day
),
stations AS (
SELECT DISTINCT start_station_name AS station
FROM `trips_table`
)
SELECT s.*
FROM (SELECT * FROM stations CROSS JOIN days) AS s
LEFT JOIN (SELECT * FROM `trips_table`,
UNNEST(GENERATE_DATE_ARRAY(start_date, end_date)) AS day) AS a
ON s.day = a.day AND s.station = a.start_station_name
WHERE a.day IS NULL
You can test/play it with below simple/dummy data
#standardSQL
WITH `trips_table` AS (
SELECT 1 AS trip_id, DATE '2015-01-01' AS start_date, DATE '2015-12-01' AS end_date, '111' AS start_station_name UNION ALL
SELECT 2, DATE '2015-12-10', DATE '2015-12-31', '111'
),
days AS (
SELECT day
FROM UNNEST(GENERATE_DATE_ARRAY('2015-01-01', '2015-12-31')) AS day
),
stations AS (
SELECT DISTINCT start_station_name AS station
FROM `trips_table`
)
SELECT s.*
FROM (SELECT * FROM stations CROSS JOIN days) AS s
LEFT JOIN (SELECT * FROM `trips_table`,
UNNEST(GENERATE_DATE_ARRAY(start_date, end_date)) AS day) AS a
ON s.day = a.day AND s.station = a.start_station_name
WHERE a.day IS NULL
ORDER BY station, day
the output is like below
station day
111 2015-12-02
111 2015-12-03
111 2015-12-04
111 2015-12-05
111 2015-12-06
111 2015-12-07
111 2015-12-08
111 2015-12-09
Use recursion for this purpose: try this SQL SERVER
WITH sample AS (
SELECT CAST('2015-01-01' AS DATETIME) AS dt
UNION ALL
SELECT DATEADD(dd, 1, dt)
FROM sample s
WHERE DATEADD(dd, 1, dt) < CAST('2016-01-01' AS DATETIME)
)
SELECT * FROM sample
Where CAST(sample.dt as date) NOT IN (
SELECT CAST(start_date as date)
FROM tablename
WHERE start_date >= '2015-01-01 00:00:00'
AND start_date < '2016-01-01 00:00:00'
)
Option(maxrecursion 0)
If you want the station data with it then you can use left join as :
WITH sample AS (
SELECT CAST('2015-01-01' AS DATETIME) AS dt
UNION ALL
SELECT DATEADD(dd, 1, dt)
FROM sample s
WHERE DATEADD(dd, 1, dt) < CAST('2016-01-01' AS DATETIME)
)
SELECT * FROM sample
left join tablename
on CAST(sample.dt as date) = CAST(tablename.start_date as date)
where sample.dt>= '2015-01-01 00:00:00' and sample.dt< '2016-01-01 00:00:00' )
Option(maxrecursion 0)
For mysql, see this fiddle. I think this would help you....
SQL Fiddle Demo

SQL: Return '0' for a row if it doesn't exist

I have a SQL query which displays count, date, and time.
This is what the output looks like:
And this is my SQL query:
select
count(*),
to_char(timestamp, 'MM/DD/YYYY'),
to_char(timestamp, 'HH24')
from
MY_TABLE
where
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by
to_char(timestamp, 'MM/DD/YYYY'), to_char(timestamp, 'HH24')
Now, in COUNT column, I want to display 0 if the count doesn't exist for that hour. So on 3/2/2016 at 8am, the count was 6. Then at 9am the count was 0 so that row didn't get displayed. I want to display that row. And at 10am & 11am, the counts are displayed then it just goes to next day.
So how do I display count of 0? I want to display 0 count for each day every hour doesn't matter if it's 0 or 6 or whatever. Thanks :)
Use a partition outer join:
SELECT m.day,
h.hr,
COALESCE( freq, 0 ) AS freq
FROM ( SELECT LEVEL - 1 AS hr
FROM DUAL
CONNECT BY LEVEL <= 24
) h
LEFT OUTER JOIN
( SELECT COUNT(*) AS freq,
TO_CHAR( "timestamp", 'mm/dd/yyyy' ) AS day,
EXTRACT( HOUR FROM "timestamp" ) AS hr
FROM MY_TABLE
WHERE "timestamp" >= TIMESTAMP '2016-03-01 00:00:00'
GROUP BY
TO_CHAR( "timestamp", 'mm/dd/yyyy' ),
EXTRACT( HOUR FROM "timestamp" )
) m
PARTITION BY ( m.day, m.hr )
ON ( m.hr = h.hr );
Use a cte to generate numbers for all the hours in a day. Then cross join the result with all the possible dates from the table. Then left join on the cte which has all date and hour combinations, to get a 0 count when a row is absent for a particular hour.
with nums(n) as (select 1 from dual
union all
select n+1 from nums where n < 24)
,dateshrscomb as (select n,dt
from nums
cross join (select distinct trunc(timestamp) dt from my_table
where timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
) alldates
)
select count(trunc(m.timestamp)), d.dt, d.n
from dateshrscomb d
left join MY_TABLE m on to_char(m.timestamp, 'HH24') = d.n
and trunc(m.timestamp) = d.dt
and m.timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by d.dt, d.n
with cteHours(h) as (select 0 from dual
union all
select h+1 from cteHours where h < 24)
, cteDates(d) AS (
SELECT
trunc(MIN(timestamp)) as d
FROM
My_Table
WHERE
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
UNION ALL
SELECT
d + 1 as d
FROM
cteDates
WHERE
d + 1 <= (SELECT trunc(MAX(timestamp)) FROM MY_TABLE)
)
, datesNumsCross (d,h) AS (
SELECT
d, h
FROM
cteDates
CROSS JOIN cteHours
)
select count(*), to_char(d.d, 'MM/DD/YYYY'), d.h
from datesNumsCross d
LEFT JOIN MY_TABLE m
ON d.d = trunc(m.timestamp)
AND d.h = to_char(m.timestamp, 'HH24')
group by d.d, d.h
#VPK is doing a good job at answering, I just happened to be writing this at the same time as his last edit to generate a date hour cross join. This solution differs from his in that it will get all dates between your desired max and min. Where as his will get only the dates within the table so if you have a day missing completely it would not be represented in his but would in this one. Plus I did a little clean up on the joins.
Here is one way to do that.
Using Oracle's hierarchical query feature and level psuedo column, generate the dates and hours.
Then do an outer join of above with your data.
Need to adjust the value of level depending upon your desired range (This example uses 120). Start date needs to be set as well. It is ( trunc(sysdate, 'hh24')-2/24 ) in this example.
select nvl(c1.cnt, 0), d1.date_part, d1.hour_part
from
(
select
to_char(s.dt - (c.lev)/24, 'mm/dd/yyyy') date_part,
to_char(s.dt - (c.lev)/24, 'hh24') hour_part
from
(select level lev from dual connect by level <= 120) c,
(select trunc(sysdate, 'hh24')-2/24 dt from dual) s
where (s.dt - (c.lev)/24) < trunc(sysdate, 'hh24')-2/24
) d1
full outer join
(
select
count(*) cnt,
to_char(timestamp, 'MM/DD/YYYY') date_part,
to_char(timestamp, 'HH24') hour_part
from
MY_TABLE
where
timestamp >= to_timestamp('03/01/2016','MM/DD/YYYY')
group by
to_char(timestamp, 'MM/DD/YYYY'), to_char(timestamp, 'HH24')
) c1
on d1.date_part = c1.date_part
and d1.hour_part = c1.hour_part

Is it possible to map multiple select statement into columns?

I have this table:
code(integer) |number_of_data(integer)| date (Date)
I need to:
Group by day, this for tho month of june only
Select sum of number_of_data for the day for each code != 0
Select n_data for code = 0
For the first 2 points I came up with:
select sum(number_of_data) nData, TO_CHAR(date, 'DD') dayOfMonth from T1
where to_char( date, 'mm') = 6
and code <> 0
group by TO_CHAR(date, 'DD') order by TO_CHAR(date, 'DD');
it gives me this table result:
nData | dayOfMonth
which is fine, anyway I'm missing requirement 3, whose query would be the same but with the opposite condition (code=0).
Is there a way to add it to the above query so to get this result:
nData | nDataZero | dayOfMonth
?
Whit some regards to the syntax in MS Sql. This is a way i would solve this in a oracle sql-like way:)
SELECT sum(nData) nData , sum(nDataZero) nDataZero, T1
from (
select sum(number_of_data) nData, 0 nDataZero , TO_CHAR(date, 'DD') dayOfMonth
from T1
where to_char( date, 'mm') = 6 and code <> 0
group by TO_CHAR(date, 'DD')
order by TO_CHAR(date, 'DD')
UNION
select 0 nData, sum(number_of_data) nDataZero , TO_CHAR(date, 'DD') dayOfMonth
from T1
where to_char( date, 'mm') = 6 and code == 0
group by TO_CHAR(date, 'DD')
order by TO_CHAR(date, 'DD'))
group by T1;
Rgds
Assuming that there will only be one entry with CODE = 0 for each day, then you can do:
SELECT SUM( CASE CODE WHEN 0 THEN NULL ELSE number_of_data END ) AS nData,
MAX( CASE CODE WHEN 0 THEN number_of_data END ) AS nDataZero,
EXTRACT( DAY FROM "Date" ) AS dayOfMonth
FROM T1
WHERE EXTRACT( MONTH FROM "Date" ) = 6
GROUP BY EXTRACT( DAY FROM "Date" )
ORDER BY EXTRACT( DAY FROM "Date" );
If there will be more than one entry then you will need to specify how it is to be handled (i.e. change MAX to SUM if you want the total of the CODE = 0 values).
You can use CASE to separate data into 2 slots, code=0 and code<>0:
select sum(number_of_data) nData, TO_CHAR(date, 'DD') dayOfMonth, CASE WHEN code = 0 THEN 0 ELSE 1 AS x
from T1
where to_char( date, 'mm') = 6
group by TO_CHAR(date, 'DD'), x
order by TO_CHAR(date, 'DD');

Select records for a certain year Oracle

I have an oracle table that store transaction and a date column. If I need to select records for one year say 2013 I do Like this:
select *
from sales_table
where tran_date >= '01-JAN-2013'
and tran_date <= '31-DEC-2013'
But I need a Straight-forward way of selecting records for one year say pass the Parameter '2013' from an Application to get results from records in that one year without giving a range. Is this Possible?
Use the extract function to pull the year from the date:
select * from sales_table
where extract(YEAR from tran_date) = 2013
You can use to_date function
http://psoug.org/reference/date_func.html
select *
from sales_table
where tran_date >= to_date('1.1.' || 2013, 'DD.MM.YYYY') and
tran_date < to_date('1.1.' || (2013 + 1), 'DD.MM.YYYY')
solution with explicit comparisons (tran_date >= ... and tran_date < ...) is able to use index(es) on tran_date field.
Think on borders: e.g. if tran_date = '31.12.2013 18:24:45.155' than your code tran_date <='31-DEC-2013' will miss it
select last_name,hire_date
from employees
where extract(year from hire_date) = 2006;
select FIRST_NAME , to_char(hire_date, 'YYYY') YR FROM employees where to_char(hire_date, 'YYYY')= '2006'
select * from table_name where YEAR(date_column_name) = 2019