SQLPLUS (Oracle) - WHERE subquery to determine if date expression greater than n Days - sql

I am new to SQL and have had some trouble grasping the use of subqueries and where to place them in respect to the outer query. The queries bellow are apart of a problem ive been working on and cant seem to get the desired results.
I need to extract the number of days between a start and end date. Then check if that is greater than 2 and apply that to the outer query. This particular attempt returns "Missing Expression" while other iterations (2nd Query Bellow) have returned an error stating the inner query returns multiple rows.(Modifying to use the "ALL" keyword Did not produce the right results either)
QUERY1
SELECT P.PETNAME,P.PETTYPE
FROM PETTREATMENT PT, EXAMINATION E, PET P, TREATMENT T
WHERE PT.EXAMNO = E.EXAMNO
AND E.PETNO = P.PETNO
AND PT.TREATNO = T.TREATNO
AND (SELECT TO_DATE(PETTREATMENT.ENDDATE,'DD-MON-YYYY') - TO_DATE(PETTREATMENT.STARTDATE,'DD-MON-YYYY') AS TOTALDAYS FROM PETTREATMENT WHERE TOTALDAYS > 2)
AND T.COST >100
ORDER BY P.PETNAME;
QUERY 2
SELECT P.PETNAME,P.PETTYPE
FROM PETTREATMENT PT, EXAMINATION E, PET P, TREATMENT T
WHERE PT.EXAMNO = E.EXAMNO
AND E.PETNO = P.PETNO
AND PT.TREATNO = T.TREATNO
AND 2 < (SELECT TO_DATE(PETTREATMENT.ENDDATE,'DD-MON-YYYY') - TO_DATE(PETTREATMENT.STARTDATE,'DD-MON-YYYY') FROM PETTREATMENT)
AND T.COST >100
ORDER BY P.PETNAME;

Avoid using old-style joins. Because enddate and startdate come from pettreatment table, and you are already selecting from it, you can just specify the condition in the where clause. No need for a select.
SELECT P.PETNAME,P.PETTYPE
FROM PETTREATMENT PT
JOIN EXAMINATION E ON PT.EXAMNO = E.EXAMNO
JOIN PET P ON E.PETNO = P.PETNO
JOIN TREATMENT T ON PT.TREATNO = T.TREATNO
WHERE TO_DATE(PT.ENDDATE,'DD-MON-YYYY') - TO_DATE(PT.STARTDATE,'DD-MON-YYYY') > 2
AND T.COST > 100
ORDER BY P.PETNAME;

Related

SQL add a column with COUNT(*) to my query

I need to add a column with the content of this query :
SELECT COUNT(*) FROM account_subscriptiongroups WHERE account_subscriptiongroups.active = true AND account_subscriptiongroups.user_id = account_user.id
to this query :
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
LEFT JOIN
account_subscriptiongroup ON account_adminaction.sub_group_id = account_subscriptiongroup.id
WHERE
account_adminaction.created_on >= '2021-04-07' AND account_adminaction.created_on <= '2021-04-13' AND
((account_adminaction.description LIKE 'Arrêt de l''abonnement%') OR (account_adminaction.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY
subscription_ended_on
I tried adding a LEFT JOIN like that:
LEFT JOIN
account_subscriptiongroup all_sg ON account_user.id = account_subscriptiongroup.user_id
with this line in my WHERE statement :
AND all_sg.active = true
and this line in my SELECT :
COUNT(all_sg.id)
but I get an error :
ERROR: column "account_user.id" must appear in the GROUP BY clause or be used in an aggregate function
LINE 2: account_user.id as user_id, account_user.email, account_us...
^
I don't understand how I could perform this action properly
To count something, you need to specify a group where that count applies.
So every column that you select (and is not used in an aggregate function, like COUNT or SUM), you need to mention in the GROUP BY clause.
Or to put it the other way around: the non-aggregate columns must apply to all rows that are contained in that particular COUNT.
So between the WHERE and ORDER BY clauses, add a GROUP BY clause:
GROUP BY account_user.id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id,
account_adminaction.description,
account_adminaction.id,
account_adminaction.created_on
If, on the other hand, you want a count from a different table, you can add a sub-select:
SELECT
account_user.id as user_id, account_user.email, account_user.first_name, account_user.last_name, account_user.phone,
account_subscriptiongroup.id as sub_group_id,
account_adminaction.description,
account_adminaction.id as admin_action_id,
account_adminaction.created_on as subscription_ended_on,
(SELECT COUNT(*)
FROM account_subscriptiongroups
WHERE account_subscriptiongroups.active = true
AND account_subscriptiongroups.user_id = account_user.id) AS groupcount
FROM
account_adminaction
LEFT JOIN
account_user ON account_user.id = account_adminaction.user_id
You can left join to to a derived table that does the grouping and counting:
SELECT au.id as user_id, au.email, au.first_name, au.last_name, au.phone,
asg.id as sub_group_id,
ad.description,
ad.id as admin_action_id,
ad.created_on as subscription_ended_on,
asgc.num_groups
FROM account_adminaction ad
LEFT JOIN account_user au ON au.id = ad.user_id
LEFT JOIN account_subscriptiongroups asg on ON ad.sub_group_id = asg.id
LEFT JOIN (
SELECT user_id, count(*) as num_groups
FROM account_subscriptiongroups ag
WHERE ag.active
GROUP by user_id
) asgc on asgc.user_id = au.id
WHERE ad.created_on >= '2021-04-07'
AND ad.created_on <= '2021-04-13'
AND ((ad.description LIKE 'Arrêt de l''abonnement%') OR (ad.description LIKE 'L''utilisateur a arrêté%'))
ORDER BY subscription_ended_on
It's not entirely clear to me, what you are trying to count, but another option (most probably slower) could be to use a window function combined with a filter clause:
count(*) filter (where asg.active) over (partition by asg.user_id) as num_groups
EDIT: my answer is the same as submitted by a_horse_with_no_name
Two answers, a literal one just solving the problem you posed, and then another one questioning whether what you asked for is really what you want.
Simple answer: modify your desired query to add user_id to the Select and remove user_id from the Where clause. Now you have a table that can be left-joined to the rest of your larger query.
...
Left Join (Select user_id, count(*) as total_count
From account_subscriptiongroup
Where account_subscriptiongroups.active = true
Group By user_id) Z
On Z.user_id=account_user.id
I question whether this count is what you really want here. This counts every account_subscriptiongroup entry for all time but only the active ones. Your larger query brings back inactive as well as active records, so if your goal is to create a denominator for a percentage, you are mixing 'apples and oranges' here.
If you decide you want a total by user of the records in your query instead, then you can add one more element to your larger query without adding any more tables. Use a windowing function like this:
Select ..., Sum(Case When account_subscriptiongroup.active Then 1 else 0 End) Over (Group By account_user.id) as total count
This just counts the records within the date range and having the desired actions.

Display only result > 2

I face a problem with the result on my script.
My formula for MARGIN is ((plnamt-(ibhexc/ibhand))/plnamt)*100.
I want to display only result > 2. How to do this? Please help.
This my script:
select
a.plnitm,a.plnstr,max(a.plncdt),max(a.plnndt),max(a.plnamt),max(a.plnevt),b.idept,c.ibhand,c.ibhexc,
decimal((c.ibhexc/c.ibhand),12,4) as AVG_COST,
decimal(((((max(a.plnamt)-(c.ibhexc/c.ibhand))/(max(a.plnamt))))*100),12,4) as MARGIN
from prcpln a
inner join invmst b on a.plnitm = b.inumbr
inner join invbal c on a.plnitm = c.inumbr and a.plnstr = c.istore and c.ibhand <> 0
where a.plnstr = ''14006''
group by a.plnitm,a.plnstr,b.idept,c.ibhand,c.ibhexc
order by a.plnitm
Simplistically, for situations like this, you can take your query and put it inside a cte:
WITH q AS (
select
a.plnitm,a.plnstr,max(a.plncdt),max(a.plnndt),max(a.plnamt),max(a.plnevt),b.idept,c.ibhand,c.ibhexc,
decimal((c.ibhexc/c.ibhand),12,4) as AVG_COST,
decimal(((((max(a.plnamt)-(c.ibhexc/c.ibhand))/(max(a.plnamt))))*100),12,4) as MARGIN
from prcpln a
inner join invmst b on a.plnitm = b.inumbr
inner join invbal c on a.plnitm = c.inumbr and a.plnstr = c.istore and c.ibhand <> 0
where a.plnstr = ''14006''
group by a.plnitm,a.plnstr,b.idept,c.ibhand,c.ibhexc
)
SELECT * FROM q WHERE margin > 2
ORDER BY q.plnitm
This is similar to the advice to use HAVING - a HAVING is a "where clause that is done after a GROUP BY"
A cte is a way of taking some calculated block of data and giving it an alias that can be used just like a table. I wanted to answer this way to point out to you that queries don't have to be formed purely from tables; tables are just blocks of data (with a name), and the output from a select is also "just a block of data" that can be given a name (by use of a cte or subquery) and then used just like a table is

Sql query - Struggling to write/structure

I work for a distribution centre in the uk and i've recently started using sql queries to access data directly from the db.
I've been tasked with writing a query that gives the total number of locations we have in each aisle (from table locn_hdr) and total number of locations emtpy in each of those aisles. I'vr managed to get all the info i need but by using twp seperate queries. I'm struggling to combine them into the below headers
Aisle - count of locations - count of empty locations
The two queries i have are below
Count of locations
select AISLE, COUNT(AISLE)
from LOCN_HDR LH
where LH.LOCN_CLASS = 'A'
and BAY >= '0030'
AND BAY <= '0230'
AND PICK_DETRM_ZONE LIKE 'HG%'
AND LH.AISLE <= 'QA'
group by aisle
Order by aisle;
 
Count of empties
SELECT  aisle, COUNT(dsp_locn)
FROM locn_hdr lh
WHERE lh.locn_class = 'A'
AND bay >= '0030'
AND bay <= '0230'
AND pick_detrm_zone LIKE 'HG%'
AND lh.aisle <= 'QA'
AND NOT EXISTS
(SELECT 1
FROM wm_inventory wi
WHERE wi.location_id = lh.locn_id
AND wi.on_hand_qty > '0')
GROUP BY aisle
ORDER BY aisle;
Ideally indont just want the answer with the sql re written. I want to understand how i can do something similar myself in the future.
Thanks in advance guys! Sorry if I havent given enough info, go easy on me i'm new!
edit
Hi, thanks for the help first of all! It is appreciated. However, it isn't working as i need it. the column count(lh.aisle) is counting the number of empty locations rather than the total number of locations in the aisle. I had to change the sql slightly because i was getting error messages so i temporarily used
SELECT lh.AISLE, COUNT(lh.AISLE), COUNT(wi.location_id) -- count(lh.aisle) gives me the total empty locatins. Count(wi.location_id) gives me nothing...
FROM LOCN_HDR lh
LEFT OUTER JOIN wm_inventory wi ON wi.location_id = lh.locn_id AND wi.on_hand_qty > '0'
WHERE lh.LOCN_CLASS = 'A'
AND lh.BAY BETWEEN '0030' AND '0230'
AND lh.PICK_DETRM_ZONE LIKE 'HG%'
AND lh.AISLE <= 'QA'
AND wi.location_id IS NULL -- where there is no matching record for lh.locn_id with a quantity > 0
GROUP BY lh.AISLE
ORDER BY lh.AISLE;
This has given me a count of the empty locations which is fantastic. But I don't have a count of the total number of locations (empty or not).
Any more ideas would be appreciated!
SELECT lh.AISLE, COUNT(lh.AISLE), COUNT(wi.dsp_locn)
FROM LOCN_HDR lh
LEFT OUTER JOIN wm_inventory wi ON wi.location_id = lh.locn_id AND wi.on_hand_qty > '0'
WHERE lh.LOCN_CLASS = 'A'
AND lh.BAY BETWEEN '0030' AND '0230'
AND lh.PICK_DETRM_ZONE LIKE 'HG%'
AND lh.AISLE <= 'QA'
AND wi.ID IS NULL -- where there is no matching record for lh.locn_id with a quantity > 0
GROUP BY lh.AISLE
ORDER BY lh.AISLE;
Something similar to this should work, although I can't really test it without data. Your two queries are basically the same, with the notable exception of AND NOT EXISTS (... FROM wm_inventory ...).
Basically, you can LEFT OUTER JOIN this table on location_id and on_hand_qty. LEFT OUTER JOIN will return null where there is no match (instead of excluding them).
This means that if you have a record in LOCN_HDR with locn_id = 1, and you also have a record in wm_inventory with location_id = 1 but a quantity of 2, you will receive a NULL for this record.
Combined with the WHERE wi.ID IS NULL (change to suit the column on the wi table), this replaces the NOT EXISTS clause in the second query.
You may run into an issue with selecting COUNT(wi.dsp_locn) due to the GROUP BY, but you should be able to add GROUP BY lh.AISLE, wi.dsp_locn - this will combine all entries that have both the same lh.AISLE AND wi.dsp_locn.
If you need any additional explanation, please let me know - I'd be happy to give it a shot.

Include those groups which are not present as zero

I have a query as below which outputs sum only when records of column booklet_type are present. It groups results on types like 'GR' and 'PI'.
For instance, if GR has end_leaf_no and start_leaf_no as '5' and '4' and same for PI then records will be displayed on matching booking_date like,
GR 2
PI 2
But, these does not return any records for 0 occurences of booklets of specific type. I want it as,
GR 0
PI 0
How do I go about accomplishing this result? I tried case when in select clause but to no avail.
Thanks.
Select Booklet_type, SUM(End_Leaf_No - Start_Leaf_No +1) as No_of_Coupons
from Coupon_Sale
where date_format(Booklet_Sale_Date, '%Y%m') = :ccyymm
and Booklet_type = “GR” or “PI”
group by Booklet_Type
You can do this with a left outer join and a subquery that generates the rows you want. The following works in most databases:
Select driver.Booklet_type, SUM(cs.End_Leaf_No - cs.Start_Leaf_No +1) as No_of_Coupons
from (select 'GR' as Booklet_type union all
select 'PI'
) driver left outer join
Coupon_Sale cs
on driver.Booklet_Type = cs.Booklet_Type and
date_format(cs.Booklet_Sale_Date, '%Y%m') = :ccyymm and
cs.Booklet_type in ('GR', 'PI')
group by driver.Booklet_Type;
To make this work, I moved the where conditions into the on clause.

SQL Server Query for Many to Many Relationship

I have the following Many to many relationship (See the picture below) in my SQL server.
In most cases there's are 2 rows in table tblWavelengths related to the table tblSensors, (in some cases only 1, and in extreme cases there can be 20 rows)
I made the following simple query to retrieve the data from those 3 tables :
select W.DateTimeID,S.SensorName,S.SensorType,W.Channel,W.PeakNr,W.Wavelength
from tblWavelengths as W
Left Join tblSensorWavelengths as SW on W.tblWavelengthID = SW.WavelengthID
Left Join tblSensors as S on SW.SensorID = S.SensorID
order by W.DateTimeID
After running this query I got the following results :
Here comes my problem. I want to write a query which filters only those Sensors (SensorName) which at a given moment in time (DateTimeID) has two rows (two different wavelengths) in the tblWavelengths table. So for example I want to have the results without
the 77902/001 Sensor - because it has only one row (one Wavelength) related to the tblSensors at a given moment in time
You could use a windowed function to find out the number of wavelengths for each sensorname/datetimeid combination:
WITH Data AS
( SELECT W.DateTimeID,
S.SensorName,
S.SensorType,
W.Channel,
W.PeakNr,
W.Wavelength,
[Wcount] = COUNT(*) OVER(PARTITION BY s.SensorName, d.DateTimeID)
from tblWavelengths as W
LEFT JOIN tblSensorWavelengths as SW
ON W.tblWavelengthID = SW.WavelengthID
LEFT JOIN tblSensors as S
ON SW.SensorID = S.SensorID
)
SELECT DateTimeID, SensorName, SensorType, Channel, PeakNr, WaveLength
FROM Data
WHERE Wcount = 2
ORDER BY DateTimeID;
ADDENDUM
As an after thought I realised that you might have two results for one sensor at the same time with the same wavelength, which would return 2 records, but not have two different wavelengths. Since windowed functions don't support the use of DISTINCT an alternative is below
WITH Data AS
( SELECT W.DateTimeID,
S.SensorName,
S.SensorType,
W.Channel,
W.PeakNr,
W.Wavelength,
W.tblWaveLengthID
from tblWavelengths as W
LEFT JOIN tblSensorWavelengths as SW
ON W.tblWavelengthID = SW.WavelengthID
LEFT JOIN tblSensors as S
ON SW.SensorID = S.SensorID
)
SELECT d.DateTimeID, d.SensorName, d.SensorType, d.Channel, d.PeakNr, d.WaveLength
FROM Data d
INNER JOIN
( SELECT DateTimeID, SensorName
FROM Data
GROUP BY DateTimeID, SensorName
HAVING COUNT(DISTINCT tblWaveLengthID) = 2
) t
ON t.DateTimeID = d.DateTimeID
AND t.SensorName = d.SensorName
ORDER BY d.DateTimeID;