Outer join - oracle - sql

I have 2 tabels
Current Ecpense table
-----------------------
Month-----Type-------spent
Feb 12 Shopping 100
Feb 12 Food 200
Jan 12 Shopping 456
Jan 12 Food 452
Jan 12 Fuel 120
Jan 12 Rent 900
Previous Expense
-----------------------
Type------ spent
Shopping 100
Food 100
Fuel 100
Rent 100
Now i want to join these two tables, the expected result is;
Month-----Type-------spent-----Previous Spent
Feb 12 Shopping 100 100
Feb 12 Food 200 100
Feb 12 Fuel 0 100
Feb 12 Rent 0 100
Jan 12 Shopping 456 100
Jan 12 Food 452 100
Jan 12 Fuel 120 100
Jan 12 Rent 900 100
Is there a way to do this?

Try:
select m.month,
p.type,
coalesce(c.spent,0) spent,
p.spent previous_spent
from (select distinct month from current_expense) m
cross join previous_expense p
left join current_expense c
on m.month = c.month and p.type = c.type

Common Oracle syntax
Select a.*, b.spent 'previous Sent'
from current_expense a, previous_expense b
where a.type = b.type
or the same thing in standard
Select a.*, b.spent 'previous Sent'
from current_expense as a
inner join previous_expense as b on a.type = b.type

I tend to write in SQL92 (more SQLness and less Oracle-ness)
Here is my answer:
select
z.month as month ,
z.type as type ,
nvl(c.spent,0) as spent ,
nvl(p.spent,0) as previous_spent
from
(select
x.month as month ,
y.type as type
from
(select distinct month
from current_expense) x
cross join
(select distinct type
from current_expense) y) z
left outer join current_expense c
on z.month = c.month and
z.type = z.type
left outer join previous_expense p
on z.type = p.type;

Related

Select each distinct of table 1 for every value of table 2

I'm essentially working with three tables; first: a Month/Year calendar, second: Customer data (that does have a parent/child relationship with itself), and third: sales data. I would like to be able to show sales for each customer for each month in a date range, regardless of months where there may have been no sales for one or more customer.
I can get queries to show every month/year in my range, and calculate sales totals for months with sales. However, since the account numbers are joining to the calendar through a table that doesn't have values for every month, I can't figure out how to list the accounts with null sales.
The closest I've gotten so far:
with cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select c.monthno [Month]
, c.year [Year]
, (select account from customers where account=s.account) [Account]
, s.sales
from cumulative s
right join calendar c
on datefromparts(s.year, s.month, 1) = datefromparts(c.year,c.monthno,1)
order by c.year, c.monthno
resulting with;
Month Year Account sales
1 2020 1 25
1 2020 2 90
2 2020 null null
3 2020 3 45
3 2020 4 65
4 2020 null null
5 2020 1 120
5 2020 2 45
6 2020 null null
7 2020 null null
etc.
example setup here: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=b8ae260f2901693bf4cca75fb2451649
If I try to use a left or right join to bring in the customer table, collapses results to only months and accounts with sales values.
with cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select c.monthno [Month]
, c.year [Year]
, c2.account [Account]
, s.sales
from cumulative s
right join calendar c
on datefromparts(s.year, s.month, 1) = datefromparts(c.year,c.monthno,1)
right join customers c2
on s.account=c2.account
order by c.year, c.monthno
gives:
Month Year Account sales
1 2020 1 25
1 2020 2 90
3 2020 3 45
3 2020 4 65
5 2020 2 45
5 2020 1 120
1 2021 2 75
Output I'd like to see:
Month Year Account sales
1 2020 1 25
1 2020 2 90
1 2020 3 null
1 2020 4 null
2 2020 1 null
2 2020 2 null
2 2020 3 null
2 2020 4 null
How can I get every account number from customer to show up for each month in calendar?
If you combine Calendar and Customers with a cross join, you get a complete set of accounts and dates. Make that a CTE, and then use your Cumulative CTE to gather those results. Now you can select from the Calendar/Customers table with a left join to the sales data:
with BaseTable
as (
select c.MonthNo as Month
, c.Year
, cust.Account
from [CALENDAR] c
cross join Customers cust
), cumulative as (
select MONTH(s.docdate) [Month]
, YEAR(s.docdate) [Year]
, s.account [Account]
, sum(s.amount) [sales]
from sales s
group by MONTH(s.docdate), YEAR(s.docdate), s.account
)
select bt.Month
, bt.Year
, bt.Account
, c.sales
from BaseTable bt
left join cumulative c
on c.month = bt.Month
and c.Year = bt.Year
and c.Account = bt.Account

Left outer join for multiple users

I have a database for rounds of golf. I want to see how many rounds each user has per month for the last year.
To do that I created this view last_12_months with this code
SELECT date_part('month'::text, dates.date) AS month,
date_part('year'::text, dates.date) AS year
FROM ( SELECT (generate_series((now() - '1 year'::interval), now(), '1 mon'::interval))::date AS date) dates;
month
year
1
2020
2
2020
and so on to ...
12
2020
The summary view for counting the rounds is very simple also rounds_count_by_users
user_id
month
year
count_all
1
1
2020
15
1
3
2020
12
1
5
2020
10
2
4
2020
7
2
8
2020
6
2
9
2020
3
Now for what I want, querying for each user with left outer join is quite simple with
select *
from last_12_months
left outer join rounds_count_by_users
on last_12_months.month = rounds_count_by_users.month
and last_12_months.year = rounds_count_by_users.year
and user_id = 1
Which gives me all the months even when the user has no played rounds. What I would like however is to be able to do this for every user and make a materialized view for easy querying. Is there a nice and easy way of doing this? To be clear this is the final table I want.
This query doesn't work at least, that much I know.
select *
from last_12_months
left outer join rounds_count_by_users
on last_12_months.month = rounds_count_by_users.month
and last_12_months.year = rounds_count_by_users.year
where user_id = 1
user_id
month
year
count_all
1
1
2020
15
1
2
2020
null
1
3
2020
12
1
4
2020
null
1
5
2020
10
1
6
2020
null
1
7
2020
null
1
8
2020
null
1
9
2020
null
1
10
2020
null
1
11
2020
null
1
12
2020
null
2
1
2020
null
2
2
2020
null
2
3
2020
null
2
4
2020
7
2
5
2020
null
2
6
2020
null
2
7
2020
null
2
8
2020
6
2
9
2020
3
2
10
2020
null
2
11
2020
null
2
12
2020
null
I made an SQL Fiddle for this (slightly different values but same schema)
PS: I know about table aliases and data modeling and that stuff. My question is strictly about how to achieve the final result.
This query:
select * from last_12_months
where (year = year(current_date) - 1 and month >= month(current_date))
or
(year = year(current_date) and month < month(current_date))
returns the rows of last_12_months for the last 12 months (not including the current month).
This query:
select distinct user_id from rounds_count_by_users
returns all the distinct user_ids (it would be better if these ids where stored in a users table).
You must CROSS join the above queries and then LEFT join rounds_count_by_users:
select u.user_id, m.month, m.year, r.count_all
from (
select * from last_12_months
where (year = year(current_date) - 1 and month >= month(current_date))
or
(year = year(current_date) and month < month(current_date))
) m cross join (select distinct user_id from rounds_count_by_users) u
left outer join rounds_count_by_users r
on m.month = r.month and m.year = r.year and u.user_id = r.user_id
order by u.user_id, m.month
See the demo.
So I managed it to do it this way after help in the comments.
This leaves me with exactly what I wanted.
with all_rows as (
select * from view_last_12_months inner join users on true
)
select id as user_id, all_rows.year, all_rows.month, count_all
from all_rows left outer join view_rounds_count_last_year last12
on last12.month = all_rows.month
and last12.year = all_rows.year
and all_rows.id = last12.user_id

fetching records for previous month

item loc year month quantity startdate
XYZ A 2020 1 3 23-06-2020
ABC B 2020 2 218 24-06-2020
SDC C 2020 6 107 25-06-2020
QWE D 2020 7 144 25-06-2020
XYZ A 2019 12 89 23-06-2020
ABC B 2019 11 218 24-06-2020
SDC C 2020 5 117 25-06-2020
QWE D 2020 6 144 25-06-2020
if i consider the above table then my output should look like this:
item loc year month quantity startdate
XYZ A 2020 1 89 23-06-2020
ABC B 2020 2 3 24-06-2020
SDC C 2020 6 117 25-06-2020
QWE D 2020 7 144 25-06-2020
so u can see that only quantities values changed and that we are taking from previos months and rest columns values are as it is.
It looks like you want window function lag(). For your sample data, this would produce the desired results:
select *
from (
select
item,
loc,
year,
month,
lag(quantity) over(partition by item, loc order by year, month) quantity,
startdate
from mytable
) t
where quantity is not null
Consider query which works in Access database:
SELECT Table1.*, (SELECT TOP 1 quantity FROM Table1 AS Dupe
WHERE Dupe.item = Table1.item AND Dupe.loc = Table1.loc
AND DateSerial(Dupe.[Year],Dupe.[Month],1)<DateSerial(Table1.[Year],Table1.[Month],1)
ORDER BY DateSerial(Dupe.[Year],Dupe.[Month],1)) AS PrevQty
FROM Table1;
If you want to return 0 when there is a gap in month sequence, consider:
SELECT Table1.*, Nz((SELECT quantity FROM Table1 AS Dupe
WHERE Dupe.item = Table1.item AND Dupe.loc = Table1.loc
AND DateSerial(Dupe.[Year],Dupe.[Month],1)=DateAdd("m",-1,DateSerial(Table1.[Year],Table1.[Month],1))
ORDER BY DateSerial(Dupe.[Year],Dupe.[Month],1)),0) AS PrevQty
FROM Table1;
Or
SELECT Q1.*, Nz(Q2.quantity,0) AS PrevQty FROM (
SELECT Table1.*, DateSerial([Year],[Month],1) AS FD FROM Table1) AS Q1
LEFT JOIN (
SELECT Table1.*, DateAdd("m",+1,DateSerial([Year],[Month],1)) AS PD FROM Table1) AS Q2
ON Q1.FD=Q2.PD AND Q1.item=Q2.item and Q1.loc=Q2.loc;

SQL Query: Joining of Two tables Using LEFT OR RIGHT OR INNER JOIN only - Need NULL values

Clause to follow is, I have provision to use LEFT or RIGHT OR INNER JOIN and GROUP_CONTACT only.
I have two tables as follows:
Collections table:
LoanID Transacction-Date Amount
12345 05/02/17 500
12345 06/02/17 1000
OverdueClollection Table:
LoanID Transaction-Date Amount
12345 07/02/17 250
12345 09/02/17 900
If I join them with the following query suppose,
SELECT
c.LoanID,
date(c.TransactionDate),
date(d.TransactionDate),
c.Amount,
d.Amount FROM Collections c LEFT JOIN Overduecollection d ON c.LoanID = d.LoanID
I am getting the following results
c.LoanID c.TransactionDate d.TransactionDate c.Amount d.Amount
12345 05 Feb, 2018 09 Feb, 2018 500.0 900.0
12345 05 Feb, 2018 07 Feb, 2018 500.0 250.0
12345 06 Jan, 2018 09 Feb, 2018 1000.0 900.0
12345 06 Jan, 2018 07 Feb, 2018 1000.0 250.0
But I need the results as follows:
c.LoanID c.TransactionDate) d.TransactionDate c.Amount d.Amount
12345 05 Feb, 2018 500.0
12345 09 Feb, 2018 250.0
12345 06 Jan, 2018 1000.0
12345 07 Feb, 2018 900.0
is it possible by considering the above clause? if so what is the way it needs to be done? or other best way to implement this?
You're really looking for a union query, not a join.
SELECT
LoanID = c.LoanID,
C_TransactionDate = date(c.TransactionDate),
D_TransactionDate = NULL,
C_Amount = c.Amount,
D_Amount = NULL
FROM
Collections c
UNION ALL
SELECT
LoanID = d.LoanID,
C_TransactionDate = NULL,
D_TransactionDate = date(d.TransactionDate),
C_Amount = NULL,
D_Amount = d.Amount
FROM
Overduecollection d
you can't outer join using the LoanId because for all the records they are the same. instead you could do a full outer join on the transaction dates, which is what is different and this way you dont have to hard code NULL values
create table #collections
(
loanId int
,transactionDate date
,amount int
)
create table #overduecollections
(
loanId int
,transactionDate date
,amount int
)
insert into #collections
select 12345,'05/02/17', 500 union all
select 12345,'06/02/17', 1000 union all
select 123456,'07/02/17', 55
insert into #overduecollections
select 12345,'07/02/17', 250 union all
select 12345,'09/02/17', 900
select
COALESCE(c.loanId,od.loanId) as loanId,
c.transactionDate as c_transactionDate,
od.transactionDate as od_transactionDate,
c.amount as c_amount,
od.amount as od_amount
from #collections c
full join #overduecollections od ON c.transactionDate = od.transactionDate --c.loanId = od.loanId
drop table #collections
drop table #overduecollections
output:
loanId c_transactionDate od_transactionDate c_amount od_amount
12345 2017-05-02 NULL 500 NULL
12345 2017-06-02 NULL 1000 NULL
123456 2017-07-02 NULL 55 NULL
12345 NULL 2017-07-02 NULL 250
12345 NULL 2017-09-02 NULL 900
UPDATE:
the join should also include the loanId to account for tranactions with matching dates but different loanIds
select
COALESCE(c.loanId,od.loanId) as loanId,
c.transactionDate as c_transactionDate,
od.transactionDate as od_transactionDate,
c.amount as c_amount,
od.amount as od_amount
from #collections c
full join #overduecollections od ON c.transactionDate = od.transactionDate and c.loanId = od.loanId

SQL Splitting SUM Rows into Columns based on Dates

I am trying to SUM values into columns based on dates.
This is my current SQL
SELECT DISTINCT
FORMAT(dbo.DR_INVLINES.TRANSDATE, 'MMM') AS Month,
dbo.DR_ACCS.NAME,
SUM(dbo.DR_INVLINES.QUANTITY * dbo.STOCK_ITEMS.X_LITERAGE) AS SUMQTY,
FORMAT(dbo.DR_INVLINES.TRANSDATE, 'yy') AS Year
FROM
dbo.DR_INVLINES
INNER JOIN
dbo.DR_TRANS ON dbo.DR_INVLINES.HDR_SEQNO = dbo.DR_TRANS.SEQNO
INNER JOIN
dbo.STOCK_ITEMS ON dbo.DR_INVLINES.STOCKCODE = dbo.STOCK_ITEMS.STOCKCODE
INNER JOIN
dbo.DR_ACCS ON dbo.DR_INVLINES.ACCNO = dbo.DR_ACCS.ACCNO
GROUP BY
FORMAT(dbo.DR_INVLINES.TRANSDATE, 'yy'),
FORMAT(dbo.DR_INVLINES.TRANSDATE, 'MMM'),
dbo.STOCK_ITEMS.STOCKGROUP, dbo.DR_ACCS.NAME
HAVING
(dbo.STOCK_ITEMS.STOCKGROUP = 3)
This query returns this result set:
Mth Name SUMQTY Year
-----------------------------
Apr Company 1 1000 16
Apr Company 2 30790.4 16
Apr Company 3 1840 16
Apr Company 1 6502.9 17
Apr Company 2 2000 17
Apr Company 3 1000 17
What I am trying to achieve is
Mth Name 2016 2017
-------------------------------
Apr Company 1 800 200
Apr Company 2 15000 13000
Apr Company 3 600 569
Apr Company 1 5000 1500
Apr Company 2 2000 1986
Apr Company 3 1000 2543
Can someone please help with this..... I have been racking my brain for ages on this one ;-)
If I understand your question, you wanted to display YEAR as column,
SELECT FORMAT(dbo.DR_INVLINES.TRANSDATE, 'MMM') AS Month,
dbo.DR_ACCS.NAME,
SUM(CASE WHEN YEAR(dbo.DR_INVLINES.TRANSDATE) = 2016
THEN dbo.DR_INVLINES.QUANTITY * dbo.STOCK_ITEMS.X_LITERAGE
ELSE 0
END ) AS [2016],
SUM(CASE WHEN YEAR(dbo.DR_INVLINES.TRANSDATE) = 2017
THEN dbo.DR_INVLINES.QUANTITY * dbo.STOCK_ITEMS.X_LITERAGE
ELSE 0
END ) AS [2017]
FROM dbo.DR_INVLINES
INNER JOIN dbo.DR_TRANS
ON dbo.DR_INVLINES.HDR_SEQNO = dbo.DR_TRANS.SEQNO
INNER JOIN dbo.STOCK_ITEMS
ON dbo.DR_INVLINES.STOCKCODE = dbo.STOCK_ITEMS.STOCKCODE
INNER JOIN dbo.DR_ACCS
ON dbo.DR_INVLINES.ACCNO = dbo.DR_ACCS.ACCNO
WHERE dbo.STOCK_ITEMS.STOCKGROUP = 3
GROUP BY FORMAT(dbo.DR_INVLINES.TRANSDATE, 'yy'),
FORMAT(dbo.DR_INVLINES.TRANSDATE, 'MMM'),
dbo.STOCK_ITEMS.STOCKGROUP,
dbo.DR_ACCS.NAME
Have you tried group by?
SELECT FORMAT(il.TRANSDATE, 'MMM') AS Month, a.NAME,
SUM(CASE WHEN year(il.TRANSDATE) = 2016 THEN anil.QUANTITY * si.X_LITERAGE END) AS SUMQTY_2017,
SUM(CASE WHEN year(il.TRANSDATE) = 2017 THEN anil.QUANTITY * si.X_LITERAGE END) AS SUMQTY_2016
FROM dbo.DR_INVLINES il INNER JOIN
dbo.DR_TRANS t
ON il.HDR_SEQNO = t.SEQNO INNER JOIN
dbo.STOCK_ITEMS si
ON il.STOCKCODE = si.STOCKCODE INNER JOIN
dbo.DR_ACCS a
ON il.ACCNO = a.ACCNO
WHERE si.STOCKGROUP = 3
GROUP BY FORMAT(il.TRANSDATE, 'MMM'), a.NAME;
Note the changes:
Table aliases make the query easier to write and to read.
The having clause is on an unaggregated column, so it should be a where clause.
You had an extra column in the group by.
I would recommend using datepart() or datename() over format().