I'm trying to find rows with max credit on my table ,
CREATE TABLE Course(
CourseNr INTEGER,
CourseTitel VARCHAR(60),
CourseTyp VARCHAR(10),
CourselenghtDECIMAL,
Credit DECIMAL,
PRIMARY KEY (CourseNr)
);
and there is more than one courses with max value. I dont want to use any default functions for that, any ideas?
Presumably, you want the rows with the maximum credit. A common method is to find any rows that have no larger credit:
select c.*
from course c
where c.credit >= all (select c2.credit from course c2);
Get the rows with Credit for which there don't exist any rows with greater Credit:
SELECT
c.*
FROM Course c
WHERE
NOT EXISTS (
SELECT 1 FROM Course WHERE Credit > c.Credit
)
Related
I have a table and want to get the average score for each student。
To be more specific, scoremonth1 has more weight to be calculated than 2,3,4,5 and 6 (1>2>3>4>5>6). And we should add no more than 3 monthly scores from the table.
For instance, the average score for Tom should be (80+90)/2 since there are only 2 scores available. As for Marry, the average score should be (90+70+80)/3 since those are the three monthly scores with more weight. And again, for Anna, the average score should be (90+100+70)/3
In my case, there would be over 100 students. Except listing all the possible cases like CASE WHEN scoremonth1 is not null, scoremonth2 is NULL . etc to calculate the average, what else method could do the calculation dynamically?
I know there is a SQL function coalesce to return the first not null value, but how could I get the second and third not null values? And is there a way to track which monthlyscores are added up? I really appreciate your help!
Stu mentioned your underlying issue. To normalize your data without changing table design you can use cross apply...
select student, sum(score)
from table
cross apply (
values(1,scoremonth1),(2,scoremonth2),(3,scoremonth3)) as scores(month,score)
group by student
I strongly suggest you redesign so you don't have to manage this query when adding months by creating a new table called studentScores.
create table studentscores
(
student varchar(200)
,scoremonth int
,score decimal(5,2)
)
And then populate it like this...
insert into studentScores(student,scoremonth,score)
select *
from table
cross apply
(values
(student,1,scoremonth1)
,(student,2,scoremonth2)
,(student,3,scoremonth3)
,(student,4,scoremonth4)
,(student,5,scoremonth5)
) ca(ca1,ca2,ca3)
where ca3 is not null
And finally, usse it like this...
select ss.student, sum(score), count(*) NumOfScores, sum(score)/Count(*) avg
from table
join studentscores ss on ss.student=table.student
where ss.scoremonth between 1 and 3
group by ss.student
My table CUSTOMER_TABLE has a nested table of references toward ACCOUNT_TABLE. Each account in ACCOUNT_TABLE has a reference toward a branch: branch_ref.
CREATE TYPE account AS object(
accid integer,
acctype varchar2(15),
balance number,
rate number,
overdraft_limit integer,
branch_ref ref branch,
opendate date
) final;
CREATE TYPE customer as object(
custid integer,
infos ref type_person,
accounts accounts_list
);
create type branch under elementary_infos(
bid integer
) final;
All tables are inherited from these object types.
I want to select the account with the highest balance per branch. I arrived to do that with this query:
select MAX(value(a).balance), value(a).branch_ref.bid
from customer_table c, table(c.accounts) a
group by value(a).branch_ref.bid
order by value(a).branch_ref.bid;
Which returns:
MAX(VALUE(A).BALANCE) VALUE(A).BRANCH_REF.BID
--------------------------------------- ---------------------------------------
176318.88 0
192678.14 1
190488.19 2
196433.93 3
182909.84 4
However, how to select as well others attribues from the max accounts displayed ? I would like to display the name of the owner plus the customer's id. The id is directly an attribute of customer. But the name is stored with a reference toward person_table. So I have to select as well c.id & deref(c.infos).names.surname.
How to select these other attributes with my MAX() query ?
Thank you
I generally use analytic functions to achieve that kind of functionality. With analytic functions, you can add aggregate columns to your query without losing the original rows. In your particular case it would be something like:
select
-- select interesting fields
accid,
acctype,
balance,
rate,
overdraft_limit,
branch_ref,
opendate,
max_balance
from (
select
-- propagate original fields to outer query
value(a).accid accid,
value(a).acctype acctype,
value(a).balance balance,
value(a).rate rate,
value(a).overdraft_limit overdraft_limit,
value(a).branch_ref branch_ref,
value(a).opendate opendate,
-- add max(balance) of our branch_ref to the row
max(value(a).balance) over (partition by value(a).branch_ref.bid) max_balance
from customer_table c, table(c.accounts) a
) data
where
-- we are only interested in rows with balance equal to the max
-- (NOTE: there might be more than one, you should fine tune the filtering!)
data.balance = data.max_balance
-- order by branch
order by data.branch_ref.bid;
I don't have any Oracle instance available right now to test this, but that would be the idea, unless there is some kind of incompatibility between analytic functions and collection columns, you should be able to have that working with little effort.
I have a database table that I need to process with either a view or a stored procedure or something else that gives me a result based on the live data.
The table holds records of people with data associated with each one. The thing is that people can be in the table more than once. Each record shows a time when one or more pieces of information was recorded for an individual.
The identifier field for the people is cardholder_index. I need to take a DISTINCT list of that field. There is also a date field called bio_complete_date. What I need to do is, for all the other fields in the table, take the most recent non-null (or possibly non-zero) value.
For instance, there is a bmi field. For each distinct cardholder index, I need to take the most recent (by the bio_complete_date field) non-null bmi for that cardholder_index. But there's also a body_fat field, and I need to take the most recent non-null value in that field, which might not necessarily be the same row as the most recent non-null bmi value.
For the record, the table itself does have its own unique identifier column, bio_id, if that helps.
I don't need to show when the most recent piece of information was taken. I just need to show the data itself.
I figure I need to do a distinct on the card_holder index, and then join to it the result sets of querys for each other field. It's writing the subqueries that is giving me problems.
From your description I guess your table looks something like this:
create table people (
bio_id int identity(1,1),
cardholder_index int,
bio_complete_date date,
bmi int,
body_fat int
)
If so, one way (of many) to do the query would be to use correlated queries to pull the latest non-null value for the cardholder_index, either using subqueries like this:
select
cardholder_index,
(
select top 1 bmi
from people
where cardholder_index = p.cardholder_index and bmi is not null
order by bio_complete_date desc
) as latest_bmi,
(
select top 1 body_fat
from people
where cardholder_index = p.cardholder_index and body_fat is not null
order by bio_complete_date desc
) as latest_body_fat
from people p
group by cardholder_index
or to use the apply operator like this:
select cardholder_index, latest_bmi.bmi, latest_body_fat.body_fat
from people p
outer apply (
select top 1 bmi
from people
where cardholder_index = p.cardholder_index and bmi is not null
order by bio_complete_date desc
) as latest_bmi
outer apply (
select top 1 body_fat
from people
where cardholder_index = p.cardholder_index and body_fat is not null
order by bio_complete_date desc
) as latest_body_fat
group by cardholder_index, latest_bmi.bmi, latest_body_fat.body_fat
Sample SQL Fiddle demo
I have a table with records that look like this:
CREATE TABLE sample (
ix int unsigned auto_increment primary key,
start_active datetime,
last_active datetime
);
I need to know how many records were active on each of the last 30 days. The days should also be sorted incrementing so they are returned oldest to newest.
I'm using MySQL and the query will be run from PHP but I don't really need the PHP code, just the query.
Here's my start:
SELECT COUNT(1) cnt, DATE(?each of last 30 days?) adate
FROM sample
WHERE adate BETWEEN start_active AND last_active
GROUP BY adate;
Do an outer join.
No table? Make a table. I always keep a dummy table around just for this.
create table artificial_range(
id int not null primary key auto_increment,
name varchar( 20 ) null ) ;
-- or whatever your database requires for an auto increment column
insert into artificial_range( name ) values ( null )
-- create one row.
insert into artificial_range( name ) select name from artificial_range;
-- you now have two rows
insert into artificial_range( name ) select name from artificial_range;
-- you now have four rows
insert into artificial_range( name ) select name from artificial_range;
-- you now have eight rows
--etc.
insert into artificial_range( name ) select name from artificial_range;
-- you now have 1024 rows, with ids 1-1024
Now make it convenient to use, and limit it to 30 days, with a view:
Edit: JR Lawhorne notes:
You need to change "date_add" to "date_sub" to get the previous 30 days in the created view.
Thanks JR!
create view each_of_the_last_30_days as
select date_sub( now(), interval (id - 1) day ) as adate
from artificial_range where id < 32;
Now use this in your query (I haven't actually tested your query, I'm just assuming it works correctly):
Edit: I should be joining the other way:
SELECT COUNT(*) cnt, b.adate
FROM each_of_the_last_30_days b
left outer join sample a
on ( b.adate BETWEEN a.start_active AND a.last_active)
GROUP BY b.adate;
SQL is great at matching sets of values that are stored in the database, but it isn't so great at matching sets of values that aren't in the database. So one easy workaround is to create a temp table containing the values you need:
CREATE TEMPORARY TABLE days_ago (d SMALLINT);
INSERT INTO days_ago (d) VALUES
(0), (1), (2), ... (29), (30);
Now you can compare a date that is d days ago to the span between start_active and last_active of each row. Count how many matching rows in the group per value of d and you've got your count.
SELECT CURRENT_DATE - d DAYS, COUNT(*) cnt,
FROM days_ago
LEFT JOIN sample ON (CURRENT_DATE - d DAYS BETWEEN start_active AND last_active)
GROUP BY d
ORDER BY d DESC; -- oldest to newest
Another note: you can't use column aliases defined in your select-list in expressions until you get to the GROUP BY clause. Actually, in standard SQL you can't use them until the ORDER BY clause, but MySQL supports using aliases in GROUP BY and HAVING clauses as well.
Turn the date into a unix timestamp, which is seconds, in your query and then just look for the difference to be <= the number of seconds in a month.
You can find more information here:
http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_unix-timestamp
If you need help with the query please let me know, but MySQL has nice functions for dealing with datetime.
[Edit] Since I was confused as to the real question, I need to finish the lawn but before I forget I want to write this down.
To get a count of the number by day you will want your where clause to be as I described above, to limit to the past 30 days, but you will need to group by day, and so select by converting each start to a day of the month and then do a count of those.
This assumes that each use will be limited to one day, if the start and end dates can span several days then it will be trickier.
I'm getting odd results from a MySQL SELECT query involving a LEFT JOIN, and I can't understand whether my understanding of LEFT JOIN is wrong or whether I'm seeing a genuinely odd behavior.
I have a two tables with a many-to-one relationship: For every record in table 1 there are 0 or more records in table 2. I want to select all the records in table 1 with a column that counts the number of related records in table 2. As I understand it, LEFT JOIN should always return all records on the LEFT side of the statement.
Here's a test database that exhibits the problem:
CREATE DATABASE Test;
USE Test;
CREATE TABLE Dates (
dateID INT UNSIGNED NOT NULL AUTO_INCREMENT,
date DATE NOT NULL,
UNIQUE KEY (dateID)
) TYPE=MyISAM;
CREATE TABLE Slots (
slotID INT UNSIGNED NOT NULL AUTO_INCREMENT,
dateID INT UNSIGNED NOT NULL,
UNIQUE KEY (slotID)
) TYPE=MyISAM;
INSERT INTO Dates (date) VALUES ('2008-10-12'),('2008-10-13'),('2008-10-14');
INSERT INTO Slots (dateID) VALUES (3);
The Dates table has three records, and the Slots 1 - and that record points to the third record in Dates.
If I do the following query..
SELECT d.date, count(s.slotID) FROM Dates AS d LEFT JOIN Slots AS s ON s.dateID=d.dateID GROUP BY s.dateID;
..I expect to see a table with 3 rows in - two with a count of 0, and one with a count of 1. But what I actually see is this:
+------------+-----------------+
| date | count(s.slotID) |
+------------+-----------------+
| 2008-10-12 | 0 |
| 2008-10-14 | 1 |
+------------+-----------------+
The first record with a zero count appears, but the later record with a zero count is ignored.
Am I doing something wrong, or do I just not understand what LEFT JOIN is supposed to do?
You need to GROUP BY d.dateID. In two of your cases, s.DateID is NULL (LEFT JOIN) and these are combined together.
I think you will also find that this is invalid (ANSI) SQL, because d.date is not part of a GROUP BY or the result of an aggregate operation, and should not be able to be SELECTed.
I think you mean to group by d.dateId.
Try removing the GROUP BY s.dateID
The dateid for 10-12 and 10-13 are groupd together by you. Since they are 2 null values the count is evaluated to 0
I don't know if this is valid in MySQL but you could probably void this mistake in the future by using the following syntax instead
SELECT date, count(slotID) as slotCount
FROM Dates LEFT OUTER JOIN Slots USING (dateID)
GROUP BY (date)
By using the USING clause you don't get two dateID's to keep track of.
replace GROUP BY s.dateID with d.dateID.