Complex filtering SQL Server query with multiple conditions - sql

I have a SQL Server query in which I am joining several tables together and we are using SQL Server 2012. I have a customer number, customer name, item type, a field for document number (which can be one document number in either of the two tables I will mention; even though there are two fields mentioned later, I do want to combine this into one field using a CASE statement), a document amount, document date, due date, amount remaining to be paid, and a description.
The problem is that for my item type, one table (RM20101), doesn't recognize an item type associated with it because no item types are in that table. The other table (Custom_RecData), recognizes the correct item type for all pertinent document numbers but that tables only has a record of document numbers associated with items--which not all document numbers in the system have.
So here's an example of the trimmed down version:
CUSTNMBR | ItemType | DocumentNumberRM | DocNumCUSTOM | DocAmount
------------+--------------+--------------------+---------------+-----------
12345ABC | NULL | PYMNT01234567 | NULL | - 28.50
12345ABC | TOYS | 9010456778 | 9010456778 | 300.00
12345ABC | NULL | 9010456778 | NULL | 300.00
12345ABC | NULL | 9019888878 | NULL | 47.90
12345ABC | CRAFTS | 9502345671 | 9502345671 | 145.25
12345ABC | NULL | 9502345671 | NULL | 145.25
I named the columns in this example for document number as "RM" to come out of the RM20101 table and "CUSTOM" to come out of the Custom_RecDate table.
So I've tried lots of ways, from CASE statements, to my WHERE clause, to a HAVING clause, to subqueries... I can't figure it out. Here's what I'd like to see:
CUSTNMBR | ItemType | DocumentNumberRM | DocNumCUSTOM | DocAmount
------------+--------------+--------------------+---------------+-----------
12345ABC | NULL | PYMNT01234567 | NULL | - 28.50
12345ABC | TOYS | 9010456778 | 9010456778 | 300.00
12345ABC | NULL | 9019888878 | NULL | 47.90
12345ABC | CRAFTS | 9502345671 | 9502345671 | 145.25
So why did I call this multiple conditions? Well, if you look at the table, I am cutting out items based on the following:
If both the ItemType And DocNumCUSTOM fields are NULL then show it.
If the ItemType IS NOT NULL and both document number fields are NOT NULL, then show it.
If the ItemType IS NULL and DocNumCUSTOM IS NULL, but we've already seen the document number in the RM20101 DocumentNumberRM field (which I suspect would be in a HAVING clause for a COUNT or something), then don't show it a second time.
If I don't find a way to do step 3, I will get the duplicates as shown in my initial example.
I basically do not want duplication. I want to show an item type whether it be NULL (non-existent for that document number in either table) or show it only once if existent in the Custom_RecDate table, which is the only table that has the item type information in it.
Does this make sense? I know it sounds complicated as heck, but hopefully someone can make sense of it all. :)
Thanks!
By the way, here's the important part (my query, albeit very trimmed down for the example):
SELECT DISTINCT
R1.CUSTNMBR,
I.ITMCLSCD AS [ItemType],
R1.DOCNUMBR AS [DocumentNumberRM],
I.DOCNUMBR AS [DocNumCUSTOM],
R1.ORTRXAMT AS [DocAmount]
FROM
RM20101 R1
JOIN
RM40401 R2 ON R2.RMDTYPAL = R1.RMDTYPAL
JOIN
RM00401 R3 ON R3.DOCNUMBR = R1.DOCNUMBR
JOIN
RM00101 R4 ON R4.CUSTNMBR = R1.CUSTNMBR
LEFT OUTER JOIN
SR_ITCMCD C ON C.CUSTNMBR = R1.CUSTNMBR
LEFT OUTER JOIN
AR_Description D ON D.SOPNUM = R1.DOCNUMBR
LEFT OUTER JOIN
Custom_RecData I ON I.ITEMNMBR = C.ITEMNMBR
AND I.DOCNUMBR = R1.DOCNUMBR
Again, I've tried using CASE statements and putting various conditions in my WHERE clause, I just can't figure it out.

Purely based on the sample data, MAX() should do the trick:
SELECT
R1.CUSTNMBR,
MAX(I.ITMCLSCD) AS [ItemType],
R1.DOCNUMBR AS [DocumentNumberRM],
MAX(I.DOCNUMBR) AS [DocNumCUSTOM],
R1.ORTRXAMT AS [DocAmount]
FROM RM20101 R1
JOIN RM40401 R2 ON R2.RMDTYPAL = R1.RMDTYPAL
JOIN RM00401 R3 ON R3.DOCNUMBR = R1.DOCNUMBR
JOIN RM00101 R4 ON R4.CUSTNMBR = R1.CUSTNMBR
LEFT OUTER JOIN SR_ITCMCD C ON C.CUSTNMBR = R1.CUSTNMBR
LEFT OUTER JOIN AR_Description D ON D.SOPNUM = R1.DOCNUMBR
LEFT OUTER JOIN Custom_RecData I ON I.ITEMNMBR = C.ITEMNMBR
AND I.DOCNUMBR = R1.DOCNUMBR
GROUP BY
R1.CUSTNMBR,
R1.DOCNUMBR,
R1.ORTRXAMT
Removed the DISTINCT since GROUP BY will accomplish the same and is needed for MAX()

Related

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;

SQL Help - Join small lookup table where not all columns are required (and an other option)

I have one large table with transactions and a smaller lookup table with values I want to add based on 4 common columns. The trick here is not every combination of these 4 columns will exist in the lookup table and there are scenarios where I want it to stop checking and accept the match instead of going to the next column. I also have an "Other" option to default to if it doesn't match any of the options.
Table structures are something like this:
transaction_table
country, trans_id, store_type, store_name, channel, browser, purchase_amount, currency
lookup_table
country, store_name, channel, browser, trans_fee
The data could be something like this:
transaction_table:
country| trans_id| store_type |store_name |channel |browser |amt |currency
US | 001 | Big Box | Target | B&M |N/A |1.45 |USD
US | 002 | Big Box | Target | Online |Chrome |1.79 |USD
US | 003 | Small | Bob's Store| B&M |N/A |2.50 |USD
US | 004 | Big Box | Walmart | B&M |N/A |1.12 |USD
US | 005 | Big Box | Walmart | Online |Firefox |3.79 |USD
US | 006 | Big Box | Amazon | Online |IE |4.54 |USD
US | 007 | Small | Jim's Plc | B&M |IE |2.49 |USD
lookup_table:
country|store_name |channel |browser |trans_fee
US |Target |B&M |N/A |0.25
US |Target |Online | |0.15
US |Walmart | | |0.30
US |Other | | |0.45
So looking at the lookup_table data:
Row 1 is very specific and would be a match on all 4 of the join
columns.
Row 2 would not care what browser was used to shop at Target so
regardless of the "browser" value, the trans_fee should come back
the same (other stores may care though).
Row 3 is saying any transaction with a country='US' and the
store_name='Walmart', regardless of the rest of the join columns
would have the same trans_fee
Row 4 is the "other" scenario where it should look first at the
store_name column and if it doesn't find a match, go to Other.
The lookup_table data can change and may end up being time dependent (start_date and end_date columns added) so it really wouldn't be a good candidate for a long, complex CASE statement.
I was thinking of a combination of checking each column with an IF IN statement but I'm hoping there's a more straightforward conditional join type statement I can use to go column by column and have an other option.
Thanks!
edit: I didn't specify this but I want to basically return all of the data from transaction_table and add the corresponding trans_fee to each line.
You will need to use a conditional JOIN.
Something like this
SELECT *
FROM lookup_table
LEFT OUTER JOIN transaction_table
ON CASE WHEN lookup_table.store_name IS NOT NULL
THEN transacton_table.store_name = lookup_table.store_name END
Such partial matching is tricky. And your problem is not really that well set up. You seem to have NULLs in some columns and general values in others.
In any case, you can solve this by matching what you can and then using order by to get the best match. In your case, I think this looks like this:
select tt.*,
(select trans_fee
from lookup l
where l.country = tt.country and
l.store_name in ('other', tt.store_name) and
(l.channel = tt.channel or l.channel is null) and
(l.browser = tt.browser or l. browser is null)
order by (case when l.store_name = tt.store_name then 1 else 2 end),
(case when l.channel = tt.channel then 1 else 2 end),
(case when l.browser = tt.browser then 1 else 2 end)
fetch first 1 row only
) as trans_fee
from transaction_table tt;
This is generic SQL. But the same idea should work in any database.

Having two SQL Server related tables, select complete rows of one and partial of other

In MS Access was very easy to acomplish but I'm having troubles with SQL Server
I have this query:
SELECT Organigrama.Item, Organigrama.Id, Organigrama.ParentItem, Rol_Menu.Cod_Rol
FROM Rol_Menu RIGHT JOIN
Organigrama ON Rol_Menu.Cod_Menu = Organigrama.Id
WHERE (Rol_Menu.Cod_Rol = '5')
The purpose is to get all the items of Organigrama and the elements in common with Rol_Menu.Col_Rol appears with 5, the others with Null
I need to fill a menu structure into a treeview
When the user select another Rol just get nodes checked that rol have access to
im determining if in the row the Col_Rol isn't null so the query I need to get
something like this:
Item | Id | ParentItem | Cod_Rol
A | 3 | null | 5
B | 4 | A | 5
C | 5 | A | null
D | 6 | B | 5
E | 7 | C | null
F | 8 | E | null
I think you just need to include the extra restriction in the join criteria rather then the where clause. The criteria are evaluated before the outer join adds the null columns. The where clause is evaluated afterwards, and eliminates the nulls.
select
Organigrama.Item,
Organigrama.Id,
Organigrama.ParentItem,
Rol_Menu.Cod_Rol
from
Rol_Menu
right join
Organigrama
on Rol_Menu.Cod_Menu = Organigrama.Id and
Rol_Menu.Cod_Rol = '5'
either that or add or Rol_Menu.Cod_Rol is null to the end of the where clause.

My SQL query within a query

I have 2 tables that I am trying to combine in a specific way
Table 1: ‘part_defs’ Table 2 Items_part_values
in value_defs:
ID | Name
-------------
1 | color
2 | size
3 | weight
in Items_part_values
ItemID | valueID | Value
-------------------------
10 | 1 | red
11 | 1 | blue
What I need is a query where for a given item all the rows from value_defs appear and if they have a value in Items_part_values the value.
So for Item 11 I want
ID | Name | Value
--------------------
1 | color | red
2 | size | NULL
3 | weight | NULL
I’m new to MySQL, in access I would have created a subquery with the ItemID as a parameter and then done a Left Join with value_defs on the result.
Is there a way of doing something similar in MySQL?
Thanks
Use:
SELECT p.id,
p.name,
ipv.value
FROM PART_DEFS p
LEFT JOIN ITEMS_PART_VALUES ipv ON ipv.valueid = p.id
AND ipv.itemid = ?
Replace the "?" with the itemid you want to search for.
This means all the PARTS_DEF rows will be returned, and if the ITEMS_PART_VALUES.valueid matches the PART_DEFS.id value, then the ITEMS_PART_VALUES.value value will be displayed for the item you are looking for. If there's no match, the value column will be NULL for that record.
There's a difference in OUTER JOINs, when specifying criteria in the JOIN vs the WHERE clause. In the JOIN, the criteria is applied before the JOIN occurs while in the WHERE clause the criteria is applied after the JOIN.
Use a left join:
SELECT * FROM Table1 LEFT JOIN Table2 USING (ID);
Edit:
SELECT * FROM part_defs LEFT JOIN Items_part_values ON part_defs.ID = Items_part_values.valueID;

SQL Server optional joins

I have several tables (to be exact, 7) tables I cross join in one another. This part gives me some problems;
Table "Actions"
-----------------------------------------
| ID | Package ID | Action Type | Message |
-----------------------------------------
| 40 | 100340 | 0 | OK |
| 41 | 100340 | 12 | Error |
| 42 | 100340 | 2 | OK |
| 43 | 100341 | 4 | OK |
| 44 | 100341 | 0 | Error |
| 45 | 100341 | 12 | OK |
-----------------------------------------
Table "Packages"
----------------------
| ID | Name |
----------------------
| 100340 | Testpackage |
| 100341 | Package xy |
----------------------
I accomplished cross joingin thm, but when there is no Package with an ID specified in Actions, all actions on that package are completely missing, rather than just leavin Name blank - which is what I'm trying to get.
So, if a reference is missing, just leave the corresponding joined column blank or as an empty string...:
----------------------------------------------------------------------
| Package ID | Name | Action 0 | Action 2 | Action 4 | Action 12 |
----------------------------------------------------------------------
| 100340 | Testpackage | OK | OK | | Error |
| 100341 | Package xy | Error | | OK | OK |
----------------------------------------------------------------------
How is that possible?
Edit
Sorry, I just saw my example was completety wrong, I updated it how it should look like in the end.
My current query looks something like this (as said above, just an extract as the actual one is about three times as long including even more tables)
SELECT
PackageTable.ID AS PackageID,
PackageTable.Name,
Action0Table.Message AS Action0,
Action2Table.Message AS Action2,
Action4Table.Message AS Action4,
Action12Table.Message AS Action12
FROM
Packages AS PackageTable LEFT OUTER JOIN
Actions AS Action0Table ON PackageTable.ID = Action0Table.PackageID LEFT OUTER JOIN
Actions AS Action2Table ON PackageTable.ID = Action2Table.PackageID LEFT OUTER JOIN
Actions AS Action4Table ON PackageTable.ID = Action4Table.PackageID LEFT OUTER JOIN
Actions AS Action12Table ON PackageTable.ID = Action12Table.PackageID
WHERE
Action0Table.ActionType = 0 AND
Action2Table.ActionType = 2 AND
Action4Table.ActionType = 4 AND
Action12Table.ActionType = 12
So why can't you just do an outer JOIN, such as:
SELECT `Package ID`, Name, `Action Type`, `Action Date`
FROM Actions
LEFT OUTER JOIN Packages
ON Actions.`Package ID` = Packages.`Package ID`
?
Do you have a 'where' clause which is excluding the missing action records? Even if you use an outer join, your where clause will still be applied and the action records will not be included if they don't match.
EDIT: The problem is your ActionType filters. If there is no matching action record, then ActionType is null, which does not match any of your filters. So, you could add 'or ActionType is null' to your where clause. I don't know your business requirement, but this may include more records than you want.
As what the others has said, I have the same answer.
Just showing u the different result
declare #Actions table(id int , packageid int, actiontype int,dt date)
declare #Packages table(id int,name varchar(50))
insert into #Actions
select 40,100340,0,'2009/01/01 3:00pm' union all
select 41,100340,12,'2009/01/01 5:00pm' union all
select 42,100340,2,'2009/01/01 5:30pm' union all
select 43,100341, 4,'2009/01/02 8:00am'
insert into #Packages
select 100340,'Testpackage'
Left outer join query
select a.packageid,p.name,a.actiontype,a.dt
from #Actions a
left join #Packages p
on a.packageid = p.id
Full join(in this case u will get the same result)
select a.packageid,p.name,a.actiontype,a.dt
from #Actions a
full join #Packages p
on a.packageid = p.id
Output:
packageid name actiontype dt
100340 Testpackage 0 2009-01-01
100340 Testpackage 12 2009-01-01
100340 Testpackage 2 2009-01-01
100341 NULL 4 2009-01-02
Inner join query(which u don't want)
select a.packageid,p.name,a.actiontype,a.dt
from #Actions a
join #Packages p
on a.packageid = p.id
Output:
packageid name actiontype dt
100340 Testpackage 0 2009-01-01
100340 Testpackage 12 2009-01-01
100340 Testpackage 2 2009-01-01
Your are left (outer) joining on the packages table. This means that if the record is not in the packages table (the table on the left side of the join condition) then don't include it in the final result.
You could right (outer) join on the action table in which case you will get all of the action records whether or not they have a match in the package table.
You could do a full (outer) join, in other words, give me all of the records in both tables.
And finally you can do an inner join, or the records which are present in both tables.
You may find it helpful to picture a Venn diagram here with the left table as the left circle and the right table as the right circle. The inner join thus represents the overlapping region of the diagram.
So, to answer your question, you are going to need to tweak your joins to be full outer joins or right joins depending on whether or not you want to see packages without actions or not. And you are going to need to adjust your where clause to include null actions as has been suggested by many other posters. Though you may want to add an additional clause to that where expression which excludes records where all of the actions are null. Plus, as your example is written you are only going to see packages where the actions on that package are 0, 2, 4 and 12; which sounds incorrect given the information you've provided.
SELECT
PackageTable.ID AS PackageID,
PackageTable.Name,
Action0Table.Message AS Action0,
Action2Table.Message AS Action2,
Action4Table.Message AS Action4,
Action12Table.Message AS Action12
FROM
Packages AS PackageTable
RIGHT OUTER JOIN Actions AS Action0Table ON
PackageTable.ID = Action0Table.PackageID
RIGHT OUTER JOIN Actions AS Action2Table ON
PackageTable.ID = Action2Table.PackageID
RIGHT OUTER JOIN Actions AS Action4Table ON
PackageTable.ID = Action4Table.PackageID
RIGHT OUTER JOIN Actions AS Action12Table ON
PackageTable.ID = Action12Table.PackageID
WHERE
(Action0Table.ActionType = 0 OR Action0Table.ActionType IS NULL) AND
(Action2Table.ActionType = 2 OR Action2Table.ActionType IS NULL) AND
(Action4Table.ActionType = 4 OR Action4Table.ActionType IS NULL) AND
(Action12Table.ActionType = 12 OR Action12Table.ActionType IS NULL) AND
NOT (Action0Table.ActionType IS NULL AND Action2Table.ActionType IS NULL AND
Action4Table.ActionType IS NULL AND Action12Table.ActionType IS NULL)
You will need to remove that final NOT clause if you want to see packages without any of those actions. Also, depending on the quality of the data you may begin receiving duplicate records once you start including null values; in which case your problem has become a lot harder to solve and you will need to get back to us.
read up on 'outer joins'
Instead of INNER JOIN use LEFT JOIN. That will make it.-