Oracle SQL subtracting between subqueries - sql

I have two queries, the first is this:
SELECT * FROM "HOTELS"
LEFT JOIN "ROOM" ON "HOTELS"."HOTEL_ID"="ROOM"."HOTEL_ID"
LEFT JOIN "ROOM_TYPE" ON "ROOM"."ROOM_T"="ROOM_TYPE"."ROOM_T"
WHERE ("ROOM_TYPE"."ROOM_T"='Two beds' AND "HOTELS"."NAME"='Sunset Hotel');
The second is this:
SELECT * FROM "HOTELS"
LEFT JOIN "RESERVATIONS" ON "HOTELS"."HOTEL_ID"="RESERVATIONS"."HOTEL_ID"
WHERE ("HOTELS"."NAME"='Sunset Hotel' AND "RESERVATIONS"."DATE"='7/20/2016');
(I translated the strings from my native language)
OK, I am running the first one in Oracle's APEX SQL Commands page and it returns 4 entries, which are the rooms I am interested in.
I am running the second and it returns the reserved rooms for the date I am interested in, 1 row result.
Now I want to do the subtraction so that I get left with only the 3 rooms that are not reserved at that date.
But I try different formats that I found after googling and I always get some useless error messages.

You can use a left join and check for o matches:
SELECT *
FROM "HOTELS" h JOIN
"ROOM" r
ON h."HOTEL_ID" = r."HOTEL_ID" JOIN
"ROOM_TYPE" rt
ON r."ROOM_T" = rt."ROOM_T" LEFT JOIN
"RESERVATIONS" r
ON h."HOTEL_ID" r."HOTEL_ID" AND r."DATE" = '7/20/2016'
WHERE h."NAME= 'Sunset Hotel' AND
rt."ROOM_T"='Two beds' AND
r."HOTEL_ID" IS NULL;
You should not need LEFT JOIN for the hotels/rooms/room type connections. The ids should be valid -- and even if they are not, the check on room type requires matches anyway. The LEFT JOIN is needed for RESERVATIONS.

I always get some useless error messages
Error messages are never useless, in fact they are very useful to understand what's wrong your are doing or asking your RDBMS to do.
"RESERVATIONS"."DATE"='7/20/2016'
This is wrong since you are comparing a DATE with a string literal. Always, explicitly convert the string into date using TO_DATE and proper format mask. Or, if you dealing only with the date part and not concerned with the time portion, then use the ANSI date literal which uses a fixed format DATE 'YYYY-MM-DD'
Coming back to your original requirement about the query:
SELECT *
FROM "HOTELS" h
LEFT JOIN "ROOM" r
ON h."HOTEL_ID" = r."HOTEL_ID"
LEFT JOIN "ROOM_TYPE" t
ON r."ROOM_T" = t."ROOM_T"
LEFT JOIN "RESERVATIONS" v
ON ( h."HOTEL_ID" = v."HOTEL_ID"
AND v."DATE" = TO_DATE('2016-07-20', 'YYYY-MM-DD' )
WHERE t."ROOM_T" = 'Two beds'
AND h."NAME" = 'Sunset Hotel'
AND v."HOTEL_ID" IS NULL;

You can use a LEFT JOIN to include the RESERVATIONS table and put the date filter as part of the join condition (the other tables can use INNER JOINs):
SELECT *
FROM "HOTELS" h
INNER JOIN "ROOM" r
ON h."HOTEL_ID" = r."HOTEL_ID"
INNER JOIN "ROOM_TYPE" t
ON r."ROOM_T" = t."ROOM_T"
LEFT JOIN "RESERVATIONS" v
ON ( h."HOTEL_ID" = v."HOTEL_ID" AND v."DATE"= DATE '2016-07-20' )
WHERE t."ROOM_T" = 'Two beds'
AND h."NAME" = 'Sunset Hotel'
AND v."HOTEL_ID" IS NULL;
Then to test for a non-reservation you can check that the RESERVATIONS primary key is NULL (as per the last line).
Also note, that if you are comparing to a DATE data type then if you are using a string value Oracle will do an implicit TO_DATE() using the NLS_DATE_FORMAT parameter as the format mask - if this does not match then it will throw exceptions and if it does work and then the parameter gets changed it will break the query. It would be better to use a date literal value instead (e.g. DATE '2016-07-20').
Finally, you probably don't need to use double quotes around all the table and column names. By default, Oracle will convert column and table names to upper-case (which is what you are using so this isn't an issue) and the only reason to double-quote identifiers is if you want to specify a case-sensitive format (which you should try to avoid) or if you want to use keywords as names (which you also ought to avoid). So, if possible change the RESERVATIONS.DATE column to another name as DATE is a keyword in oracle (but the rest of the column names should not need quotes).

Another option is to use MINUS:
Subquery1
MINUS
Subquery2
https://docs.oracle.com/cd/B19306_01/server.102/b14200/queries004.htm

Related

Join on Update Statement?

I have two tables: One is InvWarehouse which contains most of the data. The other is InvMaster+ which contains what I'm filtering on (Which is SalesDemandClass).
How do I join these in an update statement?
Here's what I have so far, but it doesn't work. I tried to connect them on StockCode (which is in both), but if I put InvWarehouse A it says it expects the SET to be there instead.
update InvWarehouse A
set MinimumQty = SafetyStockQty, MaximumQty = (SafetyStockQty * 3)
from InvWarehouse
left join [InvMaster+] B on A.StockCode = B.StockCode
where SalesDemandClass in ('D4','D8','D12')
Making the assumption from the square bracket delimiters this is SQL Server, you need the correct syntax:
update iw set
iw.MinimumQty = im.SafetyStockQty,
iw.MaximumQty = SafetyStockQty * 3
from InvWarehouse iw
left join [InvMaster+] im on im.StockCode = iw.StockCode
where iw.SalesDemandClass in ('D4','D8','D12');
Try to get in the good habit of using meaningful table aliases and using them for all columns to avoid you and others guessing.

Should I use an SQL full outer join for this?

Consider the following tables:
Table A:
DOC_NUM
DOC_TYPE
RELATED_DOC_NUM
NEXT_STATUS
...
Table B:
DOC_NUM
DOC_TYPE
RELATED_DOC_NUM
NEXT_STATUS
...
The DOC_TYPE and NEXT_STATUS columns have different meanings between the two tables, although a NEXT_STATUS = 999 means "closed" in both. Also, under certain conditions, there will be a record in each table, with a reference to a corresponding entry in the other table (i.e. the RELATED_DOC_NUM columns).
I am trying to create a query that will get data from both tables that meet the following conditions:
A.RELATED_DOC_NUM = B.DOC_NUM
A.DOC_TYPE = "ST"
B.DOC_TYPE = "OT"
A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999
A.DOC_TYPE = "ST" represents a transfer order to transfer inventory from one plant to another. B.DOC_TYPE = "OT" represents a corresponding receipt of the transferred inventory at the receiving plant.
We want to get records from either table where there is an ST/OT pair where either or both entries are not closed (i.e. NEXT_STATUS < 999).
I am assuming that I need to use a FULL OUTER join to accomplish this. If this is the wrong assumption, please let me know what I should be doing instead.
UPDATE (11/30/2021):
I believe that #Caius Jard is correct in that this does not need to be an outer join. There should always be an ST/OT pair.
With that I have written my query as follows:
SELECT <columns>
FROM A LEFT JOIN B
ON
A.RELATED_DOC_NUM = B.DOC_NUM
WHERE
A.DOC_TYPE IN ('ST') AND
B.DOC_TYPE IN ('OT') AND
(A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999)
Does this make sense?
UPDATE 2 (11/30/2021):
The reality is that these are DB2 database tables being used by the JD Edwards ERP application. The only way I know of to see the table definitions is by using the web site http://www.jdetables.com/, entering the table ID and hitting return to run the search. It comes back with a ton of information about the table and its columns.
Table A is really F4211 and table B is really F4311.
Right now, I've simplified the query to keep it simple and keep variables to a minimum. This is what I have currently:
SELECT CAST(F4211.SDDOCO AS VARCHAR(8)) AS SO_NUM,
F4211.SDRORN AS RELATED_PO,
F4211.SDDCTO AS SO_DOC_TYPE,
F4211.SDNXTR AS SO_NEXT_STATUS,
CAST(F4311.PDDOCO AS VARCHAR(8)) AS PO_NUM,
F4311.PDRORN AS RELATED_SO,
F4311.PDDCTO AS PO_DOC_TYPE,
F4311.PDNXTR AS PO_NEXT_STATUS
FROM PROD2DTA.F4211 AS F4211
INNER JOIN PROD2DTA.F4311 AS F4311
ON F4211.SDRORN = CAST(F4311.PDDOCO AS VARCHAR(8))
WHERE F4211.SDDCTO IN ( 'ST' )
AND F4311.PDDCTO IN ( 'OT' )
The other part of the story is that I'm using a reporting package that allows you to define "virtual" views of the data. Virtual views allow the report developer to specify the SQL to use. This is the application where I am using the SQL. When I set up the SQL, there is a validation step that must be performed. It will return a limited set of results if the SQL is validated.
When I enter the query above and validate it, it says that there are no results, which makes no sense. I'm guessing the data casting is causing the issue, but not sure.
UPDATE 3 (11/30/2021):
One more twist to the story. The related doc number is not only defined as a string value, but it contains leading zeros. This is true in both tables. The main doc number (in both tables) is defined as a numeric value and therefore has no leading zeros. I have no idea why those who developed JDE would have done this, but that is what is there.
So, there are matching records between the two tables that meet the criteria, but I think I'm getting no results because when I convert the numeric to a string, it does not match, because one value is, say "12345", while the other is "00012345".
Can I pad the numeric -> string value with zeros before doing the equals check?
UPDATE 4 (12/2/2021):
Was able to finally get the query to work by converting the numeric doc num to a left zero padded string.
SELECT <columns>
FROM PROD2DTA.F4211 AS F4211
INNER JOIN PROD2DTA.F4311 AS F4311
ON F4211.SDRORN = RIGHT(CONCAT('00000000', CAST(F4311.PDDOCO AS VARCHAR(8))), 8)
WHERE F4211.SDDCTO IN ( 'ST' )
AND F4311.PDDCTO IN ( 'OT' )
AND ( F4211.SDNXTR < 999
OR F4311.PDNXTR < 999 )
You should write your query as follows:
SELECT <columns>
FROM A INNER JOIN B
ON
A.RELATED_DOC_NUM = B.DOC_NUM
WHERE
A.DOC_TYPE IN ('ST') AND
B.DOC_TYPE IN ('OT') AND
(A.NEXT_STATUS < 999 OR B.NEXT_STATUS < 999)
LEFT join is a type of OUTER join; LEFT JOIN is typically a contraction of LEFT OUTER JOIN). OUTER means "one side might have nulls in every column because there was no match". Most critically, the code as posted in the question (with a LEFT JOIN, but then has WHERE some_column_from_the_right_table = some_value) runs as an INNER join, because any NULLs inserted by the LEFT OUTER process, are then quashed by the WHERE clause
See Update 4 for details of how I resolved the "data conversion or mapping" error.

Return a query with datetime existing in both tables

I am trying to return a table which joins data from an ads table and a website traffic table, both with hour level data. However, a timestamp that exists for a particular day in the ads table may not exist in the website table. For example, timestamp "2017-09-27 20:00:00+00" exists in the website traffic table but not in the ads table, and vice versa.
I am using using a query which selects the ads table timestamp, but using a left join. A full outer join does not seem to solve this issue, most likely due to selecting the ads timestamp and not the website traffic timestamp.
Is there a way in PostgreSQL to return the timestamp of both tables in one column?
Much appreciated.
The query currently being used is as follows:
SELECT
ads.phase AS "phase",
ads.datetime_utc AS "datetime",
lower(array_to_string((regexp_split_to_array(ads.placement, '_'))[1:9], '_')) AS "delim_dims",
a.name AS " name",
ads.device AS "device",
sum(ads.impressions) AS "impressions",
sum(ads.clicks) AS "clicks",
sum(ads.spend) AS "spend",
web.sessions AS "sessions",
web.bounces AS "bounces"
FROM
ads_data AS ads
INNER JOIN
lookup.names_lookup AS a ON
ads.lookup_code = a.lookup_code
LEFT JOIN -- tested with FULL OUTER JOIN, returns same results
web.website_traffic AS web ON
ads.datetime_utc = web.datetime_est
AND
a.lookup_code = web.lookup_code
AND
ads.device = web.device
GROUP BY
ads.phase,
datetime,
delim_dims,
a.audience_name,
web.sessions,
web.bounces,
device
HAVING
sum(ads.spend) > 0
I'm confused by your wording because your question title asks for "a datetime existing in both tables" which suggests you only want rows with a matching datetime in both ads_data and web.website_traffic. But then you want to use a LEFT JOIN or a FULL OUTER JOIN for some reason, which makes me think you want rows with a datetime in one of the columns. The way I interpret this is that you want a column with the datetime from either table; if it happens to be row with matching timestamps, great; if there's only a timestamp in one or the other table, return that.
It looks like your problem is you're doing an INNER JOIN between lookup.names_lookup (a) and ads_data. TheN when you join to web.website_traffic, one of your join conditions is a.lookup_code = web.lookup_code. That essentially converts your LEFT JOIN into an INNER JOIN, so you're only getting results for data in ads_data, and none of the cases with a row in web.website_traffic but not ads_data.
Instead, I would start with a subquery (CTE) that is a full outer join of ads_data with web.website_traffic to get all non-matching rows + all matching rows together, then inner join that with lookup.names_lookup.
A few more issues I noticed:
The ads_data datetime column's name refrs to UTC time, whereas the web.website_traffic column refers to EST time.
I think it's weird that you're grouping by sessions and bounces, since those are numeric: I'd probably do a min on those instead.
You reference "delim_dims" in the GROUP BY, but since you calculated that in the SELECT clause, you have to repeat the calculation in the GROUP BY (the SELECT clause is evaluated last)
Here's some SQL to try (ignoring that potential UTC vs EST issue):
WITH alldata AS (
SELECT
ads.phase,
COALESCE(ads.datetime_utc, web.datetime_est) AS "datetime",
COALESCE(ads.lookup_code, web.lookup_code) AS "lookup_code",
COALESCE(ads.device, web.device) AS "device",
ads.placement,
ads.impressions,
ads.clicks,
ads.spend,
web.sessions,
web.bounces
FROM
ads_data AS ads FULL OUTER JOIN web.website_traffic AS web ON
ads.datetime_utc = web.datetime_est AND
ads.lookup_code = web.lookup_code AND
ads.device = web.device
)
SELECT
alldata.phase AS "phase",
alldata.datetime AS "datetime",
lower(array_to_string((regexp_split_to_array(alldata.placement, '_'))[1:9], '_')) AS "delim_dims",
a.name AS "name",
alldata.device AS "device",
sum(alldata.impressions) AS "impressions",
sum(alldata.clicks) AS "clicks",
sum(alldata.spend) AS "spend",
min(alldata.sessions) AS "sessions",
min(alldata.bounces) AS "bounces"
FROM
alldata INNER JOIN lookup.names_lookup AS a ON
alldata.lookup_code = a.lookup_code
GROUP BY
alldata.phase,
alldata.datetime,
lower(array_to_string((regexp_split_to_array(alldata.placement, '_'))[1:9], '_')),
a.audience_name,
alldata.device
HAVING
sum(ads.spend) > 0

CONCAT value in ON condition of JOIN

I have two tables.
Table: Geonames:
Country (2 character ISO code, e.g. SE)
AdminArea (Char code, e.g. 0330)
Table AdminAreas
AdminCode, (Combination of Coutry and AdminArea, e.g. "SE.0330")
So the ID of the AdminAreas that I want to join the tables on is a combination of columns on the first table. To join it I will need to join the two values from the Geonames table. Something like.
SELECT
geoname.geonameid, geoname.name, geoname.latitude, geoname.longitude,
geoname.country, geoname.admin1, admin_one.admin_id, admin_one.geoname_id
FROM geoname
INNER JOIN admin_one ON admin_one.admin_id = CONCAT(geoname.country, '.', geoname.admin1)
WHERE country='SE' LIMIT 10
Unfortunately, this is not working. It does not seem like i can CONCAT or do string_agg() on a JOIN. How do I get this JOIN working?
Your code is fine. Perhaps one issue is that you have spaces or some other character. I would recommend investigating this using:
SELECT gn.geonameid, gn.name, gn.latitude, gn.longitude,
gn.country, gn.admin1, ao.admin_id, ao.geoname_id
FROM geoname gn LEFT JOIN
admin_one ao
ON ao.admin_id = CONCAT(TRIM(gn.country), '.', TRIM(gn.admin1))
WHERE gn.country = 'SE'
LIMIT 10;
This will return even unmatched results (because of the LEFT JOIN). That might help you investigate the issue.

Why is my left join not working on two fields where one was parsed?

I have a table and a query that I want to join. The fields that I need to compare are Job/JobParse and Suffix/SuffixParse. The parsed fields came from dbo_JOB000 using RIGHT and LEFT functions to pull out the string. It may be useful to note that in the Table the Job field is of type text and the Suffix field is of type number.
dbo_job
job suf job_date Uf_Production_Line
H000001534 23 6/1/2015 LN4200
dbo_RESSCHD000
RESID JOBTAG STARTDATE
LN4200 147 6/8/2015 6:00:00 AM
LN4200 147 6/8/2015 2:00:00 PM
dbo_JOB000
JOBTAG JSID
147 .H000001534 .00023.0000000010
qry_Schedule - this query is built using dbo_RESSCHD000 and dbo_JOB000
SELECT
dbo_JOB000.JSID,
dbo_RESSCHD000.RESID,
dbo_RESSCHD000.GROUPID,
dbo_RESSCHD000.STARTDATE,
dbo_RESSCHD000.ENDDATE,
1 AS NumberofShifts,
Right(Left([JSID],11),10) AS JobParse, Left(Right([JSID],13),2) AS SuffixParse,
dbo_RESSCHD000.STATUSCD
FROM dbo_JOB000 INNER JOIN dbo_RESSCHD000 ON dbo_JOB000.JOBTAG = dbo_RESSCHD000.JOBTAG
WHERE
(((dbo_RESSCHD000.STARTDATE)>=Date()) AND ((dbo_RESSCHD000.ENDDATE)<=Date()+([Forms]![MainForm]![Text43]-1)) AND ((dbo_RESSCHD000.STATUSCD) Not Like "S"))
ORDER BY
dbo_JOB000.JSID;
qry_JobCompare - this query is built using dbo_job and qry_Schedule
SELECT
qry_Schedule.JobParse,
qry_Schedule.SuffixParse,
dbo_job.job_date,
dbo_job.Uf_Production_Line
FROM qry_Schedule
LEFT JOIN dbo_job
ON qry_Schedule.JobParse = dbo_job.job;
The goal is to return the job and suffix information from qry_Schedule and then use those to find other corresponding information from the dbo_job. For example, I want to use H000001534 and 23 to find that job in the dbo_job and find its job date, line number, etc... But I need to use the query's list of jobs.
The problem is that I am getting the error message "JOIN expression not supported".
Does anyone know how to fix this? Let me know if you need more info.
Thanks!
You can't use the equals operator to compare text fields. Try using the 'like' keyword.
SELECT
qry_Schedule.JobParse,
qry_Schedule.SuffixParse,
dbo_job.job_date,
dbo_job.Uf_Production_Line
FROM qry_Schedule
LEFT JOIN dbo_job
ON ( qry_Schedule.JobParse like dbo_job.job );
You also can't index a text field so this may be slow for large tables. Ideally the Job field would be a varchar or nvarchar type so that it can be indexed and the = operator can be used.
You can put brackets as
SELECT qry_Schedule.JobParse,
qry_Schedule.SuffixParse,
dbo_job.job_date,
dbo_job.Uf_Production_Line
FROM qry_Schedule INNER JOIN dbo_job ON (qry_Schedule .id_field=dbo_job.id_field);
If i get you right, the problem seems to be, that you have a value in one of your tables, which you have to parse first and join then.
I would use a subselect.
select * from (
select substring(bla,23,2) as jobParse, <otherfields> from qry_Schedule) as innertab left join dbo_job on innertab.job=dbo_job.job
or something like that.