Calculate mode in SQL - sql

I have seen the answer from a previous post, which works fine but I have a small dilemma.
Taking the same scenario:
A table that list students' grades per class. I want a result set that looks like:
BIO...B
CHEM...C
Where the "B" and "C" are the modes for the class and want to get the mode for the class.
Once I applied the below query, i got the following output:
Class | Score | Freq | Ranking
2010 | B | 8 | 1
2010 | C | 8 | 1
2011 | A | 10 | 1
2012 | B | 11 | 1
In 2010, I have two grades with the same frequency. What if..I just want to display the highest score, in this case will be "B". How can I achieve that? I would need to assign rankings to the letter grades, but I'm not sure how. Please advise.
Thanks.
Prior post:
SQL Server mode SQL
The query I used to retrieve the data was the answer from Peter:
;WITH Ranked AS (
SELECT
ClassName, Grade
, GradeFreq = COUNT(*)
, Ranking = DENSE_RANK() OVER (PARTITION BY ClassName ORDER BY COUNT(*) DESC)
FROM Scores
GROUP BY ClassName, Grade
)
SELECT * FROM Ranked WHERE Ranking = 1

Change:
SELECT * FROM Ranked WHERE Ranking = 1
To:
SELECT Class, MIN(Grade) AS HighestGrade, Freq, Ranking
FROM Ranked
WHERE Ranking = 1
GROUP BY Class, Freq, Ranking

Related

Top-N per group (MSSQL) [duplicate]

This question already has answers here:
Select top 10 records for each category
(14 answers)
Closed 2 years ago.
I have 10k - 1m goods wich are discribed by fields product_id, name, category, price. Which is the fastest way to fetched 10 most expensive goods from each category? Previously I checked this answer https://stackoverflow.com/a/176985/9513268.
My table:
-------------------------------------
|product_id| name | category| price |
-------------------------------------
| 1 |Phone | Gadgets | 599.99|
------------------------------------
| 2 |Jacket| Clothes | 399.00|
-------------------------------------
| ... | ... | ... | ... |
-------------------------------------
You can use window functions, as showned in the answer that you linked.
select *
from (
select t.*, rank() over(partition by category order by price desc) rn
from mytable t
) t
where rn <= 10
order by category, rn
The key is to properly define the over() clause of the window function. You want the top 10 by category, so this column goes to the partition by; you want the top most expensive goods, so the order by criteria is on descending price.
You can run the subquery separately and stare and the rn column to better understand the logic.

SQL Server, complex query

I have an Azure SQL Database table which is filled by importing XML-files.
The order of the files is random so I could get something like this:
ID | Name | DateFile | IsCorrection | Period | Other data
1 | Mr. A | March, 1 | false | 3 | Foo
20 | Mr. A | March, 1 | true | 2 | Foo
13 | Mr. A | Apr, 3 | true | 2 | Foo
4 | Mr. B | Feb, 1 | false | 2 | Foo
This table is joined with another table, which is also joined with a 3rd table.
I need to get the join of these 3 tables for the person with the newest data, based on Period, DateFile and Correction.
In my above example, Id=1 is the original data for Period 3, I need this record.
But in the same file was also a correction for Period 2 (Id=20) and in the file of April, the data was corrected again (Id=13).
So for Period 3, I need Id=1, for Period 2 I need Id=13 because it has the last corrected data and I need Id=4 because it is another person.
I would like to do this in a view, but using a stored procedure would not be a problem.
I have no idea how to solve this. Any pointers will be much appreciated.
EDIT:
My datamodel is of course much more complex than this sample. DateFile and Period are DateTime types in the table. Actually Period is two DateTime columns: StartPeriod and EndPeriod.
Well looking at your data I believe we can disregard the IsCorrection column and just pick the latest column for each user/period.
Lets start by ordering the rows placing the latest on top :
SELECT ROW_NUMBER() OVER (PARTITION BY Period, Name ORDER by DateFile DESC), *
And from this result you select all with row number 1:
;with numberedRows as (
SELECT ROW_NUMBER() OVER (PARTITION BY Period, Name ORDER by DateFile DESC) as rowIndex, *
)
select * from numberedRows where rowIndex=1
The PARTITION BY tells ROW_NUMBER() to reset the counter whenever it encounters change in the columns Period and Name. The ORDER BY tells the ROW_NUMBER() that we want th newest row to be number 1 and then older posts afterwards. We only need the latest row.
The WITH declares a "common table expression" which is a kind of subquery or temporary table.
Not knowing your exact data, I might recommend you something wrong, but you should be able to join your with last query with other tables to get your desired result.
Something like:
;with numberedRows as (
SELECT ROW_NUMBER() OVER (PARTITION BY Period, Name ORDER by DateFile DESC) as rowIndex, *
)
select * from numberedRows a
JOIN periods b on b.empId = a.Id
JOIN msg c on b.msgId = c.Id
where a.rowIndex=1

Closest match per group?

I have table that looks similar to this:
Motor MotorType CalibrationValueX CalibrationValueY
A Car 1.2343 2.33343
B Boat 1.2455 2.55434
B1 Boat 1.4554 2.11211
C Car 1.4323 4.56555
D Car 1.533 4.6666
..... 500 entries
In my SQL query, I am trying to find average of CalibrationValueY where CalibrationValueX is a certain value:
SELECT avg(CalibrationValueY), MotorType, Motor FROM MotorTable
WHERE CalibrationValueX = 1.23333
GROUP BY MotorType
This will not return anything, since there is not a CalibrationValueX value that equals exactly 1.23333.
I am able to find closest match separately for each MotorTable with:
SELECT TOP 1 CalibrationValueY, FileSize, MotorType, Motor FROM MotorTable
where FileType = 'text' order by abs(FileSize - 1.23333)
however, I can't get it to work with a group by statement.
How can I do it so that if I am grouping by MotorType and I am searching CalibrationValueX = 1.23333, I would get this:
A Car 1.2343 2.33343
B Boat 1.2455 2.55434
Using ROW_NUMBER and PARTITION BY You combinate TOP 1 for each group
SQL Fiddle Demo
with cte as (
SELECT MotorType, CalibrationValueX, CalibrationValueY,
ROW_NUMBER() over (partition by MotorType order by abs(CalibrationValueX - 1.23333)) rn
from historyCR
)
SELECT *
from cte
where rn = 1
OUTPUT
| MotorType | CalibrationValueX | CalibrationValueY | rn |
|-----------|-------------------|-------------------|----|
| Boat | 1.2455 | 2.55434 | 1 |
| Car | 1.2343 | 2.33343 | 1 |

Oracle Apex Pie Chart SQL Statement

i'm not really much into SQL & Apex, but i need one statement and I would really appreciate your help on this.
The syntax of Apex pie charts is this:
SELECT link, label, value
My table looks like these simple sketch:
+------+-----------+---------+
| ID | Company | Item |
+------+-----------+---------+
| 1 | AAA |Some |
| 2 | BB |Stuff |
| 3 | BB |Not |
| 4 | CCCC |Important|
| 5 | AAA |For |
| 6 | DDDDD |Question?|
+------+-----------+---------+
I want to show the percentage of the companies.
Problem: All companies with less than 5 items should combine to one colum "other". The difficulty for me is to combine the "unimportant" companies.
Until now my statement looks like this:
SELECT null link,
company label,
COUNT(ID) value FROM table HAVING COUNT(ID) > 5 GROUP BY company
Here a wonderful diagram-sketch. :D
Thank you for your ideas!
I've not got SQL Developer in front of me but this (or a close variation) should work for you:
WITH company_count
AS (
SELECT CASE
WHEN count(*) < 5
THEN 'Other'
ELSE company
END AS company_name,
id
FROM tablename
),
company_group
AS (
SELECT company_name,
count(id) item_count
FROM company_count
GROUP BY company_name
)
SELECT NULL AS link,
company_name AS label,
item_count AS value
FROM company_group;
Hope it helps!
Okay Guys, I found the answer for my use case. Its quite similar to Ollies answer. Thanks again for the help!
WITH sq1 AS (SELECT company, COUNT (*) AS count FROM
(SELECT CASE WHEN COUNT (*) OVER (Partition By company) > 5 THEN company
ELSE 'other' END AS company, id FROM table) GROUP BY company)
SELECT null link, company label, count value FROM sq1 ORDER BY count desc

SQL Server COUNT query with unique values

This is done in Microsoft SQL Server 2008 R2.
I'll start out with an example table.
Organization | MoneyAmount | MoneyAmountAvg
ISD | 500 |
ISD | 500 |
ISD | 500 |
QWE | 250 |
ISD | 500 |
QWE | 250 |
OLP | 800 |
ISD | 500 |
I need the MoneyAmountAvg column to have a value of MoneyAmount/(# of times that organization shows up
So for example, the MoneyAmountAvg column for the ISD rows would have a value of 100.
QWE would have 125 for each row in the MoneyAmountAvg column and OLP would have a value of 800 since it is there only once.
This is only an example table. The actual table is much bigger and has more organizations, but it has the same criteria. Some organizations have multiple rows, while others are there only once.
I just need a way for it to count how many times each organization is listed when I use an update statement for that organization's MoneyAmountAvg column. Hard coding it for each organization is definitely not an option since they can change at any moment.
Any help is appreciated.
Here is my answer:
select organization, moneyamount,
moneyamount / count(*) over (partition by organization)
from t
This is a simple application of a window function. I think most of the other answers are producing the overall average.
For an update statement, simply do:
with toupdate as (
select organization, moneyamount,
moneyamount / count(*) over (partition by organization) as newval
from t
)
update toupdate
set MoneyAmountAvg = newval
Try something like this:
;WITH CTE AS
(
SELECT
Org, Moneyamount,
MoneyAvg = AVG(MoneyAmount) OVER(PARTITION BY Org),
OrgCount = COUNT(*) OVER (PARTITION BY Org)
FROM
dbo.YourTableHere
)
SELECT DISTINCT Org, MoneyAmount, OrgCount, MoneyAvg / OrgCount
FROM CTE
That seems to return what you're looking for:
You can do this using analytic functions:
SELECT Organization,
MoneyAmount,
MoneyAmountAvg = MoneyAmount / COUNT(*) OVER(PARTITION BY Organization)
FROM T
Example on SQL Fiddle
SELECT (MIN(MoneyAmount) / COUNT(*)) AS AvgMoneyAmount
FROM your_table
GROUP BY Organization
This works if MoneyAmout for a single Organization is always equal.
But I'd say: Refactor the table:
Organization | MoneyAmount | count
----------------------------------
ISD | 500 | 5
QWE | 250 | 2
...
select example.Organization, example.MoneyAmount,
example.MoneyAmount / count(group_table.Organization) as MoneyAmountAvg
from example
inner join example group_table
on example.Organization = group_table.Organization
select
organization
,avg(MoneyAmount) / count(1)
from tab
group by organization
The purpouse of avg(Moneyamount) is to handle possible different values of amount for the same organization.