SQL - Left Join many-to-many only once - sql

I have a two tables that are setup like the following examples
tablea
ID | Name
1 | val1
1 | val2
1 | val3
2 | other1
3 | other
tableb
ID | Amount
1 | $100
2 | $50
My desired output would be to left join tableb to tablea but only join tableb once on each value. ID is the only relationship
tablea.ID | tablea.Name | tableb.id | tableb.amount
1 | val1 | 1 | $100
1 | val2
1 | val3
2 | other1 | 2 | $50
3 | other
Microsoft SQL

You can do the following:
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea
which gives you :
RowNum | RowID | Name
1 | 1 | val1
2 |1 | val2
3 |1 | val3
4 |2 | other1
5 |3 | other
You then get the minimum row number for each RowID:
Select RowId, min(RowNum)
From (
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea )
Group By RowId
Once you have this you can then join tableb onto tablea only where the RowId is the minimum
WITH cteTableA As (
select ROW_NUMBER() OVER(ORDER BY RowID ASC) as RowNum, ID , Name
from tablea ),
cteTableAMin As (
Select RowId, min(RowNum) as RowNumMin
From cteTableA
Group By RowId
)
Select a.RowID, a.Name, b.Amount
From cteTableA a
Left join cteTableAMin amin on a.RowNum = amin.RowNumMin
and a.ID = amin.RowId
Left join tableb b on amin.ID = b.ID
This can be tidied up... but helps to show whats going on.

Then you MUST specify which row in tableA you wish to join to. If there are more than one row in the other table, How can the query processor know which one you want ?
If you want the one with the lowest value of name, then you might do this:
Select * from tableB b
join tableA a
on a.id = b.Id
and a.name =
(Select min(name) from tableA
where id = b.id)
but even that won't work if there multiple rows with the same values for both id AND name. What you might really need is a Primary Key on tableA.

Use:
select
a.id,
a.name,
b.amount
from
(select
id,
name,
row_number() over (partition by id order by name) as rn
from tablea) a
left join (
select
id,
amount,
row_number() over (partition by id order by amount) as rn
from tableb) b
on a.id = b.id
and a.rn = b.rn
order by a.id, a.name

Related

how to combine multiple column of different table into one table

I have three tables a, b and c and need to arrange these table data as target table and all of these tables (a, b, c) are not in database they are fetched from from single table using queries as alias and need to arrange these tables into target table using query. How to do that
table a | table b | table c
| |
id | a_vals | id | b_vals | id | c_vals
------------ | -------------- | -------------
1 | 123 | 1 | 123 | 1 | 123
2 | 124 | 2 | 142 | 2 | 142
3 | 234 | 4 | 234 | 5 | 234
target table
id | a_val| b_val| c_val
1 | 123 | 123 | 123
2 | 124 | 142 | 142
3 | 234 | - | -
4 | - | 234 | -
5 | - | | 234
Since a, b and c share the same name for the column you want to join, you could use "USING" to avoid duplicate keys in the resulting table:
SELECT *
FROM a
FULL OUTER JOIN b USING (id)
FULL OUTER JOIN c USING (id);
Alternativly, since a, b and c's value column all have distinct names you could use NATURAL JOIN:
SELECT *
FROM a
NATURAL FULL OUTER JOIN b
NATURAL FULL OUTER JOIN c;
Be careful not to accidentally rename any of the other columns tho, since natural join removes any duplicate columns.
You can also omit the "OUTER" keyword if you like, but i would leave it for clarity, (since LEFT, RIGHT, and FULL imply an outer join).
See https://www.postgresql.org/docs/10/static/queries-table-expressions.html for details
Please try this:
select aa.id, a_val, b_val, c_val from
(select distinct id as id from table_a
union
select distinct id as id from table_b
union
select distinct id as id from table_c)aa
left join (select id, a_val from table_a)bb on aa.id = bb.id
left join (select id, b_val from table_b)cc on aa.id = cc.id
left join (select id, c_val from table_c)dd on aa.id = dd.id order by aa.id;
Try this code
SELECT
CASE
WHEN t1.id IS not null THEN t1.id
WHEN t2.id IS not null THEN t2.id
ELSE t3.id
END
AS id,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id FULL OUTER JOIN c t3 ON
CASE
WHEN t1.id IS not null THEN t1.id
ELSE t2.id
END = t3.id
OR
SELECT COALESCE(t1.id, t2.id, t3.id) as id ,
t1.a_vals AS a_val,
t2.b_vals as b_val,
t3.c_vals as c_val
FROM a t1 FULL OUTER JOIN b t2 ON t1.id=t2.id
FULL OUTER JOIN c t3 ON COALESCE(t1.id, t2.id) = t3.id
You are looking for the ANSI-standard FULL OUTER JOIN:
select coalesce(a.id, b.id, c.id) as id, a.val, b.val, c.val
from a full join
b
on a.id = b.id full join
c
on c.id = coalesce(a.id, b.id);
You can also implement this with union all/group by:
select id, max(a_val) as a_val, max(b_val) as b_val, max(c_val) as c_val
from ((select id, val as a_val, null as b_val, null as c_val
from a
) union all
(select id, null as a_val, val as b_val, null as c_val
from b
) union all
(select id, null as a_val, null as b_val, val as c_val
from c
)
) abc
group by id;
This is probably better done in the 'front end' e.g. this is the kind of thing a reporting tool is designed for.
Avoiding nulls and outer joins (because they by definition produce nulls):
SELECT a_val, b_val, c_val
FROM a
NATURAL JOIN b
NATURAL JOIN c
UNION
SELECT a_val, '-' AS b_val, '-' AS c_val
FROM a
WHERE id NOT IN ( SELECT id FROM b )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, b_val, '-' AS c_val
FROM b
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM c )
UNION
SELECT '-' AS a_val, '-' AS b_val, c_val
FROM c
WHERE id NOT IN ( SELECT id FROM a )
AND id NOT IN ( SELECT id FROM b );

pgsql 1 to n relation into json

Long story short, how can i use 1 to n select data to build json like shown in example:
SELECT table1.id AS id1,table2.id AS id2,t_id,label
FROM table1 LEFT JOIN table2 ON table2.t_id = table1.id
result
|id1|id2|t_id|label|
+---+---+----+-----+
|1 | 1 | 1 | a |
| | 2 | 1 | b |
| | 3 | 1 | c |
| | 4 | 1 | d |
|2 | 5 | 2 | x |
| | 6 | 2 | y |
turn into this
SELECT table1.id, build_json(table2.id,table2.label) AS json_data
FROM table1 JOIN table2 ON table2.t_id = table1.id
GROUP BY table1.id
|id1|json_data
+--+-----------------
|1 |{"1":"a","2":"b","3":"c","4":"d"}
|2 |{"5":"x","6":"y"}
My guess the best start woulb be building an array from columns
Hstore instead of json would be ok too
well your table structure is a bit strange (is looks more like report than table), so I see two tasks here:
Replace nulls with correct id1. You can do it like this
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select *
from cte2
Now you have to aggregate data into json. As far as I remember, there's no such a function in PostgreSQL, so you have to concatenate data manually:
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select
id1,
('{' || string_agg('"' || id2 || '":' || to_json(label), ',') || '}')::json as json_data
from cte2
group by id1
sql fiddle demo
And if you want to convert into hstore:
with cte1 as (
select
sum(case when id1 is null then 0 else 1 end) over (order by t_id) as id1_partition,
id1, id2, label
from Table1
), cte2 as (
select
first_value(id1) over(partition by id1_partition) as id1,
id2, label
from cte1
)
select
c.id1, hstore(array_agg(c.id2)::text[], array_agg(c.label)::text[])
from cte2 as c
group by c.id1
sql fiddle demo

Insert data into temp table from 2 source tables

I have 2 SELECT statements that both return 13 rows from dirrefernt tables
I would like to create 1 temporary table with 2 columns and insert the 2 result rows into the 2 columns. Is there a way to do this?
So
1 - SELECT INPOS FROM TABLE1 returns
1,2,3,4,5,6,7,18,9,10,11,12,13
2 - SELECT CODE FROM TABLE2 returns
CODEA,CODEB,CODEC,CODED,CODEE,CODEF,CODEG,CODEH,CODEI,CODEJ,CODEK,CODEL,CODEM
I would like my temporary table to be
1 | CODEA
2 | CODEB
3 | CODEC
4 | CODED
5 | CODEE
6 | CODEF
7 | CODEG
8 | CODEH
9 | CODEI
10 | CODEJ
11 | CODEK
12 | CODEL
13 | CODEM
Try this:
WITH T1 AS (
SELECT ROW_NUMBER() OVER(ORDER BY INPOS) ID, INPOS FROM TABLE1
),
WITH T2 AS
(
SELECT ROW_NUMBER() OVER(ORDER BY CODE) ID, CODE FROM TABLE2
),
SELECT T1.INPOS, T2.CODE
FROM T1 INNER JOIN T2 ON T1.ID = T2.ID
Try something like this:
SELECT a.impos, b.code
FROM (
(
SELECT impos, RANK() OVER (ORDER BY impos ASC) AS link
FROM table1
) AS a INNER JOIN (
SELECT code, RANK() OVER (ORDER BY code ASC) AS link
FROM table2
) AS b ON a.link = b.link
)
sqlfiddle demo

Select Distinct From Table - Two Columns No Column Should Have Repetition

How do I a select against a table A for example which contains these records.
|Column1|Column2|
| A |F |
| A | G |
| B |G |
| B |H |
| C |H |
| D |H |
| E |I |
My expected result is:
|Column1 |Column2|
| A | F |
| B | G |
| C | H |
| E | I |
All columns should have a unique value in them.
What query statement can I use for this?
Thanks
Please try:
select
MIN(Column1) Column1,
Column2
from(
select
Column1,
MIN(Column2) Column2
from YourTable
group by Column1
)x group by Column2
order by 1
SQL Fiddle Demo
It didn't work for this scenario.
create table YourTable (Column1 varchar2(10),
Column2 varchar2(10));
insert into YourTable values ('B','F');
insert into YourTable values ('B','G');
insert into YourTable values ('B','H');
insert into YourTable values ('C','F');
insert into YourTable values ('C','G');
insert into YourTable values ('C','H');
insert into YourTable values ('D','F');
insert into YourTable values ('D','G');
insert into YourTable values ('D','H');
My expectation is
B F
C G
D H
but I only got
B F
Thanks a lot!
SELECT a.val, b.val FROM
(
SELECT val, rownum as rno
FROM
(
SELECT distinct column1 as val
FROM YourTable
)) a,
(
SELECT val, rownum as rno
FROM
(
SELECT distinct column2 as val
FROM YourTable
)) b
WHERE a.rno = b.rno
ORDER BY 1
/
VAL VAL_1
-----------
B F
C G
D H
OR
select column1 as val from YourTable
UNION
select column2 from YourTable
VAL
-----
B
C
D
F
G
H

sql group by only rows which are in sequence

Say I have the following table:
MyTable
---------
| 1 | A |
| 2 | A |
| 3 | A |
| 4 | B |
| 5 | B |
| 6 | B |
| 7 | A |
| 8 | A |
---------
I need the sql query to output the following:
---------
| 3 | A |
| 3 | B |
| 2 | A |
---------
Basically I'm doing a group by but only for rows which are together in the sequence. Any ideas?
Note that the database is on sql server 2008. There is a post on this topic however it uses oracle's lag() function.
This is known as the "islands" problem. Using Itzik Ben Gan's approach:
;WITH YourTable AS
(
SELECT 1 AS N, 'A' AS C UNION ALL
SELECT 2 AS N, 'A' AS C UNION ALL
SELECT 3 AS N, 'A' AS C UNION ALL
SELECT 4 AS N, 'B' AS C UNION ALL
SELECT 5 AS N, 'B' AS C UNION ALL
SELECT 6 AS N, 'B' AS C UNION ALL
SELECT 7 AS N, 'A' AS C UNION ALL
SELECT 8 AS N, 'A' AS C
),
T
AS (SELECT N,
C,
DENSE_RANK() OVER (ORDER BY N) -
DENSE_RANK() OVER (PARTITION BY C ORDER BY N) AS Grp
FROM YourTable)
SELECT COUNT(*),
C
FROM T
GROUP BY C,
Grp
ORDER BY MIN(N)
this will work for you...
SELECT
Total=COUNT(*), C
FROM
(
SELECT
NGroup = ROW_NUMBER() OVER (ORDER BY N) - ROW_NUMBER() OVER (PARTITION BY C ORDER BY N),
N,
C
FROM MyTable
)RegroupedTable
GROUP BY C,NGroup
Just for fun, without any SQL-specific functions and NOT assuming that the ID column is monotonically increasing:
WITH starters(name, minid, maxid) AS (
SELECT
a.name, MIN(a.id), MAX(a.id)
FROM
mytable a RIGHT JOIN
mytable b ON
(a.name <> b.name AND a.id < b.id)
WHERE
a.id IS NOT NULL
GROUP BY
a.name
),
both(name, minid, maxid) AS (
SELECT
name, minid, maxid
FROM
starters
UNION ALL
SELECT
name, MIN(id), MAX(id)
FROM
mytable
WHERE
id > (SELECT MAX(maxid) from starters)
GROUP BY
name
)
SELECT
COUNT(*), m.name, minid
FROM
both INNER JOIN
mytable m ON
id BETWEEN minid AND maxid
GROUP BY
m.name, minid
Result (ignore the midid column):
(No column name) name minid
3 A 1
3 B 4
2 A 7