Postgresql: Values of multiple rows in one row - sql

I have the following database:
Car: {[CarID, HorsePower, Brand, HeadDesigner]}
DesignsCar:{[CarID, DesID]}
Designer:{[DesID, Name]}
You should note that while every Car has only 1 HeadDesigner, multiple people can design cars (as in work on them).
Say I have 10 cars in my database. For CarID (1..9) only one DesID per CarID in DesignsCar.
However, for carID 10 we have 3 people working on it (carID has 3 entries in DesignsCar because 3 people worked on it).
Say I do this:
select *
from car c
left outer join designscar ds on c.carid = ds.carid
left outer join designer d on frb.persnr = r.persnr
This gives me 12 rows, when I only want 10. The reason why this gives me 12 rows should be clear: for carID 10 we have 3 people working on it (carID has 3 entries in DesignsCar because 3 people worked on it).
I hope I've done a good job explaining this problem, so here comes my question:
How do I modify the query above so I get 10 Rows. For CarID 10 I'd like the 3 designers to be written in one column (like, comma separated but anything works as long it's in one column).
Is that possible?

You need to aggregate the values. Here is one possibility:
select c.*,
array_agg(d.name) as designer_names
from car c left outer join
designscar ds
on c.carid = ds.carid left outer join
designer d
on frb.persnr = r.persnr
group by c.carid ; -- allowed assuming `carid` is the primary key

Related

PostgreSQL Return Row if Value Exists in One of Several Columns

Ok, I am stuck on this one.
I have a PostgreSQL table customers that looks like this:
id firm1 firm2 firm3 firm4 firm5 lastname firstname
1 13 8 2 0 0 Smith John
2 3 2 0 0 0 Doe Jane
Each row corresponds to a client/customer. Each client/customer can be associated with one or multiple firms; the numeric value under each firm# columns corresponds to the firm id in a different table.
So I am looking for a way of returning all rows of customers that are associated with a specific firm.
For example, SELECT id, lastname, firstname where 8 exists in firm1, firm2, firm3, firm4, firm5 would just return the John Smith row as he is associated with firm 8 under the firm2 column.
Any ideas on how to accomplish that?
You can use the IN operator for that:
SELECT *
FROM customer
where 8 IN (firm1, firm2, firm3, firm4, firm5);
But it would be much better in the long run if your normalized your data model.
You should consider to normalize your tables, with the current schema you should join firms tables as many times as the number of firm fields in your customer table.
select *
from customers c
left join firms f1
on f1.firm_id = c.firm1
left join firms f2
on f2.firm_id = c.firm2
left join firms f3
on f3.firm_id = c.firm3
left join firms f4
on f4.firm_id = c.firm4
You can "unpivot" using a combination of array and unnest, as specified in this answer: unpivot and PostgreSQL.
In your case, I think this should work:
select lastname,
firstname,
unnest(array[firm1, firm2, firm3, firm4, firm5]) as firm_id
from customer
Now you can select from this table (using either a with statement or an inner query) where firm_id is the value you care about

SQL Server: Two COUNTs in one query multiplying with one another in output

I have a query is used to display information in a queue and part of that information is showing the amount of child entities (packages and labs) that belong to the parent entity (change). However instead of showing the individual counts of each type of child, they multiply with one another.
In the below case, there are supposed to be 3 labs and 18 packages, however the the multiply with one another and the output is 54 of each.
Below is the offending portion of the query.
SELECT cef.ChangeId, COUNT(pac.PackageId) AS 'Packages', COUNT(lab.LabRequestId) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN dbo.Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN dbo.Package pac
ON (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId
I feel like this is obvious but it's not occurring to me how to fix it so the two counts are independent of one another like to me they should be. There doesn't seem to be a scenario like this in any of my research either. Can anyone guide me in the right direction?
Because you do multiply source rows by each left join. So sometimes you have more likely cross join here.
SELECT cef.ChangeId, p.Packages, l.Labs
FROM dbo.ChangeEvaluationForm cef
OUTER APPLY(
SELECT COUNT(*) as Labs
FROM dbo.Lab
WHERE cef.ChangeId = Lab.ChangeId
) l
OUTER APPLY(
SELECT COUNT(*) AS Packages
FROM dbo.Package pac
WHERE (cef.ChangeId = pac.ChangeId AND pac.PackageStatus != 6 AND pac.PackageStatus !=7)
) p
WHERE cef.ChangeId = 255
GROUP BY cef.ChangeId
perhaps GROUP BY is not needed now.
From you question its difficult to derive what result do you expect from your query. So I presume you want following result:
+----------+----------+------+
| ChangeId | Packages | Labs |
+----------+----------+------+
| 255 | 18 | 3 |
+----------+----------+------+
Try below query if you are looking for above mentioned result.
SELECT cef.ChangeId, ISNULL(pac.PacCount, 0) AS 'Packages', ISNULL(Lab.LabCount, 0) AS 'Labs'
FROM dbo.ChangeEvaluationForm cef
LEFT JOIN (SELECT Lab.ChangeId, COUNT(*) LabCount FROM dbo.Lab GROUP BY) Lab
ON cef.ChangeId = Lab.ChangeId
LEFT JOIN (SELECT pac.ChangeId, COUNT(*) PacCount FROM dbo.Package pac WHERE pac.PackageStatus != 6 AND pac.PackageStatus !=7 GROUP BY pac.ChangeId) pac
ON cef.ChangeId = pac.ChangeId
WHERE cef.ChangeId = 255
Query Explanation:
In your query you didn't use group by, so it ended up giving you 54 as count which is Cartesian product.
In this query I tried to group by 'ChangeId' and find aggregate before joining tables. So 3 labs and 18 packages will be counted before join.
Your will also notice that I have moved PackageStatus filter before group by in pac table. So unwanted record won't mess with our count.
You start with a particular ChangeId from the dbo.ChangeEvaluationForm table (ChangeId = 255 from your example), then join to the dbo.Lab table. This join makes your result go from 1 row to 3, considering there are 3 Labs with ChangeId = 255. Your problem is on the next join, you are joining all 3 resulting rows from the previous join with the dbo.Package table, which has 18 rows for ChangeId = 255. The resulting count for columns pac.PackageId and lab.LabRequestId will then be 3 x 18 = 54.
To get what you want, there are 2 easy solutions:
Use COUNT DISTINCT instead of COUNT. This will just count the different values of pac.PackageId and lab.LabRequestId and not the repeated ones.
Split the joins into 2 subqueries and join their result (by ChangeId)

SQL oracle: Display records that are not found in another table

I have tried to fetch a record that will return me with the doctor's ID and the total number of all the prescriptions they have given.
SELECT doc.DID, COUNT(pr.DID)
FROM DOCTOR doc, PRESCRIPTION pr
WHERE doc.DID = pr.DID
GROUP BY doc.DID;
By using this statement, I am able to receive the information as long as there is at least one prescription made by a doctor. This is how my results looks like
DID COUNT(PR.DID)
-------------------- -------------
3292848 1
3292885 10
3293063 10
3332949 15
3332950 2
But I want it to display such that even doctors that has not prescribed before will be shown in the record with a count of 0
DID COUNT(PR.DID)
-------------------- -------------
3292848 1
3292885 10
3293042 0
3293063 10
3332949 15
3332950 2
334021 0
First of all, please avoid using old join syntax. Use proper JOIN syntax.
Now here you need a LEFT JOIN which would give you everything from first table and matching records from second table. For non matching records, you will get null, which you can utilize in where or select clause.
SELECT doc.DID, COUNT(pr.DID)
FROM DOCTOR doc
left join
PRESCRIPTION pr
on doc.DID = pr.DID
GROUP BY doc.DID;

Multiple Joins to get data and counts from multiple tables

This is probably really simple, but I have been struggling. Basically I need to combine 2 different queries:
Get a list of accounts plus some info for each
Based on each of those accounts, get the count of users and forms associated with each.
So given the following table structure:
I want to get back:
Name Users Forms Active
====================================
Child 1 3 4 T
Child 2 4 3 F
So the problem is that I want to query first based on the Master id:
Select * from ACCOUNT where MasterId = 1026
AccntId Name Master Id Active
====================================
2 Child 1 1026 T
3 Child 2 1026 F
Then for each of those returned I would like to get the counts of users and forms.
Select Count(AccntId) as Users from Form Where AccntId=2
And of course all in one query. I have messed around with Joins and Left Joins and the stumbling block in the initial query.
Ok, the final query for anyone who cares turned out to be:
SELECT
A.Id as AccountId, A.Name, A.Active,
(select count(*) as Users FROM UserProfile UP where A.Id = UP.AccountId),
(select count(*) as Forms FROM Form F where A.Id = F.AccountId)
FROM
Account A
WHERE
A.MasterId = 1026
Group By A.Id, A.Name, A.Active
Which gave me ultimately the numbers I was looking for:
AccountId Name Active Users Forms
1 Child T 3 4
5 Child2 F 4 3
Not sure if that is the most efficient or proper approach, but it does work! Thanks for the hints from the commentators
SELECT acc.MasterId, count(up.AccntId) as Users, count(f.AccntId) as Forms
from Account acc
full join UserProfile up
on up.AccntId = acc.AccntId
full join Form f
on f.AccntId = acc.AccntId
-- Where /* Your conditions */
group by acc.MasterId;
This should work.
Edit: joins changed to left join

Apportioning data into new columns

Morning,
I am quite new to SQL Server 2008 so I was wondering if you could help me.
I currently have:
SELECT
c.code, d.date, d.date_previous,
CAST(d.date-date_previous as int) AS Days,
d.units, d.cost
FROM table1 AS d
INNER JOIN table2 AS p ON d.ID = p.ID
INNER JOIN table3 AS c ON p.c_id = c.ID
WHERE date_previous > '31/12/2012'
This is bringing back one row per invoice received after 31/12/2012. The aim is to get the following columns:
Code Jan data Feb data Mar data etc...
one unique code per line (so I'm assuming row partitioning is required)
Where a bill has a period of 3 months with, for example, 300 units, I'd like that separated out across 3 months (100 in each)
I'm aware I'd probably need to use a pivot function and some temp tables but I'm not that advanced yet.