Oracle Query hierarchical level cintaining when case with GROUP BY - sql

I cannot solve a problem with my GROUP BY problem in my query containing CASE...WHEN
Could you help me please with that?
select ID,
CODE,
NOM AS TITLE,
level,
ID_PARENT,
CASE ID_PARENT
WHEN 1111 THEN 'MAIN'
ELSE
(
SUBSTR(
(
SELECT CODE FROM units o
INNER JOIN LIB_UNITS_MV oLab on oLab.ID = o.ID WHERE o.ID = units.ID_PARENT AND LNG_CD = 'ENG'
)
, 7)
)
END AS "PARENT_CODE"
from units
INNER JOIN LIB_UNITS_MV orgLab on orgLab.ID = units.ID
WHERE orgLab.LNG ='ENG'
start with units.id = 1111
connect by prior u.ID = u.ID_PARENT
GROUP BY ID, CODE, NOM, level, ID_PARENT
I obtain the error "not a GROUP BY expression" because I have the WHEN...CASE
Thank you in advance for your help
Regards

Becuase when you group by you need to group by sufficient number of columns, which you use in select statement, outside aggregating functions (min, max, sum etc). So in your case this means - you can either group by all columns used in your case statement, or group by the whole case statement (just copy it over to your group by), or any set of sub-combinations of the whole case, altogether covering it completely. However - since you are not using any aggregate functions I would just do distinct and drop group by altogether.

Related

1407. Top Travellers ending in runtime error

I attempted the 1407. Top Travellers. But am struggling with my Oracle query below, 'Runtime error'. A little too tired to understand why. Any idea where I am going wrong? Have been rusty with SQL of late. :(
select name as name,
case when rides.distance is null then 0 else sum(rides.distance) end as travelled_distance
from users
left join rides
on users.id = rides.user_id
group by rides.users_id
order by travelled_distance desc, name;
As commented, is another way round:
select
name,
sum(case when rides.distance is null then 0 else rides.distance end) as travelled_distance
from users left join rides on users.id = rides.user_id
group by name
order by travelled_distance desc, name;
Or, simpler, use the nvl function:
select
name,
sum(nvl(rides.distance, 0)) as travelled_distance
from ...
Though, a few more objections:
you should use table aliases (as they simplify query and improve readability)
moreover, you should precede all column names with table aliases; in your case, you failed to do so for the name column. It probably belongs to the users table, but we can't tell for sure as we don't have your data model nor access to your database
group by clause should contain column(s) that aren't aggregated. In your query, that's the name column. You can put rides.users_id into that clause, but you must put name in there
The below solution works. Thanks to one of the Discussion posts at leetcode I could figure out the issue:
select r.name,
case when x.td is null
then 0
else x.td
end travelled_distance
from Users r
left join
(
select user_id, sum(distance) td
from Rides
group by user_id
) x
on r.id = x.user_id
order by travelled_distance desc, r.name;

TSQL "where ... group by ..." issue that needs solution like "having ..."

I have 3 sub-tables of different formats joined together with unions if this affects anything into full-table. There I have columns "location", "amount" and "time". Then to keep generality for my later needs I union full-table with location-table that has all possible "location" values and other fields are null into master-table.
I query master-table,
select location, sum(amount)
from master-table
where (time...)
group by location
However some "location" values are dropped because sum(amount) is 0 for those "location"s but I really want to have full list of those "location"s for my further steps.
Alternative would be to use HAVING clause but from what I understand HAVING is impossible here because i filter on "time" while grouping on "location" and I would need to add "time" in grouping which destroys the purpose. Keep in mind that the goal here is to get sum(amount) in each "location"
select location, sum(amount)
from master-table
group by location, time
having (time...)
To view the output:
with the first code I get
loc1, 5
loc3, 10
loc6, 1
but I want to get
loc1, 5
loc2, 0
loc3, 10
loc4, 0
loc5, 0
loc6, 1
Any suggestions on what can be done with this structure of master-table? Alternative solution to which I have no idea how to code would be to add numbers from the first query result to location-table (as a query, not actual table) with the final result query that I've posted above.
What you want will require a complete list of locations, then a left-outer join using that table and your calculated values, and IsNull (for tsql) to ensure you see the 0s you expect. You can do this with some CTEs, which I find valuable for clarity during development, or you can work on "putting it all together" in a more traditional SELECT...FROM... statement. The CTE approach might look like this:
WITH loc AS (
SELECT DISTINCT LocationID
FROM location_table
), summary_data as (
SELECT LocationID, SUM(amount) AS location_sum
FROM master-table
GROUP BY LocationID
)
SELECT loc.LocationID, IsNull(location_sum,0) AS location_sum
FROM loc
LEFT OUTER JOIN summary_data ON loc.LocationID = summary_data.LocationID
See if that gets you a step or two closer to the results you're looking for.
I can think of 2 options:
You could move the WHERE to a CASE WHEN construction:
-- Option 1
select
location,
sum(CASE WHEN time <'16:00' THEN amount ELSE 0 END)
from master_table
group by location
Or you could JOIN with the possible values of location (which is my first ever RIGHT JOIN in a very long time 😉):
-- Option 2
select
x.location,
sum(CASE WHEN m.time <'16:00' THEN m.amount ELSE 0 END)
from master_table m
right join (select distinct location from master_table) x ON x.location = m.location
group by x.location
see: DBFIDDLE
The version using T-SQL without CTEs would be:
SELECT l.location ,
ISNULL(m.location_sum, 0) as location_sum
FROM master-table l
LEFT JOIN (
SELECT location,
SUM(amount) as location_sum
FROM master-table
WHERE (time ... )
GROUP BY location
) m ON l.location = m.location
This assumes that you still have your initial UNION in place that ensures that master-table has all possible locations included.
It is the where clause that excludes some locations. To ensure you retain every location you could introduce "conditional aggregation" instead of using the where clause: e.g.
select location, sum(case when (time...) then amount else 0 end) as location_sum
from master-table
group by location
i.e. instead of excluding some rows from the result, place the conditions inside the sum function that equate to the conditions you would have used in the where clause. If those conditions are true, then it will aggregate the amount, but if the conditions evaluate to false then 0 is summed, but the location is retained in the result.

How can I COUNT rows from another table using a SELECT statement when joining?

this is the first time I've tried including a row count within a select statement. I've tried the following but including COUNT(other row) is apparently not allowed in the way I'm trying to do it.
How can I include a row count from another table in a select statement, mainly consisting of objects from the first table?
-Thanks
...
SELECT
Reports.ReportID,
EmployeeADcontext,
ReportName,
CreatedDate,
COUNT(Expenses.ExpID) AS ExpCount,
ReportTotal,
Status
FROM
[dbo].[Reports]
INNER JOIN
[dbo].[Expenses]
ON
[dbo].[Expenses].ReportID = [dbo].[Reports].ReportID
WHERE EmployeeADcontext = #rptEmployeeADcontext
You are missing your GROUP BY. Whenever you aggregate (SUM, COUNT, MAX, etc..) you always need to include a GROUP BY statement that includes all visible fields except your aggregated fields. So your code should read:
SELECT
Reports.ReportID,
EmployeeADcontext,
ReportName,
CreatedDate,
COUNT(Expenses.ExpID) AS ExpCount,
ReportTotal,
Status
FROM
[dbo].[Reports]
INNER JOIN
[dbo].[Expenses]
ON
[dbo].[Expenses].ReportID = [dbo].[Reports].ReportID
WHERE EmployeeADcontext = #rptEmployeeADcontext
GROUP BY Reports.ReportID, EmployeeADcontext, ReportName, CreatedDate,
ReportTotal, Status
Here is some additional documentation on T-SQL GROUP BY.
You need a group by clause.
Add:
GROUP BY
Reports.ReportID,
EmployeeADcontext,
ReportName,
CreatedDate,
ReportTotal,
Status
You could use a sub-query to return the count. That way you don't need any joins. For example:
SELECT
r.ReportID,
r.EmployeeADcontext,
r.ReportName,
r.CreatedDate,
(select COUNT(e1.ExpID) FROM Expenses e1 where e1.ReportID = r.ReportId) AS ExpCount,
r.ReportTotal,
r.Status
FROM Reports r
WHERE r.EmployeeADcontext = #rptEmployeeADcontext

distinct value per column

I am looking at a report on policy exceptions based on various criteria such as Beacon Score, Debt to Income, and Loan to Value. This information is kept in multiple different tables, and right now the Loan to Value column is causing multiple entries in my report because a specific loan might have multiple pieces of collateral. For proper exception monitoring, I only need one entry.
With all that said, how might I execute the following code, with a distinct value for dbo.Folders.Id? Just putting 'DISTINCT' after the SELECT statement does not seem to work. (Sensitive values masked with '#'.)
SELECT dbo.Folders.LoanOfficerId,
dbo.Folders.Id,
dbo.CollateralType.Description,
dbo.Customers.CUSTNAME,
dbo.Folders.DateLoanActivated,
dbo.Folders.CurrentAccountBalance,
dbo.Folders.UnadvancedCommitAmount,
dbo.Folders.BeaconScore,
dbo.Folders.DebtToIncome,
dbo.Collateral.LoanToValue
FROM dbo.Folders
INNER JOIN dbo.Customers
ON dbo.Folders.CustomersNAMEKEY = dbo.Customers.NAMEKEY
INNER JOIN dbo.Collateral
ON dbo.Folders.Id = dbo.Collateral.FoldersID
INNER JOIN dbo.CollateralType
ON dbo.Collateral.CollateralTypeCollCode = dbo.CollateralType.CollCode
WHERE ( (dbo.Folders.BeaconScore < ###)
AND (dbo.Folders.BeaconScore > ###)
AND (dbo.Folders.CloseCode = 'O')
AND (dbo.Folders.CollateralCode <> ##)
)
OR ( (dbo.Folders.CloseCode = 'O')
AND (dbo.Folders.CustomerType <> '###')
AND (dbo.Folders.CustomerType <> '###')
AND (dbo.Folders.DebtToIncome > ##)
)
OR ( (dbo.Folders.CloseCode = 'O')
AND (dbo.Folders.CustomerType = '###')
AND (dbo.Folders.DebtToIncome > ##)
)
OR ( (dbo.Folders.CloseCode = 'O')
AND (dbo.Folders.CustomerType = '###')
AND (dbo.Folders.DebtToIncome > ##)
)
OR (dbo.Collateral.LoanToValue > dbo.CollateralType.LTV)
Any constructive criticism on my code is welcome. (Static values in the above statement are on the docket to be corrected later with a thresholds/criteria table.) From what I have seen, others have suggested using ROW_COUNT() with PARTITION, but I am unable to make the syntax work.
Comment about formatting: learn to use table aliases. They make the query easier to read and write.
If you only need one row from the results, you can use row_number(). This enumerates the rows for each folder (in your case) and you would just use the first one. You can do this using:
with t as (
<your query here>
)
select t.*
from (select t.*,
row_number() over (partition by FoldersId order by (select NULL)) as seqnum
from t
) t
where seqnum = 1;
On the other hand, if you needed to aggregate information from the collateral tables, then you would use group by in your query with the appropriate aggregation functions.

Optimization of multiple aggregate sorting in SQL

I have a postgres query written for the Spree Commerce store that sorts all of it's products in the following order: In stock (then first available), Backorder (then first available), Sold out (then first available).
In order to chain it with rails scopes I had to put it in the order by clause as opposed to anywhere else. The query itself works, and is fairly performant, but complex. I was curious if anyone with a bit more knowledge could discuss a better way to do it? I'm interested in performance, but also different ways to approach the problem.
ORDER BY (
SELECT
CASE
WHEN tt.count_on_hand > 0
THEN 2
WHEN zz.backorderable = true
THEN 1
ELSE 0
END
FROM (
SELECT
row_number() OVER (dpartition),
z.id,
bool_or(backorderable) OVER (dpartition) as backorderable
FROM (
SELECT DISTINCT ON (spree_variants.id) spree_products.id, spree_stock_items.backorderable as backorderable
FROM spree_products
JOIN "spree_variants" ON "spree_variants"."product_id" = "spree_products"."id" AND "spree_variants"."deleted_at" IS NULL
JOIN "spree_stock_items" ON "spree_stock_items"."variant_id" = "spree_variants"."id" AND "spree_stock_items"."deleted_at" IS NULL
JOIN "spree_stock_locations" ON spree_stock_locations.id=spree_stock_items.stock_location_id
WHERE spree_stock_locations.active = true
) z window dpartition as (PARTITION by id)
) zz
JOIN (
SELECT
row_number() OVER (dpartition),
t.id,
sum(count_on_hand) OVER (dpartition) as count_on_hand
FROM (
SELECT DISTINCT ON (spree_variants.id) spree_products.id, spree_stock_items.count_on_hand as count_on_hand
FROM spree_products
JOIN "spree_variants" ON "spree_variants"."product_id" = "spree_products"."id" AND "spree_variants"."deleted_at" IS NULL
JOIN "spree_stock_items" ON "spree_stock_items"."variant_id" = "spree_variants"."id" AND "spree_stock_items"."deleted_at" IS NULL
) t window dpartition as (PARTITION by id)
) tt ON tt.row_number = 1 AND tt.id = spree_products.id
WHERE zz.row_number = 1 AND zz.id=spree_products.id
) DESC, available_on DESC
The FROM shown above determines whether or not a product is backorderable, and the JOIN shown above determines the stock in inventory. Note that these are very similar queries, except that I need to determine if something is backorderable based on a locations ability to support backorders and its state, WHERE spree_stock_locations.active=true.
Thanks for any advice!