sql query with extra relation rows or null - sql

I have
accounts (id, name)
deals (id, name, account_id) many to one accounts
pos (id, name, deal_id) many to one deals
I want to have an export that has all accounts, deals and pos.
Not all deals have a po so if I from POs then I will miss some deals.
If I from deals I will only get 0 or 1 PO even if there is 2 or more. I might also miss some accounts because not all accounts have deals.
All pos have a deal, all deals have an account.
I believe I need to do a seperate report for each where it doesnt have relation and then union each together. I dont quite have the syntax correct.
table could be
account_name | deal_name | po_name
cool account | null | null
another | sweet deal | null
another | bitter sweet | null
last for best | deal 1 | po here
last for best | deal 1 | another po
last for best | deal 2 | null
last for best | deal 3 | o yea

You need left joins from accounts to deals and finally pos:
select
a.name account_name,
d.name deal_name,
p.name po_name
from accounts a
left join deals d on d.account_id = a.id
left join pos p on p.deal_id = d.id

Related

SQL Server avoid repeat same joins

I´m doing the query below where I´m repeating the same joins multiple times, there is a better way to do it? (SQL Server Azure)
Ex.
Table: [Customer]
[Id_Customer] | [CustomerName]
1 | Tomy
...
Table: [Store]
[Id_Store] | [StoreName]
1 | SuperMarket
2 | BestPrice
...
Table: [SalesFrutes]
[Id_SalesFrutes] | [FruteName] | [Fk_Id_Customer] | [Fk_Id_Store]
1 | Orange | 1 | 1
...
Table: [SalesVegetable]
[Id_SalesVegetable] | [VegetableName] | [Fk_Id_Customer] | [Fk_Id_Store]
1 | Pea | 1 | 2
...
Select * From [Customer] as C
left join [SalesFrutes] as SF on SF.[Fk_Id_Customer] = C.[Id_Customer]
left join [SalesVegetable] as SV on SV.[Fk_Id_Customer] = C.[Id_Customer]
left join [Store] as S1 on S1.[Id_Store] = SF.[Fk_Id_Store]
left join [Store] as S2 on S1.[Id_Store] = SV.[Fk_Id_Store]
In my real case, I have many [Sales...] to Join with [Customer] and many other tables similar to [Store] to join to each [Sales...]. So it starts to scale a lot the number on joins repeating. There is a better way to do it?
Bonus question: I do like also to have FruteName, VegetableName, StoreName, and each Food table name under the same column.
The Expected Result is:
[CustomerName] | [FoodName] | [SalesTableName] | [StoreName]
Tomy | Orange | SalesFrute | SuperMarket
Tomy | Pea | SalesVegetable | BestPrice
...
Thank you!!
So based on the information provided, I would have suggested the below, to use a cte to "fix" the data model and make writing your query easier.
Since you say your real-world scenario is different to the info provided it might not work for you, but could still be applicable if you have say 80% shared columns, you can just use placeholder/null values where relevant for unioning the data sets and still minimise the number of joins eg to your store table.
with allSales as (
select Id_SalesFrutes as Id, FruitName as FoodName, 'Fruit' as SaleType, Fk_Id_customer as Id_customer, Fk_Id_Store as Id_Store
from SalesFruits
union all
select Id_SalesVegetable, VegetableName, 'Vegetable', Fk_Id_customer, Fk_Id_Store
from SalesVegetable
union all... etc
)
select c.CustomerName, s.FoodName, s.SaleType, st.StoreName
from Customer c
join allSales s on s.Id_customer=c.Id_customer
join Store st on st.Id_Store=s.Id_Store

Complex SQL Joins with Where Clauses

Being pretty new to SQL, I ask for your patience. I have been banging my head trying to figure out how to create this VIEW by joining 3 tables. I am going to use mock tables, etc to keep this very simple. So that I can try to understand the answer - no just copy and paste.
ICS_Supplies:
Supplies_ ID Item_Description
-------------------------------------
1 | PaperClips
2 | Rubber Bands
3 | Stamps
4 | Staples
ICS_Orders:
ID SuppliesID RequisitionNumber
----------------------------------------------------
1 | 1 | R1234a
6 | 4 | R1234a
2 | 1 | P2345b
3 | 2 | P3456c
4 | 3 | R4567d
5 | 4 | P5678e
ICS_Transactions:
ID RequsitionNumber OrigDate TransType OpenClosed
------------------------------------------------------------------
1 | R1234a | 06/12/20 | Req | Open
2 | P2345b | 07/09/20 | PO | Open
3 | P3456c | 07/14/20 | PO | Closed
4 | R4567d | 08/22/20 | Req | Open
5 | P5678e | 11/11/20 | PO | Open
And this is what I want to see in my View Results
Supplies_ID Item RequsitionNumber OriginalDate TransType OpenClosed
---------------------------------------------------------------------------------------
1 | Paper Clips | P2345b | 07/09/20 | PO | OPEN
2 | Rubber Bands | Null | Null | Null | Null
3 | Stamps | Null | Null | Null | Null
4 | Staples | P56783 | 11/11/20 | PO | OPEN
I just can't get there. I want to always have the same amount of records that we have in the ICS_Supplies Table. I need to join to the ICS_Orders Table in order to grab the Requisition Number because that's what I need to join on the ICS_Transactions Table. I don't want to see data in the new added fields UNLESS ICS_Transactions.TransType = 'PO' AND ICS_Transactions.OpenClosed = 'OPEN', otherwise the joined fields should be seen as null, regardless to what they contain. IF that is possible?
My research shows this is probably a LEFT Join, which is very new to me. I had made many attempts on my own, and then posted my question yesterday. But I was struggling to ask the correct question and it was recommended by other members that I post the question again . .
If needed, I can share what I have done, but I fear it will make things overly confusing as I was going in the wrong direction.
I am adding a link to the original question, for those that need some background info
Original Question
If there is any additional information needed, just ask. I do apologize in advance if I have left out any needed details.
This is a bit tricky, because you want to exclude rows in the second table depending on whether there is a match in the third table - so two left joins are not what you are after.
I think this implements the logic you want:
select s.supplies_id, s.item_description,
t.requisition_number, t.original_date, t.trans_type, t.open_closed
from ics_supplies s
left join ics_transaction t
on t.transtype = 'PO'
and t.open_closed = 'Open'
and exists (
select 1
from ics_order o
where o.supplies_id = s.supplies_id and o.requisition_number = t.requisition_number
)
Another way to phrase this would be an inner join in a subquery, then a left join:
select s.supplies_id, s.item_description,
t.requisition_number, t.original_date, t.trans_type, t.open_closed
from ics_supplies s
left join (
select o.supplies_id, t.*
from ics_order o
inner join ics_transaction t
on t.requisition_number = o.requisition_number
where t.transtype = 'PO' and t.open_closed = 'Open'
) t on t.supplies_id = s.supplies_id
This query should return the data for supplies. The left join will add in all orders that have a supply_id (and return null for the orders that don't).
select
s.supplies_id
,s.Item_Description as [Item]
,t.RequisitionNumber
,t.OrigDate as [OriginalDate]
,t.TransType
,t.OpenClosed
from ICS_Supplies s
left join ICS_Orders o on o.supplies_id = s.supplies_id
left join ICS_Transactions t on t.RequisitionNumber = o.RequisitionNumber
where t.TransType = 'PO'
and t.OpenClosed = 'Open'
The null values will automatically show null if the record doesn't exist. For example, you are joining to the Transactions table and if there isn't a transaction_id for that supply then it will return 'null'.
Modify your query, run it, then maybe update your question using real examples if it's possible.
In the original question you wrote:
"I only need ONE matching record from the ICS_Transactions Table.
Ideally, the one that I want is the most current
'ICS_Transactions.OriginalDate'."
So the goal is to get the most recent transaction for which the TransType is 'PO' and OpenClosed is 'Open'. That the purpose of the CTE 'oa_cte' in this code. The appropriate transactions are then LEFT JOIN'ed on SuppliesId. Something like this
with oa_cte(SuppliesId, RequsitionNumber, OriginalDate,
TransType, OpenClosed, RowNum) as (
select o.SuppliesId, o.RequsitionNumber,
t.OrigDate, t.TransType, t.OpenClosed,
row_number() over (partition by o.SuppliesId
order by t.OrigDate desc)
from ICS_Orders o
join ICS_Transactions t on o.RequisitionNumber=t.RequisitionNumber
where t.TransType='PO'
and t.OpenClosed='OPEN')
select s.*, oa.*
from ICS_Supplies s
left join oa_cte oa on s.SuppliesId=oa.SuppliesId
and oa.RowNum=1;

How can I do a group-concat call with a max value?

I'm tracking game prices across multiple stores. I have a games table:
id | title | platform_id
---|-------------|-----------
1 | Super Mario | 1
2 | Tetris | 3
3 | Sonic | 2
a stores table:
id | title
---|-------------
1 | Target
2 | Amazon
3 | EB Games
and a copies table with one entry for Target's copy of a given game, one entry for Amazon's, etc. I store the SKU so I can use it when scraping their websites.
game_id | store_id | sku
--------|----------|----------
1 | 2 | AMZ-3F4YK
1 | 3 | 001481
I run one scrape a day or a week or however long, and I store the result as cents in a prices table:
sku | price | time
----------|---------|------
AMZ-3F4YK | 4010 | 13811101
001481 | 3210 | 13811105
Plus a platforms table that just maps IDs to names.
Here's where I get confused and stuck.
I want to issue a query that selects each game, plus its most recent price at each store. So it would net results like
games.title | platform_name | info
------------|---------------|------
Super Mario | NES | EB Games,1050;Amazon,3720;Target,5995
Tetris | Game Boy | EB Games,3720;Amazon,410;Target,5995
My best attempt thus far is
select
games.title as title,
platforms.name as platform,
group_concat(distinct(stores.name) || "~" || prices.price) as price_info
from games
join platforms on games.platform_id = platforms.id
join copies on copies.game_id = games.id
join prices on prices.sku = copies.sku
join stores on stores.id = copies.store_id
group by title
Which nets results like
Super Mario | NES | EB Games~2300,Target~2300,Target~3800
that is, it includes every price listed, when I only want one per store (and for it to be the most recent). Figuring out how to integrate the 'select price where id = (select id from max(time)...' etc subquery to sort this out has totally stumped me all night and I'd appreciate any advice anyone could offer me.
I'm using SQLite, but if there's a better option in Postgres I could do it there.
You need two levels of aggregation . . . And, Postgres is much simpler for this, so I'll use Postgres syntax:
select title, platform,
string_agg(s.name || '~' pr.price order by s.name)
from (select distinct on (g.title, p.name, s.name) g.title as title, p.name as platform, s.name, pr.price
from games g join
platforms p
on g.platform_id = p.id join
copies c
on c.game_id = g.id join
prices pr
on pr.sku = c.sku join
stores s
on s.id = c.store_id
group by g.title, p.name, s.name, pr.time desc
) gps
group by title, platform

UNION or JOIN for SELECT from multiple tables

My Issue
I am trying to select one row from multiple tables based on parameters, but my limited knowledge of SQL joining is holding me back. Could somebody possibly point me in the right direction?
Consider these table structures:
+-----------------------+ +---------------------+
| Customers | | Sellers |
+-------------+---------+ +-----------+---------+
| Customer_ID | Warning | | Seller_ID | Warning |
+-------------+---------+ +-----------+---------+
| 00001 | Test 1 | | 00008 | Testing |
| 00002 | Test 2 | | 00010 | Testing |
+-------------+---------+ +-----------+---------+
What I would like to do is one SELECT to retrieve only one row, and in this row will be the 'Warning' field for each of the tables based on the X_ID field.
Desired Results
So, if I submitted the following information, I would receive the following results:
Example 1:
Customer_ID = 00001
Seller_ID = 00008
Results:
+-----------------------------------+
| Customer_Warning | Seller_Warning |
+------------------+----------------+
| Test 1 | Testing |
+------------------+----------------+
Example 2:
Customer_ID = 00001
Seller_ID = 00200
Results:
+-----------------------------------+
| Customer_Warning | Seller_Warning |
+------------------+----------------+
| Test 1 | NULL |
+------------------+----------------+
What I Have Tried
This is my current code (I am receiving loads of rows):
SELECT c.Warning 'Customer_Warning', s.Warning AS 'Seller_Warning'
FROM Customers c,Sellers s
WHERE c.Customer_ID = #Customer_ID
OR s.Seller_ID = #Seller_ID
But I have also played around with UNION, UNION ALL and JOIN. Which method should I go for?
Since you're not really joining tables together, just selecting a single row from each, you could do this:
SELECT
(SELECT Warning
FROM Customers
WHERE Customer_ID = #Customer_ID) AS Customer_Warning,
(SELECT Warning
FROM Sellers
WHERE Seller_ID = #Seller_ID) AS Seller_Warning
The problem is you're getting a cartesian product of rows in each table where either column has the value you're looking for.
I think you just want AND instead of OR:
SELECT c.Warning 'Customer_Warning', s.Warning AS 'Seller_Warning'
FROM Customers c
JOIN Sellers s
ON c.Customer_ID = #Customer_ID
AND s.Seller_ID = #Seller_ID
If performance isn't good enough you could join two filtered subqueries:
SELECT c.Warning 'Customer_Warning', s.Warning AS 'Seller_Warning'
FROM (SELECT Warnning FROM Customers WHERE c.Customer_ID = #Customer_ID) c,
(SELECT Warning FROM Sellers s WHERE s.Seller_ID = #Seller_ID) s
But I suspect SQL will be able to optimize the filtered join just fine.
it wont return a row if one of the ID's doesnt exist.
Then you want a FULL OUTER JOIN:
SELECT c.Warning 'Customer_Warning', s.Warning AS 'Seller_Warning'
FROM Customers c
FULL OUTER JOIN Sellers s
ON c.Customer_ID = #Customer_ID
AND s.Seller_ID = #Seller_ID
The problem that you are facing is that when one of the tables has no rows, you are going to get no rows out.
I would suggest solving this with a full outer join:
SELECT c.Warning as Customer_Warning, s.Warning AS Seller_Warning
FROM Customers c FULL OUTER JOIN
Sellers s
ON c.Customer_ID = #Customer_ID AND s.Seller_ID = #Seller_ID;
Also, I strongly discourage you from using single quotes for column aliases. Use single quotes only for string and date constants. Using them for column names can lead to confusion. In this case, you don't need delimiters on the names at all.
What I have seen so far here are working examples for your scenario. However, there is no real sense behind putting unrelated data together in one row. I would propose using a UNION and separate the values in your code:
SELECT 'C' AS Type, c.Warning
FROM Customers c
WHERE c.Customer_ID = #Customer_ID
UNION
SELECT 'S' AS Type, s.Warning
FROM Sellers s
WHERE s.Seller_ID = #Seller_ID
You can use the flag to distinguish the warnings in your code. This will be more efficient then joining or sub queries and will be easy to understand later on (when refactoring). I know this is not 100% what you ask for in your question but that's why I challenge the question :)

Building a SQL query that doesn't include data based on hierarchy

I've spent quite a bit of time on Google and SO the last few days, but I can't seem to find an answer to my problem. Not knowing exactly how to phrase the issue into a reasonable question makes it a little more difficult. You don't know what you don't know, right?
Due to business limitations I can't post my exact code and database structure, so I'll do my best to give a solid example.
Customer Table - Holds customer data
[CustId] | [CustName]
---------------------
1 | John Smith
2 | Jane Doe
3 | John Doe
Code Table - Holds code data
[CodeId] | [CodeDesc]
---------------------
A | A Desc
B | B Desc
C | C Desc
D | D Desc
E | E Desc
CustomerCode Table - Combines customers with codes
[CustId] | [CodeId]
-------------------
1 | A
1 | B
2 | B
2 | C
2 | D
3 | C
3 | E
CodeHierarchy Table - Hierarchy of codes that shouldn't be included (DropCode) if a customer has a ConditionCode
[ConditionCode] | [DropCode]
----------------------------
A | B
B | C
B | D
Now I'll try to explain my actual question.
What I'm trying to accomplish is writing a query (view) that will list codes based on the CodeHierarchy table.
Results would be something like this:
[CustName] | [CodeId]
-------------------
John Smith | A
Jane Doe | B
John Doe | C
John Doe | E
Code B isn't listed for John Smith since he has code A. Codes C and D aren't listed for Jane Doe since she also has code B. John Doe has all codes listed (notice that E isn't even in the CodeHierarchy table).
I've tried a few different things (inner joins, left/right joins, subqueries, etc) but I just can't get the results I'm looking.
As a base query, this returns all codes:
SELECT
Customer.CustomerName,
Code.CodeDesc
FROM
Customer
INNER JOIN CustomerCode
ON Customer.CodeId = CustomerCode.CodeId
INNER JOIN Code
ON CustomerCode.CodeId = Code.CodeId
This only returns codes that are ConditionCodes (I understand why, but I though it might be worth a shot at the time):
SELECT
Customer.CustomerName,
Code.CodeDesc
FROM
Customer
INNER JOIN CustomerCode
ON Customer.CodeId = CustomerCode.CodeId
INNER JOIN Code
ON CustomerCode.CodeId = Code.CodeId
INNER JOIN CodeHierarchy
ON Customer.CodeId = CodeHierarchy.ConditionCode
AND Customer.CodeId != CodeHierarchy.DropCode
I tried a subquery (don't have that code available) that ended up dropping all DropCodes, regardless if a member did or did not have a qualifying hierarchy (i.e. customer rows with B weren't returned even if they didn't have A)
I had an idea of making the base query above a subquery and the joining it with the CodeHierarchy table, but I'm stuck on how to write the query:
SELECT
*
FROM
(
base query (with all codes)
) CustomerCodesAll
INNER/LEFT JOIN CodeHierarchy
ON ?
I've also been doing some reading on CTEs, but I'm not sure how I could make use of that technique.
This will end up being a view to be queried against for reporting purposes. The customer table contains much more data including dob, gender, company status, etc. The view will be straightforward and pull everything. Queries against the view will include where clauses for dob, gender, etc.
Can anyone point me in the right direction?
Thanks for any help.
SELECT
Customer.CustName,
Code.CodeDesc
FROM
Customer
INNER JOIN CustomerCode AS posCustomerCode
ON Customer.CustId = posCustomerCode.CustId
INNER JOIN Code
ON posCustomerCode.CodeId = Code.CodeId
LEFT JOIN CodeHierarchy
ON posCustomerCode.CodeId = CodeHierarchy.DropCode
WHERE
CodeHierarchy.ConditionCode NOT IN (
SELECT CodeId
FROM CustomerCode AS negCustomerCode
WHERE negCustomerCode.CustId=posCustomerCode.CustId
)
OR CodeHierarchy.ConditionCode IS NULL
SQLfiddle