Grouping the data and showing 1 row per group in postgres - sql

I have two tables which look like this :-
Component Table
Revision Table
I want to get the name,model_id,rev_id from this table such that the result set has the data like shown below :-
name model_id rev_id created_at
ABC 1234 2 23456
ABC 5678 2 10001
XYZ 4567
Here the data is grouped by name,model_id and only 1 data for each group is shown which has the highest value of created_at.
I am using the below query but it is giving me incorrect result.
SELECT cm.name,cm.model_id,r.created_at from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by cm.name,cm.model_id,r.created_at
ORDER BY cm.name asc,
r.created_at DESC;
Result :-
Anyone's help will be highly appreciated.

use max and sub-query
select T1.name,T1.model_id,r.rev_id,T1.created_at from
(
select cm.name,
cm.model_id,
MAX(r.created_at) As created_at from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by cm.name,cm.model_id
) T1
left join revision r
on T1.created_at =r.created_at
http://www.sqlfiddle.com/#!17/68cb5/4
name model_id rev_id created_at
ABC 1234 2 23456
ABC 5678 2 10001
xyz 4567

In your SELECT you're missing rev_id
Try this:
SELECT
cm.name,
cm.model_id,
MAX(r.rev_id) AS rev_id,
MAX(r.created_at) As created_at
from dummy.component cm
left join dummy.revision r on cm.model_id=r.model_id
group by 1,2
ORDER BY cm.name asc,
r.created_at DESC;
What you were missing is the statement to say you only want the max record from the join table. So you need to join records, but the join will bring in all records from table r. If you group by the 2 columns in component, then select the max from r, on the id and created date, it'll only pick the top out the available to join

I would use distinct on:
select distinct on (m.id) m.id, m.name, r.rev_id, r.created_at
from model m left join
revision r
on m.model_id = r.model_id
order by m.id, r.rev_id;

Related

Filtering Join in Oracle DB

Problem:
Each KEY in Table A should have one RF record and one SJ record however I have some duplicated SJ records.
Objective:
I wish to use the SJ records in Table B to identify which SJ record in Table A to keep.
Info:
Table A and Table B share a KEY and SEQ_NBR field.
Inputs:
Table A looks as follows
KEY ID_TYPE SEQ_NBR BUS_NAME
1234 RF 1 COMP_A
1234 SJ 2 COMP_B
1234 SJ 4 COMP_C
5678 RF 1 COMP_L
5678 SJ 2 COMP_M
5678 SJ 3 COMP_N
Table B looks as follows
KEY SEQ_NBR BUS_NAME
1234 2 COMP_B
5678 3 COMP_N
Desired Outcome:
My output would look as follows
KEY ID_TYPE SEQ_NBR BUS_NAME
1234 RF 1 COMP_A
1234 SJ 2 COMP_B
5678 RF 1 COMP_L
5678 SJ 3 COMP_N
Here is one way:
select key, id_type, seq_nbr, bus_name
from (
select a.*,
row_number() over (partition by a.key, a.id_type
order by b.key) as rn
from a left outer join b on a.key = b.key and a.seq_nbr = b.seq_nbr
)
where rn = 1
;
The left outer join adds columns from table b to those of table a. We need that for a single purpose: as we partition by key and id_type, we have partitions of either a single row or (two or more) rows. In the latter case, only one row has a non-null value in b.key. If we order by b.key, the row with non-null b.key will get row number = 1 (and we don't care about the rest).
Then the outer query simply keeps all the rows with row number = 1 and ignores the rest.
An alternative solution, using the union all of the two tables (slightly modified as needed) and basic aggregation using the last aggregate function:
select key, id_type,
min(seq_nbr) keep (dense_rank last order by source) as seq_nbr,
min(bus_name) keep (dense_rank last order by source) as bus_name
from (
select 'A' as source, a.* from a
union all
select 'B', key, 'SJ', seq_nbr, bus_name from b
)
group by key, id_type
;
You can test both to see which is more efficient on your data (if performance is important).
Here goes your code:
select * from tablea a
where exists
(select 1 from tableb b where b.key=a.key and b.seq_nbr=a.seq_nbr)
or not exists (select tablea.id_type from tablea inner join tableb on tablea.key=tableb.key and tablea.SEQ_NBR=tableb.SEQ_NBR and tablea.id_type=a.id_type)
If I understand correctly, you can count the number of duplicates. Then use left join and filter based on both the count and the match:
select a.*
from (select a.*,
count(*) over (partition by key, id_type) as cnt
from a
) a left join
b
on b.key = a.key and
b.seq_nbr = a.seq_nbr and
b.bus_name = a.bus_name
where cnt = 1 or b.key is not null;

How to fix sql query problem with two or more position which have one ID

I have sql query where I have to join three tables. One of this is a table with data of invoice, it looks like this:
INVOICE
ID CUSTOMER_NAME TAXID NUMBER LABEL GUID
1 CUSTOMER1 8739281100 FV001/2019 1 04EABFB3-0B9D-4749-B99D-A4EBEE079633
POSITION OF INVOICE
ID ID_INV POSITION_NAME COUNT
1 1 NAME1 3
2 1 NAME2 2,5
TABLE WITH LABEL
ID NAME VALUE GUID_INV
1 LABEL1 true 04EABFB3-0B9D-4749-B99D-A4EBEE079633
When I want to run this query I have statement like this multiple rows in singleton select.
This is for Firebird 2.5.
SELECT
a.ID,
a.GUID,
a.NUMBER,
a.CUSTOMER_NAME,
b.COUNT,
(select usrd.LABEL from USER_FIELD_DEFS usrd
where usrd.GUID_INV=a.GUID and (usrd.ID=1 and usrb.VALUE='true')) as LABEL_NAME
FROM INVOICE a
join POSITION_INVOICE b ON a.ID=b.ID_INV
I want to get result like this
1 04EABFB3-0B9D-4749-B99D-A4EBEE079633 FV001/2019 CUSTOMER1 3 LABEL1
1 04EABFB3-0B9D-4749-B99D-A4EBEE079633 FV001/2019 CUSTOMER1 2,5 LABEL1
Please help with this. I know that solution maybe is very simple but I have some eclipse of the mind:)
This should give you the rows you want based on the 3 tables you provided. If there is a chance that an invoice has no position then simply replace the inner join with left join
SELECT
I.[Id]
,I.[GUID]
,I.[NUMBER]
,I.[CUSTOMER_NAME]
,IP.[POSITION_NAME]
,L.[NAME]
FROM [INVOICE] I
INNER JOIN [IN_P] IP ON IP.ID_INV = I.Id
LEFT JOIN [LABEL] L ON L.[GUID_INV] = I.[GUID]
You are just missing one more join here. Assuming USER_FIELD_DEFS is the same as TABLE WITH LABEL that you have mentioned here
SELECT
a.ID,
a.GUID,
a.NUMBER,
a.CUSTOMER_NAME,
b.COUNT,
c.NAME
FROM INVOICE a
JOIN POSITION_INVOICE b ON a.ID=b.ID_INV
JOIN USER_FIELD_DEFS c ON c.GUID_INV = a.GUID AND c.ID=1 and c.VALUE='true'

Finding max of a column while doing inner join of two tables

I have two tables as follows:
Table A
=====================
student_id test_week
-------- ---------
s1 2018-12-01
s1 2018-12-08
Table B
======================
student_id last_updated remarks
-------- ------------ --------
s1 2018-12-06 Fail
s1 2018-12-10 Pass
Above two tables, I want to fetch following columns:
student_id, last(test_week) and remarks such that
last_updated>=test_week -1 and last_updated<=test_week-15,
i.e. last_updated should be within two weeks of last(test_week), so following will be the result for above entries:
s1 2018-12-08 Pass
I have written like following:
select a.student_id, test_week, remarks
from A inner join B
on A.student_id = B.student_id
and DATEDIFF(last_updated, test_week)>=1
and DATEDIFF(last_updated, test_week)<=15;
But how I will handle the last(test_week), that I am not getting.
If you need the only record related to the last test_week then you can do the following. If I understood this right.
select top 1 a.student_id, test_week, remarks
from A inner join B
on A.student_id = B.student_id
and DATEDIFF(last_updated, test_week)>=1
and DATEDIFF(last_updated, test_week)<=15
order by last_week desc;
You can try to use window function row_number(). The following query will give the max(test_week) for every student_id.
select * from (
select id, test_week, remarks, row_number()
over (partition by id order by test_week desc) as rn
from (
select a.id, test_week, remarks from A join B on A.id = B.id and last_updated - test_week >=1 and last_updated - test_week <=15)tb1
)tb2 where rn=1;
Note : The above query is supported in postgresql, you might want to convert it into equivalent Mysql query

How to group results by count of relationships

Given tables, Profiles, and Memberships where a profile has many memberships, how do I query profiles based on the number of memberships?
For example I want to get the number of profiles with 2 memberships. I can get the number of profiles for each membership with:
SELECT "memberships"."profile_id", COUNT("profiles"."id") AS "membership_count"
FROM "profiles"
INNER JOIN "memberships" on "profiles"."id" = "memberships"."profile_id"
GROUP BY "memberships"."profile_id"
That returns results like
profile_id | membership_count
_____________________________
1 2
2 5
3 2
...
But how do I group and sum the counts to get the query to return results like:
n | profiles_with_n_memberships
_____________________________
1 36
2 28
3 29
...
Or even just a query for a single value of n that would return
profiles_with_2_memberships
___________________________
28
I don't have your sample data, but I just recreated the scenario here with a single table : Demo
You could LEFT JOIN the counts with generate_series() and get zeroes for missing count of n memberships. If you don't want zeros, just use the second query.
Query1
WITH c
AS (
SELECT profile_id
,count(*) ct
FROM Table1
GROUP BY profile_id
)
,m
AS (
SELECT MAX(ct) AS max_ct
FROM c
)
SELECT n
,COUNT(c.profile_id)
FROM m
CROSS JOIN generate_series(1, m.max_ct) AS i(n)
LEFT JOIN c ON c.ct = i.n
GROUP BY n
ORDER BY n;
Query2
WITH c
AS (
SELECT profile_id
,count(*) ct
FROM Table1
GROUP BY profile_id
)
SELECT ct
,COUNT(*)
FROM c
GROUP BY ct
ORDER BY ct;

pad database out with NULL criteria

If I have the following sample table (order by ID)
ID Date Type
-- ---- ----
1 01/01/2000 A
2 22/04/1995 A
2 14/02/2001 B
Where you can immediate see that ID=1 does not have a Type=B, but ID=2 does. What I want to do, if fill in a line to show this:
ID Date Type
-- ---- ----
1 01/01/2000 A
1 NULL B
2 22/04/1995 A
2 14/02/2001 B
where there could potentially be 100's of different types, (so may need to end up inserting 100's rows per person if they lack 100's Types!)
Is there a general solution to do this?
Could I possibly outer join the table on itself and do it that way?
You can do this with a cross join to generate all the rows and a left join to get the actual data values:
select i.id, s.date, t.type
from (select distinct id from sample) i cross join
(select distinct type from sample) t left join
sample s
on s.id = i.id and
s.type = t.type;