I have 4 source tables:
CREATE TABLE TABLE1_NAMES (NAMES VARCHAR2(12));
INSERT INTO TABLE1_NAMES VALUES ('JOHN');
INSERT INTO TABLE1_NAMES VALUES ('RYAN');
INSERT INTO TABLE1_NAMES VALUES ('OLAN');
INSERT INTO TABLE1_NAMES VALUES ('TONY');
CREATE TABLE TABLE2_LINES (NAMES VARCHAR2(12), LINE_NO NUMBER, UNITS NUMBER(18,4), CODE VARCHAR2(4));
INSERT INTO TABLE2_LINES VALUES ('JOHN',1,1,'101');
INSERT INTO TABLE2_LINES VALUES ('JOHN',2,1,'202');
INSERT INTO TABLE2_LINES VALUES ('JOHN',3,1,'180');
INSERT INTO TABLE2_LINES VALUES ('JOHN',4,2,'300');
INSERT INTO TABLE2_LINES VALUES ('RYAN',1,2,'180');
INSERT INTO TABLE2_LINES VALUES ('RYAN',2,1,'180');
INSERT INTO TABLE2_LINES VALUES ('RYAN',3,1,'500');
INSERT INTO TABLE2_LINES VALUES ('OLAN',1,1,'301');
INSERT INTO TABLE2_LINES VALUES ('TONY',1,1,'201');
CREATE TABLE TABLE3_DATES (NAMES VARCHAR2(12), FR_DT TIMESTAMP(3), TO_DT TIMESTAMP(3), START_DT TIMESTAMP(3),END_DT TIMESTAMP(3));
INSERT INTO TABLE3_DATES VALUES ('JOHN','01-DEC-22 12.00.00.000000000 AM','05-DEC-22 12.00.00.000000000 AM','03-DEC-22 12.00.00.000000000 AM','09-DEC-22 12.00.00.000000000 AM');
INSERT INTO TABLE3_DATES VALUES ('RYAN','01-DEC-22 12.00.00.000000000 AM','04-DEC-22 12.00.00.000000000 AM','03-DEC-22 12.00.00.000000000 AM','09-DEC-22 12.00.00.000000000 AM');
INSERT INTO TABLE3_DATES VALUES ('OLAN','01-DEC-22 12.00.00.000000000 AM','05-DEC-22 12.00.00.000000000 AM','03-DEC-22 12.00.00.000000000 AM','09-DEC-22 12.00.00.000000000 AM');
INSERT INTO TABLE3_DATES VALUES ('TONY','01-DEC-22 12.00.00.000000000 AM','05-DEC-22 12.00.00.000000000 AM','03-DEC-22 12.00.00.000000000 AM','09-DEC-22 12.00.00.000000000 AM');
CREATE TABLE TABLE4_CODES (CD_NM VARCHAR2(12), B_CODE VARCHAR2(4), E_CODE VARCHAR2(4));
INSERT INTO TABLE4_CODES VALUESS ('CODELIST','100','101');
INSERT INTO TABLE4_CODES VALUESS ('CODELIST','180','180');
INSERT INTO TABLE4_CODES VALUESS ('CODELIST','200','219');
COMMIT;
I have 2 result tables : RESULT1_CALC, RESULT2_FINAL (one table is for calculation of total units per person and other table is the final result table which stores the person name with code)
CREATE TABLE RESULT1_CALC (NAMES VARCHAR2(12), FR_DT TIMESTAMP(3), TO_DT TIMESTAMP(3), START_DT TIMESTAMP(3), END_DT TIMESTAMP(3), TOT_UNITS NUMBER);
CREATE TABLE RESULT2_FINAL (NAMES VARCHAR2(12), EX_CD VARCHAR2(3));
Logic Explanation:
We have TABLE2_LINES data where we calculate SUM(UNITS) per person based on CODE value present in TABLE4_CODES in such a way that
If 180 code only is present for a person then calculate SUM(UNITS) and compare with formula TO_DT - FR_DT, if not equal insert record (person name and 'ABC' code) into RESULT2_FINAL table
If 180 code is present along with other eligible codes (here eligible code is nothing but code present in TABLE4_CODES) for a person then calculate SUM(UNITS) and compare with formula END_DT - START_DT, if not equal insert record (person name and 'ABC' code) into RESULT2_FINAL table
If 180 code is not present but other eligible codes are present(here eligible code is nothing but code present in TABLE4_CODES) for a person then calculate SUM(UNITS) and compare with formula END_DT - START_DT, if not equal insert record (person name and 'ABC' code) into RESULT2_FINAL table
Example:
Case 1: For person JOHN, We have eligible codes i.e. 101,202, 180 (these codes present in TABLE4_CODES table) ..It has other eligible codes along with 180 so SUM(UNITS) is 3 and this value is compared with formula TO_DT - FR_DT i.e. December 5 - December 1 which is 4 (not equal to SUM(UNITS)) so insert into RESULT2_FINAL table.
Case 2: For person RYAN, We have eligible code i.e. 180 only (this code present in TABLE4_CODES table) ... so SUM(UNITS) is 3 and this value is compared with formula END_DT - START_DT i.e. December 9 - December 3 which is 6 (not equal to SUM(UNITS)) so insert into RESULT2_FINAL table.
Case 3: For person OLAN, we don't have eligible codes so need to do anything.
Case 4: For person TONY, we have eligible code 201 but we don't have 180 so (this code present in TABLE4_CODES table) ... so SUM(UNITS) is 1 and this value is compared with formula END_DT - START_DT i.e. December 9 - December 3 which is 6 (not equal to SUM(UNITS)) so insert into RESULT2_FINAL table.
But in SQL query which I have mentioned below, it is not inserting person RYAN and also TONY records into RESULT2_FINAL table as I am not sure how to write query to differ the above 2 cases with calculation to get expected result.
INSERT INTO RESULT1_CALC
(
SELECT T1.NAMES, T3.FR_DT, T3.TO_DT, T3.START_DT, T3.END_DT, RES.TOT_UNITS
FROM TABLE1_NAMES T1
JOIN (
SELECT T2.NAMES, SUM(T2.UNITS), TOT_UNITS
FROM TABLE2_LINES T2
JOIN TABLE4_CODES T4
ON T4.CD_NM = 'CODELIST'
AND T2.CODE BETWEEN T4.B_CODE AND T4.E_CODE
GROUP BY T2.NAMES
) RES
ON T1.NAMES = RES.NAMES
JOIN TABLE3_DATES T3
ON T1.NAMES = T3.NAMES
);
COMMIT;
INSERT INTO RESULT2_FINAL
(
SELECT DISTINCT R1.NAMES, 'ABC' EX_CD
FROM RESULT1_CALC R1
WHERE R1.TOT_UNITS <> (EXTRACT(DAY FROM TO_DT - FR_DT))
);
COMMIT;
Output at the end:
RESULT2_FINAL table should be populated with the help of RESULT1_CALC table.
NAMES
EX_CD
JOHN
ABC
RYAN
ABC
TONY
ABC
You don't need RESULT1_CALC and can just insert the rows directly using the source tables into the final table:
INSERT INTO RESULT2_FINAL (names, ex_cd)
SELECT l.names, 'ABC'
FROM (
SELECT names,
SUM(units) AS total_units,
COUNT(CASE WHEN l.code = '180' THEN 1 END) AS has180,
COUNT(CASE WHEN l.code = '180' THEN NULL ELSE 1 END) AS hasOther
FROM TABLE4_CODES c
INNER JOIN TABLE2_LINES l
ON TO_NUMBER(l.code) BETWEEN TO_NUMBER(c.b_code) AND TO_NUMBER(c.e_code)
WHERE c.cd_nm = 'CODELIST'
GROUP BY names
) l
INNER JOIN TABLE3_DATES d
ON l.names = d.names
WHERE ( l.has180 > 0 AND l.hasOther = 0
AND NUMTODSINTERVAL(l.total_units, 'DAY') = d.to_dt - d.fr_dt
)
OR ( l.hasOther > 0
AND NUMTODSINTERVAL(l.total_units, 'DAY') != d.end_dt - d.start_dt
);
Which, for the sample data, inserts the values:
NAMES
EX_CD
JOHN
ABC
RYAN
ABC
TONY
ABC
fiddle
Related
I have a table TABLE1 with 3 columns NAME, ROLLNO, CASHDATE.
CREATE TABLE TABLE1
(
NAME VARCHAR2(4) NOT NULL,
ROLLNO NUMBER(4) NOT NULL,
CASHDATE VARCHAR2(8) NOT NULL
);
INSERT INTO TABLE1 VALUES('SAMY', 1234, '15990101');
INSERT INTO TABLE1 VALUES('TOMY', 1324, '15990101');
INSERT INTO TABLE1 VALUES('DANY', 1342, '15990101');
TABLE1 looks like:
NAME ROLLNO CASHDATE
----------------------------------
SAMY 1234 15990101
TOMY 1324 15990101
DANY 1342 15990101
CASHDATE value is in the form of YYYYMMDD
I have a second table TABLE2 with 3 columns NAME, ID, ID_DATE:
CREATE TABLE TABLE2
(
NAME VARCHAR2(4) NOT NULL,
ID VARCHAR2(2) NOT NULL,
ID_DATE TIMESTAMP(3)
);
INSERT INTO TABLE2 VALUES('SAMY', '01', timestamp '2021-08-21 00:00:00');
INSERT INTO TABLE2 VALUES('SAMY', 'A1', timestamp '2018-08-19 00:00:00');
INSERT INTO TABLE2 VALUES('TOMY', '01', timestamp '2021-08-22 00:00:00');
INSERT INTO TABLE2 VALUES('TOMY', 'B1', timestamp '2000-08-15 00:00:00');
TABLE2 looks like:
NAME ID ID_DATE
--------------------------------------------------------
SAMY 01 21-AUG-2021 12.00.00.000000000 AM
SAMY A1 19-AUG-2018 12.00.00.000000000 AM
TOMY 01 22-AUG-2021 12.00.00.000000000 AM
TOMY B1 15-AUG-2000 12.00.00.000000000 AM
And I have a third table TABLE3 with 2 columns NAME, SEC_DATE:
CREATE TABLE TABLE3
(
NAME VARCHAR2(4) NOT NULL,
SEC_DATE TIMESTAMP(3)
);
INSERT INTO TABLE3 VALUES('SAMY', timestamp '2021-08-21 00:00:00');
INSERT INTO TABLE3 VALUES('TOMY', timestamp '2021-08-22 00:00:00');
INSERT INTO TABLE3 VALUES('DANY', timestamp '2021-08-29 00:00:00');
TABLE3 looks like:
NAME SEC_DATE
----------------------------------------------
SAMY 21-AUG-2021 12.00.00.000000000 AM
TOMY 22-AUG-2021 12.00.00.000000000 AM
DANY 29-AUG-2021 12.00.00.000000000 AM
As we see I have a TABLE1 having CASHDATE value as 15990101 which is default value.
So we need to UPDATE CASHDATE column which is having 15990101 in TABLE1 based on following conditions.
First it checks in TABLE2 based on NAME in TABLE2, if there is a record having same NAME with ID = '01' then ID_DATE value should update in CASHDATE column of TABLE1.
If it is not found in TABLE2 based on NAME with ID = '01', then it checks in TABLE3 and based on NAME only if there is a record, we need to update (SEC_DATE - 1) value in CASHDATE column of TABLE1.
Finally after update, the result TABLE1 looks like:
NAME ROLLNO CASHDATE
----------------------------------
SAMY 1234 20210821 --This record found in TABLE2
TOMY 1324 20210822 --This record found in TABLE2
DANY 1342 20210828 --This record found in TABLE3 (SEC_DATE - 1)
I understand we need to update statement but I am not sure using CASE WHEN in UPDATE statement.
You can use COALESCE in combination with subqueries for this:
update table1 t1
set cashdate =
coalesce
(
(select to_char(id_date, 'yyyymmdd') from table2 t2 where t2.name = t1.name and t2.id = '01'),
(select to_char(sec_date - interval '1' day, 'yyyymmdd') from table3 t3 where t3.name = t1.name),
'15990101'
)
where cashdate = '15990101';
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=b6c5b08f5b303a23d26e5349228ee301
From my point of view, a simple option is a two-step option, i.e. two updates. In this case, I actually prefer MERGE over UPDATE.
Initial table1 contents:
SQL> select * From table1;
NAME ROLLNO CASHDATE
---- ---------- --------
SAMY 1234 15990101
TOMY 1324 15990101
DANY 1342 15990101
Update cashdate based on table2's contents (that's your 1st condition):
SQL> merge into table1 a
2 using table2 b
3 on (a.name = b.name)
4 when matched then update set
5 a.cashdate = to_char(b.id_date, 'yyyymmdd')
6 where a.cashdate = '15990101'
7 and b.id = '01';
2 rows merged.
Update cashdate based on table3's contents (that's your 2st condition) (not exists is here to skip rows already updated in previous step):
SQL> merge into table1 a
2 using table3 c
3 on (c.name = a.name)
4 when matched then update set
5 a.cashdate = to_char(c.sec_date - 1, 'yyyymmdd')
6 where a.cashdate = '15990101'
7 and not exists (select null from table2 b
8 where b.name = a.name
9 and b.id = '01'
10 );
1 row merged.
Final result:
SQL> select * from table1;
NAME ROLLNO CASHDATE
---- ---------- --------
SAMY 1234 20210821
TOMY 1324 20210822
DANY 1342 20210828
SQL>
You can query the data and update the table rows from the query result. This is called an updateable query. The great advantage is that we only update rows that we want updated (in your case rows that are on default value and have a match in table2 and/or table3).
For a query to be updateable, Oracle must see it guaranteed that there is just one target value selected per row. This means that we need unique constraints to ensure that joining table2 and table3 rows to a table1 row still results in no more than one row per table1 row.
The constraints needed here are
table2: primary key (name, id)
table3: primary key (name)
With these constraints in place, Oracle should be able to update our query:
update
(
select
t1.cashdate,
coalesce(t2.id_date, t3.sec_date - interval '1' day) as found_date
from table1 t1
left join table2 t2 on t2.name = t1.name and t2.id = '01'
left join table3 t3 on t3.name = t1.name
where t1.cashdate = '15990101'
and coalesce(t2.id_date, t3.sec_date - interval '1' day) is not null
)
set cashdate = to_char(found_date, 'yyyymmdd');
This may still fail. It really depends on whether Oracle considers this query updateable. It should (and it does in Oracle 18c, as you can see in the demo inked below), but sometimes Oracle struggles with such queries. You'll have to try whether this already works in Oracle 11g.
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=36287406a7f5591d4b53df4a7b990ef6
I have a SQL table with two types of ID column. e.g.
ID_1 Name Date ID_2
487 Joe 09/06/2004 332
731 Mike 06/01/2004 116
487 Joe 09/06/2004 354
777 Rich 01/01/2002 455
745 Mike 06/01/2004 116
Sometimes ID_1 has multiple rows, with different values for ID_2. And vice versa, sometimes ID_2 has multiple rows, with different values for ID_1.
I would like to keep all rows where there is a one-to-one match between ID_1 and ID_2. Ideally, I would also make another table with the remaining rows, so I can easily look at them later. So the above example, only one row (the 4th one) has a one-to-one match between ID_1 and ID_2:
ID_1 Name Date ID_2
777 Rich 01/01/2002 455
All of the other rows have rows where one of the IDs is duplicated. So it is basically removing any rows where either of the ID columns is duplicated at all.
I have tried using DISTINCT, but that keeps one of the duplicate rows, while I want them all removed.
p.s. this is not a question about joining tables - the example is a single table.
create table #one_to_one
(id_1 int, name varchar(20), dt date, id_2 int)
insert into #one_to_one values( 487, 'Joe', '09/06/2004' , 332)
insert into #one_to_one values( 731, 'Mike', '06/01/2004' , 116)
insert into #one_to_one values(487, 'Joe', '09/06/2004' , 354)
insert into #one_to_one values( 777, 'Rich', '01/01/2002', 455)
insert into #one_to_one values( 745, 'Mike', '06/01/2004', 116)
select id_1, name, dt, id_2
from (select *, count(*) over(partition by id_1) as id_1_count,
count(*) over(partition by id_2) as id_2_count
from #one_to_one) res
where id_1_count = 1 and id_2_count = 1;
You could use windowed COUNT:
CREATE TABLE only_one_to_one
AS
SELECT ID_1, Name, Date, ID_2
FROM (SELECT *,COUNT(*) OVER(PARTITION BY ID_1) AS ID1_cnt,
COUNT(*) OVER(PARTITION BY ID_2) AS ID2_cnt
FROM tab) sub
WHERE ID1_cnt = 1 AND ID2_cnt = 1;
db<>fiddle demo
Only one to one
SELECT *
FROM Table A
WHERE (SELECT Count(1)
FROM Table B
WHERE A.ID_1 = B.ID_1) = 1
AND (SELECT Count(1)
FROM Table B
WHERE A.ID_2 = B.ID_2) = 1
More than one
SELECT *
FROM Table A
WHERE (SELECT Count(1)
FROM Table B
WHERE A.ID_1 = B.ID_1) > 1
OR (SELECT Count(1)
FROM Table B
WHERE A.ID_2 = B.ID_2) > 1
this code will help you please
create table #temp (ID_1 int,name varchar(255),[Date] date,ID_2 int)
insert into #temp values (487 , 'Joe','09/06/2004', 332)
insert into #temp values (731 , 'Mike' , '06/01/2004' , 116 )
insert into #temp values (487 , ' Joe' , '09/06/2004' , 354 )
insert into #temp values (777 , 'Rich' , '01/01/2002' , 455 )
insert into #temp values (745 , 'Mike' , '06/01/2004' , 116 )
Select * from (
Select ROW_NUMBER() OVER(ORDER BY id_1 DESC) AS Row#,ID_1,Name,Date,ID_2
FROM #temp
) as T
Where Row# = 4
Drop table #temp
In the below example, I'm trying to count the number of drinks I can make based on the availability of ingredients per bar location that I have.
To further clarify, as seen in the below example: based on the figures highlighted in the chart below; I know that I can only make 1 Margarita on 6/30/2018 (in either DC or FL if I ship the supplies to the location).
Sample of data table
Please use the below code to enter the relevant data above:
CREATE TABLE #drinks
(
a_date DATE,
loc NVARCHAR(2),
parent NVARCHAR(20),
line_num INT,
child NVARCHAR(20),
avail_amt INT
);
INSERT INTO #drinks VALUES ('6/26/2018','CA','Long Island','1','Vodka','7');
INSERT INTO #drinks VALUES ('6/27/2018','CA','Long Island','2','Gin','5');
INSERT INTO #drinks VALUES ('6/28/2018','CA','Long Island','3','Rum','26');
INSERT INTO #drinks VALUES ('6/26/2018','DC','Long Island','1','Vodka','15');
INSERT INTO #drinks VALUES ('6/27/2018','DC','Long Island','2','Gin','18');
INSERT INTO #drinks VALUES ('6/28/2018','DC','Long Island','3','Rum','5');
INSERT INTO #drinks VALUES ('6/26/2018','FL','Long Island','1','Vodka','34');
INSERT INTO #drinks VALUES ('6/27/2018','FL','Long Island','2','Gin','14');
INSERT INTO #drinks VALUES ('6/28/2018','FL','Long Island','3','Rum','4');
INSERT INTO #drinks VALUES ('6/30/2018','DC','Margarita','1','Tequila','6');
INSERT INTO #drinks VALUES ('7/1/2018','DC','Margarita','2','Triple Sec','3');
INSERT INTO #drinks VALUES ('6/29/2018','FL','Margarita','1','Tequila','1');
INSERT INTO #drinks VALUES ('6/30/2018','FL','Margarita','2','Triple Sec','0');
INSERT INTO #drinks VALUES ('7/2/2018','CA','Cuba Libre','1','Rum','1');
INSERT INTO #drinks VALUES ('7/8/2018','CA','Cuba Libre','2','Coke','5');
INSERT INTO #drinks VALUES ('7/13/2018','CA','Cuba Libre','3','Lime','14');
INSERT INTO #drinks VALUES ('7/5/2018','DC','Cuba Libre','1','Rum','0');
INSERT INTO #drinks VALUES ('7/19/2018','DC','Cuba Libre','2','Coke','12');
INSERT INTO #drinks VALUES ('7/31/2018','DC','Cuba Libre','3','Lime','9');
INSERT INTO #drinks VALUES ('7/2/2018','FL','Cuba Libre','1','Rum','1');
INSERT INTO #drinks VALUES ('7/19/2018','FL','Cuba Libre','2','Coke','3');
INSERT INTO #drinks VALUES ('7/17/2018','FL','Cuba Libre','3','Lime','2');
INSERT INTO #drinks VALUES ('6/30/2018','DC','Long Island','3','Rum','4');
INSERT INTO #drinks VALUES ('7/7/2018','FL','Cosmopolitan','5','Triple Sec','7');
The expected results are as follows:
Please note, as seen in the expected results, children are interchangeable. For example, on 7/7/2018 Triple Sec arrived for the drink cosmopolitan; however because the child is also rum, it changes the availability of Margaritas for FL.
Also not the update to the DC region for Cuba Libre's on both 06/30 and 06/31.
Please take into consideration that parts are interchangeable and also that each time a new item arrives it makes available any item previously now.
Lastly - It would be awesome if I could add another column that shows kit availability regardless of location based only on availability of the child. For Ex. If there is a child #3 in DC and none in FL they FL can assume that they have enough inventory to make drink based on inventory in another location!
I've created a couple of extra tables to help with writing the query, but these could be generated from the #drinks table if you wanted:
CREATE TABLE #recipes
(
parent NVARCHAR(20),
child NVARCHAR(20)
);
INSERT INTO #recipes VALUES ('Long Island', 'Vodka');
INSERT INTO #recipes VALUES ('Long Island', 'Gin');
INSERT INTO #recipes VALUES ('Long Island', 'Rum');
INSERT INTO #recipes VALUES ('Maragrita', 'Tequila');
INSERT INTO #recipes VALUES ('Maragrita', 'Triple Sec');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Coke');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Rum');
INSERT INTO #recipes VALUES ('Cuba Libre', 'Lime');
INSERT INTO #recipes VALUES ('Cosmopolitan', 'Cranberry Juice');
INSERT INTO #recipes VALUES ('Cosmopolitan', 'Triple Sec');
CREATE TABLE #locations
(
loc NVARCHAR(20)
);
INSERT INTO #locations VALUES ('CA');
INSERT INTO #locations VALUES ('FL');
INSERT INTO #locations VALUES ('DC');
The query then becomes:
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = '2018-06-26'
SET #EndDateTime = '2018-07-31';
--First, build a range of dates that the report has to run for
WITH DateRange(a_date) AS
(
SELECT #StartDateTime AS DATE
UNION ALL
SELECT DATEADD(d, 1, a_date)
FROM DateRange
WHERE a_date < #EndDateTime
)
SELECT a_date, parent, loc, avail_amt
FROM (--available_recipes_inventory
SELECT a_date, parent, loc, avail_amt,
LAG(avail_amt, 1, 0) OVER (PARTITION BY loc, parent ORDER BY a_date) AS previous_avail_amt
FROM (--recipes_inventory
SELECT a_date, parent, loc,
--The least amount of the ingredients for a recipe is the most
--amount of drinks we can make for it
MIN(avail_amt) as avail_amt
FROM (--ingredients_inventory
SELECT dr.a_date, r.parent, r.child, l.loc,
--Default ingredients we don't have with a zero amount
ISNULL(d.avail_amt, 0) as avail_amt
FROM DateRange dr CROSS JOIN
#recipes r CROSS JOIN
#locations l OUTER APPLY
(
--Find the total amount available for each
--ingredient at each location for each date
SELECT SUM(d1.avail_amt) as avail_amt
FROM #drinks d1
WHERE d1.a_date <= dr.a_date
AND d1.loc = l.loc
AND d1.child = r.child
) d
) AS ingredients_inventory
GROUP BY a_date, parent, loc
) AS recipes_inventory
--Remove all recipes that we don't have enough ingredients for
WHERE avail_amt > 0
) AS available_recipes_inventory
--Selects the first time a recipe has enough ingredients to be made
WHERE previous_avail_amt = 0
--Selects when the amount of ingredients has changed
OR previous_avail_amt != avail_amt
ORDER BY a_date
--MAXRECURSION needed to generate the date range
OPTION (MAXRECURSION 0)
GO
The innermost SELECT creates a pseudo inventory table (ingredients_inventory) consisting of location, ingredient, date and amount available. When an ingredient is not available at a location for a particular date, then a zero is used.
The next SELECT query out finds how many of each recipe can be made for each location/date (again this may be zero).
The next SELECT query out is an intermediate table necessary to gather how many of each recipe for each location could be made for the previous day (whilst also removing any drinks that could not be made).
And finally, the outermost SELECT query uses the previous day's data to find when the quantity of each particular recipe that can be made has changed.
This query produces slightly different numbers to your table, but I think that's because yours is wrong? Taking Florida for example, an extra Rum comes in on 2nd July, so the number of Long Islands that can be made goes up to 5. And 2 Cuba Libres can be made by the 19th.
Results:
+------------+-------------+-----+-----------+
| a_date | parent | loc | avail_amt |
+------------+-------------+-----+-----------+
| 2018-06-28 | Long Island | DC | 5 |
| 2018-06-28 | Long Island | CA | 5 |
| 2018-06-28 | Long Island | FL | 4 |
| 2018-06-30 | Long Island | DC | 9 |
| 2018-07-01 | Maragrita | DC | 3 |
| 2018-07-02 | Long Island | FL | 5 |
| 2018-07-07 | Maragrita | FL | 1 |
| 2018-07-13 | Cuba Libre | CA | 5 |
| 2018-07-19 | Cuba Libre | FL | 2 |
| 2018-07-31 | Cuba Libre | DC | 9 |
+------------+-------------+-----+-----------+
I think this would give the required result.
Created a function that'll get the inventory.
Create function GetInventoryByDateAndLocation
(#date DATE, #Loc NVARCHAR(2))
RETURNS TABLE
AS
RETURN
(
Select child,avail_amt from
(Select a_date, child,avail_amt,
ROW_NUMBER() over (partition by child order by a_date desc) as ranking
from drinks where loc = #Loc and a_date<=#date)c
where ranking = 1
)
Then the query:
with parentChild as
(Select distinct parent, line_num, child from drinks),
ParentChildNo as
(Select parent, max(line_num) as ChildNo from parentChild group by parent)
,Inventory as
(Select a_date,loc,s.* from drinks d cross apply
GetInventoryByDateAndLocation(d.a_date, d.loc)s)
, Available as
(Select a_date,parent,loc,count(*) as childAvailable,min(avail_amt) as quantity
from Inventory i
join parentChild c
on i.child = c.child
group by parent,loc,a_date)
Select a_date,a.parent,loc,quantity from available a
join ParentChildNo pc
on a.parent = pc.parent and a.childAvailable = pc.ChildNo
where quantity > 0 order by 1
This would give all the drinks which can be made from the inventory. Hope it solves your issue.
These are just my 2 cents. There are better ways of doing this and I hope more people would read this and suggest better.
don't think this is exactly what your looking for... maybe it will help.
SELECT DISTINCT #drinks.loc,#drinks.parent,avail.Avail
FROM #drinks
LEFT OUTER JOIN (
SELECT DISTINCT #drinks.parent, MIN(availnow.maxavailnow / line_num)
OVER(PARTITION BY parent) as Avail
FROM #drinks
LEFT OUTER JOIN (
SELECT #drinks.child,SUM(avail_amt) maxavailnow
FROM #drinks
LEFT OUTER JOIN (SELECT MAX(a_date) date,loc,child FROM #drinks GROUP BY loc,child) maxx ON #drinks.loc = maxx.loc AND #drinks.child = maxx.child AND maxx.date = #drinks.a_date
GROUP BY #drinks.child
) availnow ON #drinks.child = availnow.child
) avail ON avail.parent = #drinks.parent
SELECT ( SELECT MAX(d2.a_date)
FROM #drinks AS d2
WHERE d2.parent = d.parent
AND d2.loc = d.loc) AS a_date
,d.loc
,d.parent
,SUM(d.avail_amt) AS [avail_amt(SUM)]
,COUNT(d.avail_amt) AS [avail_amt(COUNT)]
FROM #drinks AS d
GROUP BY d.loc
,d.parent
ORDER BY a_date
For the following question it is said that the answer should be C. But I think the correct answer is Answer D as NOT MATCHED block inserts all unmatching records to target table. Can anybody explain this?
Thank you.
Q)View the Exhibit and examine the data in ORDERS_MASTER and MONTHLY_ORDERS tables.
Evaluate the following MERGE statement:
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE (m.order_total IS NULL)
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total);
What would be the outcome of the above statement?
A. The ORDERS_MASTER table would contain the ORDER_IDs 1 and 2.
B. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 3.
C. The ORDERS_MASTER table would contain the ORDER_IDs 1,2 and 4.
D. The ORDERS_MASTER table would contain the ORDER IDs 1,2,3 and 4.
Answer: C
The correct answer is indeed C, this is because the source of the merge operation is the monthly_orders table, which only contains two records with order_id 2 and 3 respectively.
Think about what will happen for each of these records:
For order_id = 2, because this id exists in the order_master table, we'll execute the MATCHED part of the merge statement, updating the order_total to 2500. Since the quantity for this record is not NULL, the DELETE won't do anything.
For order_id = 3, again, the id exists in the order_master table, so we execute the MATCHED part of the merge statement, updating the order_total to NULL and then issuing a DELETE on order_master for the row we just updated because the quantity on monthly_order is NULL.
This leaves us with order_id 1, 2 and 4, which matches answer C.
Code
CREATE TABLE orders_master (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
CREATE TABLE monthly_orders (
order_id NUMBER(1) NOT NULL
,order_total NUMBER(10) NULL
)
/
INSERT INTO orders_master (order_id, order_total) VALUES (1, 1000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (2, 2000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (3, 3000)
/
INSERT INTO orders_master (order_id, order_total) VALUES (4, NULL)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (2, 2500)
/
INSERT INTO monthly_orders (order_id, order_total) VALUES (3, NULL)
/
MERGE INTO orders_master o
USING monthly_orders m
ON (o.order_id = m.order_id)
WHEN MATCHED THEN
UPDATE SET o.order_total = m.order_total
DELETE WHERE m.order_total IS NULL
WHEN NOT MATCHED THEN
INSERT VALUES (m.order_id, m.order_total)
/
COMMIT
/
SQL> select * from orders_master
2 /
ORDER_ID ORDER_TOTAL
---------- -----------
1 1000
2 2500
4
I have one Scenario where I need to find missing records in Table using SQL - without using Cursor, Views, SP.
For a particular CustID initial Start_Date will be 19000101 and End_date will be any random date.
Then for next Record for the same CustID will have its Start_Date as End_Date (of previous Record) + 1.
Its End_Date again will be any random date.
And so on….
For Last record of same CustID its end Date will be 99991231.
Following population of data will explain it better.
CustID Start_Date End_Date
1 19000101 20121231
1 20130101 20130831
1 20130901 20140321
1 20140321 99991231
Basically I am trying to populate data like in SCD2 scenario.
Now I want to find missing record (or CustID).
Like below we don’t have record with CustID = 4 with Start_Date = 20120606 and End_Date = 20140101
CustID Start_Date End_Date
4 19000101 20120605
4 20140102 99991231
Code for Creating Table
CREATE TABLE TestTable
(
CustID int,
Start_Date int,
End_Date int
)
INSERT INTO TestTable values (1,19000101,20121231)
INSERT INTO TestTable values (1,20130101,20130831)
INSERT INTO TestTable values (1,20130901,20140321)
INSERT INTO TestTable values (1,20140321,99991231)
INSERT INTO TestTable values (2,19000101,99991213)
INSERT INTO TestTable values (3,19000101,20140202)
INSERT INTO TestTable values (3,20140203,99991231)
INSERT INTO TestTable values (4,19000101,20120605)
--INSERT INTO TestTable values (4,20120606,20140101) --Missing Value
INSERT INTO TestTable values (4,20140102,99991231)
Now SQL should return CustID = 4 as its has missing Value.
My idea is based on this logic. Lets assume 19000101 as 1 and 99991231 as 10. Now for all IDs, if you subtract the End_date - start_date and add them up, the total sum must be equal to 9 (10 - 1). You can do the same here
SELECT ID, SUM(END_DATE - START_DATE) as total from TABLE group by ID where total < (MAX_END_DATE - MIN_START_DATE)
You might want to find the command in your SQL that gives the number of days between 2 days and use that in the SUM part.
Lets take the following example
1 1900 2003
1 2003 9999
2 1900 2222
2 2222 9977
3 1900 9999
The query will be executed as follows
1 (2003 - 1900) + (9999 - 2003) = 1 8098
2 (2222 - 1900) + (9977 - 2222) = 2 9077
3 (9999 - 1900) = 3 8098
The where clause will eliminate 1 and 3 giving you only 2, which is what you want.
If you just need the CustID then this will do
SELECT t1.CustID
FROM TestTable t1
LEFT JOIN TestTable t2
ON DATEADD(D, 1, t1.Start_Date) = t2.Start_Date
WHERE t2.CustID IS NULL
GROUP BY t1.CustID
You need rows if the one of the following conditions is met:
Not a final row (99991231) and no matching next row
Not a start row (19000101) and no matching previous row
You can left join to the same table to find previous and next rows and filter the results where you don't find a row by checking the column values for null:
SELECT t1.CustID, t1.StartDate, t1.EndDate
FROM TestTable t1
LEFT JOIN TestTable tPrevious on tPrevious.CustID = t1.CustID
and tPrevious.EndDate = t1.StartDate - 1
LEFT JOIN TestTable tNext on tNext.CustID = t1.CustID
and tNext.StartDate = t1.EndDate + 1
WHERE (t1.EndDate <> 99991231 and tNext.CustID is null) -- no following
or (t1.StartDate <> 19000101 and tPrevious.CustID is null) -- no previous