Self join query using SQL - demo query attached - sql

I have a scenario where I need my query to pull out data based on Code value.
Example: The table #temp1 has the combination of Person ID, Program assignments and dataset. For any person '1001', I want to pull out the admission date of first program and discharge date and dataset of the last program under similar code 'PS'.
So, my desired output is:
Demo code:
https://rextester.com/ADDL95491
Any help?!

It seems to me you need below query
with cte1 as
(select cid,code,admissiondate,dischargedate,program
from #temp1 t1 where
t1.row_number = (select min(row_number) from #temp1)
) , cte2 as
(select * from #temp1 where dataset is not null
)
select cte1.cid,cte1.code,cte1.admissiondate,
cte2.dischargedate,cte2.dataset
from cte1 left join cte2 on cte1.code=cte2.code
https://rextester.com/BNVK71028
cid code admissiondate dischargedate dataset
1 1001 PR 01/01/2011 5/1/2011 discharge data
2 1001 PS 06/01/2011 7/1/2011 discharge data
3 1001 PQ 08/01/2011

Related

ORACLE SQL - multiple JOINs from same table

I have data related material transactions in one table and log history header data related to materials is in another table and detailed log history data in third table. I'm trying to get different status update dates matched to material table but I get duplicate rows for one material transaction
Original material transaction table:
ORDER_NO
MATERIAL
QTY
0001
MAT01
2
0002
MAT02  
5
Original Log History Header transaction table:
ORDER_NO
LOG_ID
0001
1001
0001
1002
Status code 1 refers to Opened and code 2 to Closed
Detailed Log History table:
LOG_ID
STATUS_CODE
DATE
1001
1
11/12/2021
1002
2  
15/12/2021
With following SQL query:
SELECT
TO_CHAR (m.order_no) order_no,
m.material,
a.date opened_date,
ab.closed_date
FROM MATERIAL_TRANSACTIONS m
INNER JOIN HISTORY_LOG t
ON m.ORDER_NO = t.ORDER_NO
INNER JOIN HISTORY_LOG_DETAILED a
ON t.LOG_ID = a.LOG_ID
AND a.STATUS_CODE = '1'
INNER JOIN HISTORY_LOG_DETAILED ab
ON t.LOG_ID = ab.LOG_ID
AND ab.STATUS_CODE = '2'
I get following result:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
0001
MAT01  
2
15/12/2021
And I would like to get the status dates to the same row as below:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
15/12/2021
I would appreciate all the help I can get and am very sorry if there already is topic for similar issue.
Your problem occurs because you join the history table, which holds 2 records for the order. You could flatten this if you use 2 inline tables that hold exactly 1 record.
with opened_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '1'
), closed_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '2'
)
select to_char (m.order_no) order_no,
m.material,
o.date opened_date,
c.date closed_date
from material_transactions m
join opened_dates o on m.order_no = o.order_no
join closed_dates c on m.order_no = c.order_no
;
Just an idea :
I joined HISTORY_LOG and HISTORY_LOG_DETAILED tables to get dates for specific status, and set as OPENED_DATE and CLOSED_DATE (if status 1 , then opened date is DATE column, otherwise set it as 01.01.0001)
After that grouped those records by ORDER_NO and maxed the date values to get actual OPENED_DATE and CLOSED_DATE .
Finally joined this subquery with MATERIAL_TRANSACTIONS table :
SELECT
TO_CHAR (M.ORDER_NO) ORDER_NO,
M.MATERIAL,
QTY,
L_T.OPENED_DATE,
L_T.CLOSED_DATE
FROM MATERIAL_TRANSACTIONS M
INNER JOIN
(
SELECT L.ORDER_NO ,
MAX( CASE WHEN LD.STATUS_CODE = 1 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) OPENED_DATE
MAX( CASE WHEN LD.STATUS_CODE = 2 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) CLOSED_DATE
FROM
HISTORY_LOG L
INNER JOIN HISTORY_LOG_DETAILED LD ON LD.LOG_ID = L.LOG_ID
GROUP BY L.ORDER_NO
) L_T on L_T.ORDER_NO = M.ORDER_NO
Note: I didnt test it. So there can be small syntax errors. Please check it and for better help add a fiddle so i can test my query

Extract non-existent values based on previous months?

I HAVE tb1
code Name sal_month
==== ===== ========
101 john 02/2017
102 mathe 02/2017
103 yara 02/2017
104 sara 02/2017
101 john 03/2017
102 mathe 03/2017
103 yara 03/2017
104 sara 03/2017
101 john 04/2017
103 yara 04/2017
In February all of them received salaries as well as March
How do I extract non-existent values based on previous months?
the result should be come
code sal_month
==== =======
102 04/2017
104 04/2017
Thank in advance
First I created this table:
create table #T(code int, sal_month varchar(10))
insert into #T values(101,'2/2017'),(102,'2/2017'),(103,'2/2017'),(104,'2/2017'),
(101,'3/2017'),(102,'3/2017'),(104,'3/2017'),(101,'4/2017'),(103,'4/2017')
Second, I executed this query:
SELECT code, Max(sal_Month)
From #T
Where code not in (select code from #T where sal_Month = (select Max(sal_Month) from #T))
Group by code
Then I got the following results:
Note: I am using SQL SERVER 2012
I think you can count salary_month grouped by id, something like this, and select the rows that shows less than 3 times.
select code, count (sal_month) from tb1
group by code
having count (sal_month) < 3
After that you join with initial table (just to filter the full rows which you need) on code.
So the final query will look like his:
select code, sal_month
from tb1 a
join (select code, count (sal_month) from tb1
group by code
having code < 3) X on a.code = X.code
Something like this:
DECLARE #DataSource TABLE
(
[code] INT
,[sal_month] VARCHAR(12)
);
INSERT #DataSource ([code], [sal_month])
VALUES (101, '2/2017')
,(102, '2/2017')
,(103, '2/2017')
,(104, '2/2017')
,(101, '3/2017')
,(102, '3/2017')
,(104, '3/2017')
,(101, '4/2017')
,(103, '4/2017');
WITH DataSource AS
(
SELECT *
,DENSE_RANK() OVER (ORDER BY [sal_month]) AS [MonthID]
,MAX([sal_month]) OVER () AS [MaxMonth]
FROM #DataSource DS1
)
SELECT DS1.[code]
,DS1.[sal_month]
FROM DataSource DS1
LEFT JOIN DataSource DS2
ON DS1.[code] = DS2.[code]
AND DS1.[MonthID] = DS2.[MonthID] - 1
LEFT JOIN DataSource DS3
ON DS1.[code] = DS3.[code]
AND DS1.[MonthID] = DS3.[MonthID] + 1
WHERE DS2.[code] IS NULL
AND DS3.[code] IS NOT NULL
AND DS1.[sal_month] <> DS1.[MaxMonth];
Some notes:
we need a way to sort the months and it is not easy as you are storing them in very unpractical way; you are not using a date/datetime column and your string is not a valid date; also, the string you are using is not good at all, because if you have [sal_month] from different years, we will not be able to sort them; you should think about this - one alternative is to use this format:
201512
201701
201702
201711
In this way we can sort by string.
in the core I am using ROW_NUMBER and sorting months as strings;
the idea is to look for all records, that have not exists in the next month, but have a record in the previous; at the same time, excluded records which are from the last month, as it's not possible for them to have record in the next month;
Try this:
select tb2.code, tb2.sal_month from tb
right join (
select code, sal_month, datepart(month, sal_month) + 1 as next_sal_month from tb) as tb2
on (tb.code = tb2.code and datepart(month, tb.sal_month) = tb2.next_sal_month)
where tb2.next_sal_month < 5 and tb.sal_month is null
In the result set there's one additional record: code 103 didn't receive salary in March, but did so in February, so it is included as well.
Here's SQL fiddle, to try :)
In the absence of more facts about your tables, create a cartesian product of the 2 axes of month & code, then left join the stored data. Then it is easy to identify missing items when no stored data exists when compared to every possible combination.
You might already have master tables of sal_month and/or code to use, if you do use those, but if not you can dynamically create them using select distinct as seen below.
create table tbl1 (code int, sal_month varchar(10))
insert into tbl1 values(101,'2/2017'),(102,'2/2017'),(103,'2/2017'),(104,'2/2017'),
(101,'3/2017'),(102,'3/2017'),(104,'3/2017'),(101,'4/2017'),(103,'4/2017')
select c.code, m.sal_month
from ( select distinct sal_month from tbl1 ) m
cross join ( select distinct code from tbl1 ) c
Left join tbl1 t on m.sal_month = t.sal_month and c.code = t.code
Where t.sal_month IS NULL
code | sal_month
---: | :--------
103 | 3/2017
102 | 4/2017
104 | 4/2017
dbfiddle here

How to join 2 queries with different number of records and columns in oracle sql?

I have three tables:
Employee_leave(EmployeeID,Time_Period,leave_type)
Employee(EID,Department,Designation)
leave_eligibility(Department,Designation, LeaveType, LeavesBalance).
I want to fetch the number of leaves availed by a particular employee in each LeaveTypes(Category) so I wrote following query Query1
SELECT LEAVE_TYPE, SUM(TIME_PERIOD)
FROM EMPLOYEE_LEAVE
WHERE EMPLOYEEID=78
GROUP BY LEAVE_TYPE
order by leave_type;
output for Query1
Leave_Type | SUM(Time_Period)
Casual 1
Paid 4
Sick 1
I want to fetch the number of leaves an employee is eligible for each leave_type(category). Following query Query2 gives the desire result.
Select UNIQUE Leavetype,LEAVEBALANCE
from LEAVE_ELIGIBILITY
INNER JOIN EMPLOYEE
ON LEAVE_ELIGIBILITY.DEPARTMENT= EMPLOYEE.DEPARTMENT
AND LEAVE_ELIGIBILITY.DESIGNATION= EMPLOYEE.DESIGNATION
WHERE EID=78
order by leavetype;
output for Query2
LeaveType | LeaveBalance
Casual 10
Paid 15
Privlage 6
Sick 20
Now I want to join these 2 queries Query1 and Query2 or create view which displays records from both queries. Also as you can see from output there are different no. of records from different queries. For a record which is not present in output of query1, it should display 0 in final output. Like in present case there is no record in output of query1 like privlage but it should display 0 in Sum(time_period) in Privlage of final output. I tried creating views of these 2 queries and then joining them, but I'm unable to run final query.
Code for View 1
create or replace view combo_table1 as
Select UNIQUE Leavetype,LEAVEBALANCE,EMPLOYEE.DEPARTMENT,EMPLOYEE.DESIGNATION, EID
from LEAVE_ELIGIBILITY
INNER JOIN EMPLOYEE
ON LEAVE_ELIGIBILITY.DEPARTMENT= EMPLOYEE.DEPARTMENT
AND LEAVE_ELIGIBILITY.DESIGNATION= EMPLOYEE.DESIGNATION
WHERE EID='78';
Code for View 2
create or replace view combo_table2 as
SELECT LEAVE_TYPE, SUM(TIME_PERIOD) AS Leave_Availed
FROM EMPLOYEE_LEAVE
WHERE EMPLOYEEID='78'
GROUP BY LEAVE_TYPE;
Code for joining 2 views
SELECT combo_table1.Leavetype, combo_table1.LEAVEBALANCE, combo_table2.leave_availed
FROM combo_table1 v1
INNER JOIN combo_table2 v2
ON v1.Leavetype = v2.LEAVE_TYPE;
But I'm getting "%s: invalid identifier" while executing the above query. Also I know I can't use union as it requires same column which here it is not.
I'm using Oracle 11g, so please answer accordingly.
Thanks in advance.
Desired final output
LeaveType | LeaveBalance | Sum(Time_period)
Casual 10 1
Paid 15 4
Privlage 6 0
Sick 20 1
To get the final desired output ...
"For a record which is not present in output of query1, it should display 0 in final output. "
... use an outer join to tie the taken leave records to the other tables. This will give zero time_duration for leave types which the employee has not taken.
select emp.Employee_ID
, le.leavetype
, le.leavebalance
, sum (el.Time_Duration) as total_Time_Duration
from employee emp
inner join leave_eligibility le
on le.department= emp.department
and le.designation= emp.designation
left outer join Employee_leave el
on el.EmployeeID = emp.Employee_ID
and el.leave_type = le.leavetype
group by emp.Employee_ID
, le.leavetype
, le.leavebalance
;
Your immediate problem:
I'm getting "%s: invalid identifier"
Your view has references to a column EID although none of your posted tables have a column of that name. Likewise there is confusion between Time_Duration and time_period.
More generally, you will find life considerably easier if you use the exact same name for common columns (i.e. consistently use either employee_id or employeeid, don't chop and change).
Try this examle:
with t as (
select 'Casual' as Leave_Type, 1 as Time_Period, 0 as LeaveBalance from dual
union all
select 'Paid', 4,0 from dual
union all
select 'Sick', 1,0 from dual),
t1 as (
select 'Casual' as Leave_Type, 0 as Time_Period, 10 as LeaveBalance from dual
union all
select 'Paid', 0, 15 from dual
union all
select 'Privlage', 0, 6 from dual
union all
select 'Sick', 0, 20 from dual)
select Leave_Type, sum(Time_Period), sum(LeaveBalance)
from(
select *
from t
UNION ALL
select * from t1
)
group by Leave_Type
Ok, edit:
create or replace view combo_table1 as
Select UNIQUE Leavetype, 0 AS Leave_Availed, LEAVEBALANCE
from LEAVE_ELIGIBILITY INNER JOIN EMPLOYEE ON LEAVE_ELIGIBILITY.DEPARTMENT= EMPLOYEE.DEPARTMENT AND LEAVE_ELIGIBILITY.DESIGNATION= EMPLOYEE.DESIGNATION
WHERE EID='78';
create or replace view combo_table2 as
SELECT LEAVE_TYPE as Leavetype, SUM(TIME_PERIOD) AS Leave_Availed, 0 as LEAVEBALANCE
FROM EMPLOYEE_LEAVE
WHERE EMPLOYEEID='78'
GROUP BY LEAVE_TYPE, LEAVEBALANCE;
SELECT Leavetype, sum(LEAVEBALANCE), sum(leave_availed)
FROM (
select *
from combo_table1
UNION ALL
select * from combo_table2
)
group by Leavetype;

Adding in missing dates from results in SQL

I have a database that currently looks like this
Date | valid_entry | profile
1/6/2015 1 | 1
3/6/2015 2 | 1
3/6/2015 2 | 2
5/6/2015 4 | 4
I am trying to grab the dates but i need to make a query to display also for dates that does not exist in the list, such as 2/6/2015.
This is a sample of what i need it to be:
Date | valid_entry
1/6/2015 1
2/6/2015 0
3/6/2015 2
3/6/2015 2
4/6/2015 0
5/6/2015 4
My query:
select date, count(valid_entry)
from database
where profile = 1
group by 1;
This query will only display the dates that exist in there. Is there a way in query that I can populate the results with dates that does not exist in there?
You can generate a list of all dates that are between the start and end date from your source table using generate_series(). These dates can then be used in an outer join to sum the values for all dates.
with all_dates (date) as (
select dt::date
from generate_series( (select min(date) from some_table), (select max(date) from some_table), interval '1' day) as x(dt)
)
select ad.date, sum(coalesce(st.valid_entry,0))
from all_dates ad
left join some_table st on ad.date = st.date
group by ad.date, st.profile
order by ad.date;
some_table is your table with the sample data you have provided.
Based on your sample output, you also seem to want group by date and profile, otherwise there can't be two rows with 2015-06-03. You also don't seem to want where profile = 1 because that as well wouldn't generate two rows with 2015-06-03 as shown in your sample output.
SQLFiddle example: http://sqlfiddle.com/#!15/b0b2a/2
Unrelated, but: I hope that the column names are only made up. date is a horrible name for a column. For one because it is also a keyword, but more importantly it does not document what this date is for. A start date? An end date? A due date? A modification date?
You have to use a calendar table for this purpose. In this case you can create an in-line table with the tables required, then LEFT JOIN your table to it:
select "date", count(valid_entry)
from (
SELECT '2015-06-01' AS d UNION ALL '2015-06-02' UNION ALL '2015-06-03' UNION ALL
'2015-06-04' UNION ALL '2015-06-05' UNION ALL '2015-06-06') AS t
left join database AS db on t.d = db."date" and db.profile = 1
group by t.d;
Note: Predicate profile = 1 should be applied in the ON clause of the LEFT JOIN operation. If it is placed in the WHERE clause instead then LEFT JOIN essentially becomes an INNER JOIN.

SQL Server 2008 - need help on a antithetical query

I want to find out meter reading for given transaction day. In some cases there won’t be any meter reading and would like to see a meter reading for previous day.
Sample data set follows. I am using SQL Server 2008
declare #meter table (UnitID int, reading_Date date,reading int)
declare #Transactions table (Transactions_ID int,UnitID int,Transactions_date date)
insert into #meter (UnitID,reading_Date,reading ) values
(1,'1/1/2014',1000),
(1,'2/1/2014',1010),
(1,'3/1/2014',1020),
(2,'1/1/2014',1001),
(3,'1/1/2014',1002);
insert into #Transactions(Transactions_ID,UnitID,Transactions_date) values
(1,1,'1/1/2014'),
(2,1,'2/1/2014'),
(3,1,'3/1/2014'),
(4,1,'4/1/2014'),
(5,2,'1/1/2014'),
(6,2,'3/1/2014'),
(7,3,'4/1/2014');
select * from #meter;
select * from #Transactions;
I expect to get following output
Transactions
Transactions_ID UnitID Transactions_date reading
1 1 1/1/2014 1000
2 1 2/1/2014 1010
3 1 3/1/2014 1020
4 1 4/1/2014 1020
5 2 1/1/2014 1001
6 2 3/1/2014 1001
7 3 4/1/2014 1002
Your SQL Query to get your desired out put will as following:
SELECT Transactions_ID, T.UnitID, Transactions_date
, (CASE WHEN ISNULL(M.reading,'') = '' THEN
(
SELECT MAX(Reading) FROM #meter AS A
JOIN #Transactions AS B ON A.UnitID=B.UnitID AND A.UnitID=T.UnitID
)
ELSE M.reading END) AS Reading
FROM #meter AS M
RIGHT OUTER JOIN #Transactions AS T ON T.UnitID=M.UnitID
AND T.Transactions_date=M.reading_Date
I can think of two ways to approach this - neither of them are ideal.
The first (and slightly better) way would be to create a SQL Function that took the Transactions_date as a parameter and returned the reading for Max(Reading_date) where reading_date <= transactions_date. You could then use this function in a select statement against the Transactions table.
The other approach would be to use a cursor to iterate through the transactions table and use the same logic as above where you return the reading for Max(Reading_date) where reading_date <= transactions_date.
Try the below query:
Please find the result of the same in SQLFiddle
select a.Transactions_ID, a.UnitID, a.Transactions_date,
case when b.reading IS NULL then c.rd else b.reading end as reading
from
Transactions a
left outer join
meter b
on a.UnitID = b.UnitID
and a.Transactions_date = b.reading_Date
inner join
(
select UnitID,max(reading) as rd
from meter
group by UnitID
) as C
on a.UnitID = c.UnitID