How to perform division over two SELECT queries? - sql

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.

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;

Using aggregate function withouhg using GROUP BY in SQL Server

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 ()

Assign null if subquery retrieves multiple records. How can it be done?

I have the following query. I simplified it for demo purpose. I am using SQL Server - t-sql
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID )
from tblMain tm
if the subquery returns multiple records, I like to assign tm.LocID to null else if there is only 1 record returned then assign it to tm.LocID. I am looking for a simple way to do this. Any help would be appreciated.
One way I can see is to have a CASE statement and check if (Count * > 1 ) then assign null else return the value but that would require a select statement within a select statement.
You have the right idea about using a case expression for count(*), but it will not require another subquery:
SELECT tm.LocID = (SELECT CASE COUNT(*) WHEN 1 THEN MAX(LocID) END
FROM tblLoc tl
WHERE tl.LocID = tm.LodID )
FROM tblMain tm
or just use a HAVING clause, like
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID
group by locID
having count(*) = 1)
)
from tblMain tm
Your query above (and many of the other answers here) is a correlated subquery which will be very slow since it performs a separate aggregation query on each record. This following will address both your problem and potentially perform a bit better since the count happens in a single pass.
SELECT
CASE
WHEN x.locid IS NOT NULL THEN x.locid
ELSE NULL
END
FROM tblMain m
LEFT JOIN (
SELECT
locid
FROM tblLoc
GROUP BY locid
HAVING COUNT(1) = 1
) x
ON x.locid = m.locid
;
The above is in Postgres syntax (what I'm familiar with) so you would have to make it TSQL compatible.

SQL Nested Select statements with COUNT()

I'll try to describe as best I can, but it's hard for me to wrap my whole head around this problem let alone describe it....
I am trying to select multiple results in one query to display the current status of a database. I have the first column as one type of record, and the second column as a sub-category of the first column. The subcategory is then linked to more records underneath that, distinguished by status, forming several more columns. I need to display every main-category/subcategory combination, and then the count of how many of each sub-status there are beneath that subcategory in the subsequent columns. I've got it so that I can display the unique combinations, but I'm not sure how to nest the select statements so that I can select the count of a completely different table from the main query. My problem lies in that to display the main category and sub category, I can pull from one table, but I need to count from a different table. Any ideas on the matter would be greatly appreciated
Here's what I have. The count statements would be replaced with the count of each status:
SELECT wave_num "WAVE NUMBER",
int_tasktype "INT / TaskType",
COUNT (1) total,
COUNT (1) "LOCKED/DISABLED",
COUNT (1) released,
COUNT (1) "PARTIALLY ASSEMBLED",
COUNT (1) assembled
FROM (SELECT DISTINCT
(t.invn_need_type || ' / ' || s.code_desc) int_tasktype,
t.task_genrtn_ref_nbr wave_num
FROM sys_code s, task_hdr t
WHERE t.task_genrtn_ref_nbr IN
(SELECT ship_wave_nbr
FROM ship_wave_parm
WHERE TRUNC (create_date_time) LIKE SYSDATE - 7)
AND s.code_type = '590'
AND s.rec_type = 'S'
AND s.code_id = t.task_type),
ship_wave_parm swp
GROUP BY wave_num, int_tasktype
ORDER BY wave_num
Image here: http://i.imgur.com/JX334.png
Guessing a bit,both regarding your problem and Oracle (which I've - unfortunately - never used), hopefully it will give you some ideas. Sorry for completely messing up the way you write SQL, SELECT ... FROM (SELECT ... WHERE ... IN (SELECT ...)) simply confuses me, so I have to restructure:
with tmp(int_tasktype, wave_num) as
(select distinct (t.invn_need_type || ' / ' || s.code_desc), t.task_genrtn_ref_nbr
from sys_code s
join task_hdr t
on s.code_id = t.task_type
where s.code_type = '590'
and s.rec_type = 'S'
and exists(select 1 from ship_wave_parm p
where t.task_genrtn_ref_nbr = p.ship_wave_nbr
and trunc(p.create_date_time) = sysdate - 7))
select t.wave_num "WAVE NUMBER", t.int_tasktype "INT / TaskType",
count(*) TOTAL,
sum(case when sst.sub_status = 'LOCKED' then 1 end) "LOCKED/DISABLED",
sum(case when sst.sub_status = 'RELEASED' then 1 end) RELEASED,
sum(case when sst.sub_status = 'PARTIAL' then 1 end) "PARTIALLY ASSEMBLED",
sum(case when sst.sub_status = 'ASSEMBLED' then 1 end) ASSEMBLED
from tmp t
join sub_status_table sst
on t.wave_num = sst.wave_num
group by t.wave_num, t.int_tasktype
order by t.wave_num
As you notice, I don't know anything about the table with the substatuses.
You can use inner join, grouping and count to get your result:
suppose tables are as follow :
cat (1)--->(n) subcat (1)----->(n) subcat_detail.
so the query would be :
select cat.title cat_title ,subcat.title subcat_title ,count(*) as cnt from
cat inner join sub_cat on cat.id=subcat.cat_id
inner join subcat_detail on subcat.ID=am.subcat_detail_id
group by cat.title,subcat.title
Generally when you need different counts, you need to use the CASE statment.
select count(*) as total
, case when field1 = "test' then 1 else 0 end as testcount
, case when field2 = 'yes' then 1 else 0 endas field2count
FROM table1

Multiple Counts On Same Row With Conditions

I am trying to calculate a summarised view of the data in my table, at the moment the query works but it isn't very efficient seeing as my table will eventually hold 10,000 + contracts. I also need to filter each by the same date, I realise I could do this in the where statement for each count but this isn't very efficient.
Also the final calculation at the end would be much easier if I could operate on fields rather than selects.
(SELECT COUNT (*) FROM Contracts WHERE contractWon = 1) Won,
(SELECT COUNT (*) FROM Contracts WHERE contractPassedToCS = 1) PassedtoCS,
(SELECT COUNT (*) FROM Contracts WHERE contractPassedToCS = 0) as OutstandingWithSales,
(SELECT COUNT (*) FROM Contracts WHERE contractIssued = 1) as ContractsIssued,
(SELECT COUNT (*) FROM Contracts WHERE contractReturned = 1) as ContractsReturned,
(CONVERT(decimal, (SELECT COUNT (*) FROM Contracts WHERE contractReturned = 1)) / CONVERT(decimal, (SELECT COUNT (*) FROM Contracts WHERE contractIssued = 1))) * 100 as '% Outstanding'
I understand there is probably some joining needed but I'm a little confused.
Thanks.
SELECT
SUM(contractWon) AS Won,
SUM(contractPassedToCS ) AS PassedtoCS,
SUM(1-contractPassedToCS) AS OutstandingWithSales,
SUM(contractIssued) AS contractIssued ,
SUM(contractReturned) AS contractReturned,
100.0 * SUM(contractReturned) / SUM(contractIssued) AS '% Outstanding'
FROM
Mytable
Alternate formulations for "bit" datatypes which can not be aggregated. If the columns are int, say, then the above query works
--Either CAST first to a type that aggregates...
SUM(CAST(contractWon AS int)) AS Won,
-- .. or rely on COUNT ignores NULL values
COUNT(CASE contractWon WHEN 1 THEN 1 ELSE NULL END) AS Won,