SQL Join multiple table without repetition - tsql

I've got 3 tables
Table A
----------------------
| ID| Data1 | Data2 |
---------------------
| 1 |John | 2021 |
| 2 |Steve | 2020 |
Table B
----------------------
|Row|ID|Value1|Value2|
----------------------
|1 |1 |iR3000|0.5 |
|2 |1 |iRC252|0.7 |
|3 |2 |Dr2000|0.4 |
Table C
----------------------
|Row|ID|Value3|Value4|
----------------------
|1 |1 |aaaaaa|12345 |
|2 |1 |bbbbbb|6789 |
My goal is to add a result like this :
-------------------------------------------------
| ID| Data1 | Data2 |Value1|Value2|Value3|Value4|
-------------------------------------------------
| 1 |John | 2021 |iR3000|0.5 |aaaaaa|12345 |
| 1 |John | 2021 |iRC252|0.7 |bbbbbb|6789 |
| 2 |Steve | 2020 |Dr2000|0.4 |null |null |
Actually with my query, the ID 1 is duplicate 4 times.
Here is my query :
SELECT
a.id, a.data1,a.data2
,b.value1, b.value2
,c.value3,c.value4
FROM TableA a
JOIN TableB b
ON b.ID=a.ID
JOIN TableC c
ON c.ID=a.ID

What you had was close; only the JOIN to TableC was wrong. It needs to be an OUTER JOIN and also match on the Row column:
SELECT a.ID, a.Data1, a.Data2, b.Value1, b.Value2, c.Value3, c.Value4
FROM TableA a
INNER JOIN TableB b on b.ID = a.ID
LEFT JOIN TableC c on c.ID = b.ID AND c.Row = b.Row
Update based on the comment:
I cannot use row column cause they are not always match with the same number.
Okay. If the Row column at least exists, we can still work with that to create projections that might be more consistent between tables:
With TableB2 AS (
SELECT *, row_number() over (partition by ID order by row) As Row2
FROM TableB
),
TableC2 As (
SELECT *, row_number() over (partition by ID order by row) As Row2
FROM TableC
)
SELECT a.ID, a.Data1, a.Data2, b.Value1, b.Value2, c.Value3, c.Value4
FROM TableA a
INNER JOIN TableB2 b on b.ID = a.ID
LEFT JOIN TableC2 c on c.ID = b.ID AND c.Row = b.Row
What we cannot do is rely on the order of the records on disk or the insertion order. There MUST be some field to indicate, e.g. the iR3000 row in TableB relates to the aaaaaa row in TableC rather than the bbbbbb row.
The order records appear in the table is not good enough. Databases are based on relational set theory, so what we think of as "Tables" are more-formally defined as "Unordered Relations". Note the word "unordered" in that definition. While table order may seem to be stable over stretches, databases are free to re-ordered the rows on disk after insertion. They can and will do this to make queries more efficient, conform better with indexes, fill up pages, etc.

Related

Left Join two tables - dont include the joins where second table has more than 1 row for value from first table; rejects

As title said, I want to reject rows, so I will not create duplicates.
And first step is not to join on values that have more rows in second table.
Here is an example if needed:
Table a:
aa |bb |
---|----|
1 |111 |
2 |222 |
Table h:
hh |kk |
---|----|
1 |111 |
2 |111 |
3 |222 |
Using Normal Left join:
SELECT
*
FROM a
LEFT JOIN h
ON a.bb = h.kk
;
I get:
aa |bb |hh |kk |
---|----|---|----|
1 |111 |1 |111 |
1 |111 |2 |111 |
2 |222 |3 |222 |
I want to get rid of first two rows, where aa = 1.
...
And second step would be for another query, probably with some case, where is table a I will filter out only those rows which have in table b more than 2 rows.
Therefore I want to create table c, where i will have:
aa |bb |
---|----|
1 |111 |
Can someone help me please?
Thank you.
To get only the 1:1 joins
SELECT a.aa,h.hh,h.kk FROM a
LEFT JOIN h ON a.bb = h.kk
GROUP BY bb HAVING COUNT(kk)=1
To get only the 1:n joins
SELECT a.aa,h.hh,h.kk FROM a
LEFT JOIN h ON a.bb = h.kk
GROUP BY bb HAVING COUNT(kk)>1

Aggregating a table based on one column and then joining it with another table

I am working with the following two tables;
Table 1
Key |Clicks |Impressions
-------------+-------+-----------
USA-SIM-CARDS|55667 |544343
DE-SIM-CARDS |4563 |234829
AU-SIM-CARDS |3213 |232242
UK-SIM-CARDS |3213 |1333223
CA-SIM-CARDS |4321 |8883111
MX-SIM-CARDS |3193 |3291023
Table 2
Key |Conversions |Final Conversions|Active Sims
-----------------+------------+-----------------+-----------
USA-SIM-CARDS |456 |43 |4
USA-SIM-CARDS |65 |2 |1
UK-SIM-CARDS |123 |4 |3
UK-SIM-CARDS |145 |34 |5
The goal is to get the following output;
Key |Clicks |Impressions|Conversions|Final Conversions|Active Sims
-------------+-------+-----------+-----------+-----------------+-----------
USA-SIM-CARDS|55667 |544343 |521 |45 |5
DE-SIM-CARDS |4563 |234829 | | |
AU-SIM-CARDS |3213 |232242 | | |
UK-SIM-CARDS |3213 |1333223 |268 |38 |8
CA-SIM-CARDS |4321 |8883111 | | |
MX-SIM-CARDS |3193 |3291023 | | |
The most crucial part of this function involves aggregating the second table based on conversions
I would then I imagine execute this with an inner join.
Thank you.
Take this in two steps then:
1) Aggregate the second table:
SELECT Key, sum(Conversions) as Conversions, sum("Final Conversions") as FinalConversions, Sum("Active Sims") as ActiveSims FROM Table2 GROUP BY key
2) Use that as a subquery/derived table joining to your first table:
SELECT
t1.key,
t1.clicks,
t1.impressions,
t2.conversions,
t2.finalConversions,
t2.ActiveSims
From Table1 t1
LEFT OUTER JOIN (SELECT Key, sum(Conversions) as Conversions, sum("Final Conversions") as FinalConversions, Sum("Active Sims") as ActiveSims FROM Table2 GROUP BY 2) t2
ON t1.key = t2.key;
As an alternative, you could join and then group by as well since there isn't any need to aggregate twice or anything:
SELECT
t1.key,
t1.clicks,
t1.impressions,
sum(Conversions) as Conversions,
sum("Final Conversions") as FinalConversions,
Sum("Active Sims") as ActiveSims
From Table1 t1
LEFT OUTER JOIN table2 t2
ON t1.key = t2.key
GROUP BY t1.key, t1.clicks, t1.impressions
The only other important thing here is that we are using a LEFT OUTER JOIN since we want all record from Table1 and any records from Table2 that match on the key.

Postgresql select, show fixed count rows

Simple question. I have a table "tablename" with 3 rows. I need show 5 rows in my select when count rows < 5.
select * from tablename
+------------------+
|colname1 |colname2|
+---------+--------+
|1 |AAA |
|2 |BBB |
|3 |CCC |
+---------+--------+
In this query I show all rows in the table.
But I need show 5 rows. 2 rows is empty.
For example (I need):
+------------------+
|colname1 |colname2|
+---------+--------+
|1 |AAA |
|2 |BBB |
|3 |CCC |
| | |
| | |
+---------+--------+
Last 2 rows is empty.
It is possible?
Something like this:
with num_rows (rn) as (
select i
from generate_series(1,5) i -- adjust here the desired number of rows
), numbered_table as (
select colname1,
colname2,
row_number() over (order by colname1) as rn
from tablename
)
select t.colname1, t.colname2
from num_rows r
left outer join numbered_table t on r.rn = t.rn;
This assigns a number for each row in tablename and joins that to a fixed number of rows. If you know that your values in colname1 are always sequential and without gaps (which is highly unlikely) then you can remove the generation of row numbers in the second CTE using row_number().
If you don't care which rows are returned, you can leave out the order by part - but then the rows that are matched will be random. Leaving out the order by will be a bit more efficient.
The above will always return exactly 5 rows, regardless of how many rows tablename contains. If you want at least 5 rows, then you need to flip the outer join:
....
select t.colname1, t.colname2
from numbered_table t
left outer join num_rows r on r.rn = t.rn;
SQLFiddle example: http://sqlfiddle.com/#!15/e5770/3

How to eliminate repeated field with GROUP BY clause?

I have 3 tables called:
1.app_tenant pk:id, fk:pasar_id
---+--------+-----------+
id | nama | pasar_id |
----+--------+-----------+
1 | joe | 1 |
2 | adi | 2 |
3 | adam | 3 |
2.app_pasar pk:id
----+------------- +
id | nama |
----+------------- +
1 | kosambi |
2 | gede bage |
3 | pasar minggu |
3.app_kios pk:id, fk:tenant_id
----+---------------+----------
id | nama |tenant_id
----+-------------- +----------
1 | kios1 |1
2 | kios2 |2
3 | kios3 |3
4 | kios4 |1
5 | kios5 |1
6 | kios6 |2
7 | kios7 |2
8 | kios8 |3
9 | kios9 |3
Then with a LEFT JOIN query and grouping by id in every table I want to displaying data like this:
----+---------------+------------+-----------
id | nama_tenant |nama_pasar |nama_kios
----+-------------- +------------------------
1 | joe |kosambi |kios 1
2 | adi |gede bage |kios 2
2 | adam |pasar minggu|kios 3
but after I execute this query, data are not shown as expected. The problem is
redundancy in the nama_tenant field. How can I eliminate repeated nama_tenantrecords?
This is my query:
select a.id,a.nama as nama_tenant,
b.nama as nama_pasar,
c.nama as nama_kios
from app_tenant a
left join app_pasar b on a.id=b.id
left join app_kios c on a.id= c.tenant_id
group by
a.id,
b.id,
c.id
Table definitions:
CREATE TABLE app_tenant (
id serial PRIMARY KEY,
nama character varying,
pasar_id integer);
CREATE TABLE app_kios (
id serial PRIMARY KEY,
nama character varying,
tenant_id integer REFERENCES app_tenant);
The problem is that tenants can have multiple kiosks. From your sample data it looks like you want to display the first kiosk of every tenant (although "first" is a vague concept on strings, here I use alphabetical sort order). Your query would be like this:
SELECT t.id, t.nama AS nama_tenant, p.nama AS nama_pasar, k.nama AS nama_kios
FROM app_tenant t
LEFT JOIN app_pasar p ON p.id = t.pasar_id
LEFT JOIN (
SELECT tenant_id, nama, rank() OVER (PARTITION BY tenant_id ORDER BY nama) AS rnk
FROM app_kios
WHERE rnk = 1) k ON k.tenant_id = t.id
ORDER BY t.id
The sub-query on app_kios uses a window function to get the first kiosk name after sorting the names of the kiosk for each tenant.
I would also suggest to use meaningful aliases for table names instead of simply a, b, c.

Replacing a comma seperate value in table with another in select query (postgres)

I have two tables, table A has ID column whose values are comma separated, each of those ID value has a representation in table B.
Table A
+-----------------+
| Name | ID |
+------------------
| A1 | 1,2,3|
| A2 | 2 |
| A3 | 3,2 |
+------------------
Table B
+-------------------+
| ID | Value |
+-------------------+
| 1 | Apple |
| 2 | Orange |
| 3 | Mango |
+-------------------+
I was wondering if there is an efficient way to do a select where the result would as below,
Name, Value
A1 Apple, Orange, Mango
A2 Orange
A3 Mango, Orange
Any suggestions would be welcome. Thanks.
You need to first "normalize" table_a into a new table using the following:
select name, regexp_split_to_table(id, ',') id
from table_a;
The result of this can be joined to table_b and the result of the join then needs to be grouped in order to get the comma separated list of the names:
select a.name, string_agg(b.value, ',')
from (
select name, regexp_split_to_table(id, ',') id
from table_a
) a
JOIN table_b b on b.id = a.id
group by a.name;
SQLFiddle: http://sqlfiddle.com/#!12/77fdf/1
There are two regex related functions that can be useful:
http://www.postgresql.org/docs/current/static/functions-string.html
regexp_split_to_table()
regexp_split_to_array()
Code below is untested, but you'd use something like it to match A and B:
select name, value
from A
join B on B.id = ANY(regexp_split_to_array(A.id, E'\\s*,\\s*', 'g')::int[]))
You can then use array_agg(value), grouping by name, and format using array_to_string().
Two notes, though:
It won't be as efficient as normalizing things.
The formatting itself ought to be done further down, in your views.