Get some data which corresponds to the maximum date - sql

I have these 3 tables:
Table ORG:
Fields:historyid, personid
Table PERSON:
Fields: id
Table HISTORY:
Fields: id,date,personid
Both HISTORY and ORG are linked to PERSON with an 1:N relationship. Also, ORG is linked to HISTORY with an 1:N relationship. I want to get from table ORG for each person just one row: this which corresponds to the HISTORY row with the highest date. The following SQL gives the highest date for a certain person. However, I do not know how to combine this with the above requirement.
SELECT ash1.id
FROM
(SELECT * FROM history a WHERE a.personid=person.id) ash1
LEFT JOIN
(SELECT * FROM history b WHERE b.personid=person.id) ash2
ON ash1.personid=ash2.personid
AND ash1.date < ash2.date
WHERE ash2.date IS NULL

I think you can do it by using MAX() and GROUP BY:
SELECT
o.historyid AS o_hist,
o.personid AS o_per,
h.id AS h_id,
MAX(h.date) AS h_date,
h.personid AS h_person
FROM
org o
LEFT JOIN
person p ON p.id = o.personid
LEFT JOIN
history h ON h.id = o.historyid AND h.personid = p.id
GROUP BY o_per

Try the below query..
;WITH_CTE_HighestHistory
AS (SELECT PersonID,MAX([Date]) HDate
FROM History
GROUP BY PersonID)
SELECT org.*,h.*
FROM org o
LEFT JOIN History h ON o.Historyid=h.Id and o.PersonID=h.PersonId
INNER JOIN WITH_CTE_HighestHistory ch ON h.Personid=ch.Personid and h.[Date]=ch.[Date]
WHERE EXISTS(SELECT 1 FROM Person p WHERE p.Id=o.PersonID )

There are multiple ways to approach this, depending on the database. However, your data structure is awkward. Why does org have historyid? That doesn't really make sense to me.
In any case, based on your description, this should work:
select o.*, h.*
from org o join
history h
on h.personid = o.personid
where h.date = (select max(h2.date)
from history h2
where h2.personid = h.personid
);
You might want to start the from clause as:
from (select distinct personid from org) o
So, you only get one person, if they are repeated in the table.

Related

Postgres do array agg for each row

I have a query which will take jobs_locum_hospital_ids from my doctor table, it will then join this to the hospital table on id and fetch the name, then placing all of these into an array.
so [187,123] --> ("George Eliot Hospital - Acute Services"),("Good Hope Hospital")
select array_agg(t)
from (
select h.name from (select jsonb_array_elements_text(d.jobs_locum_hospital_ids)::int as id from doctor d
where d.id = 11720) as q1
left join hospital h on h.id = q1.id
)t
But this is only performing this for where d.id = 11720
What I'd like to do is do this for each row. So in a way joining to
select * from doctor
left join that thing above
It is a bit hard to figure out your data structure or why you are using json functions for this. From what I can tell, doctors have an array of hospital ids and you want the names:
select d.*,
(select array_agg(h.name)
from unnest(d.jobs_locum_hospital_ids) dh join
hospital h
on dh = h.id
) as hospital_names
from doctors;
Just the fact that you want to do this suggests that you really want a junction table, doctorHospitals with one row per doctor and per hospital.

SQL: IN condition from array

The best way I can describe my problem is by giving you an example of my problem:
I have a table called "person" with the columns id | name | hobbys
hobbys would be a manyToMany association
So I have this statement: SELECT * FROM person p LEFT JOIN hobbys h ON p.hobby_id = h.id WHERE p.hobby_id IN($array);
The problem here is, it will select all persons that have one of the hobbys in that array, but I want the selected persons MUST have all of the hobbys in that array.
Is there a function in sql?
Use GROUP BY and HAVING.
SELECT p.id, p.name
FROM person p
JOIN hobbys h ON p.hobby_id = h.id
WHERE p.hobby_id IN($array)
GROUP BY p.id, p.name
HAVING count(distinct h.id) = <size_of_array>
There are also other solutions using INTERSECTION, IN, or EXISTS however this one will keep the list of values behind IN.
You have to search each item hobby in person and match them with a AND conjunction with all contents in hobby table
first, You have to get all records persons have in hobby
SELECT * FROM person p LEFT JOIN hobbys h ON p.hobby_id = h.id
Now build the AND conditional statement, and later query it with
SELECT * FROM person p LEFT JOIN hobbys h ON p.hobby_id = h.id
WHERE first_result IN($array) and second_result in ($array) and....;

SQL ecommerce database - getting count of products purchased by only one user

In my rails app I have a typical ecommerce schema inside a Postgres 9.6 database. Here's a simplified version of it:
users table
:name
products table
:name
shopping_carts table
:user_id
line_items table
:price
:qty
:product_id
:shopping_cart_id
I've got a working query to return the number of distinct products bought by each user:
SELECT COUNT(distinct p.*), u.name FROM products p
INNER JOIN line_items l ON p.id = l.product_id
INNER JOIN shopping_carts sc ON l.shopping_cart_id = sc.id
INNER JOIN users u ON sc.user_id = u.id
GROUP BY u.name
But I also want a count of products for each user that only that particular user has purchased. A possible method for this in Ruby (once everything was set up with ActiveRecord) might look something like:
def unique_prod(user)
user.products.select { |p| p.users.length == 1 }.count
end
But how to do it in SQL? I think I need to do it using two counts - one for the number of different user_ids in a given product's shopping_carts (let's call this count user_count), and then a count of products for which user_count = 1. I'm having trouble incorporating the multiple COUNT and GROUP BY statements in working fashion. Any suggestions?
To do it all in one query:
SELECT scl.user_id, u.name, ct_dist_prod, ct_dist_prod_exclusive
FROM (
SELECT sc.user_id
, count(DISTINCT l.product_id) AS ct_dist_prod
, count(DISTINCT l.product_id)
FILTER (WHERE NOT EXISTS (
SELECT 1
FROM shopping_carts sc1
JOIN line_items l1 ON l1.shopping_cart_id = sc1.id
WHERE l1.product_id = l.product_id
AND sc1.user_id <> sc.user_id)) AS ct_dist_prod_exclusive
FROM shopping_carts sc
JOIN line_items l ON l.shopping_cart_id = sc.id
GROUP BY 1
) scl
JOIN users u ON u.id = scl.user_id;
I added the user_id to the result, because I cannot assume that name is defined unique (which would make your original query slightly incorrect).
The aggregate FILTER clause requires Postgres 9.4 or later:
How can I simplify this game statistics query?
How?
Assuming referential integrity enforced by a FK constraint, you do not need to join to the table products at all for this query.
Neither, at first, to the users table. The basic query boils down to:
SELECT sc.user_id, count(DISTINCT l.product_id)
FROM shopping_carts sc
JOIN line_items l ON l.shopping_cart_id = sc.id
GROUP BY 1;
Add the 2nd count to this cheaper query, where all rows with products are excluded for which another row with the same product and a different user exists (i.e. bought by a different user, too).
Then join to users to add the name. Cheaper.
Computing only the exclusive count is simpler. Example:
SELECT sc.user_id, count(DISTINCT l.product_id) AS ct_dist_prod_exclusive
FROM shopping_carts sc
JOIN line_items l ON l.shopping_cart_id = sc.id
LEFT JOIN (
shopping_carts sc1
JOIN line_items l1 ON l1.shopping_cart_id = sc1.id
) ON l1.product_id = l.product_id
AND sc1.user_id <> sc.user_id
WHERE l1.product_id IS NULL
GROUP BY 1;
Note the essential parentheses.
Related:
Select rows which are not present in other table
Or (in response to your comment):
SELECT user_id, count(*) AS ct_dist_prod_exclusive
FROM (
SELECT max(user_id) AS user_id, l1.product_id
FROM line_items l1
INNER JOIN shopping_carts sc1 ON l.shopping_cart_id = sc1.id
GROUP BY l1.product_id
HAVING COUNT(DISTINCT sc1.user_id) = 1 -- DISTINCT!
) p1
GROUP BY user_id;
HAVING COUNT(DISTINCT sc1.user_id) = 1 because
products purchased by only one user
allows the product to be bought by the same user multiple times.

SQL Query - max(count()) - possible without CTE?

I'm studying for my Database Systems exam at the moment, having some trouble with an exercise creating a query.
I have four tables:
A referent-table with personal data of referents,
A course-table with course data (with the responsible referent as foreign key),
A workshop-table with workshop data (with the corresponding course as foreign key),
A booking-table which manages bookings (with the corresponding workshop which has been booked as a foreign key)
My exercise is to find out how much money a referent earns (there's a price-column in workshop)
It's not very difficult to list how much money he earns per workshop; I created this query to show me:
SELECT r.referentid,
r.name,
(SELECT COUNT(*) FROM g22_courses c WHERE c.responsiblerefid = r.referentid)*c.price AS income
FROM referent r
LEFT JOIN g22_courses c ON (c.responsiblerefid = r.referentid)
LEFT JOIN g22_workshop w ON (w.courseid = c.id)
LEFT JOIN g22_booking b ON (b.workshopid = w.id)
GROUP BY r.referentid, c.responsiblerefid
This returns this:
2;"Anna";0.60
4;"Ahmed";3.5
1;"Hans";
2;"Anna";13.20
3;"Wolfgang";
As you can see, it works fine.
I now have two rows for Anna (because she is responsible for two courses..) and want to have one row with the sum of both tables.
Unfortunately, the only way to do this (as I found out) is with a Common Table Expression (CTE) - with a CTE it works:
WITH incomepercourse AS (
SELECT r.referentid,
r.name,
(SELECT COUNT(*) FROM g22_courses c WHERE c.responsiblerefid = r.referentid)*c.price AS income
FROM referent r
LEFT JOIN g22_courses c ON (c.responsiblerefid = r.referentid)
LEFT JOIN g22_workshop w ON (w.courseid = c.id)
LEFT JOIN g22_booking b ON (b.workshopid = w.id)
GROUP BY r.referentid, c.responsiblerefid
)
SELECT referentid, name, SUM(income) FROM incomepercourse GROUP BY referentid, name
this returns:
3;"Wolfgang";
4;"Ahmed";3.50
2;"Anna";13.80
1;"Hans";
Is there any way to avoid a CTE?
My professor didn't talk about CTE, and it also isn't in his lecture notes - so there has to be some other, simpler way.
Is there anyone out there who knows a better way to achieve this?
Thanks in Advance!
You can wrap the CTE part and query as shown below.
SELECT tbl1.referentid, tbl1.name, SUM(tbl1.income) FROM (
SELECT r.referentid,
r.name,
(SELECT COUNT(*) FROM g22_courses c WHERE c.responsiblerefid = r.referentid)*c.Preis AS income
FROM referent r
LEFT JOIN g22_courses c ON (c.responsiblerefid = r.referentid)
LEFT JOIN g22_workshop w ON (w.courseid = c.id)
LEFT JOIN g22_booking b ON (b.workshopid = w.id)
GROUP BY r.referentid, c.responsiblerefid)tbl1
GROUP BY tbl1.referentid, tbl1.name
You do not need to join the workshop and booking table if you do not use it.
If the price is in the course table as shown in your first request I think what you are asking for is answered by this request:
SELECT referent.referentid, referent.name, sum(price)
FROM referent LEFT JOIN g22_courses ON g22_courses.responsiblerefid=referent.id
GROUP BY referent.referentid, referent.name
If the price is in the workshop table, just add a join to this table.
Okay, I solved it.
It was a problem with my understanding of the problem after all... ._.
I know managed to make a query like a_horse_with_no_name said in his comment:
SELECT r.referentid,
r.name,
SUM(c.price) AS income
FROM referent r
LEFT JOIN g22_courses c ON (c.responsiblerefid = r.referentid)
LEFT JOIN g22_workshop w ON (w.courseid = c.id)
LEFT JOIN g22_booking b ON (b.workshopid = w.id)
GROUP BY r.referentid
ORDER BY r.referentid
This solves my problem perfectly, returning the right values.
Thank you!

Count rows in hierarchy structure including subtrees

I'm trying to use recursive query to get number of events for every category including subcategories. I have 3 tables - ContentTabs (hierarchical table), Events and intermediate table RelEventsToContentTabs so it's simple many-to-many relationship.
The problem is when I use a query such as one below I get number of Events for every category but without number of events for subcategories.
I'm using SQL Server 2008.
Any ideas?
WITH ContentTabsStructure (Id, Name)
AS
(
SELECT Id, Name,parentId FROM ContentTabs
WHERE Id =1
UNION ALL
SELECT ct.Id, ct.Name,ct.parentId FROM ContentTabs AS ct
INNER JOIN ContentTabsStructure AS cts
ON ct.ParentId = cts.Id
)
SELECT cts.id,cts.Name, Count(distinct e.id) as NumberOfEvents
FROM ContentTabsStructure cts
INNER JOIN RelEventsToContentTabs etct
ON cts.id = etct.contentTabId
INNER JOIN Events e
ON etct.eventId = e.id
GROUP BY cts.id,cts.Name
You can include parentId also in CTE as:
WITH ContentTabsStructure (Id, Name,parentId)
to get subcategories for every category and then include some thing like below in selected columns to get number of events for subcategories:
, NumberOfSubCatagoryEvents = isnull(
(
Count(distinct e.id)
FROM ContentTabsStructure cts1
INNER JOIN RelEventsToContentTabs etct1
ON cts1.id = etct1.contentTabId
INNER JOIN Events e1
ON etct1.eventId = e1.id
where cts1.parentId<cts.parentId
GROUP BY cts1.id,cts1.Name
), 0 )