Related
I have a oracle sql table that looks like so
"STUDENT_ID","FULL_NAME","SEMESTER_ID","STIP_ID"
"1","Liam Bottrill","1","1"
"1","Liam Bottrill","2","3"
"1","Liam Bottrill","3","2"
"1","Liam Bottrill","4","5"
"2","Maurits Smitham","1","6"
"2","Maurits Smitham","2",""
"2","Maurits Smitham","3","2"
"2","Maurits Smitham","4","6"
"43","Jackie Cotton","1",""
"43","Jackie Cotton","2",""
"43","Jackie Cotton","3",""
"43","Jackie Cotton","4",""
I want to group this table by "STUDENT_ID" and exclude from result any students that have any of "STIP_ID" rows empty
Im aiming for result like this:
"STUDENT_ID","FULL_NAME"
"1","Liam Bottrill"
Liam Bottrill should be displayed while Maurits Smitham and Jackie Cotton should be excluded from result
Can you please help me with such aggregate function?
Here is one way, using aggregation:
SELECT *
FROM yourTable
WHERE STUDENT_ID IN (
SELECT STUDENT_ID
FROM yourTable
GROUP BY STUDENT_ID
HAVING COUNT(CASE WHEN STIP_ID IS NULL THEN 1 END) = 0
);
Another way, using exists logic:
SELECT t1.*
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.STUDENT_ID = t1.STUDENT_ID AND
t2.STIP_ID IS NULL
);
You can group by the identifier and then use conditional aggregation to find the student where the count when STIP_ID is NULL (which, in Oracle, is the same as an empty string):
SELECT student_id,
MAX(full_name) AS full_name
FROM table_name
GROUP BY student_id
HAVING COUNT(CASE WHEN stip_id IS NULL THEN 1 END) = 0;
Which, for your sample data:
CREATE TABLE table_name (STUDENT_ID, FULL_NAME, SEMESTER_ID, STIP_ID) AS
SELECT 1, 'Liam Bottrill', 1, 1 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 2, 3 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 3, 2 FROM DUAL UNION ALL
SELECT 1, 'Liam Bottrill', 4, 5 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 1, 6 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 2, NULL FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 3, 2 FROM DUAL UNION ALL
SELECT 2, 'Maurits Smitham', 4, 6 FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 1, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 2, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 3, NULL FROM DUAL UNION ALL
SELECT 43, 'Jackie Cotton', 4, NULL FROM DUAL;
Outputs:
STUDENT_ID
FULL_NAME
1
Liam Bottrill
db<>fiddle here
I have a database table which looks like
ID
Book_no
Book_name
Book_category
ID1
1
B1
CB1
ID1
2
B1
CB1
ID1
3
B2
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
1
B1
CB2
ID2
2
B1
CB2
ID2
3
B2
CB2
And the expected result is like
ID
Book_No
Book_name
Book_category
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
I want to delete duplicate records from table on the basis of ID, Book_name and Book_category. Below query deletes the duplicate records, but the result is not expected one. As I want to delete all the duplicate records except the highest Book_no. Want to maintain the highest Book_no and delete all other duplicates.
You can DELETE correlating on the ROWID pseudo-column:
DELETE FROM table_name
WHERE ROWID IN (
SELECT rid
FROM (
SELECT ROWID AS rid,
ROW_NUMBER() OVER (
PARTITION BY id, book_name, book_category
ORDER BY book_no DESC
) AS rn
FROM table_name
)
WHERE rn > 1
);
Which, for the sample data:
CREATE TABLE table_name (id, book_no, book_name, book_category) AS
SELECT 'ID1', 1, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 2, 'B1', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 3, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 4, 'B2', 'CB1' FROM DUAL UNION ALL
SELECT 'ID1', 5, 'B3', 'CB1' FROM DUAL UNION ALL
SELECT 'ID2', 1, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 2, 'B1', 'CB2' FROM DUAL UNION ALL
SELECT 'ID2', 3, 'B2', 'CB2' FROM DUAL;
Then the remaining rows are:
SELECT * FROM table_name;
ID
BOOK_NO
BOOK_NAME
BOOK_CATEGORY
ID1
2
B1
CB1
ID1
4
B2
CB1
ID1
5
B3
CB1
ID2
2
B1
CB2
ID2
3
B2
CB2
sqlfiddle here
You can use lead() and filter where the name changes:
select t.*
from (select t.*,
lead(book_name) over (partition by id, book_category order by book_no) as next_book_name
from t
) t
where next_book_name is null or next_book_name <> book_name;
Assuming your table looks like this:
create table books (id, book_no, book_name, book_category) as
select 'ID1', 1, 'B1', 'CB1' from dual union all
select 'ID1', 2, 'B1', 'CB1' from dual union all
select 'ID1', 3, 'B2', 'CB1' from dual union all
select 'ID1', 4, 'B2', 'CB1' from dual union all
select 'ID1', 5, 'B3', 'CB1' from dual union all
select 'ID2', 1, 'B1', 'CB2' from dual union all
select 'ID2', 2, 'B1', 'CB2' from dual union all
select 'ID2', 3, 'B2', 'CB2' from dual
;
You can use a delete statement in which you compare each row to the ones you want to keep. The ones you want to keep have max(book_no) when grouped by the other columns. So:
delete from books
where (id, book_no, book_name, book_category) not in
(
select id, max(book_no), book_name, book_category
from books
group by id, book_name, book_category
)
;
This assumes that the columns are non-null; if you may have null in the table, you will need to rewrite this a little more carefully, with a not exists condition instead of not in, but the idea is the same.
You can use first_value for finding the required id and can remove others using delete.
SELECT DISTINCT a.*, FIRST_VALUE(a.book_name)
OVER (ORDER BY a.book_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM ( select * from books group by id, Book_name, book_category ) a;
SELECT DISTINCT FIRST_VALUE(column_name)
OVER (ORDER BY column_name DESC
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)
AS "HIGHEST"
FROM (select * from tables group by "column_names_to_be_grouped");
I have got 3 tables as follows:
table_teams (id_team, name, city)
table_matches (id_match, date_of_match, city)
table_game (id, id_team (FK), id_match (FK), goals) -- this one is a junction table between the first two
The same match is introduced into table_game as two separate records, one for each team with its number of goals, but having the same id_match,
INSERT INTO table_game(id_team, id_match, goals) VALUES('GER', 1234, 7);
INSERT INTO table_game(id_team, id_match, goals) VALUES('BRA', 1234, 1);
and as a single record into table_matches,
INSERT INTO table_matches(id_match, date_of_match, city)
VALUES (1234, '8-JUL-2014', 'Belo Horizonte');
Now, I would like to create a view that selects data from these tables and displays it as follows:
Date | Team1 | Goals1 | Goals2 | Team2 | City
The best I managed to do so far is display each record on separate lines in the table.
SELECT tm.date_of_match, tt.name, tg.goals, tm.city
FROM table_matches tm, table_teams tt, table_game tg
WHERE tm.id_match = tg.id_match AND tt.id_team = tg.id_team;
But I have no idea how to display data as stated above. All my tries resulted in a big mess.
Will appreciate some help. Thank you.
Using JOIN syntax
-- Test data
WITH table_game(ID, id_team, id_match, goals) AS (
SELECT 10, 'USA', 100, 5 FROM dual UNION ALL -- error (3 records for same match)
SELECT 11, 'RUS', 100, 4 FROM dual UNION ALL -- error (3 records for same match)
SELECT 12, 'BRA', 100, 3 FROM dual UNION ALL -- error (3 records for same match)
SELECT 20, 'ARG', 1234, 7 FROM dual UNION ALL
SELECT 21, 'BRA', 1234, 1 FROM dual UNION ALL
SELECT 30, 'ENG', 4321, 6 FROM dual UNION ALL
SELECT 31, 'FRA', 4321, 2 FROM dual
)
,table_matches(id_match, date_of_match, city) AS (
SELECT 1234, DATE '2014-07-08', 'Belo Horizonte' FROM dual UNION ALL
SELECT 4321, DATE '2014-08-09', 'Sao Paulo' FROM dual UNION ALL
SELECT 100, DATE '2014-09-10', 'Brasilia' FROM dual)
,table_teams(id_team, NAME, city) AS (
SELECT 'ARG', 'Argentina', 'Buenos Aires' FROM dual UNION ALL
SELECT 'BRA', 'Brazil', 'Sao Paulo' FROM dual UNION ALL
SELECT 'ENG', 'England', 'London' FROM dual UNION ALL
SELECT 'FRA', 'France', 'Paris' FROM dual UNION ALL
SELECT 'RUS', 'Russia', 'Moscow' FROM dual UNION ALL
SELECT 'USA', 'United States', 'New York' FROM dual)
-- End Test data
,match_teams AS (
SELECT id_match, MAX(id_team) team1, MIN(id_team) team2
FROM table_game
GROUP BY id_match
HAVING COUNT(*) = 2 -- any valid match should have only 2 teams, otherwise ignored
)
SELECT M.date_of_match, t1.NAME team1, g1.goals goals1, t2.NAME team2, g2.goals goals2, M.city city_match
FROM table_matches M
JOIN match_teams mt ON mt.id_match = M.id_match
JOIN table_game g1 ON g1.id_match = M.id_match AND g1.id_team = mt.team1
JOIN table_game g2 ON g2.id_match = M.id_match AND g2.id_team = mt.team2
JOIN table_teams t1 ON t1.id_team = mt.team1
JOIN table_teams t2 ON t2.id_team = mt.team2
/
Joins on WHERE clause
-- Test data
WITH table_game(ID, id_team, id_match, goals) AS (
SELECT 10, 'USA', 100, 5 FROM dual UNION ALL -- error (3 records for same match)
SELECT 11, 'RUS', 100, 4 FROM dual UNION ALL -- error (3 records for same match)
SELECT 12, 'BRA', 100, 3 FROM dual UNION ALL -- error (3 records for same match)
SELECT 20, 'ARG', 1234, 7 FROM dual UNION ALL
SELECT 21, 'BRA', 1234, 1 FROM dual UNION ALL
SELECT 30, 'ENG', 4321, 6 FROM dual UNION ALL
SELECT 31, 'FRA', 4321, 2 FROM dual
)
,table_matches(id_match, date_of_match, city) AS (
SELECT 1234, DATE '2014-07-08', 'Belo Horizonte' FROM dual UNION ALL
SELECT 4321, DATE '2014-08-09', 'Sao Paulo' FROM dual UNION ALL
SELECT 100, DATE '2014-09-10', 'Brasilia' FROM dual)
,table_teams(id_team, NAME, city) AS (
SELECT 'ARG', 'Argentina', 'Buenos Aires' FROM dual UNION ALL
SELECT 'BRA', 'Brazil', 'Sao Paulo' FROM dual UNION ALL
SELECT 'ENG', 'England', 'London' FROM dual UNION ALL
SELECT 'FRA', 'France', 'Paris' FROM dual UNION ALL
SELECT 'RUS', 'Russia', 'Moscow' FROM dual UNION ALL
SELECT 'USA', 'United States', 'New York' FROM dual)
-- End Test data
,match_teams AS (
SELECT id_match, MAX(id_team) team1, MIN(id_team) team2
FROM table_game
GROUP BY id_match
HAVING COUNT(*) = 2 -- any valid match should have only 2 teams, otherwise ignored
)
SELECT M.date_of_match, t1.NAME team1, g1.goals goals1, t2.NAME team2, g2.goals goals2, M.city city_match
FROM table_matches M
,match_teams mt
,table_game g1
,table_game g2
,table_teams t1
,table_teams t2
WHERE mt.id_match = M.id_match
AND g1.id_match = M.id_match
AND g1.id_team = mt.team1
AND g2.id_match = M.id_match
AND g2.id_team = mt.team2
AND t1.id_team = mt.team1
AND t2.id_team = mt.team2
/
you can use ROW_NUMBER to assign a value then join against it, something like this:
;with cte (team,match,goals,row) as
(
select
id_team,
id_match,
goals,
ROW_NUMBER () over (partition by match_id order by team_id)
from table_game
)
select
t1.date_of_match,c.team, c.goals, c1.goals, c1.team, t1.city
from cte c
left join cte c1 on c.match = c1.match and c1.row = 2
inner join table_matches t1 on c.match = t1.id_match
where c.row = 1
select date_of_match, g1.id_team team1, g1.goals goals1,
g2.goals goals2, g2.id_team team2, m.city
from matches m
join games g1 on g1.id_match = m.id_match
join games g2 on g1.id_match = m.id_match and g2.id > g1.id
test:
with games(id, id_team, id_match, goals) as (
select 1, 'GER', 1234, 7 from dual union all
select 2, 'BRA', 1234, 1 from dual),
matches(id_match, date_of_match, city) as (
select 1234, date '2014-07-08', 'Belo Horizonte' from dual)
select date_of_match, g1.id_team team1, g1.goals goals1,
g2.goals goals2, g2.id_team team2, m.city
from matches m
join games g1 on g1.id_match = m.id_match
join games g2 on g1.id_match = m.id_match and g2.id > g1.id
result:
DATE_OF_MATCH TEAM1 GOALS1 GOALS2 ID_TEAM CITY
------------- ------- ------ ------ ------- --------------------
2014-07-08 GER 7 1 BRA Belo Horizonte
I need a table A with 4 columns for order numbers: Order1, Order2, Order3, Order4 (I know, it's horrible, but that's a given).
And I have to find the records in table B, where the match is that any value in any order column in table A could be in any order in column B:
A.Order1 = B.Order1 OR
A.Order1 = B.Order2 OR
A.Order1 = B.Order3 OR
A.Order1 = B.Order4 OR
A.Order2 = B.Order1 etc
Is there a better way to write this?
I dread the moment they tell me they want to use 5 or 6 columns.
EDITS TO ORIGINAL QUESTION
This is for SQL Server 2008 R2
Table B also has 4 order columns with order numbers
I'm looking for any order number in any order column in Table A to match any order number in any order column in Table B.
There's no anticipated most likely finding
You can unpivot the columns in one table using a cross apply and then check if the value from the cross apply is in any of the columns from the other table.
It does not automatically work if you add new columns but you will only have to add them in one or two places.
SQL Fiddle
MS SQL Server 2014 Schema Setup:
create table A
(
Order1 int,
Order2 int,
Order3 int,
Order4 int
)
create table B
(
Order1 int,
Order2 int,
Order3 int,
Order4 int
)
insert into A values
(1, 1, 40, 10),
(2, 2, 2, 20)
insert into B values
(3, 3, 3, 30),
(4, 4, 4, 40)
Query 1:
select *
from A
where exists (
select *
from B
cross apply (values(B.Order1),(B.Order2),(B.Order3),(B.Order4)) as X(O)
where X.O in (A.Order1, A.Order2, A.Order3, A.Order4)
)
Results:
| Order1 | Order2 | Order3 | Order4 |
|--------|--------|--------|--------|
| 1 | 1 | 40 | 10 |
The only way to do this with the model design you have is something (as #GordonLinoff suggested) like this:
where b.order1 in (a.order1, a.order2, a.order3, a.order4) or
b.order2 in (a.order1, a.order2, a.order3, a.order4) or
b.order3 in (a.order1, a.order2, a.order3, a.order4) or
b.order4 in (a.order1, a.order2, a.order3, a.order4)
An interesting question you might have is how can I change my data model to make this work better? ... here is how:
First you have your two tables A and B. I'm going to assume that both A and B have an unique index ID.
You can then make a support table AOrder with the following columns
AID
ORDNUM
VALUE
If you make a similar table for BOrder then to find out if a given order is the same just join on Value and you get the AID, BID, and the two order numbers.
With this design you don't care how many order numbers there are.
You could convert your current data to this design on the fly like this and get the results you want:
SELECT aord.ID as aID, bord.ID as bID, a.num as a_ordernum, b.num as b.ordernum, v
FROM (
SELECT a.ID, 1 AS num, a.order1 as V FROM a
UNION ALL
SELECT a.ID, 2 AS num, a.order2 as V FROM a
UNION ALL
SELECT a.ID, 3 AS num, a.order3 as V FROM a
UNION ALL
SELECT a.ID, 4 AS num, a.order4 as V FROM a
) aord
JOIN (
SELECT b.ID, 1 AS num, b.order1 as V FROM b
UNION ALL
SELECT b.ID, 2 AS num, b.order2 as V FROM b
UNION ALL
SELECT b.ID, 3 AS num, b.order3 as V FROM b
UNION ALL
SELECT b.ID, 4 AS num, b.order4 as V FROM b
) bord on aord.v = bord.v
Can you just normalize them? I'm assuming you have a SetID field grouping the 4(or more) orders in A and B, so you could have a table/view like:
select ID, srctbl, seq, order
from (
select AID as ID, 'A' as srctbl, 1 as seq, order1 as order from tblA union all
select AID, 'A' as srctbl, 2, order2 from tblA union all
select AID, 'A' as srctbl, 3, order3 from tblA union all
select AID, 'A' as srctbl, 4, order4 from tblA union all
select BID, 'B' as srctbl, 1, order1 from tblB union all
select BID, 'B' as srctbl, 2, order2 from tblB union all
select BID, 'B' as srctbl, 3, order3 from tblB union all
select BID, 'B' as srctbl, 4, order4 from tblB )
then you can say
select ID, srctbl, seq, order
from (select ID, srctbl, seq, order from tbl where srctbl = 'a') ta inner join
((select ID, srctbl, seq, order from tbl where srctbl = 'b') tb on
ta.order = tb.order
Or, as a CTE:
WITH orders (ID, srctbl, SEQ, orderVal)
AS (
SELECT ID, srctbl, SEQ, orderVal
FROM (
SELECT AID AS ID, 'A' AS srctbl, 1 AS SEQ, order1 AS orderVal FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 2, order2 FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 3, order3 FROM tblA UNION ALL
SELECT AID, 'A' AS srctbl, 4, order4 FROM tblA UNION ALL
SELECT BID, 'B' AS srctbl, 1, order1 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 2, order2 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 3, order3 FROM tblB UNION ALL
SELECT BID, 'B' AS srctbl, 4, order4 FROM tblB )
)
SELECT ta.ID AS a_id, tb.ID AS b_ID, ta.SEQ AS a_seq, tb.SEQ AS b_seq, ta.orderVal
FROM
(SELECT ID, SEQ, orderVal FROM orders WHERE srctbl = 'a') ta INNER JOIN
(SELECT ID, SEQ, orderVal FROM orders WHERE srctbl = 'b') tb ON
ta.orderVal = tb.orderVal
Using T-Sql I am looking to return the min date after the latest null if one exists and simply the min date on any products where there are no nulls.
Table:
DateSold Product
12/31/2012 A
1/31/2013
2/28/2013 A
3/31/2013 A
4/30/2013 A
5/31/2013
6/30/2013 A
7/31/2013 A
8/31/2013 A
9/30/2013 A
12/31/2012 B
1/31/2013 B
2/28/2013 B
3/31/2013 B
4/30/2013 B
5/31/2013 B
6/30/2013 B
7/31/2013 B
8/31/2013 B
9/30/2013 B
For product “A” 6/30/2013 is the desired return while for product “B” 12/31/2012 is desired.
Result:
MinDateSold Product
6/30/2013 A
12/31/2012 B
Any solutions will greatly be appreciated. Thank you.
This does it for me, if there's a GROUP involved, otherwise how do you know whether the NULLs are in the run of A or B products? I realise this may not be exactly what you're after, but I hope it helps anyway.
WITH DATA_IN AS (
SELECT 1 as grp,
convert(DateTime,'12/31/2012') as d_Date,
'A' AS d_ch
UNION ALL
SELECT 1, '1/31/2013', NULL UNION ALL
SELECT 1, '2/28/2013', 'A' UNION ALL
SELECT 1, '3/31/2013', 'A' UNION ALL
SELECT 1, '4/30/2013', 'A' UNION ALL
SELECT 1, '5/31/2013', NULL UNION ALL
SELECT 1, '6/30/2013', 'A' UNION ALL
SELECT 1, '7/31/2013', 'A' UNION ALL
SELECT 1, '8/31/2013', 'A' UNION ALL
SELECT 1, '9/30/2013', 'A' UNION ALL
SELECT 2, '12/31/2012', 'B' UNION ALL
SELECT 2, '1/31/2013', 'B' UNION ALL
SELECT 2, '2/28/2013', 'B' UNION ALL
SELECT 2, '3/31/2013', 'B' UNION ALL
SELECT 2, '4/30/2013', 'B' UNION ALL
SELECT 2, '5/31/2013', 'B' UNION ALL
SELECT 2, '6/30/2013', 'B' UNION ALL
SELECT 2, '7/31/2013', 'B' UNION ALL
SELECT 2, '8/31/2013', 'B' UNION ALL
SELECT 2, '9/30/2013', 'B'
)
SELECT
grp as YourGroup,
(SELECT Min(d_date) -- first date after...
FROM DATA_IN
WHERE d_date>
Coalesce( -- either the latest NULL
(SELECT max(d_Date)
FROM DATA_IN d2
WHERE d2.grp=d1.grp AND d2.d_ch IS NULL
)
, '1/1/1901' -- or a base date if no NULLs
)
) as MinDateSold
FROM DATA_IN d1
GROUP BY grp
Results :
1 2013-06-30 00:00:00.000
2 2012-12-31 00:00:00.000
One approach to this is to count the number of NULL values that appear before a given row for a given value. This divides the ranges into groups. For each group, take the minimum date. And, find the largest minimum date for each product:
select product, minDate
from (select product, NumNulls, min(DateSold) as minDate,
row_number() over (partition by product order by min(DateSold) desc
) as seqnum
from (select t.*,
(select count(*)
from table t2
where t2.product is null and t2.DateSold <= t.DateSold
) as NumNulls
from table t
) t
group by Product, NumNUlls
) t
where seqnum = 1;
In your data, there is no mixing of different products in a range, so this query sort of assumes that is true as well.