Combining two view into one result set with transform? - sql

I have a couple of views that generates the following two outputs in SQL Server.
First one (Flats output) shows the number of flats in a particular town with Tileroofs and Brickwalls. Second one shows the same, but for houses.
What I'm trying to do is to create a final table that looks like the 3rd example where the flats and house counts are combined with the corresponding Tileroof and Brickwall combinations.
I have tried union and then grouping, but I'm really struggling to get the Flats and Houses count columns side by side. Is anyone able to help please?
Thanks
--View one
| Town | Flats | TileRoofs | Brick Wall |
-----------------------------------------
| A | 3 | Y | N |
| A | 4 | N | Y |
| A | 8 | N | N |
--View two
| Town | Houses | TileRoofs | Brick Wall |
------------------------------------------
| A | 1 | Y | Y |
| A | 2 | Y | N |
| A | 5 | N | Y |
| A | 2 | N | N |
--Prefered output, by combining the two--
| Town | Flats | Houses | TileRoofs | Brick Wall |
--------------------------------------------------
| A | 0 | 1 | Y | Y |
| A | 3 | 2 | Y | N |
| A | 4 | 5 | N | Y |
| A | 8 | 2 | N | N |

Full outer join might help here.
select isnull(a.Town, b.Town) Town,
isnull(a.TileRoofs, b.TileRoofs) TileRoofs,
isnull(a.[Brick wall], b.[Brick wall]) [Brick wall],
isnull(a.Flats, 0) Flats,
isnull(b.Houses, 0) Houses
from ViewOne a
full outer join ViewTwo b
on a.Town = b.Town
and a.TileRoofs = b.TileRoofs
and a.[Brick wall] = b.[Brick wall]

select
v2.Town ,coalesce(v1.flat,0) as flat,v2.houses,v2.TileRoofs, v2.Brick, v2.Wall
from
view2 as v2 left join view1 as v1
on v1.town=v2.town

You may be after a full outer join
select
houses.town,
flats.flats,
houses.houses,
houses.BrickWall,
houses.TileRoofs
from flats
full outer join houses
on houses.town=flats.town
and houses.TileRoofs = flats.TileRoofs
and houses.BrickWall = flats.BrickWall

Related

For each unique entry, include all rows from another list

I have 2 tables as such:
cars: contains price of some parts for each car
| Car | Parts | Price |
| -------- | -------------- | -------|
| A | Windshield | 100 |
| A | Rims | 50 |
| B | Bumper | 200 |
| B | Rims | 60 |
parts: contains all possible parts for a car
| Parts |
|--------------|
| Windshield |
| Rims |
| Bumper |
| Headlights |
I want each car in cars to have every entry in parts. The end result should look like this:
| Car | Parts | Price |
| -------- | -------------- | -------|
| A | Windshield | 100 |
| A | Rims | 50 |
| A | Bumper | 0 |
| A | Headlights | 0 |
| B | Bumper | 200 |
| B | Rims | 60 |
| B | Windshield | 0 |
| B | Headlights | 0 |
Any ideas on how I could do this?
PS: The order matters less
You may use a calendar table approach:
SELECT c.Car, p.Parts, COALESCE(t.Price, 0) AS Price
FROM (SELECT DISTINCT Car FROM cars) c
CROSS JOIN parts p
LEFT JOIN cars t
ON t.Car = c.Car AND t.Parts = p.Parts
ORDER BY c.Car, p.Parts;
But as #Larnu has correctly pointed out in his comment, your schema should have a separate table containing all cars. This would avoid the distinct select I have in my answer above.
If you want to insert the additional rows into cars, then the code would look like:
insert into cars (car, parts, price)
select c.car, p.part, 0
from (select distinct car from cars) c cross join
parts p
where not exists (select 1
from cars c2
where c2.car = c.car and c2.part = c.part
);
This generates all combinations of cars and parts. It then filters out the ones that don't already exist in cars.
Note that left join and not exists are pretty much the same in this context. In an insert query, though, I usually use not exists because I think the intention is clearer.

Remove CROSS JOIN LATERAL from postgres query that spans many to many

I have the following three tables (many to many):
Location
+====+==============+===+===+=============+
| id | coord_system | x | y | last_update |
+====+==============+===+===+=============+
| | | | | |
+----+--------------+---+---+-------------+
Mapping
+=============+============+
| location_id | history_id |
+=============+============+
| | |
+-------------+------------+
History
+====+=======+======+
| id | speed | date |
+====+=======+======+
| | | |
+----+-------+------+
The location table represents physical x, y locations within a specific coordinate system. For each x, y location at least one row in the history table exists. Each row in the history table can point to multiple rows in the location table.
Important to note is that (coord_system, x, y) is indexed and is unique. I don't think it makes a difference but all ids and coord_system are UUIDs. In the code examples below I will use letter to make it easier to read. The location and history have additional columns, but do not change the scope of the question. The last_update column on the location table should match the date column on the History table (I come back to this later in the post).
The goal is to fetch the most recent history row for a range of (coor_system, x, y). Currently this is done with a CROSS JOIN LATERAl, like
SELECT *
FROM location loc
CROSS JOIN LATERAL
(SELECT *
FROM history hist
LEFT JOIN mapping map ON hist.id = map.history_id
WHERE map.location_id = loc.id
ORDER BY date DESC limit(1)) AS records
WHERE loc.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
AND (x >= 403047
AND x <= 404047)
AND (y >= 16451337
AND y <= 16452337);
For this specific range of x, y and coord_system the query takes ~25 seconds to run and returns 182 351 rows.
I am not extremely experienced in SQL, but thought that the goal of this query could also be achieved using a regular join. If I do a join across the three tables, with the same x, y and coord_system "filters" it takes about 2 seconds and returns ~3 million rows. I tried to be clever and use the dates to prune down the result:
SELECT *
FROM history hist
RIGHT JOIN mapping map ON hist.id = map.history_id
RIGHT JOIN location loc ON loc.id = map.location_id
WHERE loc.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
AND (x >= 403047
AND x <= 404047)
AND (y >= 16451337
AND y <= 16452337)
AND location.last_update = hist.date
This got very close to the same result as the original query. The result was 182 485 rows in ~3 seconds. Unfortunately the result needs to be exactly the same. I am guessing I made a logical mistake in the query I made and came here hoping someone can point it out.
My question is: is there a clever way that will allow a join to take only the rows that have the "newest" date from the history.date column? As is expected I am trying to make the query run as quickly as possible while maintaining the correct result set.
In the table below I show a toy example of the join and the results I would expect (marked in the "return_row" column).
+=============+==============+===+===+=============+============+============+=======+============+============+
| location.id | coord_system | x | y | location_id | history_id | history.id | speed | date | return_row |
+=============+==============+===+===+=============+============+============+=======+============+============+
| 0 | a | 1 | 1 | 0 | 0 | 0 | 3.0 | 2020/10/31 | * |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 0 | a | 1 | 1 | 0 | 1 | 1 | 3.1 | 2020/10/30 | |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 0 | a | 1 | 1 | 0 | 2 | 2 | 3.2 | 2020/10/29 | |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 1 | a | 1 | 2 | 1 | 3 | 3 | 3.1 | 2020/10/31 | * |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 1 | a | 1 | 2 | 1 | 4 | 4 | 3.0 | 2020/10/30 | |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 2 | a | 2 | 2 | 2 | 5 | 5 | 4 | 2020/10/31 | * |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
| 3 | b | 1 | 1 | 3 | 6 | 6 | 5 | 2020/10/1 | * |
+-------------+--------------+---+---+-------------+------------+------------+-------+------------+------------+
Does it work better with DISTINCT ON?
SELECT DISTINCT ON (l.id) l.id, h.date, ... -- enumerate the columns here
FROM location l
LEFT JOIN mapping m ON m.location_id = l.id
LEFT JOIN history h ON h.id = m.history_id
WHERE
l.coord_system = '43330ccc-3f42-4f05-8ec5-18cb659bfd2d'
AND l.x BETWEEN 403047 AND 404047
AND l.y BETWEEN 16451337 AND 16452337
ORDER BY l.id, h.date DESC

Select from a concatenation of two columns after a left join

Problem description
Let the tables C and V have those values
>> Table V <<
| UnID | BillID | ProductDesc | Value | ... |
| 1 | 1 | 'Orange Juice' | 3.05 | ... |
| 1 | 1 | 'Apple Juice' | 3.05 | ... |
| 1 | 2 | 'Pizza' | 12.05 | ... |
| 1 | 2 | 'Chocolates' | 9.98 | ... |
| 1 | 2 | 'Honey' | 15.98 | ... |
| 1 | 3 | 'Bread' | 3.98 | ... |
| 2 | 1 | 'Yogurt' | 8.55 | ... |
| 2 | 1 | 'Ice Cream' | 7.05 | ... |
| 2 | 1 | 'Beer' | 9.98 | ... |
| 2 | 2 | 'League of Legends RP' | 40.00 | ... |
>> Table C <<
| UnID | BillID | ClientName | ... |
| 1 | 1 | 'Alexander' | ... |
| 1 | 2 | 'Tom' | ... |
| 1 | 3 | 'Julia' | ... |
| 2 | 1 | 'Tom' | ... |
| 2 | 2 | 'Alexander' | ... |
Table C have the values of each product, which is associated with a bill number. Table V has the relationship between the client name and the bill number. However, the bill number has a counter that is dependent on the UnId, which is the store unity ID. That being said, each store has it`s own Bill number 1, number 2, etc. Also, the number of bills from each store are not equal.
Solution description
I'm trying to make select between the C left join V without sucess. Because each BillID is dependent on the UnID, I have to make the join considering the concatenation between those two columns.
I've used this script, but it gives me an error.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
CONCAT(C.UnID, C.BillID) = CONCAT(V.UnID, V.BillID)
GROUP BY
V.ClientName
and SQL server returns me this 'CONCAT' is not a recognized built-in function name.
I'm using Microsoft SQL Server 2008 R2
Is the use of CONCAT wrong? Or is it the way I tried to SELECT? Could you give me a hand?
[OBS: The tables I've present you are just for the purpose of explaining my difficulties. That being said, if you find any errors in the explanation, please let me know to correct them.]
You should be joining on the equality of the UnID and BillID columns in the two tables:
SELECT
c.ClientName,
COALESCE(SUM(v.Value), 0) AS total
FROM C c
LEFT JOIN V v
ON c.UnID = v.UnID AND
c.BillID = v.BillID
GROUP BY
c.ClientName;
In theory you could try joining on CONCAT(UnID, BillID). However, you could run into problems. For example, UnID = 1 with BillID = 23 would, concatenated together, be the same as UnID = 12 and BillID = 3.
Note: We wrap the sum with COALESCE, because should a given client have no entries in the V table, the sum would return NULL, which we then replace with zero.
concat is only available in sql server 2012.
Here's one option.
SELECT
SUM(C.Value),
V.ClientName
FROM
C
LEFT JOIN
V
ON
cast(C.UnID as varchar(100)) + cast(C.BillID as varchar(100)) = cast(V.UnID as varchar(100)) + cast(V.BillID as varchar(100))
GROUP BY
V.ClientName

SQL Query to Work out Every Product Combination

I require a SQL query to work out every product combination.
I have three product categories (game, accessory, upgrade) and products assigned to each of these three categories:
+----+------------+-----------+------------+
| id | category | product | prod_code |
+----+------------+-----------+------------+
| 1 | game | GTA | 100 |
| 2 | game | GTA1 | 200 |
| 3 | game | GTA2 | 300 |
| 4 | accessory | Play Pad | 400 |
| 5 | accessory | Xbox Pad | 500 |
| 6 | upgrade | Memory | 600 |
| 6 | upgrade | drive | 700 |
+----+------------+-----------+------------+
I want to take one product from each of the categories and work out every single combination:
+----+--------------+
| id | combinations |
+----+--------------+
| 1 | 100,400,600 |
| 2 | 100,500,600 |
| 3 | 100,400,700 |
| 4 | 100,500,700 |
| ? | etc |
+----+--------------+
How would I go about doing this?
Thanks in advance, Stuart
Use a CROSS JOIN:
SELECT CONCAT(t1.[prod_code], ',',
t2.[prod_code], ',',
t3.[prod_code])
FROM (
SELECT [prod_code]
FROM mytable
WHERE category = 'game') AS t1
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'accessory') AS t2
CROSS JOIN (
SELECT [prod_code]
FROM mytable
WHERE category = 'upgrade') AS t3
ORDER BY t1.[prod_code], t2.[prod_code], t3.[prod_code]
CROSS JOIN of derived tables, one for each category, produces the following cartesian product: 'game' products x 'accessory' products x 'upgrade' products
Demo here

SQL join on multiple levels

I'm struggle with a SQL join for parent client records and just posted a query, but I realised after posting it that my example was slightly wrong (but still a useful post) so i've created a new one, that's more accurate :)
If i have the following database structure:
Table Regions
|Region_no | Region_Level | owning_region_no |
| 1 | 1 | |
| 2 | 2 | 1 |
| 3 | 2 | 1 |
| 4 | 3 | 2 |
| 5 | 3 | 2 |
| 6 | 3 | 3 |
Table Postcodes
| Postcode | Region_no |
| PO32 3AE | 4 |
| PO32 3AA | 5 |
| PO32 3AF | 6 |
Table UnitsMappings
| Unit_No | region_no |
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 1 |
| 3 | 3 |
| 3 | 3 |
| 4 | 6 |
| 4 | 5 |
Table Units
| Unit_no | Unit_Name |
| 1 | South |
| 2 | SouthEast |
| 3 | Central |
| 4 | SouthWest |
[Updated sample answer]
What I really want, is the following:
| Unit_name | Postcode |
| South | PO32 3AE |
| South | PO32 3AA |
| South | PO32 3AF |
| SouthEast | PO32 3AE |
| SouthEast | PO32 3AA |
| SouthEast | PO32 3AF |
Even though South has only regions 1 and 2 mapped to it, the level 3's are mapped to the level 2's and then 1's (and the postcodes are mapped to the level 3's)
Now the difficulty is, that i just want the postcodes (from the postcode tables) which are associated to the level 3 regions levels in regions. So there may be a region level 1 associated to a unit_no, but i need all of the postcodes at the bottom that are mapped to the level 3.
There is no valid unit name in you test data for the region in region level 3.
The region with region level 3 are 4, 5 and 6.
There is no unit associated with region 4, the unit associated with region 5 and 6 is the unit 4, but in the table unit_name there is no unit 4.
If you have the data then this query will get the data
SELECT pc.postcode, u.unit_name
FROM regions reg
INNER JOIN Postcodes pc ON reg.region_no = pc.region_no
INNER JOIN UnitsMappings um ON reg.region_no = um.region_no
INNER JOIN Units u ON um.unit_no = u.unit_no
WHERE reg.region_level = 3
changing the join on Units from INNER to LEFT will get you the postcodes with NULL as unit_name with the test data.
based on your details here's my mapping ,
unit => units_mapping
units_mapping => (Postcodes or regions) // assuming using all table we use postcodes
Postcodes => regions
regions (main)
select reg.region_no, Units.name , pc.postcodes
from regions reg,
Postcodes pc,
UnitsMappings UM,
Units units
where reg.region_no = pc.region_no and
pc.region_no = UM.region_no and
UM.Unit_No = Units.Unit_no
order by reg.region_no asc
if there is null in one table, the record won't appear ...
to handle use inner join
not yet tested ..... just assume :)
Try this,
Select u.Unit_name,pc.Postcode from Units as u
inner join UnitsMappings um on u.Unit_no = um.Unit_no
inner join Postcodes as pc on pc.Region_no = um.region_no