SQL Select multiple rows into one displayed row - sql

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

Related

hown position of joined tables influence results?

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

Return filtered result form JOIN and DISTINCT query

I have these 2 tables which I would like to query for a single unique record:
create table active_pairs
(
id integer,
pair text,
exchange_id integer
);
create table exchanges
(
exchange_id integer,
exchange_full_name text
);
INSERT INTO active_pairs (pair, exchange_id)
VALUES ('London/Berlin', 2),
('London/Berlin', 3),
('Paris/Berlin', 4),
('Paris/Berlin', 3),
('Oslo/Berlin', 2),
('Oslo/Berlin', 6),
('Huston/Berlin', 2);
INSERT INTO exchanges (exchange_id, exchange_full_name)
VALUES (2, 'Exchange 1'),
(3, 'Exchange 2'),
(4, 'Exchange 3'),
(3, 'Exchange 21'),
(2, 'Exchange 12'),
(6, 'Exchange 11'),
(2, 'Exchange 31');
Query to list items with only one pair record:
SELECT * FROM active_pairs ap
INNER JOIN exchanges ce on ap.exchange_id = ce.exchange_id
WHERE ap.exchange_id = 2
GROUP BY pair, ap.exchange_id, ce.exchange_id, ap.id
HAVING COUNT(ap.pair) = 1
ORDER BY ap.pair
I also tried this:
SELECT DISTINCT ON (ap.pair) ap.pair
FROM common.active_pairs ap
INNER JOIN common.exchanges ce on ap.exchange_id = ce.exchange_id
WHERE ce.exchange_id = 2
When I run the query I don't get proper result. I Need to get only Huston/Berlin because this is unique record(NOTE we have another record with exchange_id = 2). Now I get into result Huston/Berlin and 'London/Berlin' with exchange_id = 2 which is not correct.
Another example: When I make query for exchange_id=4 I need to get empty result because as you can see I have Paris/Berlin for exchange_id 3 and 4.
Additional case to consider: When exchange_id table row is empty I should not get the row with empty exchange_id.
Can you advice how I can fix this issue?
Not sure why you need the info from common.exchanges, but you can do the following:
SELECT pair
FROM active_pairs ap
WHERE NOT EXISTS (SELECT * FROM active_pairs other_pairing
WHERE other_pairing.pair = ap.pair
AND other_pairing.exchange_id != ap.exchange_id)
AND ap.exchange_id = 2
AND EXISTS (SELECT * FROM exchanges ep WHERE ep.exchange_id = ap.exchange_id)
So basically you're looking for any records in active_pairs that don't have any other record in that table with a different exchange_id.
Working demo on dbfiddle
To select only exchanges that have one and only one pair you need to count pair occurance and filter on that count. So you seemed to be pretty near the solution, so here's your modified statement:
SELECT ce.exchange_id, count(ap.id) as pair_counter
FROM common.active_pairs ap
INNER JOIN common.exchanges ce on ap.exchange_id = ce.exchange_id
GROUP BY ce.exchange_id
HAVING count(ap.id)=1
You even don't really need the join and would be much faster using the FK only:
SELECT ap.id, ap.exchange_id, count(ap.id) as pair_counter
FROM common.active_pairs ap
GROUP BY ap.id, ap.exchange_id
HAVING count(ap.id)=1

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

Help with basic sql query

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.

Get mutually and non mutually existening Fields in same table in Two columns

This is a question similar to another question I posted here but is a little different.
I am trying to get a list of all instances of mutual and non-mutual existing Users.
What I mean is that the returned result from the query will return a list of users along with their co-worker.
It is similar to the question here, but the difference is that non mutual users will be returned too and with out the "duplicity" mutually existing users return in the list (See image below in-order simplify it all).
I took the original answer from Thomas (Thanx again Thomas)
Select D1.u_username, U1.Permission, U1.Grade, D1.f_username, U2.Permission, U2.Gradefrom tblDynamicUserList As D1 Join tblDynamicUserList As D2 On D2.u_username = D1.f_username And D2.f_username = D1.u_username Join tblUsers As U1 On U1.u_username = D1.u_username Join tblUsers As U2 On U2.u_username = D2.u_username
and after several trials I commented out 2 lines (Below).
The returned results are what I am trying to accomplish, as described in the beginning of this question, except for the "duplicity" returned by mutually existing users in the table.
How can I eliminate this duplicity?
Select D1.u_username, U1.Permission, U1.Grade, D1.f_username, U2.Permission, U2.Gradefrom tblDynamicUserList As D1 Join tblDynamicUserList As D2 On D2.u_username = D1.f_username /* And D2.f_username = D1.u_username / Join tblUsers As U1 On U1.u_username = D1.u_username Join tblUsers As U2 On U2.u_username = D2.u_username
/ WHERE D1.U_userName < D1.f_username */
*Screenshot that hopefully helps explain it all.
http://www.marketing2go.co.il/SqlQuestion.jpg
Here are the results of the updated query (with the 2 lines commented out)
http://www.marketing2go.co.il/QueryResults.jpg
The required results should be without rows 1 and 2 (or without rows 3 and 5), because they are in effect, a duplicity. That is because that pair exist in rows 3 and 5 (the order of the names in each pair is irrelevant).
For a lack of a better word, it's more like a logical duplicity.
Many thanx in advance
Ok, as I understand it, if the results already include the pairing (a,b), they shouldn't include (b,a). Is that right? If so, that means that the root cause of your problem is that the tblDynamicUserList table includes both (a,b) and (b,a) pairings. If we filter those in the query first, then the joined results will be what you expected. Note that this also means that you don't care which username is in u_username and which is in f_username; if that mattered, then you would need both in the results.
Here is a script demonstrating your current behaviour. Note that the second query in your question didn't produce the results you posted. I have updated it to what I assume you're using:
declare #tblUsers table (id int, u_username varchar(20), permission varchar(10), experience int, grade int)
declare #tblDynamicUserList table (u_username varchar(20), f_username varchar(20), f_timestamp datetime default (getdate()))
insert into #tblUsers values (1, 'RanAbraGmail', 'WiFi', 1, 2000)
insert into #tblUsers values (2, 'Ana', 'Phone', 2, 3000)
insert into #tblUsers values (3, 'RoyP', 'Phone', 5, 4000)
insert into #tblUsers values (4, 'anaHeb', 'Phone', 14, 5000)
insert into #tblUsers values (7, 'Sheleg', 'Phone', 15, 5500)
insert into #tblDynamicUserList values ('ana', 'RanAbraGmail', default)
insert into #tblDynamicUserList values ('anaHeb', 'RoyP', default)
insert into #tblDynamicUserList values ('RanAbraGmail', 'Ana', default)
insert into #tblDynamicUserList values ('RanAbraGmail', 'RoyP', default)
insert into #tblDynamicUserList values ('RoyP', 'anaHeb', default)
insert into #tblDynamicUserList values ('Sheleg', 'RanAbraGmail', default)
Select D1.u_username, U1.Permission, U1.Grade, D1.f_username, U2.Permission, U2.Grade
from #tblDynamicUserList As D1
Join #tblUsers As U1
On U1.u_username = D1.u_username
Join #tblUsers As U2
On U2.u_username = D1.f_username
Therefore, this query will produce the results you want, by first retrieving only the distinct pairings of usernames, and only then joining to the users table to retrieve the details.
Select D1.username1, U1.Permission, U1.Grade, D1.username2, U2.Permission, U2.Grade
from (select distinct case when u_username < f_username then u_username else f_username end as username1,
case when u_username < f_username then f_username else u_username end as username2
from #tblDynamicUserList) As D1
Join #tblUsers As U1
On U1.u_username = D1.username1
Join #tblUsers As U2
On U2.u_username = D1.username2