I have this question to solve:
How can you output a list of all members, including the individual who
recommended them (if any)? Ensure that results are ordered by
(surname, firstname).
which I thought to resolve writing this code
SELECT mems.firstname as memfname, mems.surname as memsname, recs.firstname as recfname, recs.surname as recsname
from
cd.members mems
left outer join cd.members recs
on mems.memid = recs.recommendedby
order by mems.surname, mems.firstname
but the correct answer was:
select mems.firstname as memfname, mems.surname as memsname, recs.firstname as recfname, recs.surname as recsname
from
cd.members mems
left outer join cd.members recs
on recs.memid = mems.recommendedby
order by memsname, memfname
I'm a little confused to understand how the order of the same table in this join on the "on", influence the results, also isn't an easy topic to disclosure via google, could someone help me to understand it? Thank you!
It is not a matter of table order, it is a matter of the meaning of your fields. If you have 2 members 1 and 2. You seem to have exported that 1 recommends 2 when you were expected to export that 2 is recommended by 1.
Take this fake example illustrating the above; with only 2 records, it should make the point clearer.
WITH members(memid, recommendedby, description) AS (
VALUES (1, NULL, 'Recommending'), (2, 1, 'Recommended')
)
SELECT * FROM members
And from there, this is an equivalent of what you have done:
WITH members(memid, recommendedby, description) AS (
VALUES (1, NULL, 'Recommending'), (2, 1, 'Recommended')
)
SELECT mems.description AS "member who is recommended",
recs.description AS "member who is recommending"
FROM members mems
LEFT OUTER JOIN members recs ON mems.memid = recs.recommendedby
But members from mems are supposed to be recommended by members from recs, not the other way around.
You were supposed to do:
WITH members(memid, recommendedby, description) AS (
VALUES (1, NULL, 'Recommending'), (2, 1, 'Recommended')
)
SELECT mems.description AS "member who is recommended",
recs.description AS "member who is recommending"
FROM members mems
LEFT OUTER JOIN members recs ON recs.memid = mems.recommendedby
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 3 tables:
Table_Cars
-id_car
-description
Table_CarDocuments
-id_car
-id_documentType
-path_to_document
Table_DocumentTypes
-id_documentType
-description
I want to select all cars that do NOT have documents on the table Table_CarDocuments with 4 specific id_documentType.
Something like this:
Car1 | TaxDocument
Car1 | KeyDocument
Car2 | TaxDocument
With this i know that i'm missing 2 documents of car1 and 1 document of car2.
You are looking for missing car documents. So cross join cars and document types and look for combinations NOT IN the car douments table.
select c.description as car, dt.description as doctype
from table_cars c
cross join table_documenttypes dt
where (c.id_car, dt.id_documenttype) not in
(
select cd.id_car, cd.id_documenttype
from table_cardocuments cd
);
UPDATE: It shows that SQL Server's IN clause is very limited and not capable of dealing with value lists. But a NOT IN clause can easily be replaced by NOT EXISTS:
select c.description as car, dt.description as doctype
from table_cars c
cross join table_documenttypes dt
where not exists
(
select *
from table_cardocuments cd
where cd.id_car = c.id_car
and cd.id_documenttype = dt.id_documenttype
);
UPDATE: As you are only interested in particular id_documenttype (for which you'd have to add and dt.id_documenttype in (1, 2, 3, 4) to the query), you can generate records for them on-the-fly instead of having to read the table_documenttypes.
In order to do that replace
cross join table_documenttypes dt
with
cross join (values (1), (2), (3), (4)) as dt(id_documentType)
You can use the query below to get the result:
SELECT
c.description,
dt.description
FROM
Table_Cars c
JOIN Table_CarDocuments cd ON c.id_car = cd.id_car
JOIN Table_DocumentTypes dt ON cd.id_documentType = dt.id_documentType
WHERE
dt.id_documentType NOT IN (1, 2, 3, 4) --replace with your document type id
Thanks to #Thorsten Kettner help
select c.description as car, dt.description as doctype
from table_cars c
cross join table_documenttypes dt
where dt.id no in (
(
select cd.id_documentType
from table_cardocuments cd
where cd.idcar = c.id AND cd.id_doctype = dt.id
)
AND dt.id IN (1, 2, 3, 4)
This can be a complicated query. The idea is to generate all combinations of cars and the four documents that you want (using cross join). Then use left join to determine if the document actually exists:
select c.id_car, dd.doctype
from cars c cross join
(select 'doc1' as doctype union all
select 'doc2' union all
select 'doc3' union all
select 'doc4'
) dd left join
CarDocuments cd
on c.id_car = cd.id_car left join
Documents d
on cd.id_document_type = d.id_document_type and d.doctype = dd.doctype
where dd.id_document_type is null;
Finally, the where clause finds the car/doctype pairs that are not present in the data.
I am new to PostgreSQL and I have a problem with the following query:
WITH relevant_einsatz AS (
SELECT einsatz.fahrzeug,einsatz.mannschaft
FROM einsatz
INNER JOIN bergefahrzeug ON einsatz.fahrzeug = bergefahrzeug.id
),
relevant_mannschaften AS (
SELECT DISTINCT relevant_einsatz.mannschaft
FROM relevant_einsatz
WHERE relevant_einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,relevant_mannschaften WHERE mannschaft.leiter = person.id AND relevant_mannschaften.mannschaft=mannschaft.id;
This query is working basically - but in "relevant_mannschaften" I am currently selecting each mannschaft, which has been to an relevant_einsatz with at least 1 bergefahrzeug.
Instead of this, I want to select into "relevant_mannschaften" each mannschaft, which has been to an relevant_einsatz WITH EACH from bergefahrzeug.
Does anybody know how to formulate this change?
The information you provide is rather rudimentary. But tuning into my mentalist skills, going out on a limb, I would guess this untangled version of the query does the job much faster:
SELECT m.id, m.rufname, p.id, p.nachname
FROM person p
JOIN mannschaft m ON m.leiter = p.id
JOIN (
SELECT e.mannschaft
FROM einsatz e
JOIN bergefahrzeug b ON b.id = e.fahrzeug -- may be redundant
GROUP BY e.mannschaft
HAVING count(DISTINCT e.fahrzeug)
= (SELECT count(*) FROM bergefahrzeug)
) e ON e.mannschaft = m.id
Explain:
In the subquery e I count how many DISTINCT mountain-vehicles (bergfahrzeug) have been used by a team (mannschaft) in all their deployments (einsatz): count(DISTINCT e.fahrzeug)
If that number matches the count in table bergfahrzeug: (SELECT count(*) FROM bergefahrzeug) - the team qualifies according to your description.
The rest of the query just fetches details from matching rows in mannschaft and person.
You don't need this line at all, if there are no other vehicles in play than bergfahrzeuge:
JOIN bergefahrzeug b ON b.id = e.fahrzeug
Basically, this is a special application of relational division. A lot more on the topic under this related question:
How to filter SQL results in a has-many-through relation
Do not know how to explain it, but here is an example how I solved this problem, just in case somebody has the some question one day.
WITH dfz AS (
SELECT DISTINCT fahrzeug,mannschaft FROM einsatz WHERE einsatz.fahrzeug IN (SELECT id FROM bergefahrzeug)
), abc AS (
SELECT DISTINCT mannschaft FROM dfz
), einsatzmannschaften AS (
SELECT abc.mannschaft FROM abc WHERE (SELECT sum(dfz.fahrzeug) FROM dfz WHERE dfz.mannschaft = abc.mannschaft) = (SELECT sum(bergefahrzeug.id) FROM bergefahrzeug)
)
SELECT mannschaft.id,mannschaft.rufname,person.id,person.nachname
FROM mannschaft,person,einsatzmannschaften WHERE mannschaft.leiter = person.id AND einsatzmannschaften.mannschaft=mannschaft.id;
My situation is I have two tables. I want to join them together and have duplicate records appear on the same line. Mock table structures given below
MainTbl
Cols: MKey1,MKey2,MData1,MData2,MData3
SuppTbl
Cols: SPrimaryKey,SKey1,SKey2,SData1,SData2
I want to LEFT JOIN MainTbl to SuppTbl. However, SuppTbl contains duplicates of SKey1 and SKey2 combo key.
The results I want are below, Where "-#" indicates the duplication number.
MKey1,MKey2,MData1,MData2,MData3,SData1-1,SData2-1,SData1-2,SData2-2
In essence, all fields from the join should be contain on one row based one Key1 and Key2.
ATTEMPTED ANSWER BY SEAN W
SELECT
MainTbl.MKey1,
MainTbl.MKey2,
tcd.SData1 AS SData11,
tcd.SData2 AS SData22,
tcr.SData1 AS SData12,
tcr.SData2 AS SData22
FROM MainTbl
LEFT JOIN SuppTbl tcd
ON MainTbl.MKey1=tcd.SKey1 AND MainTbl.MKey2=tcd.SKey2
LEFT JOIN SuppTbl tcr
ON MainTbl.MKey1=tcr.SKey1 AND MainTbl.MKey2=tcr.SKey2
WHERE tcd.SData1 < tcr.SData1
RESULT No Success. Did not pull any records.
Revised (comments after):
CREATE TABLE MainTbl (MKey1 int,MKey2 int,MData1 varchar(10),MData2 varchar(10),MData3 varchar(10))
CREATE TABLE SuppTbl (SPrimaryKey int,SKey1 int,SKey2 int,SData1 varchar(10),SData2 varchar(10))
INSERT INTO MainTbl VALUES (1, 1, '1MData1', '1MData2', '1MData3')
INSERT INTO SuppTbl VALUES (1, 1, 1, '1SData1-1', '1SData2-1')
INSERT INTO SuppTbl VALUES (2, 1, 1, '1SData1-2', '1SData2-2')
INSERT INTO MainTbl VALUES (1, 2, '2MData1', '2MData2', '2MData3')
INSERT INTO SuppTbl VALUES (3, 1, 2, '2SData1-1', '2SData2-1')
SELECT
MainTbl.MKey1,
MainTbl.MKey2,
tcd.SData1 AS SData11,
tcd.SData2 AS SData22,
tcr.SData1 AS SData12,
tcr.SData2 AS SData22
FROM MainTbl
INNER JOIN SuppTbl tcd
ON MainTbl.MKey1=tcd.SKey1 AND MainTbl.MKey2=tcd.SKey2
LEFT JOIN SuppTbl tcr
ON MainTbl.MKey1=tcr.SKey1 AND MainTbl.MKey2=tcr.SKey2
AND tcd.SPrimaryKey < tcr.SPrimaryKey
Now this won't work 100% for those instances where you have 2 rows in SuppTbl: it will give two result rows - one will be fine and the other you will want to exclude. To exclude it, you have to provide some more information on how to identify those instances where it will have >1 SuppTbl row. You mentioned above in the comments "WHERE Data1 = 4". So that would need to be part of a WHERE clause. It would be something like:
WHERE tcd.SData1 = 4
This might then EXCLUDE the single SuppTbl row. So you need to provide information on how to NOT have that row filtered out. Maybe:
WHERE tcd.SData1 IN (4, 22)
(This won't work with the data in the tables, above).
Found an answer. I've trimmed it down a bit for simplicity's sake, but it works great so long as there are WHERE conditions that can be applied, as there are in my case.
SELECT
MainTbl.MKey1,
MainTbl.MKey2,
tcd.stat AS SData11,
tcr.stat AS SData12
FROM MainTbl
LEFT JOIN(
SELECT * FROM SuppTbl WHERE SData1 <> 22
) tcd
ON MainTbl.MKey1=tcd.SKey1 AND MainTbl.MKey2=tcd.SKey2
LEFT JOIN(
SELECT * FROM SuppTbl WHERE SData1 = 22
) tcr
ON MainTbl.MKey1=tcr.SKey1 AND MainTbl.MKey2=tcr.SKey2
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.