how to get the data using Joins or Cross apply - sql

I have requirement
sample data :
Table A :
ID name
1 cat
2 Dog
3 Bird
Table B :
ID name
1 aaa
1 bbb
2 ccc
2 ddd
Table C :
ID name
1 xxx
1 yyy
1 zzz
2 www
Required Output :
ID name name name
1 cat aaa xxx
1 cat bbb yyy
1 cat null zzz
2 Dog ccc www
2 Dog ddd www
3 Bird NULL NULL
I have tried with different joins
Select a.ID,a.name,b.name,c.name from #A a
full join #b b
on a.ID = b.ID
full join #c c
on b.ID = c.ID
Can anyone suggest me the best way to Proceed?

You can use window function row_number to assign sequence number within each id in the order of increasing name for table b and c and then do a full join between them. Finally, do a left join with a table:
with b1 as (
select b.*, row_number() over (partition by id order by name) as rn
from b
),
c1 as (
select c.*, row_number() over (partition by id order by name) as rn
from c
)
select a.*, t.b_name, t.c_name
from a
left join (
select coalesce(b1.id, c1.id) as id,
b1.name as b_name,
c1.name as c_name
from b1
full join c1 on b1.id = c1.id
and b1.rn = c1.rn
) t on a.id = t.id;
This assumes that you need to join the tables b and c based on id and the position (in the order of name column).

Related

How to identify non-existing keys with reference to a table that has all mandatory keys, SQL?

I have the table 'Table01' which contains the keys that should be mandatory:
id
1
2
3
4
And I also have the table 'Table02' which contains the records to be filtered:
id
customer
weight
1
a
100
2
a
300
3
a
200
4
a
45
1
b
20
2
b
100
3
b
17
1
c
80
4
c
90
2
d
30
3
d
30
4
d
50
So I want to identify which are the mandatory id's that the table 'Table02' does not have, and in turn identify which is the 'customer' of each id's that the table 'Table02' does not have.
The resulting table should look like this:
customer
id
b
4
c
2
c
3
d
1
What I have tried so far is a 'rigth join'.
proc sql;
create table table03 as
select
b.id
from table02 a
right join table01 b
on a.id=b.id
where a.id is null;
run;
But that query is not identifying all the id's that should be mandatory.
I hope someone can help me, thank you very much.
here is one way:
select cl.customerid , a.id
from
Table1 a
cross join
( select customerid
from table2
group by customerid
) cl
where not exists ( select 1 from table2 b
where b.customerid = cl.customerid
and b.id = a.id
)
You can use an EXCEPT between two sub-selects. The first creates a matrix of all possibilities, and the except table is a selection of the extant customers.
Example:
data ids;
do id = 1 to 4; output; end;
run;
data have;
input id customer $ weight;
datalines;
1 a 100
2 a 300
3 a 200
4 a 45
1 b 20
2 b 100
3 b 17
1 c 80
4 c 90
2 d 30
3 d 30
4 d 50
run;
proc sql;
create table want(label='Customers missing some ids') as
select matrix.*
from
(select distinct have.customer, ids.id from have, ids) as matrix
except
(select customer, id from have)
;
quit;
If you are doing it in SQL server. Something like #eshirvana above posted, but also you can use with cte:
;with cte as
(
SELECT t1.id, t2.Customer
FROM Table01 t1
cross join (select distinct customer from Table02)
)
SELECT a.customer, a.id FROM cte a
LEFT JOIN Table02 b
ON a.id=b.id AND a.customer=b.customer
where b.id is null

Returning an agregated array by searching/comparing in an array of arrays

I have 2 tables:
categories
id | name | | slug | path | parent_id | depth
1 name1 slug1 {1} null 0
2 name2 slug2 {1,2} 1 1
3 name3 slug3 {1,2,3} 2 2
5 nam5 slug5 {5} null 0
......
9 nam4 slug9 {5,9} 5 1
where path is an int[]array type and works like a breadcrumb
items
id | name
1 name1
Between Item and Category there is a M2M relation
item_categories
id | item_id | category_id
1 1 | 3
2 1 9
In the example above the Item is in 3 categories:
I use the following SQL:
SELECT c1.id, c1.name, c1.slug, c1.parent_id FROM categories AS c1
WHERE ARRAY[c1.id] <# (SELECT c2.path FROM categories AS c2 WHERE c2.id=
(SELECT category_id FROM item_categories WHERE item_id=8 LIMIT 1)) order by depth
to extract a breadcrumb base on path and it works.
But I want to get all breadcrumbs(not just one). Removing LIMIT 1 and changing = to in I will have an array of arrays, instead of just an array, and will trigger an error:
more than one row returned by a subquery used as an expression
which is normal.
Example of what I want - Item in:
cat1 - > cat2 - >cat3
ca5 -> cat9
, and from database(so I can loop over them):
[ [{'name':cat1, 'slug':slug1}, {'name':cat2, 'slug':slug2}, {'name':cat3, 'slug':slug3}], [{'name':cat5, 'slug':slug5}, {'name':cat9, 'slug':slug9}]]
dbfiddle: https://dbfiddle.uk/?rdbms=postgres_10&fiddle=f756cfe568d38425dfe25cfec60b1b3f
So instead of obtaining one breadcrumb how can I get an array o breadcrumbs as result ?
using json_build_object, unnest and ordered json_agg:
select
c.id,
json_agg(
json_build_object('name',c2.name,'slug',c2.slug)
order by p.depth
)
from categories as c
inner join lateral unnest(c.path) with ordinality as p(id, depth) on true
inner join categories as c2 on
c2.id = p.id
where
exists (
select *
from item_categories as tt
where
tt.item_id = 1 and
tt.category_id = c.id
)
group by
c.id
db<>fiddle demo
Or you can use depth column from you table if you want:
select
c.id,
json_agg(
json_build_object('name',c2.name,'slug',c2.slug)
order by c2.depth
)
from categories as c
inner join categories as c2 on
c2.id = any(c.path)
where
exists (
select *
from item_categories as tt
where
tt.item_id = 1 and
tt.category_id = c.id
)
group by
c.id
db<>fiddle demo
What I don't like about json_build_object is that you have to name your columns explicitly doing double work, so I've tried to useto_json instead. It works, but honestly, I'm not that familiar with this syntax when alias of the table is passed to the function as an argument (see Using Composite Types in Queries) and could not make it work without lateral join:
select
c.id,
json_agg(to_json(d) order by c2.depth)
from categories as c
inner join categories as c2 on
c2.id = any(c.path)
cross join lateral (select c.name, c.slug) as d
where
exists (
select *
from item_categories as tt
where
tt.item_id = 1 and
tt.category_id = c.id
)
group by
c.id
db<>fiddle demo

combine three tables in oracle sql

I'm having these three tables
Table A:
code aname
----------- ----------
1 A
2 B
3 C
Table B:
code bname
----------- ----------
1 aaa
1 bbb
2 ccc
2 ddd
Table C
code cname
----------- ----------
1 xxx
1 yyy
1 zzz
2 www
How can I achieve the output like this using single query ?
code aname bname cname
----------- ---------- ---------- ----------
1 A aaa xxx
1 A bbb yyy
1 A NULL zzz
2 B ccc www
2 B ddd NULL
3 C NULL NULL
Any suggestions ?
Thanks
It looks like you want "lists" to be vertical for tables B and C. This is doable, by using row_number(). However, the trick is getting the third row in where there are no matches.
Here is one method. It uses a full outer join to combine the b and c names together. It then uses left join to bring in the a records.
select a.code, a.name, bc.bname, bc.cname
from a left join
(select coalesce(b.code, c.code) as code, bname, cname
from (select code, bname, NULL as cname,
row_number() over (partition by code order by code) as seqnum
from b
) b full outer join
(select code, NULL as bname, cname,
row_number() over (partition by code order by code) as seqnum
from c
) c
on b.code = c.code and b.seqnum = c.seqnum
) bc
on bc.code = a.code;

Get the max value of a column from set of rows

I have a table like this
Table A:
Id Count
1 4
1 16
1 8
2 10
2 15
3 18
etc
Table B:
1 sample1.file
2 sample2.file
3 sample3.file
TABLE C:
Count fileNumber
16 1234
4 2345
15 3456
18 4567
and so on...
What I want is this
1 sample1.file 1234
2 sample2.file 3456
3 sample3.file 4567
To get the max value from table A I used
Select MAX (Count) from A where Id='1'
This works well but my problem is when combining data with another table.
When I join Table B and Table A, I need to get the MAX for all Ids and in my query I dont know what Id is.
This is my query
SELECT B.*,C.*
JOIN A on A.Id = B.ID
JOIN C on A.id = B.ID
WHERE (SELECT MAX(COUNT)
FROM A
WHERE Id = <what goes here????>)
To summarise, what I want is Values from Table B, FileNumber from Table c (where the count is Max for ID from table A).
UPDATE: COrrecting table C above. Looks like I need Table A.
I think this is the query you're looking for:
select b.*, c.filenumber from b
join (
select id, max(count) as count from a
group by id
) as NewA on b.id = NewA.id
join c on NewA.count = c.count
However, you should take into account that I don't get why for id=1 in tableA you choose the 16 to match against table C (which is the max) and for id=2 in tableA you choose the 10 to match against table C (which is the min). I assumed you meant the max in both cases.
Edit:
I see you've updated tableA data. The query results in this, given the previous data:
+----+---------------+------------+
| ID | FILENAME | FILENUMBER |
+----+---------------+------------+
| 1 | sample1.file | 1234 |
| 2 | sample2.file | 3456 |
| 3 | sample3.file | 4567 |
+----+---------------+------------+
Here is a working example
Using Mosty’s working example (renaming the keyword count to cnt for a column name), this is another approach:
with abc as (
select
a.id,
a.cnt,
rank() over (
partition by a.id
order by cnt desc
) as rk,
b.filename
from a join b on a.id = b.id
)
select
abc.id, abc.filename, c.filenumber
from abc join c
on c.cnt = abc.cnt
where rk = 1;
select
PreMax.ID,
B.FileName,
C2.FileNumber
from
( select C.id, max( C.count ) maxPerID
from TableC C
group by C.ID
order by C.ID ) PreMax
JOIN TableC C2
on PreMax.ID = C2.ID
AND PreMax.maxPerID = C2.Count
JOIN TableB B
on PreMax.ID = B.ID

Sql Inner Join first record only if Exists Take Next one

This one is support hard for me. I can do inner join with first result only, but if exist I want take 2nd result.
THIS IS MY TABLE A
ID NAME VALUE
1 A 123
2 B 456
3 C 789
4 A 456
TABLE B
BID BNAME BVALUE
1 A ABC
2 A CDE
3 B 845
4 C 1234
MY SELECT SQL:
SELECT * FROM A
CROSS APPLY (
SELECT TOP 1 *
FROM B
WHERE A.Name = B.BName
) BB
It return
1 A 123 1 A ABC
2 B 456 3 B 845
3 C 789 4 C 1234
4 A 456 1 A ABC
Please help, I want this result:
1 A 123 1 A ABC
2 B 456 3 B 845
3 C 789 4 C 1234
4 A 456 2 A CDE
I accept tmp table and any kind of query :(
Following clarification in the comments that both tables will always have matching rows.
WITH A
AS (SELECT *,
ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY ID) AS RN
FROM TableA),
B
AS (SELECT *,
ROW_NUMBER() OVER (PARTITION BY BNAME ORDER BY BID) AS RN
FROM TableB)
SELECT A.ID,
A.NAME,
A.VALUE,
B.BID,
B.BNAME,
B.BVALUE
FROM A
JOIN B
ON A.NAME = B.BNAME
AND A.RN = B.RN