SQL Server: A Grouping question that's annoying me - sql

I've been working with SQL Server for the better part of a decade, and this grouping (or partitioning, or ranking...I'm not sure what the answer is!) one has me stumped. Feels like it should be an easy one, too. I'll generalize my problem:
Let's say I have 3 employees (don't worry about them quitting or anything...there's always 3), and I keep up with how I distribute their salaries on a monthly basis.
Month Employee PercentOfTotal
--------------------------------
1 Alice 25%
1 Barbara 65%
1 Claire 10%
2 Alice 25%
2 Barbara 50%
2 Claire 25%
3 Alice 25%
3 Barbara 65%
3 Claire 10%
As you can see, I've paid them the same percent in Months 1 and 3, but in Month 2, I've given Alice the same 25%, but Barbara got 50% and Claire got 25%.
What I want to know is all the distinct distributions I've ever given. In this case there would be two -- one for months 1 and 3, and one for month 2.
I'd expect the results to look something like this (NOTE: the ID, or sequencer, or whatever, doesn't matter)
ID Employee PercentOfTotal
--------------------------------
X Alice 25%
X Barbara 65%
X Claire 10%
Y Alice 25%
Y Barbara 50%
Y Claire 25%
Seems easy, right? I'm stumped! Anyone have an elegant solution? I just put together this solution while writing this question, which seems to work, but I'm wondering if there's a better way. Or maybe a different way from which I'll learn something.
WITH temp_ids (Month)
AS
(
SELECT DISTINCT MIN(Month)
FROM employees_paid
GROUP BY PercentOfTotal
)
SELECT EMP.Month, EMP.Employee, EMP.PercentOfTotal
FROM employees_paid EMP
JOIN temp_ids IDS ON EMP.Month = IDS.Month
GROUP BY EMP.Month, EMP.Employee, EMP.PercentOfTotal
Thanks y'all!
-Ricky

This gives you an answer in a slightly different format than you requested:
SELECT DISTINCT
T1.PercentOfTotal AS Alice,
T2.PercentOfTotal AS Barbara,
T3.PercentOfTotal AS Claire
FROM employees_paid T1
JOIN employees_paid T2
ON T1.Month = T2.Month AND T1.Employee = 'Alice' AND T2.Employee = 'Barbara'
JOIN employees_paid T3
ON T2.Month = T3.Month AND T3.Employee = 'Claire'
Result:
Alice Barbara Claire
25% 50% 25%
25% 65% 10%
If you want to, you can use UNPIVOT to turn this result set into the form you asked for.
SELECT rn AS ID, Employee, PercentOfTotal
FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY Alice) AS rn
FROM (
SELECT DISTINCT
T1.PercentOfTotal AS Alice,
T2.PercentOfTotal AS Barbara,
T3.PercentOfTotal AS Claire
FROM employees_paid T1
JOIN employees_paid T2 ON T1.Month = T2.Month AND T1.Employee = 'Alice'
AND T2.Employee = 'Barbara'
JOIN employees_paid T3 ON T2.Month = T3.Month AND T3.Employee = 'Claire'
) T1
) p UNPIVOT (PercentOfTotal FOR Employee IN (Alice, Barbara, Claire)) AS unpvt
Result:
ID Employee PercentOfTotal
1 Alice 25%
1 Barbara 50%
1 Claire 25%
2 Alice 25%
2 Barbara 65%
2 Claire 10%

What you want is for each month's distribution to act as a signature or pattern of values which you would then want to find in other months. What is not clear is whether the employee to whom the value went is as important as the break down of percentages. For example, would Alice=65%, Barbara=25%, Claire=10% be the same as the Month 3 in your example? In my example, I presumed that it would not be the same. Similar to Martin Smith's solution, I find the signatures by multiplying each percentage by 10. This presumes that all percentage values are less than one. If someone could have a percentage of 110% for example, that would create problems for this solution.
With Employees As
(
Select 1 As Month, 'Alice' As Employee, .25 As PercentOfTotal
Union All Select 1, 'Barbara', .65
Union All Select 1, 'Claire', .10
Union All Select 2, 'Alice', .25
Union All Select 2, 'Barbara', .50
Union All Select 2, 'Claire', .25
Union All Select 3, 'Alice', .25
Union All Select 3, 'Barbara', .65
Union All Select 3, 'Claire', .10
)
, EmployeeRanks As
(
Select Month, Employee, PercentOfTotal
, Row_Number() Over ( Partition By Month Order By Employee, PercentOfTotal ) As ItemRank
From Employees
)
, Signatures As
(
Select Month
, Sum( PercentOfTotal * Cast( Power( 10, ItemRank ) As bigint) ) As SignatureValue
From EmployeeRanks
Group By Month
)
, DistinctSignatures As
(
Select Min(Month) As MinMonth, SignatureValue
From Signatures
Group By SignatureValue
)
Select E.Month, E.Employee, E.PercentOfTotal
From Employees As E
Join DistinctSignatures As D
On D.MinMonth = E.Month

I'm assuming performance won't be great (cause of the subquery)
SELECT * FROM employees_paid where Month not in (
SELECT
a.Month
FROM
employees_paid a
INNER JOIN employees_paid b ON
(a.employee = B.employee AND
a.PercentOfTotal = b.PercentOfTotal AND
a.Month > b.Month)
GROUP BY
a.Month,
b.Month
HAVING
Count(*) = (SELECT COUNT(*) FROM employees_paid c
where c.Month = a.Month)
)
The inner SELECT does a self join to identify matching employee and percentage combinations (except those for the same month).
The > in the JOIN ensures that only one set of matches is taken i.e. if a Month1 entry = Month3 entry, we get only the Month3-Month1 entry combination instead of Month1-Month3, Month3-Month1 and Month3-Month3.
We then GROUP by COUNT of matched entries for each month-month combination
Then the HAVING excludes months that don't have as many matches as there are month entries
The outer SELECT gets all entries except the ones returned by the inner query (the ones with full set matches)

If I have understood you correctly then, for a general solution, I think you would need to concatenate the whole group together - e.g. to produce Alice:0.25, Barbara:0.50, Claire:0.25. Then select the distinct groups so something like the following would do it (rather clunkily).
WITH EmpSalaries
AS
(
SELECT 1 AS Month, 'Alice' AS Employee, 0.25 AS PercentOfTotal UNION ALL
SELECT 1 AS Month, 'Barbara' AS Employee, 0.65 UNION ALL
SELECT 1 AS Month, 'Claire' AS Employee, 0.10 UNION ALL
SELECT 2 AS Month, 'Alice' AS Employee, 0.25 UNION ALL
SELECT 2 AS Month, 'Barbara' AS Employee, 0.50 UNION ALL
SELECT 2 AS Month, 'Claire' AS Employee, 0.25 UNION ALL
SELECT 3 AS Month, 'Alice' AS Employee, 0.25 UNION ALL
SELECT 3 AS Month, 'Barbara' AS Employee, 0.65 UNION ALL
SELECT 3 AS Month, 'Claire' AS Employee, 0.10
),
Months AS
(
SELECT DISTINCT Month FROM EmpSalaries
),
MonthlySummary AS
(
SELECT Month,
Stuff(
(
Select ', ' + S1.Employee + ':' + cast(PercentOfTotal as varchar(20))
From EmpSalaries As S1
Where S1.Month = Months.Month
Order By S1.Employee
For Xml Path('')
), 1, 2, '') As Summary
FROM Months
)
SELECT * FROM EmpSalaries
WHERE Month IN (SELECT MIN(Month)
FROM MonthlySummary
GROUP BY Summary)

I just put together this solution
while writing this question, which
seems to work
I don't think it does work. Here I've added a further two groups (month = 4 and 5 respectively) which I would consider to be distinct yet the result is the same i.e. month = 1 and 2 only:
WITH employees_paid (Month, Employee, PercentOfTotal)
AS
(
SELECT 1, 'Alice', 0.25
UNION ALL
SELECT 1, 'Barbara', 0.65
UNION ALL
SELECT 1, 'Claire', 0.1
UNION ALL
SELECT 2, 'Alice', 0.25
UNION ALL
SELECT 2, 'Barbara', 0.5
UNION ALL
SELECT 2, 'Claire', 0.25
UNION ALL
SELECT 3, 'Alice', 0.25
UNION ALL
SELECT 3, 'Barbara', 0.65
UNION ALL
SELECT 3, 'Claire', 0.1
UNION ALL
SELECT 4, 'Barbara', 0.25
UNION ALL
SELECT 4, 'Claire', 0.65
UNION ALL
SELECT 4, 'Alice', 0.1
UNION ALL
SELECT 5, 'Diana', 0.25
UNION ALL
SELECT 5, 'Emma', 0.65
UNION ALL
SELECT 5, 'Fiona', 0.1
),
temp_ids (Month)
AS
(
SELECT DISTINCT MIN(Month)
FROM employees_paid
GROUP
BY PercentOfTotal
)
SELECT EMP.Month, EMP.Employee, EMP.PercentOfTotal
FROM employees_paid AS EMP
INNER JOIN temp_ids AS IDS
ON EMP.Month = IDS.Month
GROUP
BY EMP.Month, EMP.Employee, EMP.PercentOfTotal;

Related

How to determine date paid given billing and payment data in BigQuery

I need to know when a bill was paid to determine how early or late it was paid. Unfortunately, I just have billing creation data and payment records.
Example 1
--raw data
WITH bill AS (
SELECT 'b1' AS id, DATE('2022-01-01') AS created, DATE('2022-01-15') AS due, 50 AS amount UNION ALL
SELECT 'b2', DATE('2022-01-01'), DATE('2022-01-30'), 50 UNION ALL
SELECT 'b3', DATE('2022-01-03'), DATE('2022-01-17'), 50 UNION ALL
SELECT 'b4', DATE('2022-01-03'), DATE('2022-02-01'), 50 UNION ALL
SELECT 'b5', DATE('2022-01-05'), DATE('2022-01-19'), 50 UNION ALL
SELECT 'b6', DATE('2022-01-05'), DATE('2022-02-04'), 50
),
payment AS (
SELECT 'p1' AS id, DATE('2022-01-10') AS made, 50 AS amount UNION ALL
SELECT 'p2', DATE('2022-01-11'), 50
)
-- setup
SELECT * FROM bill
I want a query that returns all of the data from the bill table plus the date that the bill was paid, which is theoretically derived from the payment table.
In the example above, the solution could be to sort the bill rows by the due date and apply the payments accordingly:
--raw data
WITH bill AS (
SELECT 'b1' AS id, DATE('2022-01-01') AS created, DATE('2022-01-15') AS due, 50 AS amount UNION ALL
SELECT 'b2', DATE('2022-01-01'), DATE('2022-01-30'), 50 UNION ALL
SELECT 'b3', DATE('2022-01-03'), DATE('2022-01-17'), 50 UNION ALL
SELECT 'b4', DATE('2022-01-03'), DATE('2022-02-01'), 50 UNION ALL
SELECT 'b5', DATE('2022-01-05'), DATE('2022-01-19'), 50 UNION ALL
SELECT 'b6', DATE('2022-01-05'), DATE('2022-02-04'), 50
),
payment AS (
SELECT 'p1' AS id, DATE('2022-01-10') AS made, 50 AS amount UNION ALL
SELECT 'p2', DATE('2022-01-11'), 50
),
--start solution
p AS (
SELECT
payment.id,
payment.made,
payment.amount,
SUM( payment.amount ) OVER (
ORDER BY payment.made
) AS amount_cumulative
FROM payment
),
b AS (
SELECT
bill.id,
bill.created,
bill.due,
bill.amount,
SUM( bill.amount ) OVER (
ORDER BY bill.due
) AS amount_cumulative
FROM bill
),
repayments AS (
SELECT
b.*,
p.made,
ROW_NUMBER() OVER (
PARTITION BY b.id
ORDER BY p.made ASC
) AS seq
FROM b
LEFT JOIN p
ON b.amount_cumulative <= p.amount_cumulative
WHERE p.made IS NOT NULL
)
SELECT
b.*,
repayments.made AS payment_date
FROM b
LEFT JOIN repayments
ON b.id = repayments.id
WHERE (repayments.seq = 1 OR repayments.seq IS NULL)
ORDER BY b.id
Example 2
However, that solution breaks down if we change some of the bill and payment dates (because payments must be applied to the bill with an oustanding balance and the earliest due date at the time of the payment). For example:
--raw data
WITH bill AS (
SELECT 'b1' AS id, DATE('2022-01-01') AS created, DATE('2022-01-15') AS due, 50 AS amount UNION ALL
SELECT 'b2', DATE('2022-01-01'), DATE('2022-01-30'), 50 UNION ALL
SELECT 'b3', DATE('2022-01-03'), DATE('2022-01-17'), 50 UNION ALL
SELECT 'b4', DATE('2022-01-03'), DATE('2022-02-01'), 50 UNION ALL
SELECT 'b5', DATE('2022-01-05'), DATE('2022-01-19'), 50 UNION ALL
SELECT 'b6', DATE('2022-01-05'), DATE('2022-02-04'), 50
),
payment AS (
SELECT 'p1' AS id, DATE('2022-01-02') AS made, 100 AS amount UNION ALL
SELECT 'p2', DATE('2022-01-11'), 50
),
--start solution
p AS (
SELECT
payment.id,
payment.made,
payment.amount,
SUM( payment.amount ) OVER (
ORDER BY payment.made
) AS amount_cumulative
FROM payment
),
b AS (
SELECT
bill.id,
bill.created,
bill.due,
bill.amount,
SUM( bill.amount ) OVER (
ORDER BY bill.due
) AS amount_cumulative
FROM bill
),
repayments AS (
SELECT
b.*,
p.made,
ROW_NUMBER() OVER (
PARTITION BY b.id
ORDER BY p.made ASC
) AS seq
FROM b
LEFT JOIN p
ON b.amount_cumulative <= p.amount_cumulative
WHERE p.made IS NOT NULL
)
SELECT
b.*,
repayments.made AS payment_date
FROM b
LEFT JOIN repayments
ON b.id = repayments.id
WHERE (repayments.seq = 1 OR repayments.seq IS NULL)
ORDER BY b.id
This example is even trickier if p1 is for an amount = 75 (in which case b2 should remain not completely paid, but b3 should be paid in full on 2022-01-11).
Trickiest Example
This is the most contrived, but it best illustrates the problem. Payment 1 goes toward Bill 1 because that is the only available bill at the time of the payment (the payment almost pays the first bill in full). At the time of Payment 2, the bill with the earliest unpaid balance is now Bill 2, so all of the payment goes toward Bill 2 (and again, the payment almost pays the second bill in full). However, at the end of this sequence, 99/100 is paid toward Bill 1 and 49/50 is paid toward Bill 2, but neither Bill 1 nor Bill 2 are paid in full, so the paid in full date should be NULL for both.
--raw data
WITH bill AS (
SELECT 'b1' AS id, DATE('2022-01-01') AS created, DATE('2022-01-15') AS due, 100 AS amount UNION ALL
SELECT 'b2', DATE('2022-01-02'), DATE('2022-01-14'), 50
),
payment AS (
SELECT 'p1' AS id, DATE('2022-01-10') AS made, 99 AS amount UNION ALL
SELECT 'p2', DATE('2022-01-11'), 49
),
-- setup
SELECT * FROM bill
Question
Can I get the payment_date with a query? If so, what does that look like?
Please refer query below. Idea is to distribute payments over range.
--raw data
WITH bill AS (
SELECT 'b1' AS id, DATE('2022-01-01') AS created, DATE('2022-01-15') AS due, 50 AS amount UNION ALL
SELECT 'b2', DATE('2022-01-01'), DATE('2022-01-30'), 50 UNION ALL
SELECT 'b3', DATE('2022-01-03'), DATE('2022-01-17'), 50 UNION ALL
SELECT 'b4', DATE('2022-01-03'), DATE('2022-02-01'), 50 UNION ALL
SELECT 'b5', DATE('2022-01-05'), DATE('2022-01-19'), 50 UNION ALL
SELECT 'b6', DATE('2022-01-05'), DATE('2022-02-04'), 50
), payment AS (
SELECT 'p1' AS id, DATE('2022-01-10') AS made, 50 AS amount UNION ALL
SELECT 'p2', DATE('2022-01-11'), 50
), b as (
-- setup
SELECT
bill.id,
bill.created,
bill.due,
bill.amount,
SUM( bill.amount ) OVER (
ORDER BY bill.created, bill.due
) AS amount_cumulative_created,
SUM( bill.amount ) OVER (
ORDER BY bill.due
) as amount_cumulative_due,
row_number() over (order by bill.due) crn
FROM bill
order by created
), p as (
SELECT
p.id,
p.made,
p.amount,
case when
row_number() over (order by 0) =
max(un) over (partition by p.id) then
floor(p.amount / max(un) over (partition by p.id)) +
mod(p.amount, max(un) over (partition by p.id))
else
floor(p.amount / max(un) over (partition by p.id))
end new_amount,
row_number() over (order by 0) prn
FROM
(select *,
row_number() over (order by payment.made) prn
from payment) p join b
on p.prn = b.crn
,unnest(generate_array(1,
(select coalesce(max(b.crn),0)+1 from b
where p.amount > b.amount_cumulative_due
and p.made between b.created and b.due),
1)) un
), p_distrib as (
select *,
SUM( p.new_amount ) OVER (
ORDER BY p.prn
) AS payment_cumulative, from p
)
select b.*,
(select min(pd.made) from p_distrib pd
where
--- Adjust here for what date takes preference - created vs. due.
--- Replace amount_cumulative_created with amount_cumulative_due as needed
b.amount_cumulative_created <= pd.payment_cumulative
and pd.made between b.created and b.due)
from b;

SQL Oracle Add 'case when' if value in Join not available

I have below two tables
~ What I am looking to do: I want to append the Price from Table 2 to Table 1. If the exact Quantity in Table 1 is not there in Table 2, take the closest max Quantity from Table 2 [i.e. 12 Quantity is not there in Table 2, the closest max is 20, so I want the price of that].
BUT in some cases there are no max Quantity values, which leads to some entries to fall out of my output. In this case, I am thinking to take the closest min Quantity from Table 2, to avoid some values to fall out of my output
So the result should look like:
I have tried below query but this does not account for taking the closest min Quantity when the closest max does not exist. Product_NR '20765' therefore falls out of my output. I'm thinking of 'case when' but not sure how to incorporate it below.
select
Product_NR,
Customer,
Quantity,
min(price)
from
( select distinct
t1.Product_NR,
t1.Customer,
t1.Quantity,
t2.price,
min(t2.quantity) over (partition by t1.product_NR) as Quantity_min
from Table_1 t1
left join Table_2 t2 on t1.Product_NR = t2.Product_NR
and t1.Quantity <= t2.Quantity
)
where t2.Quantity = Quantity_min
group by
Product_NR,
Customer,
Quantity
From Oracle 12, you can use a LATERAL join and order the correlated sub-query to get the greater quantity rows before the lower quantities and then FETCH FIRST ROW ONLY:
SELECT *
FROM table1 t1
LEFT OUTER JOIN LATERAL(
SELECT price
FROM table2 t2
WHERE t1.product_nr = t2.product_nr
ORDER BY
CASE WHEN t1.quantity <= t2.quantity THEN t2.quantity END
ASC NULLS LAST,
t2.quantity DESC
FETCH FIRST ROW ONLY
)
ON (1 = 1);
Which, for the sample data:
CREATE TABLE table1 (product_nr, customer, quantity) AS
SELECT 10023, 'X', 12 FROM DUAL UNION ALL
SELECT 10023, 'Y', 10 FROM DUAL UNION ALL
SELECT 10334, 'X', 1 FROM DUAL UNION ALL
SELECT 20765, 'Z', 5 FROM DUAL;
CREATE TABLE table2 (product_nr, quantity, price) AS
SELECT 10023, 1, 100 FROM DUAL UNION ALL
SELECT 10023, 10, 120 FROM DUAL UNION ALL
SELECT 10023, 20, 250 FROM DUAL UNION ALL
SELECT 10023, 100, 400 FROM DUAL UNION ALL
SELECT 10334, 0, 250 FROM DUAL UNION ALL
SELECT 10334, 200, 600 FROM DUAL UNION ALL
SELECT 20765, 1, 40 FROM DUAL UNION ALL
SELECT 20765, 2, 50 FROM DUAL;
Outputs:
PRODUCT_NR
CUSTOMER
QUANTITY
PRICE
10023
X
12
250
10023
Y
10
120
10334
X
1
600
20765
Z
5
50
db<>fiddle here

How to write a sql to dynamically add some calculated rows in Oracle?

I have a table like this:
id name value
1 elec 10
1 water 20
2 elec 15
2 water 45
Now I need to dynamically add some rows to the result of select query:
id name value
1 elec 10
1 water 20
1 ratio 0.5
2 elec 15
2 water 45
2 ratio 0.33
Add two rows dynamically,how can i do?
It would make a lot more sense to "present" the results with ELEC, WATER and RATIO columns - one row per ID. The solution below shows how you can do that efficiently (reading the base table only one time).
with
inputs ( id, name, value ) as (
select 1, 'elec' , 10 from dual union all
select 1, 'water', 20 from dual union all
select 2, 'elec' , 15 from dual union all
select 2, 'water', 45 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id, elec, water, round(elec/water, 2) as ratio
from inputs
pivot ( min(value) for name in ('elec' as elec, 'water' as water ) )
;
ID ELEC WATER RATIO
---------- ---------- ---------- ----------
1 10 20 .5
2 15 45 .33
If instead you need the results in the format you showed in your original post, you can unpivot like so (still reading the base table only once):
with
inputs ( id, name, value ) as (
select 1, 'elec' , 10 from dual union all
select 1, 'water', 20 from dual union all
select 2, 'elec' , 15 from dual union all
select 2, 'water', 45 from dual
)
-- End of simulated inputs (not part of the solution).
-- SQL query begins BELOW THIS LINE. Use your actual table and column names.
select id, name, value
from (
select id, elec, water, round(elec/water, 2) as ratio
from inputs
pivot ( min(value) for name in ('elec' as elec, 'water' as water ) )
)
unpivot ( value for name in (elec as 'elec', water as 'water', ratio as 'ratio') )
;
ID NAME VALUE
---------- ----- ----------
1 elec 10
1 water 20
1 ratio .5
2 elec 15
2 water 45
2 ratio .33
Here is one method:
with t as (
<your query here>
)
select id, name, value
from ((select t.*, 1 as ord
from t
) union all
(select id, 'ratio',
max(case when name = 'elec' then value end) / max(case when name = 'water' then value end)
), 2 as ord
from t
group by id
)
) tt
order by id, ord;
If you are fine with slight change in ordering, try this.
SELECT id,name,value FROM yourtable
UNION ALL
SELECT
a.id ,
'ratio' name,
a.value/b.value value
FROM
yourtable a
JOIN yourtable b on a.id = b.id
WHERE a.name = 'elec'
and b.name = 'water'
ORDER BY
id ,
VALUE DESC;
If you need to add the rows to table itself, then use.
INSERT INTO yourtable
SELECT
a.id ,
'ratio' name,
a.value/b.value value
FROM
yourtable a
JOIN yourtable b on a.id = b.id
WHERE a.name ='elec'
and b.name ='water';

Sql query duplicate column with different effective start date

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.

Binary "OR" operation on a SQL column

I have a query that returns weekdays
Select PERSON_NAME, PERSON_DAY from PERSON_DAYS WHERE PERSON_ID = #myId
say I obtain
John 1 (mo)
John 3 (mo tu)
John 8 (th)
I need to obtain for John all the days when is busy. How do I a logical OR on the PERSON_DAY column in this query?
the result should be 11 (mo tu th)
well here my best so far
;with PowersOf2
as
(
select 1 as Number
union all
select A.Number * 2 from PowersOf2 as A where A.Number < 64
)
select P.PERSON_NAME, sum(distinct P.PERSON_DAY & PowersOf2.Number)
from PERSON_DAYS as P
left outer join PowersOf2 on PowersOf2.Number <= P.PERSON_DAY
where P.PERSON_ID = #myId
group by P.PERSON_NAME
SQL FIDDLE EXAMPLE
If I understand you correctly, you can use a combination of bitwise operator and and aggregate function sum to do what you want.
Example:
with person_days as (
select 'John' as person_name, 1 as weekday --mo
union select 'John', 3 -- mo, tu
union select 'John', 8 -- th
union select 'Jane', 1 -- mo
union select 'Jane', 9 -- mo, th
union select 'Jane', 40 -- th, sa
),
Bits AS (
SELECT 1 AS BitMask --mo
UNION ALL SELECT 2 --tu
UNION ALL SELECT 4 --we
UNION ALL SELECT 8 --th
UNION ALL SELECT 16 --fr
UNION ALL SELECT 32 --sa
UNION ALL SELECT 64 --su
UNION ALL SELECT 128
)
, person_single_days as (
select distinct person_name, weekday & bits.BitMask single_weekday
from person_days
inner join bits on person_days.weekday & bits.BitMask > 0
)
select person_name, sum(single_weekday) weekdays
from person_single_days
group by person_name;
result:
person_name weekdays
----------- -----------
Jane 41
John 11
"inspired" by Roman's CTE: (note that the first CTE just generates demo data)
with p as
(
select 'John' as PERSON_NAME, 1 as PERSON_DAY
union
select 'John', 3
union
select 'John', 8
union
select 'Jane', 2
union
select 'Jane', 4
),
cte as
(
select PERSON_NAME, PERSON_DAY from p
union all
select cte2.PERSON_NAME, p.PERSON_DAY | cte2.PERSON_DAY
from p
inner join cte as cte2 on p.PERSON_NAME = cte2.PERSON_NAME
where p.PERSON_DAY & cte2.PERSON_DAY = 0
)
select PERSON_NAME, MAX(PERSON_DAY) from cte
group by PERSON_NAME
I think what you are looking for is a custom aggregate that does an OR. You can write that using SQL CLR in .NET. This is probably the cleanest solution. It will be reusable, too.
Alternatively, you could use cursor-based loops to calculate the result.
You could also (mis)use CTE's for this purpose.