How can I write this query as a full join instead of a union left/right join? - sql

here's the code, showing the inputs and the required output.
Basically, I'm trying to self-join to match the results of my broker's statement with my internal records. So left set of columns is broker's list, right side is my list. If broker has a position, and I don't, NULLs on the right. If I have a position and broker doesn't, NULLs on the left.
The left join + right join + union works exactly as I want. Seems like there should be some voodoo to allow a full join to get that without two selects, but I can't figure it out.
drop table MatchPositions
go
create table MatchPositions
(
mt_source varchar (10),
mt_symbol varchar (10),
mt_qty float,
mt_price float
)
go
insert into MatchPositions values ('BROKER', 'IBM', 100, 50.25)
insert into MatchPositions values ('BROKER', 'MSFT', 75, 30)
insert into MatchPositions values ('BROKER', 'GOOG', 25, 500)
insert into MatchPositions values ('BROKER', 'SPY', 200, 113)
insert into MatchPositions values ('MODEL', 'MSFT', 75, 30)
insert into MatchPositions values ('MODEL', 'GOOG', 25, 500)
insert into MatchPositions values ('MODEL', 'GLD', 300, 150)
go
select * from MatchPositions b
left join MatchPositions m on b.mt_symbol = m.mt_symbol and m.mt_source = 'MODEL'
where b.mt_source = 'BROKER'
union
select * from MatchPositions b
right join MatchPositions m on b.mt_symbol = m.mt_symbol and b.mt_source = 'BROKER'
where m.mt_source = 'MODEL'
and here's the expected output:
mt_source mt_symbol mt_qty mt_price mt_source mt_symbol mt_qty mt_price
---------- ---------- ---------------------- ---------------------- ---------- ---------- ---------------------- ----------------------
NULL NULL NULL NULL MODEL GLD 300 150
BROKER GOOG 25 500 MODEL GOOG 25 500
BROKER IBM 100 50.25 NULL NULL NULL NULL
BROKER MSFT 75 30 MODEL MSFT 75 30
BROKER SPY 200 113 NULL NULL NULL NULL

;WITH T1 AS
(
SELECT *
FROM MatchPositions
WHERE mt_source = 'BROKER'
), T2 AS
(
SELECT *
FROM MatchPositions
WHERE mt_source = 'MODEL'
)
SELECT *
FROM T1 FULL JOIN T2 ON T1.mt_symbol = T2.mt_symbol

Possibly using an isnull function:
SELECT *
FROM MatchPositions b
FULL JOIN MatchPositions m on b.mt_symbol = m.mt_symbol
and b.mt_source != m.mt_source
WHERE isnull(b.mt_source, 'BROKER') = 'BROKER'
and isnull(m.mt_source, 'MODEL') = 'MODEL'

SELECT *
FROM MatchPositions b
FULL JOIN MatchPositions m ON b.mt_symbol = m.mt_symbol
AND b.mt_source = 'BROKER'
AND m.mt_source = 'MODEL'
This filters the table into the 'BROKER' and 'MODEL' parts before outer joining them.

Try this:
select *
from MatchPositions broker
full join MatchPositions model on model.mt_symbol = broker.mt_symbol
and model.mt_source <> broker.mt_source
where ( broker.mt_source = 'BROKER' or broker.MT_SOURCE is null )
and ( model.mt_source = 'MODEL' or model.MT_SOURCE is null )
From the first logical source table, you want either broker rows, or missing rows.
From the second logical source table, you want either model rows or missing rows.

If your RDBMS supports FULL JOIN (also known as FULL OUTER JOIN):
SELECT *
FROM (SELECT * FROM MatchPositions WHERE mt_source = 'BROKER') b
FULL
JOIN (SELECT * FROM MatchPositions WHERE mt_source = 'MODEL' ) m
ON b.mt_symbol = m.mt_symbol
This solution is basically same as Martin's, just uses a different syntax, which may be helpful in case your RDBMS doesn't support CTEs.

Related

Transpose data in SQL Server Select

I am wondering if there is a better way to write this query. It achieves the target result but my colleague would prefer it be written without the subselects into temp tables t1-t3. The main "challenge" here is transposing the data from dbo.ReviewsData into a single row along with the rest of the data joined from dbo.Prodcucts and dbo.Reviews.
CREATE TABLE dbo.Products (
idProduct int identity,
product_title varchar(100)
PRIMARY KEY (idProduct)
);
INSERT INTO dbo.Products VALUES
(1001, 'poptart'),
(1002, 'coat hanger'),
(1003, 'sunglasses');
CREATE TABLE dbo.Reviews (
Rev_IDReview int identity,
Rev_IDProduct int
PRIMARY KEY (Rev_IDReview)
FOREIGN KEY (Rev_IDProduct) REFERENCES dbo.Products(idProduct)
);
INSERT INTO dbo.Reviews VALUES
(456, 1001),
(457, 1002),
(458, 1003);
CREATE TABLE dbo.ReviewFields (
RF_IDField int identity,
RF_FieldName varchar(32),
PRIMARY KEY (RF_IDField)
);
INSERT INTO dbo.ReviewFields VALUES
(1, 'Customer Name'),
(2, 'Review Title'),
(3, 'Review Message');
CREATE TABLE dbo.ReviewData (
RD_idData int identity,
RD_IDReview int,
RD_IDField int,
RD_FieldContent varchar(100)
PRIMARY KEY (RD_idData)
FOREIGN KEY (RD_IDReview) REFERENCES dbo.Reviews(Rev_IDReview)
);
INSERT INTO dbo.ReviewData VALUES
(79, 456, 1, 'Daniel'),
(80, 456, 2, 'Love this item!'),
(81, 456, 3, 'Works well...blah blah'),
(82, 457, 1, 'Joe!'),
(84, 457, 2, 'Pure Trash'),
(85, 457, 3, 'It was literally a used banana peel'),
(86, 458, 1, 'Karen'),
(87, 458, 2, 'Could be better'),
(88, 458, 3, 'I can always find something wrong');
SELECT P.product_title as "item", t1.ReviewedBy, t2.ReviewTitle, t3.ReviewContent
FROM dbo.Reviews R
INNER JOIN dbo.Products P
ON P.idProduct = R.Rev_IDProduct
INNER JOIN (
SELECT D.RD_FieldContent AS "ReviewedBy", D.RD_IDReview
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 1
) t1
ON t1.RD_IDReview = R.Rev_IDReview
INNER JOIN (
SELECT D.RD_FieldContent AS "ReviewTitle", D.RD_IDReview
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 2
) t2
ON t2.RD_IDReview = R.Rev_IDReview
INNER JOIN (
SELECT D.RD_FieldContent AS "ReviewContent", D.RD_IDReview
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 3
) t3
ON t3.RD_IDReview = R.Rev_IDReview
EDIT: I have updated this post with the create statements for the tables as opposed to an image of the data (shame on me) and a more specific description of what exactly needed to be improved. Thanks to all for the comments and patience.
As others have said in comments, there is nothing objectively wrong with the query. However, you could argue that it's verbose and hard to read.
One way to shorten it is to replace INNER JOIN with CROSS APPLY:
INNER JOIN (
SELECT D.RD_FieldContent AS 'ReviewedBy', D.RD_IDReview
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 1
) t1
ON t1.RD_IDReview = R.Rev_IDReview
APPLY lets you refer to values from the outer query, like in a subquery:
CROSS APPLY (
SELECT D.RD_FieldContent AS 'ReviewedBy'
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 1 AND D.RD_IDReview = R.Rev_IDReview
) t1
I think of APPLY like a subquery that brings in new columns. It's like a cross between a subquery and a join. Benefits:
The query can be shorter, because you don't have to repeat the ID column twice.
You don't have to expose columns that you don't need.
Disadvantages:
If the query in the APPLY references outer values, then you can't extract it and run it all by itself without modifications.
APPLY is specific to Sql Server and it's not that widely-used.
Another thing to consider is using subqueries instead of joins for values that you only need in one place. Benefits:
The queries can be made shorter, because you don't have to repeat the ID column twice, and you don't have to give the output columns unique aliases.
You only have to look in one place to see the whole subquery.
Subqueries can only return 1 row, so you can't accidentally create extra rows, if only 1 row is desired.
SELECT
P.product_title as 'item',
(SELECT D.RD_FieldContent
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 1 AND
D.RD_IDReview = R.Rev_IDReview) as ReviewedBy,
(SELECT D.RD_FieldContent
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 2 AND
D.RD_IDReview = R.Rev_IDReview) as ReviewTitle,
(SELECT D.RD_FieldContent
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 3 AND
D.RD_IDReview = R.Rev_IDReview) as ReviewContent
FROM dbo.Reviews R
INNER JOIN dbo.Products P ON P.idProduct = R.Rev_IDProduct
Edit:
It just occurred to me that you have made the joins themselves unnecessarily verbose (#Dale K actually already pointed this out in the comments):
INNER JOIN (
SELECT D.RD_FieldContent AS 'ReviewedBy', D.RD_IDReview
FROM dbo.ReviewsData D
WHERE D.RD_IDField = 1
) t1
ON t1.RD_IDReview = R.Rev_IDReview
Shorter:
SELECT RevBy.RD_FieldContent AS 'ReviewedBy'
...
INNER JOIN dbo.ReviewsData RevBy
ON RevBy.RD_IDReview = R.Rev_IDReview AND
RevBy.RD_IDField = 1
The originally submitted query is undoubtedly and unnecessarily verbose. Having digested various feedback from the community it has been revised to the following, working splendidly. In retrospect I feel very silly for having done this with subselects originally. I am clearly intermediate at best when it comes to SQL - I had not realized an "AND" clause could be included in the "ON" clause in a "JOIN" statement. Not sure why I would have made such a poor assumption.
SELECT
P.product_title as 'item',
D1.RD_FieldContent as 'ReviewedBy',
D2.RD_FieldContent as 'ReviewTitle',
D3.RD_FieldContent as 'ReviewContent'
FROM dbo.Reviews R
INNER JOIN dbo.Products P
ON P.idProduct = R.Rev_IDProduct
INNER JOIN dbo.ReviewsData D1
ON D1.RD_IDReview = R.Rev_IDReview AND D1.RD_IDField = 1
INNER JOIN dbo.ReviewsData D2
ON D2.RD_IDReview = R.Rev_IDReview AND D2.RD_IDField = 2
INNER JOIN dbo.ReviewsData D3
ON D3.RD_IDReview = R.Rev_IDReview AND D3.RD_IDField = 3

SQL Server query for merging 2 rows in 1

There is a SQL Server table (see the screenshot below) that I cannot change:
Products have identifiers and process parameters. There are 2 processes, A and B. Every process stores data in an own row.
I would like to get a table result without useless NULL values. One product on one row, no more. Desired cells are highlighted. See the 2nd screenshot for the desired output:
select a.id,
isnull(b.state, a.state) as state,
isnull(b.process, a.process) as process,
isnull(b.length, a.length) as length,
isnull(b.force, a.force) as force,
isnull(b.angle, a.angle) as angle
from table as a
left join table as b
on a.id = b.id
and b.process = 'B'
where a.process = 'A'
DECLARE #T AS TABLE (id int, state varchar(10), process varchar(10), length int, angle int
primary key (id, process));
insert into #t (id, state, process, length, angle) values
(111, 'OK', 'A', 77, null)
,(111, 'OK', 'B', null, 30)
,(159, 'NOK', 'A', 89, null)
,(147, 'OK', 'A', 78, null)
,(147, 'NOK', 'B', null, 36);
select ta.id, --ta.*, tb.*
isnull(tb.state, ta.state) as state,
isnull(tb.process, ta.process) as process,
isnull(tb.length, ta.length) as length,
isnull(tb.angle, ta.angle) as angle
from #t ta
left join #t tb
on ta.id = tb.id
and tb.process = 'B'
where ta.process = 'A'
order by ta.id
Self join? (edited)
so,
select
a.id,
a.process,
isnull(a.length, b.length) as length,
isnull(a.force, b.force) as force,
isnull(a.angle, b.angle) as angle
from
(select * from tmpdel where process = 'A') as a left join
(select * from tmpdel where process = 'B') as b on
a.id = b.id
I've given this a quick test and I think it looks right.

Running slow with intersect

Trying to optimize a query it is updaing the records in table A based on the INTERSECT on two data sets.
UPDATE #TableA
SET IsFlag = CASE WHEN ISNULL(RJobFlag, 0) > 0 THEN 0 ELSE 1 END
FROM #TableA AS ABC
OUTER APPLY (
SELECT 1 RJobFlag
WHERE EXISTS (
SELECT ABC.COLUMN1,ABC.COLUMN2,ABC.COLUMN3,ABC.COLUMN4,ABC.COLUMN5,ABC.COLUMN6,ABC.COLUMN7,ABC.COLUMN8,ABC.COLUMN8,ABC.COLUMN9,ABC.COLUMN10,StudentID,SubjectID
INTERSECT
SELECT XYZ.COLUMN1,XYZ.COLUMN2,XYZ.COLUMN3,XYZ.COLUMN4,XYZ.COLUMN5,XYZ.COLUMN6,XYZ.COLUMN7,XYZ.COLUMN8,XYZ.COLUMN8,XYZ.COLUMN9,XYZ.COLUMN10,StudentID,SubjectID
FROM #TableB AS XYZ
WHERE XYZ.COLUMN1 = (SELECT DISTINCT ID FROM #TableC MNOP WHERE MNOP.StudentID = ABC.StudentID)
AND StudentID = ABC.StudentID
AND SubjectID = ABC.SubjectID )
) Subquery
WHERE ABC.COLUMN1= '2'
Appretiated if you have some ideas to better optimize it.
Thanks
This might be worse never know.
UPDATE tA
SET IsFlag = COALESCE(RJobFlag, 0)
FROM #TableA tA
LEFT JOIN ( SELECT 1 RJobFlag, *
FROM #TableB tB
WHERE EXISTS ( SELECT *
FROM #TableC tC
WHERE tC.ID = tB.COLUMN1 AND tC.StudentID = tB.StudentID)
) tB
ON tA.StudentID = tB.StudentID
AND tA.SubjectID = tB.SubjectID
AND tA.COLUMN1 = tB.COLUMN1
AND tA.COLUMN2 = tB.COLUMN2
AND tA.COLUMN3 = tB.COLUMN3
AND tA.COLUMN4 = tB.COLUMN4
AND tA.COLUMN5 = tB.COLUMN5
AND tA.COLUMN6 = tB.COLUMN6
AND tA.COLUMN7 = tB.COLUMN7
AND tA.COLUMN8 = tB.COLUMN8
AND tA.COLUMN9 = tB.COLUMN9
AND tA.COLUMN10 = tB.COLUMN10
If #TableA has a bunch of records and you're using outer apply or outer join every time you update it will be slow since it has to update every single record. maybe there's a way to only update the records that have changed?

Why are some rows missing from the result set when two tables are inner joined

my query is not displaying entire zero row.
i mean.. in my output there were some rows, whose output is zero among all the columns. that particular rows were not displayed in my output.
i want even zero rows to in my output
i'm getting the output like this
abc 1 2 3
def 4 5 6
xyz 2 5 4
mng 2 5 6
but the actual output i needed is
abc 1 2 3
def 4 5 6
ghf 0 0 0
xyz 2 5 4
mng 2 5 6
jkl 0 0 0
Row containing zeros is being eliminated.
i'm using joins between two tables.. the first column i'm using as group by phase.
the rest of the columns are result of sum of..
the reason for zeros in the output is, i don't have data for those names in the secondary table.. there exists no data..but i want to display as zeros instead of missing out the entire column.
Here is the query which i used.....
SELECT
[ASACCT].ACCT_MO_I as 'Types'
,sum(CASE when [TECH_V].[CLOS_T]='N4' THEN 1 ELSE 0 END) AS 'N4'
,SUM(CASE when [TECH_V].[CLOS_T]='N3' THEN 1 ELSE 0 END) AS 'N3'
,SUM(CASE when [TECH_V].[CLOS_T]='N2' THEN 1 ELSE 0 END) AS 'N2'
FROM [supt_oper_anls_dw].[dbo].[TECH_V] as [TECH_V]
LEFT OUTER JOIN [supt_oper_anls_dw].[dbo].ACCT_DATE_DIM AS [ASACCT]
ON CONVERT(varchar(10),[ASACCT].GREG_D, 101) = CONVERT(varchar(10), [TECH_V].[OPEN_TS], 101)
WHERE
[TECH_INCDT_V].[KGRP_I] ='73fd71ecf84f5080217683869fd819c3'
and ((
[ASACCT].ACCT_MO_I >(datepart(MONTH,getdate()))-1-6
and [ASACCT].ACCT_MO_I <=(datepart (MONTH,getdate()))-1
and [ASACCT].ACCT_YR_I = (datepart(year,getdate()))
)
or (
[ASACCT].ACCT_MO_I>(datepart(MONTH,getdate()))-6-1+12
and [ASACCT].ACCT_YR_I = (datepart(year,getdate()))-1
))
and [TECH_V].Notes like '%MFTFD%'
and [TECH_V].notes like '%DEV%'
group by
[ASACCT].ACCT_MO_I,[ASACCT].ACCT_YR_I
Try this
use left or right join
Try a LEFT JOIN in your query. If you do A LEFT JOIN B then everything from A will be in the results. Try this
create table #a_table
( a int)
insert into #a_table
values
( 100),
( 200),
( 300),
( 400)
create table #b_table
( b int)
insert into #b_table
values
( 100),
( 200),
( 300),
( 600),
( 700),
( 900)
--inner join
select *
from #a_table a
inner join #b_table b
on a.a = b.b
--left outer join
select *
from #a_table a
left outer join #b_table b
on a.a = b.b
Then try to comment out various parts of your query to see if other sections of the script are limiting what is being returned.
I suspect if you comment out most of the WHERE clause then the rows you want will be in the result set.
In my simplified example I can add in the following WHERE clause....
select *
from #a_table a
left outer join #b_table b
on a.a = b.b
where b.b >10
...the row has now gone from the results. So now you'd need to start applying either the ISNULL or COALESCE function...
select *
from #a_table a
left outer join #b_table b
on a.a = b.b
where ISNULL(b.b,11) >10

JOIN Issue : Correct the SQL Statement to solve : ORA-01799: a column may not be outer-joined to a subquery

As you see below; how can I implement fx.ftf_validitystartdate= ... this lines value since oracle does not allow me to do it like this below
.
select * from acc_accounts acc
join kp_paramcore p on
acc.account_no = p.accountnum
acc.suffix = p.suffixc
LEFT JOIN ftf_rates fx
ON p.maturestart = fx.ftf_vadealtsinir
AND p.maturefinish = fx.ftf_vadeustsinir
AND fx.statusrec = 'A'
AND fx.currencycode = acc.currencsw_kod
AND fx.status= 'A'
and fx.ftf_validitystartdate= (SELECT MAX(ff.ftf_validitystartdate)
FROM ftf_rates ff
WHERE ff.status = 'A'
AND ff.statusrec = 'A'
AND v_CurrentDate BETWEEN ff.systemstartdate AND ff.systemfinishdate AND ff.currencycode = acc.currencsw_kod
)
It should work if you switch this to a where clause:
select *
from acc_accounts acc join
kp_paramcore p
on acc.account_no = p.accountnum and
acc.suffix = p.suffixc LEFT JOIN
ftf_rates fx
ON p.maturestart = fx.ftf_vadealtsinir and
p.maturefinish = fx.ftf_vadeustsinir and
fx.statusrec = 'A' and
fx.currencycode = acc.currencsw_kod and
fx.status= 'A'
where fx.ftf_validitystartdate= (SELECT MAX(ff.ftf_validitystartdate)
FROM ftf_rates ff
WHERE ff.status = 'A' and
ff.statusrec = 'A'
p.v_CurrentDate BETWEEN ff.systemstartdate AND ff.systemfinishdate AND ff.currencycode = acc.currencsw_kod
)
However, you lose the 'left outer join' characteristics, so you would also want to add: or fx.ftf_validitystartdate is null. I guess that v_CurrentDate comes from "p". It is always a good idea to use table aliases before column names.
However, I question whether the subquery is really needed. It is only needed when there is more than one record that meets the conditions inside the subquery. Otherwise, I think you can just change the on clause to be:
ON p.maturestart = fx.ftf_vadealtsinir and
p.maturefinish = fx.ftf_vadeustsinir and
fx.statusrec = 'A' and
fx.currencycode = acc.currencsw_kod and
fx.status= 'A'and
p.v_CurrentDate BETWEEN fx.systemstartdate AND fx.systemfinishdate
I publish the workaround with CTE and tested only in Oracle 11g.
To make test I create this schema:
create table t_a ( a int );
create table t_b ( a int);
create table t_c ( a int);
insert into t_a values (1);
insert into t_a values (2);
insert into t_a values (3);
insert into t_b values (1);
insert into t_b values (2);
insert into t_b values (3);
insert into t_c values (1);
insert into t_c values (2);
insert into t_c values (3);
At this time I force error with this query:
select *
from t_a
left outer join t_b
on t_a.a = t_b.a and
t_b.a = ( select max( a )
from t_c);
And now I rewrite query with CTE:
with cte (a ) as (
select a
from t_b
where t_b.a = ( select min( a )
from t_c)
)
select *
from t_a
left outer join cte
on t_a.a = cte.a;
This second query returns right results.
I rewrite your query with CTE:
with CTE as (
select * from ftf_rates
where ftf_validitystartdate= (SELECT MAX(ff.ftf_validitystartdate)
FROM ftf_rates ff
WHERE ff.status = 'A'
AND ff.statusrec = 'A'
AND v_CurrentDate BETWEEN ff.systemstartdate
AND ff.systemfinishdate
AND ff.currencycode = acc.currencsw_kod )
)
select * from acc_accounts acc
join kp_paramcore p on
acc.account_no = p.accountnum
acc.suffix = p.suffixc
LEFT JOIN CTE fx
ON p.maturestart = fx.ftf_vadealtsinir
AND p.maturefinish = fx.ftf_vadeustsinir
AND fx.statusrec = 'A'
AND fx.currencycode = acc.currencsw_kod
AND fx.status= 'A'
Notice, only tested in Oracle 11g. See #a_horse_with_no_name coment:
#danihp: CTEs were available long before Oracle 11g (I think they were
introducted in 9.1 maybe even earlier - but they are definitely
available in 10.x). 11.2 introduced recursive CTEs which is not needed
in this case. –