SQL self join result of a select - sql

I have two tables, lets call them A and B which I perform an inner join on.
select
A.id,
A.serial_number as serial,
concat(B.type, '-', B.primary, '-', B.secondary) as product_number, A.parent_id as parent
from A
inner join B on A.number_id = B.id) as T1
as a result I get a set that contains parents and children (1 per parent).
+----+--------+-----------------+--------+
| id | serial | product number | parent |
+----+--------+-----------------+--------+
| 1 | 123 | abc | null |
| 2 | 234 | cde | 1 |
| 3 | 456 | abc | null |
| 4 | 895 | cde | 2 |
+----+--------+-----------------+--------+
now I'd like to do a self join to get the following
+----+---------------+------------------------+---------------+-----------------------+
| id | serial parent | product_number parent | serial child | product_number child |
+----+---------------+------------------------+---------------+-----------------------+
| 1 | 123 | abc | 234 | cde |
| 2 | 456 | abc | 895 | cde |
+----+---------------+------------------------+---------------+-----------------------+
What would be the best approach for this, I simply couldn't find an easy solution... is there a way to join T1 with itself?

I think that's more joins:
select
ap.id as id_parent,
ap.serial_number as serial_parent,
concat_ws('-', bp.type, bp.primary, bp.secondary) as product_number_parent,
ac.child as id_child,
ac.serial_number as serial_child,
concat_ws('-', bc.type, bc.primary, bc.secondary) as product_number_child
from a ap
inner join a ac on ac.parent = ap.id
inner join b bp on bp.id = ap.astrol_number_id
inner join b bc on bc.id = ac.astrol_number_id
where ap.parent is null

Related

How do I receive a pair of ids of entities in many-to-many relation with null value for the second id if condition is not met

I've got the following tables: person (id), person_agency (person_id, agency_id) and agency(id, type)
this is my query:
select p.id, a.id from person p
left join person_agency pa on p.id = pa.person_id
left join agency a on pa.agency_id = a.id
where a.type = 'agency_type1'
However, with the query I get only the persons who have a relation with an agency of "agency_type1". Instead, I would like to get a list of ids of ALL persons with ids of agencies, where the relation exists and null where it doesn't. I tried naive outer joins but it did not work.
For this content of the tables:
Person:
+-------+
| id |
+-------+
| 1 |
| 2 |
| 3 |
| 4 |
+-------+
Person_agency:
+-----------+-----------+
| person_id | agency_id |
+-----------+-----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 4 |
| 4 | 5 |
+-----------+-----------+
Agency:
+--------+------------------+
| id | type |
+--------+------------------+
| 1 | agency_type1 |
| 2 | some_other_type |
| 3 | agency_type1 |
| 4 | agency_type1 |
| 5 | some_other_type |
+--------+------------------+
I receive the folloing output of my query:
+----------+------+
| p.id | a.id |
+----------+------+
| 1 | 1 |
| 2 | 4 |
+----------+------+
The desired output would be:
+----------+------+
| p.id | a.id |
+----------+------+
| 1 | 1 |
| 2 | 4 |
| 3 | null |
| 4 | null |
+----------+------+
It looks like you don't want to distinguish between an agency which is missing and an agency which is present but the wrong type. So you would want a regular JOIN not a LEFT JOIN for the pa/a pair, and also want to filter out the unwanted type directly on that join. Then you want to do a LEFT JOIN from person to the results of that just-described join.
select p.id p_id, a.id a_id from person p
left join (person_agency pa join agency a on pa.agency_id = a.id and a.type='agency_type1')
on p.id = pa.person_id;
p_id | a_id
------+--------
1 | 1
2 | 4
3 | (null)
4 | (null)
The parenthesis around the join pair are not necessary but I find they make it clearer.
If one person is associated to multiple agencies of the correct type, all of them will be shown. I assume this is what you want, although it was not a scenario covered in your example data.
Try to change left join
to
join (inner join).

Join & Group By - Column is invalid in the select list

I'm running a query to join 2 tables and then group the rows by 2 fields and select the rows with the min ID from these groups and I get an error. The joined table looks like:
+--------+---------+-----------------+-----------+-----------+
| ID | CODE | NAME | VRACHAR01 | VRACHAR02 |
+--------+---------+-----------------+-----------+-----------+
| 290861 | 1110896 | PRODUCT NAME XX | 001 | 706 |
| 290864 | 1110899 | PRODUCT NAME XX | 001 | 706 |
| 290865 | 1110900 | PRODUCT NAME XX | 003 | 721 |
| 290870 | 1110905 | PRODUCT NAME XX | 004 | 743 |
| 290871 | 1110906 | PRODUCT NAME XX | 004 | 743 |
| 290878 | 1110913 | PRODUCT NAME XX | 006 | 806 |
| 290879 | 1110914 | PRODUCT NAME XX | 007 | 807 |
| 290908 | 1110943 | PRODUCT NAME XX | 008 | 815 |
+--------+---------+-----------------+-----------+-----------+
If I run the script below to group the results by the last 2 fields I get an error:
Column 'A.CODE' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
SELECT
min(A.ID),
A.CODE,
A.NAME,
B.VARCHAR01,
B.VARCHAR02
FROM
PRODUCTS A
INNER JOIN
EXTRAS B
ON
A.ID = B.ID
WHERE
A.COMPANY = 1002
AND
A.TYPE = 50
GROUP BY
B.VARCHAR01,
B.VARCHAR02
Any help is appreciated.
If you want the min id, then you don't need other columns:
SELECT MIN(P.ID), E.VARCHAR01, E.VARCHAR02
FROM PRODUCTS P INNER JOIN
EXTRAS E
ON P.ID = E.ID
WHERE P.COMPANY = 1002 AND P.TYPE = 50
GROUP BY E.VARCHAR01, E.VARCHAR02;
Note that this replaces the meaningless table aliases with abbreviations for the table names.
If you want the entire row, then you can use window functions:
SELECT PE.*
FROM (SELECT P.*, E.VARCHAR01, E.VARCHAR02,
ROW_NUMBER() OVER (PARTITION BY E.VARCHAR01, E.VARCHAR02 ORDER BY P.ID ASC) as seqnum
FROM PRODUCTS P INNER JOIN
EXTRAS E
ON P.ID = E.ID
WHERE P.COMPANY = 1002 AND P.TYPE = 50
) PE
WHERE seqnum = 1;

Can someone help me figure out if I'm making a mistake in my query?

I'm trying to create a query that returns the names of all people in my database that have less than half of the money of the person with the most money.
These is my query:
select P1.name
from Persons P1 left join
AccountOf A1 on A1.person_id = P1.id left join
BankAccounts B1 on B1.id = A1.account_id
group by name
having SUM(B1.balance) < MAX((select SUM(B1.balance) as b
from AccountOf A1 left join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
LIMIT 1)) * 0.5
This is the result:
+-------+
| name |
+-------+
| Evert |
+-------+
I have the following tables in the database:
+---------+--------+--+
| Persons | | |
+---------+--------+--+
| id | name | |
| 11 | Evert | |
| 12 | Xavi | |
| 13 | Ludwig | |
| 14 | Ziggy | |
+---------+--------+--+
+--------------+---------+
| BankAccounts | |
+--------------+---------+
| id | balance |
| 11 | 525000 |
| 12 | 750000 |
| 13 | 1900000 |
| 14 | 1600000 |
+--------------+---------+
+-----------+-----------+------------+
| AccountOf | | |
+-----------+-----------+------------+
| id | person_id | account_id |
| 301 | 11 | 12 |
| 302 | 13 | 12 |
| 303 | 13 | 14 |
| 304 | 14 | 11 |
| 305 | 14 | 13 |
+-----------+-----------+------------+
What am I missing here? I should get two entries in the result (Evert, Xavi)
I wouldn't approach the logic this way (I would use window functions). But your final having has two levels of aggregation. That shouldn't work. You want:
having SUM(B1.balance) < (select 0.5 * SUM(B1.balance) as b
from AccountOf A1 join
BankAccounts B1 on B1.id = A1.account_id
group by A1.person_id
order by b desc
limit 1
)
I also moved the 0.5 into the subquery and changed the left join to a join -- the tables need to match to get balances.
I would recommend window functions, if your - undisclosed! - database supports them.
You can join and aggregate just once, and then use a window max() to get the top balance. All that is then left to is to filter in an outer query:
select *
fom (
select p.id, p.name, coalesce(sum(balance), 0) balance,
max(sum(balance)) over() max_balance
from persons p
left join accountof ao on ao.person_id = p.id
left join bankaccounts ba on ba.id = ao.account_id
group by p.id, p.name
) t
where balance > max_balance * 0.5

Oracle SQL - Select duplicates based on two columns

I need to select duplicate rows based on two columns in a join, and i can't seem to figure out how that is done.
Currently i got this:
SELECT s.name,administrative_site_id as adm_id,s.external_code,si.identifier_value
FROM suppliers s
INNER JOIN suppliers_identifier si
ON s.id = si.supplier_id
And the output is something along the lines of below:
| Name | adm_id | external_code |identifier_value |
|:-----------|------------:|:------------: |:----------------:|
| Warlob | 66323 | ext531 | id444 |
| Ozzy | 53123 | ext632 | id333 |
| Motorhead | 521 | ext733 | id222 |
| Perez | 123 | ext833 | id111 |
| Starlight | 521 | ext934 | id222 |
| Aligned | 123 | ext235 | id111 |
What i am looking for, is how to simply select these 4 rows, as they are duplicates based on column: adm_id and Identifier_value
| Name | adm_id | external_code |identifier_value |
|:-----------|------------:|:------------: |:----------------:|
| Motorhead | 521 | ext733 | id222 |
| Perez | 123 | ext833 | id111 |
| Starlight | 521 | ext934 | id222 |
| Aligned | 123 | ext235 | id111 |
First group by ADM_ID, IDENTIFIER_VALUE and find groups that has more than one row in it.
Then select all rows that has these couples
SELECT S.NAME
,ADMINISTRATIVE_SITE_ID AS ADM_ID
,S.EXTERNAL_CODE
,SI.IDENTIFIER_VALUE
FROM SUPPLIERS S INNER JOIN SUPPLIERS_IDENTIFIER SI ON S.ID = SI.SUPPLIER_ID
WHERE (ADMINISTRATIVE_SITE_ID, SI.IDENTIFIER_VALUE) IN (SELECT ADMINISTRATIVE_SITE_ID AS ADM_ID, SI.IDENTIFIER_VALUE
FROM SUPPLIERS S INNER JOIN SUPPLIERS_IDENTIFIER SI ON S.ID = SI.SUPPLIER_ID
GROUP BY ADM_ID, IDENTIFIER_VALUE
HAVING COUNT(*) > 1)
Or an alternate way that may perform better on big datasets:
with t as (
SELECT s.name,administrative_site_id as adm_id,s.external_code,si.identifier_value
COUNT(*) OVER (PARTITION BY administrative_site_id ,identifier_value ) AS cnt
FROM suppliers s
INNER JOIN suppliers_identifier si
ON s.id = si.supplier_id)
select name, adm_id, external_code, identifier_value
from t
where cnt > 1

Access Queries comparing two tables

I have two tables in Access, Table A and Table B:
Table MasterLockInsNew:
+----+-------+----------+
| ID | Value | Date |
+----+-------+----------+
| 1 | 123 | 12/02/13 |
| 2 | 1231 | 11/02/13 |
| 4 | 1265 | 16/02/13 |
+----+-------+----------+
Table InitialPolData:
+----+-------+----------+---+
| ID | Value | Date |Type
+----+-------+----------+---+
| 1 | 123 | 12/02/13 | x |
| 2 | 1231 | 11/02/13 | x |
| 3 | 1238 | 10/02/13 | y |
| 4 | 1265 | 16/02/13 | a |
| 7 | 7649 | 18/02/13 | z |
+----+-------+----------+---+
All I want are the rows from table B for IDs not contained in A. My current code looks like this:
SELECT Distinct InitialPolData.*
FROM InitialPolData
WHERE InitialPolData.ID NOT IN (SELECT Distinct InitialPolData.ID
from InitialPolData INNER JOIN
MasterLockInsNew
ON InitialPolData.ID=MasterLockInsNew.ID);
But whenever I run this in Access it crashes!! The tables are fairly large but I don't think this is the reason.
Can anyone help?
Thanks
or try a left outer join:
SELECT b.*
FROM InitialPolData b left outer join
MasterLockInsNew a on
b.id = a.id
where
a.id is null
Simple subquery will do.
select * from InitialPolData
where id not in (
select id from MasterLockInsNew
);
Try using NOT EXISTS:
SELECT Distinct i.*
FROM InitialPolData AS i
WHERE NOT EXISTS (SELECT 1
FROM MasterLockInsNew AS m
WHERE m.ID = i.ID)