Oracle, SQL Conditional exclusion based on the items in the joined table - sql

The following query excludes all the products however, I am trying to exclude the products "only if" the R.OPERATING_UNITS = 'WP' and PRODUCT_CAT = 'FUEL' in the joined table. I don't know how to condition that. I wanted to know what is the best efficient way to do that. Below is the query, the RESOURCE, PRODUCT table and also the desired result set. I simplified both the tables and query for the sake of explanation.
SELECT R.DEPTID,
R.FISCAL_YEAR,
sum(R.AMOUNT) total
FROM RESOURCE R
WHERE
R.PRODUCT_ID NOT IN (
SELECT PRODUCT_ID FROM PRODUCT WHERE PRODUCT_CAT='FUEL' )
group by R.FISCAL_YEAR,R.DEPTID
the RESOURCE table
DPTID FISCAL_YEAR OPERATING_UNIT AMOUNT PRODUCT
PTT 2017 WP 1200 31000
PTT 2017 SP 3000 32000
PTT 2017 GP 1000 31000
PTT 2017 WP 1000 32000
FPP 2017 WP 1000 32000
FPP 2018 GP 2000 33000
FPP 2017 SP 1000 32000
FPP 2018 WP 2200 31000
PRODUCT Table:
PRODUCT PRODUCT_CAT
31000 FUEL
32000 NON-FUEL
33000 MATERIAL
Result set. Note that it is ignoring WP when calculating the sum.
2017 PTT 5000 (igonred 1200 since operating unit=wp and product is 31000->FUEL but included wp and 32000)
2017 FPP 2000
2018 FPP 2000 (it did not consider the 2200 since operating unit=wp and product is 31000->FUEL)

WP filter should work after you change below statement
NOT IN (
SELECT PRODUCT_ID FROM PRODUCT WHERE PRODUCT_CAT='FUEL' )
and then you can filter operating unit.
SELECT R.DEPTID,
R.FISCAL_YEAR,
sum(R.AMOUNT) total
FROM RESOURCE R
WHERE
r.OPERATING_UNIT = 'WG' and
R.PRODUCT_ID IN
(
SELECT PRODUCT_ID FROM PRODUCT WHERE PRODUCT_CAT='FUEL' )
group by R.FISCAL_YEAR,R.DEPTID

Sticking in a not-equal to clause with an OR in-between should filter out the cases where both OperatingUnit = 'WP' & ProductCat = 'Fuel'
SELECT r.DEPTID
,r.FISCAL_YEAR
,SUM(r.AMOUNT) AS TOTAL
FROM [Resource] r
INNER JOIN [Product] p ON r.PRODUCT = p.PRODUCT
WHERE r.OPERATING_UNIT != 'WP'
OR p.PRODUCT_CAT != 'FUEL'
GROUP BY r.DEPTID
,r.FISCAL_YEAR
I used the following query below to view the data and verify that it's returning the 6/8 rows I wanted.
SELECT *
FROM [Resource] r
INNER JOIN [Product] p ON r.PRODUCT = p.PRODUCT

For ease of writing - and reading - the exclusion condition, it would be nice if we could work with tuples. And we can. One benefit is that it will be easy, in the future, to add other pairs of operating unit and product category to the exclusion list, without having to write lengthy conditions with lots of OR and AND.
If you run a query like this, and then you take a look at the EXPLAIN PLAN for the query, you will see that the parser expanded the tuple condition to a long logical expression with OR (and AND, if more than one tuple is excluded) - so the end result is the same, but the code looks more natural.
select r.deptid, r.fiscal_year, sum(r.amount) as total
from resource r inner join product p on r.product = p.product
where (r.operating_unit, p.product_cat) not in ( ('WP', 'FUEL') )
group by r.deptid, r.fiscal_year
;
Regarding NULL: if either r.operating_unit or p.product_cat can be NULL, you need to state how they should be handled. If, for example, the operating unit is WP but the product category is NULL, the corresponding row will be excluded in the query above. That may be the proper handling: the unit is definitely 'WP', and since we don't know the product category, we must make a decision. Since it may be 'FUEL', we just don't know for sure, we may choose to exclude it. Obviously, if both columns are NOT NULL then this is not an issue.
Note - I hope you don't really have a table PRODUCT with a column PRODUCT; that will lead to confusion which almost always then leads to bugs.

Related

Selecting only rows that have matching column values

so I have the task of returning the a companies information if and only if ALL of their products have been discontinued.
I have a Suppliers and a Products table. The Suppliers table has a ProductID column and the Products table has a ProductID and a Discontinued column that stores a bit (1 being true or 0 being false).
If anyone has a solution to this, that would be a life saver.
EDIT: the query I'm working with would be something like this
select
s.CompanyName, p.ProductName, p.Discontinued
from
Suppliers s
join
Products p on s.SupplierID = p.SupplierID
and the output would be something like this
CompanyName ProductName Discontinued
-------------------------------------------------------------
Exotic Liquids Chai 0
Exotic Liquids Chang 0
Exotic Liquids Aniseed Syrup 0
New Orleans Cajun Delights Chef Anton's Cajun Seasoning 0
New Orleans Cajun Delights Chef Anton's Gumbo Mix 1
Grandma Kelly's Homestead Grandma's Boysenberry Spread 0
Grandma Kelly's Homestead Uncle Bob's Organic Dried Pears 0
Grandma Kelly's Homestead Northwoods Cranberry Sauce 0
Tokyo Traders Mishi Kobe Niku 1
Tokyo Traders Ikura 0
but I only want it to return the suppliers with all discontinued products
I'm going out on a limb here, but I think you're trying to JOIN two tables on the ProductID.
If that's the case, then you SQL query would look something like this:
SELECT a.ProductID, b.Discontinued FROM Suppliers a
LEFT JOIN Products b ON (a.productID = b.productID)
WHERE b.Discontinued = true
select Suppliers.SupplierID, CompanyName from Suppliers
inner join Products on
Products.SupplierID = Suppliers.SupplierID
where Discontinued = 1
and Products.SupplierID not in
(select SupplierID from Products where Discontinued = 0)
group by Suppliers.SupplierID, CompanyName
Become a better developer in every language you use. Stop trying to do everthing at once. Break the problem into digestible pieces. And learn how to post good questions. You have a query question - how do you expect others to understand your issue and supply useful suggestions without knowing your schema and understanding how you use it. Sure - it's fairly simple in this case but a script that generates your tables, populates them with sample data, and includes whatever you have tried encourages others to respond.
So the first step is to answer the question "which supplies provide only discontinued products". Apparently you have all the information you need in Product. Something like:
select P.SupplierID from dbo.Products as P
group by P.SupplierID
having min(cast(P.Discontinued as tinyint)) = 1
order by P.SupplierID;
I think that is correct but it is still a guess. Sometimes a bit column gets "used" in opposition to the name. What does that do? For each group generated by the group by clause (supplier ID), it will determine the min value of Discontinued (casting is necessary since you cannot use min/max with bit). For a supplier with all discontinued products, that column must be 1 for all associated rows. Since 1 > 0, that means that all products of a supplier are discontinued. Grouping also gives us a distinct set of rows.
Now that you know how to generate a distinct set of SupplierID values, you should be able to apply that to any other query to get information about those suppliers. You can join, use IN, use exists - try all three if you want to really make this a learning experience.

T-SQL JOIN Table On Self Based on Closest Date

Thank you in advance for reading!
The question I'm trying to answer is: "How much do parts really cost to make?" We manufacture by machining raw metal billets down to metal parts. Final parts are sold to a customer and scrap metal from the process is sold to the scrap yard.
For business/ERP configuration reasons our scrap vendor is listed as a customer and we ship him 'parts' like our other customers. These dummy parts are simply for each of the metal alloys we work with, so there is one dummy scrap part for each alloy we use. The scrap shipments are made whenever we fill our scrap bins so there's no defined time interval.
I'm trying to connect the ship date of a real part to a real customer to the closest scrap ship date of the same alloy. Then I can grab the scrap value per pound we were paid and include it in our revenue for the parts we make. If I can ask for the world it would be helpful to know how to grab the scrap shipment immediately before or immediately after the shipment of a real part - I'm sure management will change their minds several times debating if they want to use the 'before' or 'after' number.
I've tried other solutions and can't get them to work. I'm crying uncle, I simply can't get it to work....the web SQL interface our ERP uses claims it's T-SQL... thank you for reading this far!
What I'd like the output to look like is:
Customer Part Price Alloy Weight_Lost Scrap_Value Ship_Date
ABC Widget1 99.99 C182 63 2.45 10-01-2016
Here's the simplest I can boil the tables down to:
SELECT
tbl_Regular_Sales.Customer
tbl_Regular_Sales.Part
tbl_Regular_Sales.Price
tbl_Regular_Sales.Alloy
tbl_Regular_Sales.Weight_Lost
tbl_Scrap_Sales.Price AS 'Scrap_Value'
tbl_Regular_Sales.Ship_Date
FROM
(SELECT P.Part
,P.Alloy
,P.Price
,S.Ship_Date
,S.Customer
FROM Part AS P
JOIN S AS S
ON S.Part_Key = P.Part_Key
WHERE Shipper.Customer = 'Scrap_Yard'
) AS tbl_Scrap_Sales
JOIN
(SELECT P.Part
,P.Weight_Lost
,P.Alloy
,P.Price
,S.Ship_Date
,S.Customer
FROM Part AS P
JOIN S AS S
ON S.Part_Key = P.Part_Key
WHERE Shipper.Customer <> 'Scrap_Yard' ) AS tbl_Regular_Sales
ON
tbl_Regular_Sales.Alloy = tbl_Scrap_Sales.Alloy
AND <Some kind of date JOIN to get the closest scrap shipment value>
Something like this may do the trick:
WITH cteScrapSales AS (
SELECT
P.Alloy
,P.Price
,S.Ship_Date
FROM Part AS P
JOIN Shipper AS S ON S.Part_Key = P.Part_Key
WHERE S.Customer = 'Scrap_Yard'
), cteRegularSales AS (
SELECT
P.Part_Key
,P.Part
,P.Weight_Lost
,P.Alloy
,P.Price
,S.Ship_Date
,S.Customer
FROM Part AS P
JOIN Shipper AS S ON S.Part_Key = P.Part_Key
WHERE S.Customer <> 'Scrap_Yard'
)
SELECT
C.Customer
,C.Part
,C.Price
,C.Alloy
,C.Weight_Lost
,C.Scrap_Value
,C.Ship_Date
FROM (
SELECT R.*, S.Price AS Scrap_Value, ROW_NUMBER() OVER (PARTITION BY R.Part_Key ORDER BY DATEDIFF(SECOND, R.Ship_Date, S.Ship_Date)) ix
FROM cteRegularSales R
JOIN cteScrapSales S ON S.Allow = R.Allow AND S.Ship_Date > R.Ship_Date
) AS C
WHERE C.ix = 1;

Using the HAVING Clause with GROUP by to Return Unique Records

Good day all,
I am having difficulty understanding the mechanics of the GROUP BY AND HAVING clause and was hoping for some advice.
I am trying to query two tables - PRODUCTS and ORDER_ITEMS. The PRODUCT_ID column is used to link these two tables.
I wish to view products which have been ordered from a certain supplier (filtered using the SUPPLIER_ID column which is in ORDER_ITEMS); have been successfully ordered before (ORDER_STATUS 6 in ORDER_ITEMS);and which have not been deleted (RECORD_DELETED column in ORDER_ITEMS). I only use the PRODUCTS table to show the name of the product. Furthermore I only want distinct products returned, meaning I want to exclude any results which duplicate the PRODUCT_ID column
This is the query that I am using:
SELECT
PD.PRODUCT_ID,
PD.PRODUCT_NAME,
PD.BARCODE,
PD.SUPPLIER_BARCODE,
COUNT(PD.PRODUCT_ID) AS COUNTED,
ODI.ORDER_ITEM_ID
FROM PRODUCTS PD
INNER JOIN ORDER_ITEMS ODI
ON PD.PRODUCT_ID = ODI.PRODUCT_ID
WHERE ODI.SUPPLIER_ID = 34359738399
AND ORDER_STATUS = 6
AND ODI.RECORD_DELETED = 0
GROUP BY PD.PRODUCT_ID,PD.PRODUCT_NAME,PD.BARCODE,PD.SUPPLIER_BARCODE,ODI.ORDER_ITEM_ID
HAVING COUNT(ODI.PRODUCT_ID) = 1
ORDER BY PRODUCT_ID ASC
Unfortunately this is returning 502 records with many of them duplicating the PRODUCT_ID. If I remove the ORDER_ITEM_ID column from the query 175 records are returned. These 175 records are products that meet the criteria given above. The problem is that I also need to pull the ORDER_ITEM_ID from ORDER_ITEMS (along with some other columns).
I vaguely understand that when I include ORDER_ITEMS the query is going to group the data by the ORDER_ITEM column and so will count the PRODUCT_ID values based on each individual ORDER_ITEM_ID. This results in there always being a count of 1 for each product.
How does one get around this? Also, is there a more suitable way of carrying out this task which would allow me to include one ORDER_ITEM record for every duplicated product? Rather than omitting them altogether as I am doing above?
This is some of the data that is returned by the query above:
PRODUCT_ID,PRODUCT_NAME,BARCODE,SUPPLIER_BARCODE,COUNTED,ORDER_ITEM_ID
34359738628,ADCORTYL INTRA-ARTIC/DERMAL 10MG/ML 5ML,5099627022132,5012712000037,1,34359755708
34359739609,ARTELAC 3.2MG/ML EYE DROPS SOLN,5099627456722,5027519008933,1,34359741719
34359739626,ASACOLON 500MG SUPPOSITORIES,5099627516587,5015313012737,1,34359742783
34359739767,ATROVENT 250MCG/1ML UDV NEB SOLN,5099627639637,5012816012561,1,34359738421
34359739770,ATROVENT 500MCG/2ML UDV NEB SOLN,5099627460293,5012816012592,1,34359743524
34359739893,AZOPT 10MG/ML EYE DROPS SUSP,5099627831543,5015664002753,1,34359749091
34359739893,AZOPT 10MG/ML EYE DROPS SUSP,5099627831543,5015664002753,1,34359749687
34359739893,AZOPT 10MG/ML EYE DROPS SUSP,5099627831543,5015664002753,1,34359749715
34359739893,AZOPT 10MG/ML EYE DROPS SUSP,5099627831543,5015664002753,1,34359754053
34359740053,BACTIGRAS MED DRSS 10CMX10CM STERILE GMS,5099627672368,5000223421984,1,34359748101
34359740062,BACTROBAN 2% OINTMENT,5099627053914,5099211003165,1,34359755226
34359740558,BETNOVATE RD CREAM,5099627005692,5099211001642,1,34359752422
34359740558,BETNOVATE RD CREAM,5099627005692,5099211001642,1,34359738487
34359741045,BISODOL ANTACID TABS,5099627057707,5014398001438,1,34359750542
34359741995,BROLENE 0.1% EYE DROPS SOLN,5099627006323,50982790,1,34359746555
34359741995,BROLENE 0.1% EYE DROPS SOLN,5099627006323,50982790,1,34359751650
34359741995,BROLENE 0.1% EYE DROPS SOLN,5099627006323,50982790,1,34359751783
34359742132,BURINEX 1MG TABS,5099627551328,5702191004212,1,34359749705
34359742152,BUSCOPAN 20MG/ML SOLN FOR INJ,5099627006620,5012816018532,1,34359749083
In the example above, several records were returned with duplicate PRODUCT_ID values e.g ASACOLON 500MG SUPPOSITORIES
You need GROUP_CONCAT/LISTAGG equivalent in SQL Server. You can use XML, STUFF and correlated subquery as replacement.
If PRODUCT_ID is UNIQUE you can use:
WITH cte AS
(
SELECT
PD.PRODUCT_ID,
PD.PRODUCT_NAME,
PD.BARCODE,
PD.SUPPLIER_BARCODE,
ODI.ORDER_ITEM_ID
FROM PRODUCTS PD
JOIN ORDER_ITEMS ODI
ON PD.PRODUCT_ID = ODI.PRODUCT_ID
WHERE ODI.SUPPLIER_ID = 34359738399
AND ORDER_STATUS = 6
AND ODI.RECORD_DELETED = 0
)
SELECT PRODUCT_ID,
PRODUCT_NAME,
BARCODE,
SUPPLIER_BARCODE,
[COUNTED] = COUNT(PD.PRODUCT_ID),
[ORDER_ITEM_ID] = STUFF((SELECT CONCAT(',' , ORDER_ITEM_ID)
FROM cte c2
WHERE c2.PRODUCT_ID = c1.PRODUCT_ID
ORDER BY c2.ORDER_ITEM_ID
FOR XML PATH ('')), 1, 1, '')
FROM cte c1
GROUP BY PRODUCT_ID,PRODUCT_NAME,BARCODE,SUPPLIER_BARCODE
HAVING COUNT(PRODUCT_ID) = 1
ORDER BY PRODUCT_ID ASC;
LiveDemo_SimplifiedVersion
Otherwise correlate using multiple columns:
SELECT CONCAT(',' , ORDER_ITEM_ID)
FROM cte c2
WHERE c2.PRODUCT_ID = c1.PRODUCT_ID
AND c2.PRODUCT_NAME = c1.PRODUCT_NAME
AND ...
ORDER BY c2.ORDER_ITEM_ID
FOR XML PATH ('')), 1, 1, '')

Syntax error in Sql MS-Access

My question: the owners would like to know the revenue generated so far (i.e. where CheckOutDate < DATE()) for each room type in each hotel.
The calculation must be done in the SQL statement.
Determine the length of stay for each reservation (i.e. number of days) using the DateDiff function datediff('d', checkindate, checkoutdate) and multiply this value by the room rate.
Your output should be formatted as shown on the next page. Your Revenue totals may be different. Keep in mind, the Revenue amount may change on a daily basis, as we want to include only those reservations that are completed, not current or future reservations.
select
room.hotelID, room.roomtype,
datediff('d', Reservation.CheckOutDate, Reservation.CheckInDate) * ROOM_TYPE.RoomRate as Revenue
from
Reservation
inner join
Room on Room.hotelID = Reservation.HotelID
inner join
ROOM_TYPE on ROOM_TYPE.RoomType = Room.roomtype
group by
Room.HotelID, Room.roomtype;
I am getting syntax error statement missing in this.
How to resolve this error in MS Access?
When using a Group By clause, any columns that are not part of the grouping must be aggregated. In your case, Room.HotelID and Room.RoomType are the grouping columns. So they are fine in your SELECT clause, as-is. But Revenue needs to be aggregated. I expect that you will want to use the SUM aggregation to sum up all of the Revenue values for each room type. Try this...
select room.hotelID,
room.roomtype,
SUM( datediff(day,Reservation.CheckOutDate,Reservation.CheckInDate )*ROOM_TYPE.RoomRate) as Revenue
from Reservation
inner join Room on Room.hotelID=Reservation.HotelID
inner join ROOM_TYPE on ROOM_TYPE.RoomType=Room.roomtype
group by Room.HotelID, Room.roomtype;
Running the query below against your data in Access 2010 produced this result set:
hotelID roomtype Revenue
------- -------- ----------
1000 D $23,000.00
1000 F $23,100.00
1000 S $20,700.00
1111 D $36,500.00
1111 F $16,450.00
1111 S $15,300.00
SELECT
rm.hotelID,
rm.roomtype,
Sum(DateDiff('d', rs.CheckInDate, rs.CheckOutDate) * rt.RoomRate) AS Revenue
FROM
(
ROOM AS rm INNER JOIN RESERVATION AS rs
ON (rm.roomno = rs.RoomNo) AND (rm.hotelID = rs.HotelID)
)
INNER JOIN ROOM_TYPE AS rt
ON rm.roomtype = rt.RoomType
WHERE rs.CheckOutDate < Date()
GROUP BY rm.hotelID, rm.roomtype;
You should still learn how to use the Query Builder but I think the parens should look something like this:
select
Room.HotelID, Room.roomtype,
sum(
datediff('d',Reservation.CheckOutDate,Reservation.CheckInDate) *
ROOM_TYPE.RoomRate
) as Revenue
from
((Reservation inner join Room on Room.hotelID = Reservation.HotelID)
inner join ROOM_TYPE on ROOM_TYPE.RoomType = Room.roomtype)
group by
Room.HotelID, Room.roomtype;
So in summary:
Be careful with grouping columns and aggregates
Access uses quotes around it's datediff argument unlike some other systems
Nesting of joins needs parentheses
In MS Access we have to mention in brackets () on the clause in from statement. Apart from that datediff function has to be a part of aggregate function.

Pivot Table from Three Tables without Aggregate

Apologies for my lack of knowledge on this, I've researched here and elsewhere but have hit a brick wall (my brain). I'm trying to display the rates for a villa in a table like this:
SPRING SUMMER FALL WINTER MAX GUESTS
2 Rooms $343 $288 $389 $467 2
3 Rooms $456 $415 $536 $756 4
Whole Villa $809 $789 $906 $1023 6
I assume that PIVOT is the answer to my woes. I'm using SQL Server 2008 on MS Server 2008 R2
The seasons, packages and prices are stored in 3 tables like this:
CONFIGURATIONS
--------------
configurationID
configurationName
maximumGuests
SEASONS
-------
seasonID
seasonName
CONFIGURATIONSEASONRATES
------------------------
seasonID
configurationID
price
I got as far as this based on the examples I've been able to find:
SELECT 'Packages', 'Summer', 'Winter', 'Christmas', 'Tropical'
FROM
(SELECT ACCOMMODATION_configurations.configurationName, price
FROM ACCOMMODATION_configurations INNER JOIN
ACCOMMODATION_configurationSeasonRates ON
ACCOMMODATION_configurations.configurationID = ACCOMMODATION_configurationSeasonRates.configurationID INNER JOIN
ACCOMMODATION_seasons ON ACCOMMODATION_configurationSeasonRates.seasonID = ACCOMMODATION_seasons.seasonID) as somethingNice
PIVOT (sum(price) for ACCOMMODATION_configurations.configurationName IN (['Summer'],['Winter'],['Christmas'],['Tropical'])) as anyThing
But I get an error saying
The column prefix 'ACCOMMODATION_configurations' does not match with a table name or alias used in the query
I then tried replacing SELECT 'Packages' with SELECT ACCOMMODATION_configurations.configurationName but then I am told that:
SELECT ACCOMMODATION_configurations.configurationName cannot be bound
Thanks in advance for any help!
Inside of your PIVOT syntax you need to remove the ACCOMMODATION_configurations. Also remove the single quotes around the values in the FOR and you need to add the Packages column to the inner select.
So the code will be:
-- this select will display the packages and the seasons
SELECT Packages, Summer, Winter, Christmas, Tropical
FROM
(
-- add Packages to this select list
SELECT Packages, ac.configurationName, price
FROM ACCOMMODATION_configurations ac
INNER JOIN ACCOMMODATION_configurationSeasonRates sr
ON ac.configurationID = sr.configurationID
INNER JOIN ACCOMMODATION_seasons s
ON sr.seasonID = s.seasonID
) as somethingNice
PIVOT
(
sum(price)
for configurationName IN ([Summer],[Winter],[Christmas],[Tropical])
) as anyThing
Edit, based on your comment it seems like you might want:
-- this select will display the packages and the seasons
SELECT Packages, Summer, Winter, Christmas, Tropical
FROM
(
SELECT ac.configurationName as Packages, price, seasonName
FROM ACCOMMODATION_configurations ac
INNER JOIN ACCOMMODATION_configurationSeasonRates sr
ON ac.configurationID = sr.configurationID
INNER JOIN ACCOMMODATION_seasons s
ON sr.seasonID = s.seasonID
) as somethingNice
PIVOT
(
sum(price)
for seasonName IN ([Summer],[Winter],[Christmas],[Tropical])
) as anyThing