Using aggregate function withouhg using GROUP BY in SQL Server - sql

I have a script that gathers data from a lot of different tables and pull data as I want. This script is long and very sensitive, if I group by anything we might miss on any data being pulled. Is there a way we can use these functions and not have to Group every single value?
Here is the aggregate functions I am trying to use:
CONVERT (INT, ROUND (AVG (CONVERT ( DECIMAL, score)), 0))
This part also uses where clause, in simpler script I usually just have a separate select statement to grab this data but in this case it ties into a lot of other LEFT JOINS so I cant put a Where clause as well.
Here is how I am grabbing this field in single script:
SELECT
CONVERT (INT, ROUND (AVG (CONVERT (DECIMAL, score)), 0)) AS AverageScore
FROM
tbIDs scm
LEFT JOIN
tbIds2 m ON m.ID = scm.ID
WHERE
(Score <> 0) AND (m.Complete= 0)
How can I have this whole statement in another SELECT query?
For example here is how I want to grab this data within another query
SELECT
Firstname, LastName,
CONVERT (INT, ROUND (AVG (CONVERT (DECIMAL, score)), 0)) AS AverageScore
FROM
tbppl P
LEFT JOIN
tbIds ID1 ON P.PPLID = ID1.PPlID
LEFT JOIN
tbIDs2 ID2 ON ID1.ID = ID2.ID
WHERE
(Score <> 0) AND (m.Complete= 0)
When I run it I get an error
is invalid in the select list because it is not contained in either an or the GROUP BY clause.
How can I do this?

Your query should look like:
SELECT
Firstname, LastName,
CONVERT (INT, ROUND (AVG (CONVERT (DECIMAL, score)) OVER (PARTITION BY Firstname, LastName), 0)) AS AverageScore
FROM
tbppl P
LEFT JOIN
tbIds ID1 ON P.PPLID = ID1.PPlID
LEFT JOIN
tbIDs2 ID2 ON ID1.ID = ID2.ID
WHERE
(Score <> 0) AND (m.Complete= 0)
The difference to your query is the part:
OVER (PARTITION BY Firstname, LastName)
which your AVG invoke lacked.
If you specify more columns when using aggregate functions, you need to use their window alternatives (see over clause in SQL) and in PARTITION BY specify additional columns (or use standard AVG function and add GROUP BY clause).

Use over and partition by instead. It applies the aggregate function while preserving the row structure. The format is as follows:
avg(x) over (partition by Group1, Group2, Group3)
Given that you want the average over the full data without any "groups", you simply remove the partition by part.
Your query (without the data type conversions) is as follows:
SELECT
Firstname, LastName,
AVG (score) over () AS AverageScore
FROM
tbppl P
LEFT JOIN
tbIds ID1 ON P.PPLID = ID1.PPlID
LEFT JOIN
tbIDs2 ID2 ON ID1.ID = ID2.ID
WHERE
(Score <> 0) AND (m.Complete= 0)
Edit in response to comment:
If you want to take the average over a subset of rows subject to a certain condition you need to use the following:
avg(case when <conditional logic> then x else null end)
If you only want the output rows populated where the condition is met then use:
case when <conditional logic> then avg(x) end
Combining all of the above for your case gives us:
case when (Score <> 0) AND (m.Complete= 0) then avg(case when when (Score <> 0) AND (m.Complete= 0) then Score else null end) over ()

Related

Query - display zero where null in one column and select sum of two columns where not null in next column

I need to display a zero where "Silo Wt" is null, and display the sum of the two values in the Total column even if "Silo Wt" is null.. may not require any changes if I can get a zero in the Silo column
SELECT DISTINCT (coffee_type) AS "Coffee_Type",
(SELECT ItemName
FROM [T01_Item_Name_TBL]
WHERE Item = B.Coffee_Type) AS "Description",
(SELECT COUNT(Green_Inventory_ID)
FROM [Green_Inventory] AS A
WHERE A.Coffee_Type = B.Coffee_Type
AND current_Quantity > 0) AS "Current Units",
SUM((Unit_Weight) * (Current_Quantity)) AS "Green Inv Wt",
(SELECT SUM(TGWeight)
FROM [P04_Green_STotal_TBL] AS C
WHERE TGItem = Coffee_type) AS "Silo Wt",
(SUM((Unit_Weight) * (Current_Quantity)) +
(SELECT SUM(TGWeight)
FROM [P04_Green_STotal_TBL] AS C
WHERE TGItem = Coffee_type)) AS Total
FROM
[Green_Inventory] AS B
WHERE
Pallet_Status = 0
GROUP BY
Coffee_Type
SS of query results now
You just need to wrap them in ISNULL.
However, your query could do with some serious cleanup and simplification:
DISTINCT makes no sense as you are grouping by that column anyway.
Two of the subqueries can be combined using OUTER APPLY, although this requires moving the grouped Green_Inventory into a derived table.
Another subquery, the self-join on Green_Inventory, can be transformed into conditional aggregation.
Not sure whether I've got the logic right, as the subquery did not have a filter on Pallet_Status, but it looks like you would also need to move that condition into conditional aggregation for the SUM, and use a HAVING. It depends exactly on your requirements.
Don't use quoted table or column names unless you have to.
Use meaningful table aliases, rather than A B C.
Specify table names when referencing columns, especially when using subqueries, or you might get unintended results.
SELECT
gi.Coffee_Type,
(SELECT ItemName
FROM T01_Item_Name_TBL AS n
WHERE n.Item = gi.coffee_Type
) AS Description,
ISNULL(gst.TGWeight, 0) AS SiloWt,
ISNULL(gi.GreenInvWt, 0) + ISNULL(gst.TGWeight, 0) AS Total
FROM (
SELECT
gi.Coffee_Type,
COUNT(CASE WHEN gi.current_Quantity > 0 THEN 1 END) AS CurrentUnits,
SUM(CASE WHEN gi.Pallet_Status = 0 THEN gi.Unit_Weight * gi.Current_Quantity END) AS GreenInvWt
FROM
Green_Inventory AS gi
GROUP BY
gi.Coffee_Type
HAVING
SUM(CASE WHEN gi.Pallet_Status = 0 THEN gi.Unit_Weight * gi.Current_Quantity END) > 0
) AS gi
OUTER APPLY (
SELECT SUM(gst.TGWeight) AS TGWeight
FROM P04_Green_STotal_TBL AS gst
WHERE gst.TGItem = gi.Coffee_Type
) AS gst;

SQL: identify if there are multiples (not duplicates) in a column

I am currently struggling in identifying a possibility to identify certain patterns in my data using SSMS.
I wish to identify rows that contain multiples (x2, x3, or x*4) of an entry within the same column.
I really have no clue on how to even start my "where" statement right now.
SELECT [numbers], [product_ID]
FROM [db].[dbo].[tablename]
WHERE [numbers] = numbers*2
My problem is that with the code above I can obviously only identify zeros.
Google only helps me out with finding duplicates but I can't find a way to identify multiples of a value...
My desired result would be a table that only contains numbers (linked to product_IDs) that are multiples of each other
Anyone can help me out here?
If a column contains multiples, then all are multiples of the smallest non-zero value. Let me assume the values are positive or zero for this purpose.
So, you can determine if this is the case using window functions and modulo arithmetic:
select t.*
from (select t.*,
min(case when number > 0 then number end) over () as min_number
from t
) t
where number % min_number = 0 or min_number = 1;
If you want to know if all numbers meet this criteria, use aggregation:
select (case when min(number % min_number) = 0 then 'all multiples' else 'oops' end)
from (select t.*,
min(case when number > 0 then number end) over () as min_number
from t
) t
My desired result would be a table that only contains numbers (linked to product_IDs) that are multiples of each other
You'll need to test all pairs of rows, which means a CROSS JOIN.
Something like this:
with q as
(
SELECT [numbers],
[product_ID],
cast(a.numbers as float) / coalesce(b.numbers, null) ratio
FROM [tablename] a
CROSS JOIN [tablename] b
)
select *
from q
where ratio = cast(ratio as bigint)
and ratio > 1

Oracle Query with two 'HAVING' conditions

I have a query and i want to have two HAVING conditions
The first condition is where sum is more than 6000 (Which i have
done)
The second condition is where the COUNT(1) CNT is more than 1 (Which
i need help in)
SELECT SYSDATE,
CUSTOMER.CIF_NO,
CUSTOMER.LONG_NAME_ENG,
TRANSTYPE.short_desc_Eng,
LOCATION.LONG_DESC_ENG ,
COUNT(1) CNT,
SUM(TRANS.AMOUNT) SM
FROM TRANS, CUSTOMER, TRANSTYPE, LOCATION
WHERE TRANS.TRS_AC_CIF = CUSTOMER.CIF_NO
AND TRANS.BRANCH_CODE = LOCATION.BRANCH_CODE
AND TRANS.COMP_CODE = LOCATION.COMP_CODE
AND TRANSTYPE.COMP_CODE = TRANS.COMP_CODE
AND TRANSTYPE.TYPE IN ( 'D' , 'T' )
AND TRANSTYPE.CODE = TRANS.TRX_TYPE
AND TRANS.STATUS = 'P'
AND TRANS.TRS_TYPE = 'R'
AND TRANS.CB_IND = 'C'
GROUP BY CUSTOMER.CIF_NO ,CUSTOMER.LONG_NAME_ENG,
TRANSTYPE.short_desc_Eng, LOCATION.LONG_DESC_ENG
HAVING SUM(TRANS.AMOUNT) > 6000
---------------------------
second having here
----------------------------
ORDER BY CUSTOMER.CIF_NO, CUSTOMER.LONG_NAME_ENG, LOCATION.LONG_DESC_ENG
More than one HAVING clause can not be specified within a SELECT statement, e.g. it's a violation. But add your needed condition such as
HAVING SUM(TRANS.AMOUNT) > 6000 AND COUNT(1) > 1
OR
HAVING SUM(TRANS.AMOUNT) > 6000 OR COUNT(1) > 1
as long as
a GROUP BY clause is present with the SQL statement
aggregations take place within the HAVING clause
P.S. Convert your query syntax to the syntax with explicit JOIN clauses among tables rather than old-style comma-seperated JOINs, and use aliases for the table names

How to perform division over two SELECT queries?

I have two query results that produce numbers. I am wondering how I can combine the two queries into one division operation.
I have my query as
SELECT COUNT(*) FROM Games WHERE Title = "Zelda" - This gets me my numerator
SELECT COUNT(*) FROM Games - This is my denominator.
I want to write a query that is the result set of the numerator / denominator. Is this possible?
You can use SELECT inside FORM:
SELECT CAST(T1.N AS float)/T2.D FROM
(SELECT COUNT(*) AS N FROM Games WHERE Title = "Zelda") T1,
(SELECT COUNT(*) AS D FROM Games) T2
Each query of yours could be treated as a table (with one result variable), just give them names using AS and then create outer query that selects the arithmetic operation you want. (Casting the result to float to get the ratio).
With conditional sum:
select sum(case when Title = 'Zelda' then 1 else 0) / count(*) as result from Games
The above code will do integer division.
If you need more precision:
select 1.0 * sum(case when Title = 'Zelda' then 1 else 0) / count(*) as result from Games
Also if your rdbms allows it you can do this:
SELECT (SELECT COUNT(*) FROM Games WHERE Title = "Zelda") /(SELECT COUNT(*) FROM Games)
I would simply do:
SELECT AVG( (Title = 'Zelda)::int)
FROM Games;
I think this is the simplest query that does what you want (assuming that Title is never NULL.

Using SQL SUM with Case statement containing inner SELECT

I have two tables, an Orders table which contains a list of a users orders and a OrderShippingCosts table which contains a price for shipping each item based on the OrderTypeID in the Orders table.
I am running a query like below to calculate the total shipping costs:
SELECT
SUM(CASE
WHEN OR.OrderTypeID = 1
THEN (SELECT CostOfShippingSmallParcel
FROM OrderShippingCosts)
ELSE (SELECT CostOfShippingBigParcel
FROM OrderShippingCosts)
END) AS TotalShippingCost
FROM
Orders AS OR
But I'm getting the following error:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery
Does anyone know what is wrong with my query?
Function SUM takes an expression on input, which evaluates into single data value, not a dataset. Expression definition from MSDN:
Is a combination of symbols and operators that the SQL Server Database Engine evaluates to obtain a single data value.
You trying to pass to SUM function a dataset (which is result of subquery), not a single data value. This is simplification of what you trying to query:
SELECT SUM(SELECT Number FROM SomeTable)
It is not valid. The valid query would be:
SELECT SUM(Value) FROM SomeTable
In your particular case looks like you missing JOIN. Your original logic will result in summary of entire OrderShippingCosts table for each row of Orders table. I think, it should be something like this:
SELECT
SUM
(
CASE
WHEN ord.OrderTypeID = 1 THEN ship.CostOfShippingSmallParcel
ELSE ship.CostOfShippingBigParcel
END
) TotalShippingCost
FROM Orders AS ord
JOIN OrderShippingCosts ship ON /* your search condition, e.g.: ord.OrderID = ship.OrderID */
By the way, it is not a good idea to use reserved symbols as aliases, names and so on. In your query you use OR as alias for Orders table. Symbol OR is reserved for logical or operation. If you really need to use reserved symbol, wrap it into [ and ] square braces. Look here and here for more details.
The error message is clear, you can avoid it with a join:
SELECT
SUM(CASE WHEN [OR].OrderTypeID = 1
THEN CostOfShippingSmallParcel
ELSE CostOfShippingBigParcel END) AS TotalShippingCost
FROM Orders [OR]
CROSS JOIN OrderShippingCosts
You can try like this...
SELECT
CASE WHEN OR.OrderTypeID = 1
THEN (SELECT SUM(CostOfShippingSmallParcel) FROM OrderShippingCosts)
ELSE (SELECT SUM(CostOfShippingBigParcel) FROM OrderShippingCosts) END AS TotalShippingCost
FROM Orders AS OR
Let me know
select sum (or.TotalShippingCost)
FROM
SELECT
(CASE WHEN OR.OrderTypeID = 1
THEN (SELECT CostOfShippingSmallParcel FROM OrderShippingCosts)
ELSE (SELECT CostOfShippingBigParcel FROM OrderShippingCosts) END) AS TotalShippingCost
FROM Orders AS OR
Try this
SELECT
ISNULL
(
SUM
(
CASE
WHEN O.OrderTypeID = 1 THEN C.CostOfShippingSmallParcel
ELSE C.CostOfShippingBigParcel END
), 0
) AS TotalShippingCost
FROM
Orders AS O LEFT JOIN
OrderShippingCosts C ON O.Id = C.OrderId -- Your releation id