Sql query duplicate column with different effective start date - sql

i have a table x_person and x_person_name. with structure as :
x_person
eff_start_date eff_end_date person_number
01-jan-1990 31-dec-4712 2
01-feb-1990 31-dec-4712 2
01-jan-1990 31-dec-4712 1
x_person_name
eff_start_date eff_end_date person_number name
01-jan-1990 31-dec-4712 2 freida
01-feb-1990 31-dec-4712 2 sam
01-jan-1990 31-dec-4712 1 isha
Now i want to check in these two tables for same employee have same effective start dates . for those who dont i created a query
select * from
(
select distinct min(x.Effective_Start_Date) over(partition by x.person_number) Effective_Start_Date_name, y.Effective_Start_Date,x.person_number
from x_person_name x,x_person y where
x.person_number=y.person_number
)
where Effective_Start_Date_name <> Effective_Start_Date;
but this query will not work for example for person number 2 though they are 2 different ppl and have 2 differnet records in x_person. but still it is comng in the output.
Output I am getting
effective_start_date y.effective_start_date person_number
01-jan-1990 01-feb-1990 2
Whereas this shouldnt be coming.

Assuming that you need the rows from x_person not matching x_person_name, maybe this can help:
select *
from x_person p
left outer join x_person_name pn
ON (
p.person_number = pn.person_number and
p.eff_start_date = pn.eff_start_date
)
where pn.person_number is null

with x_perosn(eff_start_date, eff_end_date, person_number)
as (select to_date('01-jan-1990'), to_date('31-dec-4712'), 2 from dual
union all
select to_date('01-feb-1990'), to_date('31-dec-4712'), 2 from dual
union all
select to_date('01-jan-1990'), to_date('31-dec-4712'), 1 from dual)
, x_person_name(eff_start_date
, eff_end_date
, person_number
, name)
as (select to_date('01-jan-1990'), to_date('31-dec-4712'), 2, 'freida' from dual
union all
select to_date('01-feb-1990'), to_date('31-dec-4712'), 2, 'sam' from dual
union all
select to_date('01-jan-1990'), to_date('31-dec-4712'), 1, 'isha' from dual)
select * from (
select min(eff_start_date) over( partition by person_number) ed,y.* from x_person_name y
) t1 where not exists (select 1 from x_perosn where person_number= t1.person_number and ed = eff_start_date)
I think you are looking for something called, anti-join. Retrun only rows from table1 which there are no rows in table2.

Related

Concat multiple rows based on If/Then to poulate a 5 digit code

Hoping someone can help.
I have data as follows in two seperate columns in a table called StudentRace
Student_ID RaceCD
---------- ------
123456 1
123456 2
589645 4
987654 3
987654 4
I am looking for a way to combine the data for the students by student id to output into 00000 format. example: Student_ID 123456 RACE: 12000; Student_ID 589645 Race: 00040; Student_ID 987654 Race = 00340. I need to have it be a sub query as it is part of a large report that pulls 50+ fields. If anyone is able to help I would greatly appreciate it. I am using Toads Data Point for Oracle to create my query.
The result can be achieved with a single group by without any joins.
Setup
CREATE TABLE studentrace
(
Student_ID,
RaceCD
)
AS
SELECT 123456, 1 FROM DUAL
UNION ALL
SELECT 123456, 2 FROM DUAL
UNION ALL
SELECT 589645, 4 FROM DUAL
UNION ALL
SELECT 987654, 3 FROM DUAL
UNION ALL
SELECT 987654, 4 FROM DUAL;
Query
SELECT student_id, LPAD (SUM (racecd * POWER (10, 5 - racecd)), 5, '0') AS race
FROM studentrace
GROUP BY student_id;
Result
STUDENT_ID RACE
_____________ ________
589645 00040
987654 00340
123456 12000
You can use a partitioned outer join:
SELECT t.Student_id,
LISTAGG( COALESCE( t.raceCD, 0 ) ) WITHIN GROUP ( ORDER BY r.race )
AS RaceCDs
FROM ( SELECT LEVEL AS race
FROM DUAL
CONNECT BY LEVEL <= 5 ) r
LEFT OUTER JOIN table_name t
PARTITION BY ( t.Student_ID )
ON ( r.race = t.RaceCD )
GROUP BY t.student_id
Which, for your test data:
CREATE TABLE table_name ( Student_ID, RaceCD ) AS
SELECT 123456, 1 FROM DUAL UNION ALL
SELECT 123456, 2 FROM DUAL UNION ALL
SELECT 589645, 4 FROM DUAL UNION ALL
SELECT 987654, 3 FROM DUAL UNION ALL
SELECT 987654, 4 FROM DUAL
Outputs:
STUDENT_ID | RACECDS
---------: | :------
123456 | 12000
589645 | 00040
987654 | 00340
db<>fiddle here

Oracle SQL (Toad): Expand table

Suppose I have an SQL (Oracle Toad) table named "test", which has the following fields and entries (dates are in dd/mm/yyyy format):
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/06/2014 3
1 01/09/2014 6
2 01/04/2015 7
2 01/08/2015 43
2 01/09/2015 85
2 01/12/2015 4
I know from how the table has been created that, since there are value entries for id = 1 for February 2014 and June 2014, the values for March through May 2014 must be 0. The same applies to July and August 2014 for id = 1, and for May through July 2015 and October through November 2015 for id = 2.
Now, if I want to calculate, say, the median of the value column for a given id, I will not arrive at the correct result using the table as it stands - as I'm missing 5 zero entries for each id.
I would therefore like to create/use the following (potentially just temporary table)...
id ref_date value
---------------------
1 01/01/2014 20
1 01/02/2014 25
1 01/03/2014 0
1 01/04/2014 0
1 01/05/2014 0
1 01/06/2014 3
1 01/07/2014 0
1 01/08/2014 0
1 01/09/2014 6
2 01/04/2015 7
2 01/05/2015 0
2 01/06/2015 0
2 01/07/2015 0
2 01/08/2015 43
2 01/09/2015 85
2 01/10/2015 0
2 01/11/2015 0
2 01/12/2015 4
...on which I could then compute the median by id:
select id, median(value) as med_value from test group by id
How do I do this? Or would there be an alternative way?
Many thanks,
Mr Clueless
In this solution, I build a table with all the "needed dates" and value of 0 for all of them. Then, instead of a join, I do a union all, group by id and ref_date and ADD the values in each group. If the date had a row with a value in the original table, then that's the resulting value; and if it didn't, the value will be 0. This avoids a join. In almost all cases a union all + aggregate will be faster (sometimes much faster) than a join.
I added more input data for more thorough testing. In your original question, you have two id's, and for both of them you have four positive values. You are missing five values in each case, so there will be five zeros (0) which means the median is 0 in both cases. For id=3 (which I added) I have three positive values and three zeros; the median is half of the smallest positive number. For id=4 I have just one value, which then should be the median as well.
The solution includes, in particular, an answer to your specific question - how to create the temporary table (which most likely doesn't need to be a temporary table at all, but an inline view). With factored subqueries (in the WITH clause), the optimizer decides if to treat them as temporary tables or inline views; you can see what the optimizer decided if you look at the Explain Plan.
with
inputs ( id, ref_date, value ) as (
select 1, to_date('01/01/2014', 'dd/mm/yyyy'), 20 from dual union all
select 1, to_date('01/02/2014', 'dd/mm/yyyy'), 25 from dual union all
select 1, to_date('01/06/2014', 'dd/mm/yyyy'), 3 from dual union all
select 1, to_date('01/09/2014', 'dd/mm/yyyy'), 6 from dual union all
select 2, to_date('01/04/2015', 'dd/mm/yyyy'), 7 from dual union all
select 2, to_date('01/08/2015', 'dd/mm/yyyy'), 43 from dual union all
select 2, to_date('01/09/2015', 'dd/mm/yyyy'), 85 from dual union all
select 2, to_date('01/12/2015', 'dd/mm/yyyy'), 4 from dual union all
select 3, to_date('01/01/2016', 'dd/mm/yyyy'), 12 from dual union all
select 3, to_date('01/03/2016', 'dd/mm/yyyy'), 23 from dual union all
select 3, to_date('01/06/2016', 'dd/mm/yyyy'), 2 from dual union all
select 4, to_date('01/11/2014', 'dd/mm/yyyy'), 9 from dual
),
-- the "inputs" table constructed above is for testing only,
-- it is not part of the solution.
ranges ( id, min_date, max_date ) as (
select id, min(ref_date), max(ref_date)
from inputs
group by id
),
prep ( id, ref_date, value ) as (
select id, add_months(min_date, level - 1), 0
from ranges
connect by level <= 1 + months_between( max_date, min_date )
and prior id = id
and prior sys_guid() is not null
),
v ( id, ref_date, value ) as (
select id, ref_date, sum(value)
from ( select id, ref_date, value from prep union all
select id, ref_date, value from inputs
)
group by id, ref_date
)
select id, median(value) as median_value
from v
group by id
order by id -- ORDER BY is optional
;
ID MEDIAN_VALUE
-- ------------
1 0
2 0
3 1
4 9
If ref_date is date and is second
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, add_months(i.min_date,s.n) as ref_date
, nvl(value,0) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
And with median
with int1 as (select id
, max(ref_date) as max_date
, min(ref_date) as min_date from test group by id )
, s(n) as (select level -1 from dual connect by level <= (select max(months_between(max_date, min_date)) from int1 ) )
select i.id
, MEDIAN(nvl(value,0)) as value
from int1 i
join s on add_months(i.min_date,s.n) <= i.max_date
LEFT join test t on t.id = i.id and add_months(i.min_date,s.n) = t.ref_date
group by i.id

How do I select records with max from id column if two of three other fields are identical

I have a table that stores costs for consumables.
consumable_cost_id consumable_type_id from_date cost
1 1 01/01/2000 £10.95
2 2 01/01/2000 £5.95
3 3 01/01/2000 £1.98
24 3 01/11/2013 £2.98
27 3 22/11/2013 £3.98
33 3 22/11/2013 £4.98
34 3 22/11/2013 £5.98
35 3 22/11/2013 £6.98
If the same consumable is updated more than once on the same day I would like to select only the row where the consumable_cost_id is biggest on that day. Desired output would be:
consumable_cost_id consumable_type_id from_date cost
1 1 01/01/2000 £10.95
2 2 01/01/2000 £5.95
3 3 01/01/2000 £1.98
24 3 01/11/2013 £2.98
35 3 22/11/2013 £6.98
Edit:
Here is my attempt (adapted from another post I found on here):
SELECT cc.*
FROM
consumable_costs cc
INNER JOIN
(
SELECT
from_date,
MAX(consumable_cost_id) AS MaxCcId
FROM consumable_costs
GROUP BY from_date
) groupedcc
ON cc.from_date = groupedcc.from_date
AND cc.consumable_cost_id = groupedcc.MaxCcId
You were very close. This seems to work for me:
SELECT cc.*
FROM
consumable_cost AS cc
INNER JOIN
(
SELECT
Max(consumable_cost_id) AS max_id,
consumable_type_id,
from_date
FROM consumable_cost
GROUP BY consumable_type_id, from_date
) AS m
ON cc.consumable_cost_id = m.max_id
SELECT * FROM consumable_cost
GROUP by consumable_type_id, from_date
ORDER BY cost DESC;
Assuming consumable_cost_id is unique.
SELECT * FROM T t1
WHERE EXISTS(
SELECT t2.consumable_type_id, t2.from_date FROM T t2
GROUP by t2.consumable_type_id, t2.from_date
HAVING MAX(t2.consumable_cost_id) = t1.consumable_cost_id);
Because of comment that this was returning an incorrect result, I created a test-query for Oracle that proves that this query works. As I said, it's for Oracle, but there is really no reason why this should not work in MS Access. The only Oracle specific I used here is the FROM DUAL to generate the virtual data.
WITH T AS
(
SELECT 1 AS consumable_cost_id,1 AS consumable_type_id, TO_DATE('01/01/2000','DD/MM/YYYY') AS FROM_DATE, '£10.95' AS COST FROM DUAL
UNION ALL
SELECT 2,2,TO_DATE('01/01/2000','DD/MM/YYYY'),'£5.95' FROM DUAL
UNION ALL
SELECT 3,3,TO_DATE('01/01/2000','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 24,3,TO_DATE('01/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 27,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 33,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 34,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
UNION ALL
SELECT 35,3,TO_DATE('22/11/2013','DD/MM/YYYY'),'£1.98' FROM DUAL
)
SELECT * FROM T t1
WHERE EXISTS(
SELECT t2.consumable_type_id, t2.from_date FROM T t2
GROUP by t2.consumable_type_id, t2.from_date
HAVING MAX(t2.consumable_cost_id) = t1.consumable_cost_id);
Result:
1 1 01-JAN-00 £10.95
2 2 01-JAN-00 £5.95
3 3 01-JAN-00 £1.98
24 3 01-NOV-13 £1.98
35 3 22-NOV-13 £1.98

How to do select count(*) group by and select * at same time?

For example, I have table:
ID | Value
1 hi
1 yo
2 foo
2 bar
2 hehe
3 ha
6 gaga
I want my query to get ID, Value; meanwhile the returned set should be in the order of frequency count of each ID.
I tried the query below but don't know how to get the ID and Value column at the same time:
SELECT COUNT(*) FROM TABLE group by ID order by COUNT(*) desc;
The count number doesn't matter to me, I just need the data to be in such order.
Desire Result:
ID | Value
2 foo
2 bar
2 hehe
1 hi
1 yo
3 ha
6 gaga
As you can see because ID:2 appears most times(3 times), it's first on the list,
then ID:1(2 times) etc.
you can try this -
select id, value, count(*) over (partition by id) freq_count
from
(
select 2 as ID, 'foo' as value
from dual
union all
select 2, 'bar'
from dual
union all
select 2, 'hehe'
from dual
union all
select 1 , 'hi'
from dual
union all
select 1 , 'yo'
from dual
union all
select 3 , 'ha'
from dual
union all
select 6 , 'gaga'
from dual
)
order by 3 desc;
select t.id, t.value
from TABLE t
inner join
(
SELECT id, count(*) as cnt
FROM TABLE
group by ID
)
x on x.id = t.id
order by x.cnt desc
How about something like
SELECT t.ID,
t.Value,
c.Cnt
FROM TABLE t INNER JOIN
(
SELECT ID,
COUNT(*) Cnt
FROM TABLE
GROUP BY ID
) c ON t.ID = c.ID
ORDER BY c.Cnt DESC
SQL Fiddle DEMO
I see the question is already answered, but since the most obvious and most simple solution is missing, I'm posting it anyway. It doesn't use self joins nor subqueries:
SQL> create table t (id,value)
2 as
3 select 1, 'hi' from dual union all
4 select 1, 'yo' from dual union all
5 select 2, 'foo' from dual union all
6 select 2, 'bar' from dual union all
7 select 2, 'hehe' from dual union all
8 select 3, 'ha' from dual union all
9 select 6, 'gaga' from dual
10 /
Table created.
SQL> select id
2 , value
3 from t
4 order by count(*) over (partition by id) desc
5 /
ID VALU
---------- ----
2 bar
2 hehe
2 foo
1 yo
1 hi
6 gaga
3 ha
7 rows selected.

SQL find nearest number

Say I have a table like the following (I'm on Oracle 10g btw)
NAME VALUE
------ ------
BOB 1
BOB 2
BOB 4
SUZY 1
SUZY 2
SUZY 3
How can I select all rows where value is closest to, but not greater than, a given number. For example if I want to find all the rows where value is closest to 3 I would get:
NAME VALUE
------ ------
BOB 2
SUZY 3
This seems like it should be simple... but I'm having no luck.
Thanks!
SELECT name, max(value)
FROM tbl
WHERE value <= 3
GROUP BY name
This works (SQLFiddle demo):
SELECT name, max(value)
FROM mytable
WHERE value <= 3
GROUP BY name
Based on hagensofts answer:
SELECT name, max(value)
FROM tbl
WHERE value <= 3 AND ROWNUM <=2
GROUP BY name
With ROWNUM you can limit the output rows, so if you want 2 row, then you can limit the rownum.
WITH v AS (
SELECT 'BOB' NAME, 1 value FROM dual
UNION ALL
SELECT 'BOB', 2 FROM dual
UNION ALL
SELECT 'BOB', 4 FROM dual
UNION ALL
SELECT 'SUZY', 1 FROM dual
UNION ALL
SELECT 'SUZY', 2 FROM dual
UNION ALL
SELECT 'SUZY', 3 FROM dual
)
SELECT *
FROM v
WHERE (name, value) IN (SELECT name, MAX(value)
FROM v
WHERE value <= :num
GROUP BY name)
;