This example is simplified. I have the following design:
http://img835.imageshack.us/i/designyi.jpg/
I have inserted test data like this:
INSERT INTO Period VALUES ('Survey for 2011', 1)
INSERT INTO EvalQuestion VALUES('How do...')
INSERT INTO EvalQuestion VALUES('How many...')
INSERT INTO EvalQuestion VALUES('Which is....')
INSERT INTO EvalQuestion_Period VALUES (1, 1)
INSERT INTO EvalQuestion_Period VALUES (1, 2)
INSERT INTO EvalQuestion_Period VALUES (1, 3)
INSERT INTO Employee VALUES ('Peter', 'Smith')
INSERT INTO Employee VALUES ('Britney', 'Spears')
INSERT INTO EvalAnswer VALUES(1,'Fine',1)
INSERT INTO EvalAnswer VALUES(2,'45',1)
INSERT INTO EvalAnswer VALUES(3,'I don´t know',1)
INSERT INTO EvalAnswer VALUES(1,'Fine again',2)
INSERT INTO EvalAnswer VALUES(2,'45 again',2)
INSERT INTO EvalAnswer VALUES(3,'I don´t know again',2)
I run the following query to get question and answer for Peter:
Select Name, Answer
from EvalQuestion eq
LEFT JOIN EvalQuestion_Period eqp ON eq.Id = eqp.FK_EvalQuestion
LEFT JOIN EvalAnswer ea ON ea.FK_EvalQuestion_Period = eqp.Id
where ea.FK_Employee = 1
Result set:
Name Answer
-----------------------
How do... Fine
How many... 45
Which is.... I don´t know
This looks good. If I delete one of Peters Answers like this:
Delete from EvalAnswer where ID= 1
And run the same query I only get two rows, like this
Name Answer
-----------------------
How many... 45
Which is.... I don´t know
I need my question in the result set even if it is unanswered, like this:
Name Answer
-----------------------
How do.... NULL
How many... 45
Which is.... I don´t know
Any tips? Thanks
Your "left join" is actually an LEFT OUTER JOIN (despite other answers): the join type is implied by LEFT or RIGHT with OUTER being optional
When you use WHERE ea.FK_Employee = 1 then you are changing this to an INNER JOIN because you are not allowing for missing rows. You need to filter first (that is restrict rows on EvalAnswer before the join). This is because WHERE is processed after JOIN..ON logically.
Try this with a derived, filtered table:
Select Name, Answer
from EvalQuestion eq
LEFT JOIN
EvalQuestion_Period eqp ON eq.Id = eqp.FK_EvalQuestion
LEFT JOIN
(SELECT * FROM EvalAnswer
where FK_Employee = 1
) ea ON ea.FK_EvalQuestion_Period = eqp.Id
Or filter in the ON condition:
Select Name, Answer
from EvalQuestion eq
LEFT JOIN
EvalQuestion_Period eqp ON eq.Id = eqp.FK_EvalQuestion
LEFT JOIN
EvalAnswer ea ON ea.FK_EvalQuestion_Period = eqp.Id AND ea.FK_Employee = 1
Any time you want to return 1 or more rows from the original table, you can use an outer join. So your query would be:
Select Name, Answer
from EvalQuestion eq
LEFT JOIN EvalQuestion_Period eqp ON eq.Id = eqp.FK_EvalQuestion
LEFT OUTER JOIN EvalAnswer ea ON ea.FK_EvalQuestion_Period = eqp.Id
where ea.FK_Employee = 1
This will return nulls for the values from EvalAnswer when no corresponding records exist, but otherwise will function exactly as the LEFT JOIN.
Related
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
I have two different tables in my SQL Server (Dorig and Dotes) that are connected by column id_dotes present in both.
I need a help for a query that returns a result of Dorig with column NumeroDocRif, present only in table Dotes, where this last one must to be extracted when this condition is true
dorig.id_dorig = dorig.id_dorig_evade
I try to create this query but it doesn't work completely well:
SELECT
dorig.Id_Dorig, dotes.NumerodocRif
FROM
dorig
LEFT JOIN
dotes ON dotes.id_Dotes = dorig.id_Dotes
WHERE
id_dorig IN (SELECT DISTINCT id_dorig_evade
FROM dorig
WHERE id_dotes = dorig.id_dotes)
Could you please help me?
Many thanks in advance
create table dorig (id_dorig int, id_dotes int, id_dorig_evade int);
create table dotes(id_dotes int, numerodocrif varchar(50));
insert into dorig values(16934,3116,4894);
insert into dorig values(4894,1090,null);
insert into dotes values(1090,'7000079957');
SELECT d.Id_Dorig,d.id_dotes,dt.NumerodocRif
FROM dorig d inner join dorig dg
on d.id_dorig_evade =dg.id_dorig
inner JOIN dotes dt ON dt.id_Dotes = dg.id_Dotes
Id_Dorig
id_dotes
NumerodocRif
16934
3116
7000079957
db<>fiddle here
It seems like all you should need to do is replace the statement you already wrote out in the text of your question into your second WHERE clause:
SELECT dorig.Id_Dorig,dotes.NumerodocRif
FROM dorig
LEFT JOIN dotes ON dotes.id_Dotes = dorig.id_Dotes
WHERE id_dorig IN
(SELECT DISTINCT id_dorig_evade FROM dorig
WHERE dorig.id_dorig = dorig.id_dorig_evade)
I am using SQL Server 2008 and with the help of other threads I have been able to write the following:
insert into fml0grant (auto_key, roleid)
select fml0.auto_key, 20
from fml0
left join fml0grant on fml0.auto_key = fml0grant.auto_key
where fml0.dwgname <> ''
and fml0grant.roleid is null
However what I need to do is insert multiple rows for each record found in the where clause. So when the where clause gets a result I need to insert:
fml0.auto_key, 20
fml0.auto_key, 508
fml0.auto_key, 10
Is there any way to combine all three inserts into one statement as after the first in my query the NULL in the WHERE clause is no longer true.
You can use CROSS JOIN as the below.
insert into fml0grant (auto_key, roleid)
select fml0.auto_key, V.Id
from fml0
left join fml0grant on fml0.auto_key = fml0grant.auto_key
CROSS JOIN (VALUES (20),(508),(10)) V (Id)
where fml0.dwgname <> ''
and fml0grant.roleid is null
I have two databases running on MSSQL 2005, SOURCE and DESTINATION, which have the same structure and tables in them.
I'm trying to update data from s to d.
In this example, I'm trying to copy data from s to d using a join and only bringing across entries which aren't already in d.
I'm then trying to update the same records just inserted with vales thus:
INSERT DESTINATION.ITEM_REPLENISH_VENDOR ([ITEM_CODE],[VEND_CODE],[PRIMARY_VENDOR],[PURCHASE_MEASURE],[STD_COST],[LAST_COST],[EOQ],[VENDOR_PART_NO],[LEAD_TIME],[COST])
SELECT s.[ITEM_CODE],s.[VEND_CODE],s.[PRIMARY_VENDOR],s.[PURCHASE_MEASURE],s.[STD_COST],s.[LAST_COST],s.[EOQ],s.[VENDOR_PART_NO],s.[LEAD_TIME], s.[COST] FROM SOURCE.dbo.ITEM_REPLENISH_VENDOR s
LEFT OUTER JOIN DESTINATION.dbo.ITEM_REPLENISH_VENDOR d ON (d.ITEM_CODE = s.ITEM_CODE)
WHERE d.ITEM_CODE IS NULL
UPDATE DESTINATION.dbo.ITEM_REPLENISH_VENDOR
SET VEND_CODE='100004', PRIMARY_VENDOR='T',STD_COST='0',LAST_COST='0',COST='0'
WHERE
My issue is once I reach the second WHERE I don't know how to refer to the data I've just updated. This script is going to run either every day at a set time and I don't want to overwrite that whole column with those values, just the entries that have been inserted on this execution.
It looks like you want the output clause This will let you stash away the inserted values.
-- item_code needs to have the same type as the source table
declare #inserted table (item_code int not null primary key);
insert destination.item_replenish_vendor (
[item_code], [vend_code], [primary_vendor],
[purchase_measure], [std_cost], [last_cost],
[eoq], [vendor_part_no], [lead_time],[cost]
) -- save inserted values
output
inserted.item_code into #inserted
select
s.[item_code], s.[vend_code], s.[primary_vendor],
s.[purchase_measure], s.[std_cost], s.[last_cost],
s.[eoq], s.[vendor_part_no], s.[lead_time], s.[cost]
from
source.dbo.item_replenish_vendor s
left outer join
destination.dbo.item_replenish_vendor d
on d.item_code = s.item_code
where
d.item_code is null;
update
d
set
vend_code = '100004',
primary_vendor = 'T',
std_cost = '0',
last_cost = '0,
cost = '0'
from
destination.dbo.item_replenish_vendor d
inner join
#inserted i
on d.item_code = i.item_code;
In this case, you could just put constant values in the insert statement, instead of doing things in two steps...
In the example you have:
UPDATE DESTINATION.dbo.ITEM_REPLENISH_VENDOR
SET VEND_CODE='100004', PRIMARY_VENDOR='T',STD_COST='0',LAST_COST='0',COST='0'
If your VEND_CODE, PRIMARY_VENDOR, STD_COST, LAST_COST, COST are always going to be a static value, you could just put them into the first query.
INSERT DESTINATION.ITEM_REPLENISH_VENDOR ([ITEM_CODE],[VEND_CODE],[PRIMARY_VENDOR],[PURCHASE_MEASURE],[STD_COST],[LAST_COST],[EOQ],[VENDOR_PART_NO],[LEAD_TIME],[COST])
SELECT s.[ITEM_CODE],'100004','T',s.[PURCHASE_MEASURE],'0','0',s.[EOQ],s.[VENDOR_PART_NO],s.[LEAD_TIME], '0'
FROM SOURCE.dbo.ITEM_REPLENISH_VENDOR s
LEFT OUTER JOIN DESTINATION.dbo.ITEM_REPLENISH_VENDOR d ON (d.ITEM_CODE = s.ITEM_CODE)
WHERE d.ITEM_CODE IS NULL
but if they do need to be calculated after insert, then I agree with Laurence's approach.
select a.cust_xref_id, a.est_hour, a.phone_nbr as number, a.credit_calls, a.credit_rpcs, b.sdp_calls
from #temp0 a
full outer join #temp2 b
on a.cust_xref_id = b.sdp_cust_xref_id
and a.est_hour = b.sdp_hour
and a.phone_nbr = b.sdp_phone
Is there a way to get the data from table b with regard to sdp_cust_xref_id, sdp_hour, and sdp_phone when the data does not exist in both tables via the join? If b.sdp_calls does exist, the column values are null.
I read it a few more times and I think I know what you want. Try this. It will give you the values from table b if they are NULL in a:
select COALESCE(a.cust_xref_id, b.sdp_cust_xref_id) as cust_xref_id,
COALESCE(a.est_hour, b.spd_hour) as est_hour,
COALESCE(a.phone_nbr, b.spd_phone) as number,
a.credit_calls,
a.credit_rpcs,
b.sdp_calls
from #temp0 a
full outer join #temp2 b
on a.cust_xref_id = b.sdp_cust_xref_id
and a.est_hour = b.sdp_hour
and a.phone_nbr = b.sdp_phone