How to left join two tables on specific conditions - sql

I have two tables Price(Type, Values) and Product(Seat) and some values.
Price | Product
-------------+---------
Type Values | Seat
S 4 | FO
P 6 | CA
| FA
I know that [FO] and [CA] belong to type [P], and [FA] belongs to type [S]. How can I join these tables and shows associated type and values:
Results
Seat Type Values
----- ----- -----------
FO P 6
CA P 6
FA S 4

You can join the tables like this:
select pr.seat, sum(p.value)
from price p join
product pr
on pr.seat in ('FO', 'CA') and p.type = 'P' or
pr.seat in ('FA') and p.type = 'S'
group by pr.seat;
That said, you should have a proper table that connects the seats to the products, probably called ProductSeats with one row per product and matching seat.

I would use a derived table to store the mapping between price and seat. This is easily extensible when new requirements come up.
SELECT pri.*, pro.*
FROM price pri
INNER JOIN (
SELECT 'FO' seat, 'P' price
UNION ALL SELECT 'CA' seat, 'P' price
UNION ALL SELECT 'FA' seat, 'S' price
) map ON map.pri = pri.price
INNER JOIN product pro ON pro.seat = map.pro
This can be simplified by using the VALUES() syntax:
SELECT pri.*, pro.*
FROM price pri
INNER JOIN (
VALUES('FO', 'P'), ('CA', 'P'), ('FA', 'S')
) AS map(seat, price) ON map.pri = pri.price
INNER JOIN product pro ON pro.seat = map.pro

Related

Join table using column value as table name

Is it possible to join a table whereby the table name is a value in a column?
Here is a TABLE called food:
id food_name price_table pricing_reference_id
1 | 'apple' | 'daily_price' | 13
2 | 'banana' | 'monthly_price' | 13
3 | 'hotdog' | 'weekly_price' | 17
4 | 'sandwich' | 'monthly_price' | 9
There are three other tables (pricing tables): daily_price, weekly_price, and monthly_price tables.
Side note: Despite their names, the three pricing tables display vastly different kinds of information, which is why the three tables were not merged into one table
Each row in the food table can only be joined with one of the three pricing tables at most.
The following does not work -- it is just to illustrate what I am trying to get at:
SELECT *
FROM food
LEFT JOIN food.price_table ON food.pricing_reference_id = daily_price.id
WHERE id = 1;
Obviously the query does not work. Is there any way that the name of the table in the price_table column could be used as the table name in a join?
I would suggest left joins:
select f.*,
coalesce(dp.price, wp.price, mp.price) as price
from food f left join
daily_price dp
on f.pricing_reference_id = dp.id and
f.pricing_table = 'daily_price' left join
weekly_price wp
on f.pricing_reference_id = wp.id and
f.pricing_table = 'weekly_price' left join
monthly_price mp
on f.pricing_reference_id = mp.id and
f.pricing_table = 'monthly_price' ;
For the columns you reference, you need to use coalesce() to combine the results from the three tables. You say that the tables have different data, so you would need to list the columns separately.
The main reason I recommend this approach is performance. I think the left joins should be faster than any solution that uses union all.
Could you get your expected result using by a derived table with UNION SELECT which has a column of each table name?
SELECT *
FROM food
LEFT JOIN
(
SELECT 'daily_price' AS price_table, * FROM daily_price
UNION ALL SELECT 'monthly_price', * FROM monthly_price
UNION ALL SELECT 'weekly_price', * FROM weekly_price
) t
ON food.price_table = t.price_table AND
food.pricing_reference_id = t.id
ORDER BY food.id;
dbfiddle

Most popular pairs of shops for workers from each company

I've got 2 tables, one with sales and one with companies:
Sales Table
Transaction_Id Shop_id Sale_date Client_ID
92356 24234 11.09.2018 12356
92345 32121 11.09.2018 32121
94323 24321 11.09.2018 21231
94278 45321 11.09.2018 42123
Company table
Client_ID Company_name
12345 ABC
13322 ABC
32321 BCD
22221 BCD
What I want to achieve is distinct count of Clients from each Company for each pair of shops(Clients who had at least 1 transaction in both of shops) :
Shop_Id_1 Shop_id_2 Company_name Count(distinct Client_id)
12356 12345 ABC 31
12345 14278 ABC 23
14323 12345 BCD 32
14278 12345 BCD 43
I think that I have to use self join, but my queries even with filter for one week is killing DB, any thoughts on that? I'm using Microsoft SQL server 2012.
Thanks
I think this is a self-join and aggregation, with a twist. The twist is that you want to include the company in each sales record, so it can be used in the self-join:
with sc as (
select s.*, c.company_name
from sales s join
companies c
on s.client_id = c.client_id
)
select sc1.shop_id, sc2.shop_id, sc1.company_name, count(distinct sc1.client_id)
from sc sc1 join
sc sc2
on sc1.client_id = sc2.client_id and
sc1.company_name = sc2.company_name
group by sc1.shop_id, sc2.shop_id, sc1.company_name;
I think there are some issues with your question. I interpreted it as such that the company table contains the shop ID's, not the ClienId's.
First you can create a solution to get the shops as rows for each company. Here I chose a maximum of 5 shops per company. Don't forget the semicolon in the previous statement before the cte's.
WITH CTE_Comp AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY CompanyName ORDER BY ShopID) AS RowNumb
FROM Company AS C
)
SELECT C1.ShopID,
C2.ShopID AS ShopID_2,
C3.ShopID AS ShopID_3,
C4.ShopID AS ShopID_4,
C5.ShopID AS ShopID_5,
C1.CompanyName
INTO ShopsByCompany
FROM CTE_Comp AS C1
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C2.CompanyName AND RowNumb = 2
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C3.CompanyName AND RowNumb = 3
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C4.CompanyName AND RowNumb = 4
LEFT JOIN CTE_Comp AS C2 ON C1.CompanyName= C5.CompanyName AND RowNumb = 5
WHERE C1.RowNumb = 1
After that, in a few steps, I think you could get the desired result:
WITH ClientsPerShop AS
(
SELECT ShopID,
COUNT (DISTINCT ClientID) AS TotalClients
FROM Sales
GROUP BY ShopID
)
, ClienstsPerCompany AS
(
SELECT CompanyName,
SUM (TotalClients) AS ClientsPerComp
FROM Company AS C
INNER JOIN ClientsPerShop AS CPS ON C.ShopID = CPS.ShopID
GROUP BY CompanyName
)
SELECT *
FROM ClienstsPerCompany AS CPA
INNER JOIN ShopsByCompany AS SBC ON SBC.CompanyName = CPA.CompanyName
Hopefully this will bring you closer to your solution, best of luck!

SQL join on multiple Rows to Single row

i am struggling to do a join in the way that I need, I have two tables that house data I need to link(just to be clear). One table holds Outbound Call data, number dialed, duration ect. the other table holds contact details of the people dialed. Set out as below:
CustID | Number 1 | Number 2 | Number 3 | Number 4
1 | 072454584 | | 017726593 |
2 | |0125456852| | 0125785448
So if we wanted to call Customer 1 we would try both numbers, with only one connecting.
What i need to do is join the number dialed to the customer records but so that it compares each number till it matches match (hoping this makes sense). I've tried a case when Statement but it didn't work. Whats the best approach in doing this?!?!
I would probably take this approach to the query.
with myphones
AS
(
SELECT CustomerId, Phone1 As Phone FROM ContactDetails
UNION
SELECT CustomerId, Phone2 As Phone FROM ContactDetails
UNION
SELECT CustomerId, Phone3 As Phone FROM ContactDetails
UNION
SELECT CustomerId, Phone4 As Phone FROM ContactDetails
)
SELECT p.CustomerId, p.Phone, oc.*
FROM myphones p
INNER JOIN outboundcalls oc ON p.Phone = oc.Phone
You want to use a series of left outer join's with a conditional statement on the match:
select cd.CustId,
coalesce(oc1.number, oc2.number, oc3.number, oc4.number) as MatchingNumber,
(case when oc1.Number is not null then 'Number1'
when oc2.Number is not null then 'Number2'
when oc3.Number is not null then 'Number3'
when oc4.Number is not null then 'Number4'
end) as WhichMatch
from ContactDetails cd left outer join
OutboundCalls oc1
on cd.number1 = oc1.number left outer join
OutboundCalls oc2
on cd.number2 = oc2.number left outer join
OutboundCalls oc3
on cd.number3 = oc3.number left outer join
OutboundCalls oc4
on cd.number4 = oc4.number;
The left outer join attempts to match to each number in the list. The coalesce() will choose the first matching number, and the case tells you which number matches.
Note that if you have multiple successful outbound calls for a given customer, you will get multiple rows in the output.

How to ensure outer join with filter still returns all desired rows?

Imagine I have two tables in a DB like so:
products:
product_id name
----------------
1 Hat
2 Gloves
3 Shoes
sales:
product_id store_id sales
----------------------------
1 1 20
2 2 10
Now I want to do a query to list ALL products, and their sales, for store_id = 1. My first crack at it would be to use a left join, and filter to the store_id I want, or a null store_id, in case the product didn't get any sales at store_id = 1, since I want all the products listed:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
WHERE store_id = 1 or store_id is null;
Of course, this doesn't work as intended, instead I get:
name sales
---------------
Hat 20
Shoes 0
No Gloves! This is because Gloves did get sales, just not at store_id = 1, so the WHERE clause has filtered them out.
How then can I get a list of ALL products and their sales for a specific store?
Here are some queries to create the test tables:
create temp table test_products as
select 1 as product_id, 'Hat' as name;
insert into test_products values (2, 'Gloves');
insert into test_products values (3, 'Shoes');
create temp table test_sales as
select 1 as product_id, 1 as store_id, 20 as sales;
insert into test_sales values (2, 2, 10);
UPDATE: I should note that I am aware of this solution:
SELECT name, case when store_id = 1 then sales else 0 end as sales
FROM test_products p
LEFT JOIN test_sales s ON p.product_id = s.product_id;
however, it is not ideal... in reality I need to create this query for a BI tool in such a way that the tool can simply add a where clause to the query and get the desired results. Inserting the required store_id into the correct place in this query is not supported by this tool. So I'm looking for other options, if there are any.
Add the WHERE condition to the LEFT JOIN clause to prevent that rows go missing.
SELECT p.name, coalesce(s.sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id
AND s.store_id = 1;
Edit for additional request:
I assume you can manipulate the SELECT items? Then this should do the job:
SELECT p.name
,CASE WHEN s.store_id = 1 THEN coalesce(s.sales, 0) ELSE NULL END AS sales
FROM products p
LEFT JOIN sales s USING (product_id)
Also simplified the join syntax in this case.
I'm not near SQL, but give this a shot:
SELECT name, coalesce(sales, 0)
FROM products p
LEFT JOIN sales s ON p.product_id = s.product_id AND store_id = 1
You don't want a where on the whole query, just on your join

SQL question: excluding records

I have a database (NexusDB (supposedly SQL-92 compliant)) which contains and Item table, a Category table, and a many-to-many ItemCategory table, which is just a pair of keys. As you might expect, Items are assigned to multiple categories.
I am wanting to all the end user to select all items which are
ItemID | CategoryID
--------------------------------
01 | 01
01 | 02
01 | 12
02 | 01
02 | 02
02 | 47
03 | 01
03 | 02
03 | 14
etc...
I want to be able to select all ItemID's that are assigned to Categories X, Y, and Z but NOT assigned to Categories P and Q.
For the example data above, for instance, say I'd like to grab all Items assigned to Categories 01 or 02 but NOT 12 (yielding Items 02 and 03). Something along the lines of:
SELECT ItemID WHERE (CategoryID IN (01, 02))
...and remove from that set SELECT ItemID WHERE NOT (CategoryID = 12)
This is probably a pretty basic SQL question, but it's stumping me at the moment. Any help w/b appreciated.
You could try with EXCEPT
SELECT ItemID FROM Table
EXCEPT
SELECT ItemID FROM Table
WHERE
CategoryID <> 12
I want to be able to select all
ItemID's that are assigned to
Categories X, Y, and Z but NOT
assigned to Categories P and Q.
I can't confirm from the NexusDB documentation on SELECT that they support subqueries, but they do support LEFT OUTER JOIN and GROUP BY. So here's a query that works within these restrictions:
SELECT i1.ItemID
FROM ItemCategory i1
LEFT OUTER JOIN ItemCategory i2
ON (i1.ItemID = i2.ItemID AND i2.CategoryID IN ('P', 'Q'))
WHERE i1.CategoryID IN ('X', 'Y', 'Z')
AND i2.ItemID IS NULL
GROUP BY i1.ItemID
HAVING COUNT(i1.CategoryID) = 3;
SELECT i.ItemID, ic.CategoryID FROM Item AS i
INNER JOIN ItemCategory ic
ON i.ItemID = ic.ItemID
WHERE ic.CategoryId = 1 OR ic.CategoryId = 2
Of course you need to put in the WHERE clause what categories you want to get.
For the simple case that you have with a low and known number of categories you can simply use several joins to check for existence and non-existence:
SELECT
ItemID
FROM
Items I
INNER JOIN ItemCategories IC1 ON IC1.ItemID = I.ItemID AND IC1.CategoryID = '01'
INNER JOIN ItemCategories IC2 ON IC2.ItemID = I.ItemID AND IC2.CategoryID = '02'
LEFT OUTER JOIN ItemCategories IC3 ON IC3.ItemID = I.ItemID AND IC3.CategoryID = '12'
WHERE IC3.ItemID IS NULL
For a more general case, given an unknown number of items in the match and don't match lists, you can use the following query. I've used a table variable (available in SQL Server) for each of the lists, but you can use a select against an actual table or a list of variables/parameters as needed. The idea remains the same:
SELECT
ItemID
FROM
Items I
WHERE
(
SELECT COUNT(*)
FROM ItemCategories IC1
WHERE IC1.ItemID = I.ItemID
AND IC.CategoryID IN
(SELECT CategoryID FROM #MustHaves)
) = (SELECT COUNT(*) FROM #MustHaves) AND
(
SELECT COUNT(*)
FROM ItemCategories IC1
WHERE IC1.ItemID = I.ItemID
AND IC.CategoryID IN
(SELECT COUNT(*) FROM #MustNotHaves)
) = 0