Specific format Join results on SQL Server - sql

I have trawled the internet looking for a solution but nothing so far.
Here are 2 sample tables joined on SID/ID
SID Name Attendance Class
1 abc good 1A
2 xyz bad 1B
3 dsk good 1A
4 uij bad 1B
5 sss bad 1A
6 fff good 1D
7 ccc good 1A
ID Lesson Result
1 Read Pass-67%
1 Write Pass-89%
1 Sing Pass-99%
2 Read Pass-75%
3 Sing Fail-47%
3 Read Pass-55%
4 Write Pass-90%
4 Sing Fail-10%
The results need to be in the following format.
A row showing the student name, followed by rows of the students' results.
If a student does not have any results they will not be included.
1, abc, good, 1A
1, Read, Pass-67%
1, Write, Pass-89%
1, Sing, Pass-99%
2, xyz, bad, 1B
2, Read, Pass-75%
3, dsk, good, 1A
3, Sing, Fail-47%
3, Read, Pass-55%
4, uij, bad, 1B
4, Write, Pass-90%
4, Sing, Fail-10%
I attempted using Union to no avail, it is similar to a pivot have not had any luck with that either. Is assume i’m missing a trick here, how can I get this done?
I have included the data if it makes it any easier!
CREATE TABLE RESULTS (ID Int, Lesson varchar(12), Result nvarchar(8))
insert into RESULTS (ID, Lesson, Result)
values
(1,'Read', 'Pass-67%'),
(1,'Write', 'Pass-89%'),
(1,'Sing', 'Pass-99%'),
(2,'Read', 'Pass-75%'),
(3,'Sing', 'Fail-47%'),
(3,'Read','Pass-55%'),
(4,'Write', 'Pass-90%'),
(4,'Sing', 'Fail-10%')
CREATE TABLE STUDENTS (ID int, Name varchar(5), Attendance nvarchar(10),
Class nvarchar (3))
insert into STUDENTS values
(1,'abc','good','1A'),
(2,'xyz','bad','1B'),
(3,'dsk','good','1A'),
(4,'uij','bad','1B'),
(5,'sss','bad','1A'),
(6,'fff','good','1D'),
(7,'ccc','good','1A')

You can use a UNION with a few workarounds.
;WITH Data AS
(
SELECT
S.ID,
S.Name,
S.Attendance,
S.Class,
IsStudent = 1
FROM
Students AS S
WHERE
EXISTS (SELECT 'at least one result' FROM Results AS R WHERE R.ID = S.ID)
UNION ALL
SELECT
ID = R.ID,
Name = R.Lesson,
Attendance = R.Result,
Class = NULL,
IsStudent = 0
FROM
Results AS R
)
SELECT
D.ID,
D.Name,
D.Attendance,
D.Class
FROM
Data AS D
ORDER BY
ID,
IsStudent DESC
But, as you can see on the final column names, you are mixing different data together which is not a good thing to do.

Use union all :
select t.*
from(select ID, Name, Attendance, class
from STUDENTS s
where exists (select 1 from RESULTS where id = s.id) union all
select ID, Lesson, Result, null
from RESULTS r
) t
order by id, (case when class is not null then 0 else 1 end);

Simply concat those columns and Union
SELECT CONVERT(VARCHAR(10),id)+' , '+Name+' , '+Attendance
AS ResultSet INTO #T FROM dbo.STUDENTS
UNION ALL
SELECT CONVERT(VARCHAR(10),ID)+' , '+Lesson+' , '+ Result
FROM dbo.RESULTS
SELECT * FROM #T ORDER BY ResultSet
DROP TABLE #T

Related

LIMIT by distinct values in PostgreSQL

I have a table of contacts with phone numbers similar to this:
Name Phone
Alice 11
Alice 33
Bob 22
Bob 44
Charlie 12
Charlie 55
I can't figure out how to query such a table with LIMITing the rows not just by plain count but by distinct names. For example, if I had a magic LIMIT_BY clause, it would work like this:
SELECT * FROM "Contacts" ORDER BY "Phone" LIMIT_BY("Name") 1
Alice 11
Alice 33
-- ^ only the first contact
SELECT * FROM "Contacts" ORDER BY "Phone" LIMIT_BY("Name") 2
Alice 11
Charlie 12
Alice 33
Charlie 55
-- ^ now with Charlie because his phone 12 goes right after 11. Bob isn't here because he's third, beyond the limit
How could I achieve this result?
In other words, select all rows containing top N distinct Names ordered by Phone
I don't think that PostgreSQL provides any particularly efficient way to do this, but for 6 rows it doesn't need to be very efficient. You could do a subquery to compute which people you want to see, then join that subquery back against the full table.
select * from
"Contacts" join
(select name from "Contacts" group by name order by min(phone) limit 2) as limited
using (name)
You could put the subquery in an IN-list rather than a JOIN, but that often performs worse.
If you want all names that are in the first n rows, you can use in:
select t.*
from t
where t.name in (select t2.name
from t t2
order by t2.phone
limit 2
);
If you want the first n names by phone:
select t.*
from t
where t.name in (select t2.name
from t t2
group by t2.name
order by min(t2.phone)
limit 2
);
try this:
SELECT distinct X.name
,X.phone
FROM (
SELECT *
FROM (
SELECT name
,rn
FROM (
SELECT name
,phone
,row_number() OVER (
ORDER BY phone
) rn
FROM "Contacts"
) AA
) DD
WHERE rn <= 2 --rn is the "limit" variable
) EE
,"Contacts" X
WHERE EE.name = X.name
above seems to be working correctly on following dataset:
create table "Contacts" (name text, phone text);
insert into "Contacts" (name, phone) VALUES
('Alice', '11'),
('Alice', '33'),
('Bob', '22'),
('Bob', '44'),
('Charlie', '13'),
('Charlie', '55'),
('Dennis', '12'),
('Dennis', '66');

Simple CTE recursive query

I am sorry to bother with such a simple question, but I decided to learn CTE recursive queries and I am unable to get my query work even after scoping many sources and threads. So I am humbly asking for pointing out my mistake(s).
Here is a part of table I am querying:
ID ContainerInstanceID ItemID ContentContainerInstanceID
--------- -------------------- ----------- --------------------------
73 40 NULL 41
69 40 23885 NULL
68 40 29683 NULL
67 40 29686 NULL
72 41 27392 NULL
71 41 29235 NULL
70 41 29213 NULL
I assembled this simple CTE query:
;WITH ContainerContent_CTE(InstanceID,ItemID,ContentContainerInstanceID) AS
(
-- ROOT set accordig to input parameter
SELECT ContainerInstanceID,SCA.ItemID,SCA.ContentContainerInstanceID
FROM StockContainerAssignments as SCA
WHERE SCA.ContainerInstanceID = 40 -- input parameter
UNION ALL
-- recursive data
SELECT ContainerInstanceID,SCA2.ItemID,SCA2.ContentContainerInstanceID
FROM ContainerContent_CTE AS CC
JOIN StockContainerAssignments as SCA2 on CC.InstanceID = SCA2.ContentContainerInstanceID
)
SELECT * FROM ContainerContent_CTE;
What I am trying to do is to take a top-level container, in this example it has ID = 40, which is my input parameter. Then, I try to connect other levels by linking ContainerInstanceID with ContentContainerInstanceID. In my example it is not null ar row ID = 73. This should add another 3 rows to my result set (so it should look similar to the example data I presented above), but I still get only top level rows:
InstanceID ItemID ContentContainerInstanceID
----------- ----------- --------------------------
40 29686 NULL
40 29683 NULL
40 23885 NULL
40 NULL 41
I appreciate hints to help me stumble over this subject.
You just had a few little things out of place. This should work for you.
with ContainerContent_CTE as
(
select SCA.ContainerInstanceID
,SCA.ItemID
,SCA.ContentContainerInstanceID
FROM StockContainerAssignments as SCA
WHERE SCA.ContainerInstanceID = 40 -- input parameter
UNION ALL
select SCA.ContainerInstanceID
,SCA.ItemID
,SCA.ContentContainerInstanceID
FROM StockContainerAssignments as SCA
inner join ContainerContent_CTE cte on cte.ContentContainerInstanceID = SCA.ContainerInstanceID
)
select *
from ContainerContent_CTE
This works for me
declare #t table (id int, instance int, container int);
insert into #t values
(73, 40, 41)
, (69, 40, NULL)
, (68, 40, NULL)
, (67, 40, NULL)
, (72, 41, NULL)
, (71, 41, NULL)
, (70, 41, NULL);
select * from #t;
with cte as
( select t.id, t.instance, t.container
from #t t
where t.instance = 40
union all
select t.id, t.instance, t.container
from cte
join #t t
on t.instance = cte.container
)
select * from cte;
As expected, a dumb little mistake got in a way - in the ON clause, I was connecting the parent and child with the oposite pairs of IDs. Since I'm only learning CTE, it was hard to see for me. Here it's fixed (for referrence):
;WITH ContainerContent_CTE(InstanceID,temID,ContentContainerInstanceID) AS
(
-- ROOT set accordig to input parameter
SELECT ContainerInstanceID,SCA.ItemID,SCA.ContentContainerInstanceID
FROM StockContainerAssignments as SCA
WHERE SCA.ContainerInstanceID = 40 -- input parameter
UNION ALL
-- recursive data
SELECT ContainerInstanceID,SCA2.ItemID,SCA2.ContentContainerInstanceID
FROM ContainerContent_CTE AS CC
INNER JOIN StockContainerAssignments as SCA2 on CC.ContentContainerInstanceID = SCA2.ContainerInstanceID)
SELECT * FROM ContainerContent_CTE;
Thank you for suggestions.

SQL-Get highest record from group at another table

I have read several answers to related questions but none of them can be applied to this case.
I have a table TableA where several groups are listed, with their score:
GROUP|SCORE
Blue | 0
Green| 0
Red | 0
Orange| 0
On another table TableB, I have the parts of each group and their individual score (status), which can have three different values:
- G (Good)
- A (Average)
- B (Bad)
So tableB is:
GROUP|PART|STATUS
Blue | 3H2| A
Blue | 4NQ| G
Blue | W9X| A
Green| 65D| G
Red | 73F| B
Red | 91G| A
I need to Update the score on TableA in the following way:
If the best status between the parts of the group is G, group score is 3
If the best status between the parts of the group is A, group score is 2
If the best status between the parts of the group is B, group score is 1
I have been a couple of days going around this and I can't find a solution. Thank you guys. Btw, I am using Access 2013.
As I have already mentioned in the comments: Don't store the score redundantly; it is implicit in tableB. And to get your database straight introduce a status table:
STATUS DESCRIPTION SCORE
G Good 3
A Avarage 2
B Bad 1
If you want to select the score for each color group use this query for instance:
select b.colorgroup, max(s.score) as maxscore
from tableb as b
join status as s on s.status = b.status
group by b.colorgroup;
Two alternative ways to write the same query:
select
colorgroup,
(
select max(score)
from status as s
where s.status = b.status
) as maxscore
from tableb as b;
and
select b.colorgroup, s.maxscore
from tableb as b
join
(
select status, max(score) as maxscore
from status
group by status
) as s on s.status = b.status;
(BTW: I called your group colorgroup because GROUP is a reserved name in SQL.)
UPDATE You say you cannot add a table to the database. So you must evaluate the score in the query itself unfortunately. In standard SQL you would use CASE WHEN, which MS Access doesn't feature. MS Access provides IIF instead:
select
colorgroup,
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb
group by colorgroup;
If you even must use the column in tableA and store redundantly, use:
update tablea as a
set score =
(
select
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb as b
where b.colorgroup = a.colorgroup
);
In SQL-Server you could do in following:
QUERY
update a
set a.SCORE = MaxSTATUS
from #a a
join (select GROUP_, MAX(case when b.STATUS_ = 'G' then 3
when b.STATUS_ = 'A' then 2
when b.STATUS_ = 'B' then 1
end) MaxSTATUS
from #b b
group by GROUP_
) b ON a.GROUP_ = b.GROUP_
select * from #a
SAMPLE DATA
CREATE TABLE #a
(
GROUP_ NVARCHAR(60),
SCORE INT
)
INSERT INTO #a VALUES
('Blue',0)
,('Green',0)
,('Red',0)
,('Orange',0)
CREATE TABLE #b
(
GROUP_ NVARCHAR(60),
PART NVARCHAR(60),
STATUS_ NVARCHAR(60),
)
INSERT INTO #b VALUES
('Blue','3H2','A')
,('Blue','4NQ','G')
,('Blue','W9X','A')
,('Green','65D','G')
,('Red','73F','B')
,('Red','91G','A')
OUPUT
GROUP_ SCORE
Blue 3
Green 3
Red 2
Orange 0

disaggregate summarised table in SQL Server 2008

I've received data from an external source, which is in a summarised format. I need a way to disaggregate this to fit into a system I am using.
To illustrate, suppose the data I received looks like this:
receivedTable:
Age Gender Count
40 M 3
41 M 2
I want this is a disaggregated format like this:
systemTable:
ID Age Gender
1 40 M
2 40 M
3 40 M
4 41 M
5 41 M
Thanks
Karl
Depending of the range of your count you could use a lookup table that holds exactly x records for each integer x. Like this:
create table counter(num int)
insert into counter select 1
insert into counter select 2
insert into counter select 2
insert into counter select 3
insert into counter select 3
insert into counter select 3
insert into counter select 4
insert into counter select 4
insert into counter select 4
insert into counter select 4
then join with this table:
create table source(age int, gender char(1), num int)
insert into source select 40, 'm', 3
insert into source select 30, 'f', 2
insert into source select 20, 'm', 1
--insert into destination(age, gender)
select age, gender
from source
inner join counter on counter.num = source.num
From the "Works on my machine (TM)" stable a recursive query, with all the usual caveats about maximum recursion depth.
with Expanded(exAge, exGender, exRowIndex) as
(
select
Age as exAge,
Gender as exGender,
1 as exRowIndex
from
tblTest1
union all
select
exAge,
exGender,
exRowIndex+1
from
tblTest1 t1
inner join
Expanded e on (e.exAge = t1.Age and e.exGender = t1.Gender and e.exRowIndex < t1.Count)
)
select
exAge,
exGender,
exRowIndex
from
Expanded
order by
exAge,
exGender,
exRowIndex
option (MAXRECURSION 0) -- BE CAREFUL!!
You don't get the row identifier - but inserting the result of the query into a table with an identity column would deal with that.

Picking info using junction table(SQL SERVER 2005) [ SET BASED]

I have 3 tables
1) tblPurchaser having 2 columns:
PurchaserId PurchaserName
1 A1
2 A2
3 A3
2) tblCar having 2 columns:
CarId Carname
11 C1
12 C2
13 C3
14 C4
And the last is a junction table tblInformation where the information about those persons are given who has purchased cars.
PurchaserId CarId
1 11
1 12
2 11
2 13
Now I need to write a set based query where I can be able to obtain the information of those cars which has not been purchased by the persons
Desired Output
PurchaserId CarId
1 13
1 14
2 12
2 14
3 11
3 12
3 13
3 14
Note: This is a real time problem which I am implementing in my project. Because of privacy of company, I have changed the tables and information. But my situation is something similar
Please help me
Edited
So far I have written this query:
SELECT 1 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 1)
union all
SELECT 2 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 2)
union all
SELECT 3 as purchaserid,carid from tblcar
where carid not in (select carid from tblinformation where purchaserid = 3)
But as you can make out that i am hardcoding the purchaserid's. And also in real time I will not know how many id's will be there. So everything has to be done at runtime.
Please helpenter code here
Clue: NOT EXISTS
You should really try to do some homework yourself... 3rd question today...
LEFT JOIN ... WHERE ... IS NULL to the rescue:
SELECT tblPurchaser.PurchaserId, tblCar.CarId
FROM tblPurchaser JOIN tblCar
LEFT JOIN tblInformation ON(
tblPurchaser.PurchaserId = tblInformation.PurchaserId
AND tblCar.CarId = tblInformation.CarId)
WHERE tblInformation.CarId IS NULL
Try this
select pur.PurchaserId, car.CarId
from tblPurchaser pur, tblCar car
where not exists (select 1 from tblInformation where PurchaserId = pur. PurchaserId and CarId = car. CarId)
order by pur.PurchaserId;
Try this:
SELECT PurchaserID, CarID
FROM Purchasers
CROSS JOIN Cars
EXCEPT
SELECT *
FROM tblInformation
Here is a SQL script that demonstrates that this technique works correctly:
declare #soPurchaser table(PurchaserId int, PurchaserName varchar(4));
insert #soPurchaser select 1,'A1'
insert #soPurchaser select 2,'A2'
insert #soPurchaser select 3,'A3'
Declare #SOtblCar table(CarId int, Carname varchar(4))
insert #SOtblCar select 11,'C1'
insert #SOtblCar select 12,'C2'
insert #SOtblCar select 13,'C3'
insert #SOtblCar select 14,'C4'
Declare #SOtblInfo table(PurchaserId int, CarId int)
insert #SOtblInfo select 1,11
insert #SOtblInfo select 1,12
insert #SOtblInfo select 2,11
insert #SOtblInfo select 2,13
SELECT PurchaserID, CarID
FROM #soPurchaser
CROSS JOIN #SOtblCar
EXCEPT
SELECT *
FROM #SOtblInfo
The SQL Set operators (UNION, INTERSECT, and EXCEPT) all operate on two table-sets. You will note that they have no way to map the columns from one set to the other. In all cases in SQL when column must be mapped to each other, but there is no syntax to do it explicitly, then they are always mapped based on column order.
So in this one case, if you have one of the table's column order wrong, then it will not work correctly.