UNION ALL not performing as expected - Oracle SQL - sql

I have two tables:
tableA
| Part | Val |
|:----:|:---:|
| AA | 3 |
| AB | 2 |
| AC | 11 |
| AD | 6 |
| AE | 3 |
tableB
| Part | Val |
|:----:|:---:|
| AC | 9 |
| AF | 5 |
| AG | 1 |
| AH | 10 |
| AI | 97 |
I would like to union these tables to achieve this result:
| Part | ValA | ValB |
|:----:|:----:|:----:|
| AA | 3 | 0 |
| AB | 2 | 0 |
| AC | 11 | 9 |
| AD | 6 | 0 |
| AE | 3 | 0 |
| AF | 0 | 5 |
| AG | 0 | 1 |
| AH | 0 | 10 |
| AI | 0 | 97 |
I have tried:
SELECT * FROM tableA
UNION ALL
SELECT * FROM tableB
But that results in only one column of vals, which I do not want.
How can I merge these tables and create two columns, one for each table, where if the part does not appear in the other table, its value can just be 0?
SQL FIDDLE for reference.

It appears that you want to join the tables, not union them
select nvl(a.Part, b.Part) as Part,
nvl( a.Val, 0 ) as ValA,
nvl( b.Val, 0 ) as ValB
from tableA a
full outer join tableB b
on( a.Part = b.Part )
order by 1
Note that using case-sensitive identifiers like you do in your fiddle is generally frowned upon. It tends to make writing queries more complicated than it needs to be and it tends to get annoying to have to include the double quotes around every column name.
Demo

You can try below -
select part,max(valA),max(valB) from
(
select part, val as valA, 0 as valB from tableA
union all
select part, 0 , val from tableB
)A group by part

Related

Calculate a column value backwards over a series of previous rows/RECURSIVE/CONNECTED BY

need your help. I guess/hope there is a function for that. I found "CONNECT DBY" and "WITH RECURSIVE AS ..." but it doesn't seem to solve my problem.
GIVEN TABLES:
Table A
+------+------------+----------+
| id | prev_id | date |
+------------------------------+
| 1 | | 20200101 |
| 23 | 1 | 20200104 |
| 34 | 23 | 20200112 |
| 41 | 34 | 20200130 |
+------------------------------+
Table B
+------+-----------+
| ref_id | key |
+------------------+
| 41 | abc |
+------------------+
(points always to the lates entry in table "A". Update, no history)
Join Statement:
SELECT
id, prev_id, key, date
FROM A
LEFT OUTER JOIN B ON B.ref_id = A.id
GIVEN psql result set:
+------+------------+----------+-----------+
| id | prev_id | key | date |
+------------------------------+-----------+
| 1 | | | 20200101 |
| 23 | 1 | | 20200104 |
| 34 | 23 | | 20200112 |
| 41 | 34 | abc | 20200130 |
+------------------------------+-----------+
DESIRED output:
+------+------------+----------+-----------+
| id | prev_id | key | date |
+------------------------------+-----------+
| 1 | | abc | 20200101 |
| 23 | 1 | abc | 20200104 |
| 34 | 23 | abc | 20200112 |
| 41 | 34 | abc | 20200130 |
+------------------------------+-----------+
The rows of the result set are connected by columns 'id' and 'prev_id'.
I want to calculate the "key" column in a reasonable time.
Keep in mind, this is a very simplified example. Normally there are a lot of more rows and different keys and id's
I understand that you want to bring the hierarchy of each row in tableb. Here is one approach using a recursive query:
with recursive cte as (
select a.id, a.prev_id, a.date, b.key
from tablea a
inner join tableb b on b.ref_id = a.id
union all
select a.id, a.prev_id, a.date, c.key
from cte c
inner join tablea a on a.id = c.prev_id
)
select * from cte

Transposing Data SQL

The data looks similar to this:
+----+------+-----------+-------+---------+---------+--------+
| ID | Unit | Floorplan | Sq Ft | Name | Amenity | Charge |
+----+------+-----------+-------+---------+---------+--------+
| 1 | 110 | A1 | 750 | Alan | GARAGE | 50 |
| 2 | | | | | RENT | 850 |
| 3 | | | | | PEST | 2 |
| 4 | | | | | TRASH | 15 |
| 5 | | | | | TOTAL | 20 |
| 6 | 111 | A2 | 760 | Bill | STORAGE | 35 |
| 7 | | | | | GARAGE | 50 |
| 8 | | | | | RENT | 850 |
| 9 | | | | | PEST | 2 |
| 10 | | | | | TOTAL | 15 |
| 11 | 112 | A3 | 770 | Charlie | PETRENT | 20 |
| 12 | | | | | STORAGE | 35 |
| 13 | | | | | GARAGE | 50 |
| 14 | | | | | RENT | 850 |
| 15 | | | | | TOTAL | 2 |
+----+------+-----------+-------+---------+---------+--------+
I am new to SQL and trying my best using Microsoft Access, but I need help.
The data needs to look like this:
My first step is to separate the units from the rest with
SELECT * FROM table WHERE Unit <> NULL;
and after that I've usually just hard-input the rest.
My idea was as follows:
INSERT INTO table
VALUES (NULL,NULL,...,'Pest',$2)
FROM table
WHERE NOT EXIST 'Pest' BETWEEN x AND y
/* where x = Total 1 and y = Total 2*/
Am I on the right track? I probably need a loop or a join, but I'm not at that level yet.
You can use a crosstab query, though a bit convoluted it is:
TRANSFORM
Sum(TableUnit.Charge) AS SumOfCharge
SELECT
S.Unit,
S.Floorplan,
S.SqFt,
S.Name,
S.Amenity
FROM
TableUnit,
(SELECT
Q.Id,
Val(DMax("Id","TableUnit","Id<=" & Q.[Id] & " And Unit Is Not Null")) AS ParentId
FROM TableUnit As Q) AS T,
(SELECT
TableUnit.Id,
TableUnit.Unit,
TableUnit.Floorplan,
TableUnit.SqFt,
TableUnit.Name,
TableUnit.Amenity
FROM
TableUnit
WHERE
TableUnit.Unit Is Not Null) AS S
WHERE
TableUnit.Id=[T].[Id]
AND
T.ParentId)=[S].[Id]
GROUP BY
T.ParentId,
S.Unit,
S.Floorplan,
S.SqFt,
S.Name,
S.Amenity
PIVOT
TableUnit.Amenity In
("Garage","Pest","Trash","PetRent","Storage","Rent");
Your test data differs a little from your expected output, so:
My MSAccess is rather rusty, but something like this should work:
SELECT t0.Unit, t0.Floorplan, t0.[Sq Ft], t0.Name, t0.Amenity
, SUM(IIF(tM.Amenity = 'GARAGE', Charge, 0)) AS [Garage]
, SUM(IIF(tM.Amenity = 'PEST', Charge, 0)) AS [Pest]
FROM (
SELECT t1.id AS id0, MIN(t2.id) AS idN
FROM t AS t1
INNER JOIN t AS t2 ON t1.id < t2.id
WHERE t1.Unit <> '' AND t2.Unit <> ''
) AS groups
INNER JOIN t AS t0 ON t0.id = groups.id0
LEFT JOIN t AS tM ON tM.id > groups.id0 AND tm.id < groups.idN
GROUP BY t0.Unit, t0.Floorplan, t0.[Sq Ft], t0.Name, t0.Amenity
;
Though, if I remember correctly, and it hasn't changed in newer versions; you can't have true subqueries and will need to make groups a separate query you can join to as if it were a table/view.

Return a limit of 2 records for each distinct column value

Assume I have a table that looks like this:
| Col A | Col B | Col C |
|-------|-------|-------|
| 1 | A | 54 |
| 1 | A | 56 |
| 1 | B | 55 |
| 1 | B | 51 |
| 1 | C | 36 |
| 1 | C | 23 |
| 1 | D | 62 |
| 1 | D | 11 |
| 2 | B | 88 |
| 2 | B | 17 |
| 2 | C | 56 |
| 2 | C | 86 |
| 2 | D | 47 |
| 2 | D | 29 |
What I want to do is grab the table to look like this:
| Col A | Col B | Col C |
|-------|-------|-------|
| 1 | A | 54 |
| 1 | A | 56 |
| 2 | B | 88 |
| 2 | B | 17 |
I'm pretty sure there is a way to do this, I just don't know how. First, I thought a DISTINCT ON selector would work, but that only returns one record for each value. In this case, I need two records for each value.
One way to do this would be to use a window function to add a row number to each partition of data ordered by however you want and then select the anything with a row number less than 2.
With CTE AS (
SELECT colA, ColB, ColC, Row_Number() over (Partition by ColA ORDER By ColB , ColC) RN
FROM Table)
Select * from cte where RN <=2
Since I didn't know what values of c you wanted, I choose to order by colC (ascending) so the lowest values of C would be returned for a given A+B combination.
with
grp as (select col_a from table group by col_a) -- It should be only index scan, not scanning the whole table
select * from grp join lateral (
select * from table
where grp.col_a = table.col_a
order by <desired order here>
limit 2) on true -- It also avoiding the full scan if properly indexes provided

find other columns value based on maximum of one column using groupby particular column

I have data like below
+-------+---------+--------+
| Count | Mindif | Device |
+-------+---------+--------+
| 45 | 3 | A |
| 78 | 4 | A |
| 52 | 5 | A |
| 24 | 6 | A |
| 22 | 1 | B |
| 22 | 2 | B |
| 34 | 3 | B |
| 37 | 4 | B |
| 52 | 5 | B |
| 34 | 6 | B |
| 13 | 1 | C |
| 30 | 2 | C |
| 57 | 3 | C |
| 111 | 4 | C |
| 35 | 5 | C |
+-------+---------+--------+
Want to find Mindif and device based on max value of count.
Output be like
+-------+---------+--------+
| Count | Mindif | Device |
+-------+---------+--------+
| 78 | 4 | A |
| 52 | 5 | B |
| 111 | 4 | C |
+-------+---------+--------+
You can use a query like this:
SELECT t1.Count, t1.Mindif, t1.Device
FROM mytable AS t1
JOIN (
SELECT Device, MAX(Count) AS Count
FROM mytable
GROUP BY Device
) AS t2 ON t1.Device = t2.Device AND t1.Count = t2.Count
The query uses a derived table that returns the max Count value per Device. Joining back to the original table we can get the desired result.
using Window Function
SELECT Count, Mindif, Device
FROM
(SELECT Count, Mindif, Device,
rank() over (order by Count desc) as r
FROM table) S
WHERE S.r = 1;
OR
Simple Join with MAX
SELECT a.* FROM table a
LEFT SEMI JOIN
(SELECT MAX(Count)Cnt
FROM table)b on (a.Count = b.Cnt)

Joining multiple tables to produce one data source

I have four tables with similar format but different values.
Table A
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 2
| 16 | 10/12/24 | 1
| 17 | 10/13/24 | 1
Table B
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 1
| 17 | 10/16/24 | 1
| 18 | 10/17/24 | 1
Table C
| ID | Date | Photo
| 14 | 10/10/24 | 1
| 15 | 10/11/24 | 4
| 19 | 10/18/24 | 4
| 20 | 10/19/24 | 1
I need to get one data source that looks like this below, that is a full outer join of the above tables, where the ID and Date fields as the only fields with non values.
Table C
| ID | Date | Photo | Image | Cat
| 14 | 10/10/2014 | 1 | 1 | 1
| 15 | 10/11/2014 | 2 | 1 | 4
| 16 | 10/12/2014 | 1 | NULL | NULL
| 17 | 10/16/2014 | NULL | 1 | NULL
| 18 | 10/14/2014 | NULL | NULL | NULL
| 18 | 10/17/2014 | NULL | 1 | NULL
| 19 | 10/15/2014 | NULL | NULL | 4
| 20 | 10/16/2014 | 1 | NULL | NULL
| 20 | 10/19/2014 | NULL | NULL | 1
You mention FULL JOIN so I'm assuming you use a database that supports them. You can use COALESCE() to return the populated ID and Date, and also to simplify your JOIN criteria:
SELECT COALESCE(a.ID,b.ID,c.ID) AS ID
,COALESCE(a.Date,b.Date,c.Date) AS Date
,a.Photo
,b.Image
,c.Cat
FROM TableA a
FULL JOIN TableB b
ON a.ID = b.ID AND a.Date = b.Date
FULL JOIN TableC c
ON COALESCE(a.ID,b.ID) = c.ID AND COALESCE(a.Date,b.Date) = c.Date
You can use a FULL OUTER JOIN and COALESCE or NVL to ensure that the ID and Date columns are not null.
SELECT COALESCE(a.ID, b.ID,c.ID) AS ID,
COALESCE(a."Date", b."Date", c."Date") AS "Date",
a.Photo,
b.Image,
c.Cat
FROM TableA a
FULL OUTER JOIN
TableB b
ON ( a.ID = b.ID )
FULL OUTER JOIN
TableC c
ON ( c.ID = COALESCE( a.ID, b.ID ) );
SQLFIDDLE