Oracle - complex sql query - sql

In sql i got a scenario like to add a constant value to one of the condition data of query also the constant data that we going to add is coming from a sub query. The rough sql will be like this(As most of the sql datas are confidential I can't able to share the actual query).
SELECT * FROM test_table tt
INNER JOIN test_table_1 tt1
ON tt.id = tt1.id
WHERE TO_DATE(tt1.CONDITION_VALUE, 'yyyy-mm-dd') >=
TO_DATE('2011-08-25', 'yyyy-mm-dd') + (SELECT CONDITION_VALUE
FROM test_table tt
INNER JOIN test_table_1 tt1
ON tt.id = tt1.id
WHERE CONDITION_SEQUENCE='2') AND
CONDITION_SEQUENCE='1'.
Issue is subselect will return a set of data. So I getting the error ORA-01427: single-row subquery returns more than one row.
Also, what is the best way to address this issue.
Simply: The scenario is, I want to calulate one rows values, based on the value from another row, but of the same parent object.
As far my understanding, now the joins were behaving like this
obj1.data + [set of subquery data]
obj2.data + [set of subquery data]
....
objN.data + [set of subquery data]
Rather I want like
obj1.data + obj1.anotherData
obj2.data + obj2.anotherData
....
objN.data + objN.anotherData
Updated question
Input is purchase date from: 2011-08-19 to: 2011-08-25, that i'll get from UI
Table value:
id | CONDITION_SEQUENCE | CONDITION_VALUE |
------------------------|------------------
1 | 1 | 6 | --- purchase date
2 | 1 | 4 |
3 | 2 | 2011-08-25 | --- stay start date
4 | 1 | 2011-11-25 | --- stay end date
--------------------------------------------
But in the purchase date, I having a value 6, which I want to add or subtract from stay start date. Where all the condition_value belongs to one parent object, that i 'll determine by its parent table
Output: I want to calculate the purchase date as 2011-08-25(stay start date) + 6
(purchase date) = 2011-08-19

To accomplish what you seem to be trying to do you might try something like
SELECT *
FROM test_table tt
INNER JOIN test_table_1 tt1
ON tt.id = tt1.id
WHERE TO_DATE(tt1.CONDITION_VALUE, 'yyyy-mm-dd') >=
TO_DATE('2011-08-25', 'yyyy-mm-dd') + tt.CONDITION_VALUE AND
tt.CONDITION_SEQUENCE = '2' AND
tt1.CONDITION_SEQUENCE = '1'
Best of luck.

according to my understanding you can try like below,
SELECT tt.*,tt1.* FROM test_table tt
INNER JOIN test_table_1 tt1
ON tt.id = tt1.id
INNER JOIN
test_table tt2
on tt.id = tt2.id
and tt2.CONDITION_SEQUENCE='2'
WHERE TO_DATE(tt1.CONDITION_VALUE, 'yyyy-mm-dd') >=
(TO_DATE('2011-08-25', 'yyyy-mm-dd') + tt2.CONDITION_VALUE)
AND tt.CONDITION_SEQUENCE='1'

Related

JOIN when no data exists

Disclaimer: I don't have a lot of tech background and just learning SQL so apologies.
I have 2 table for acct information - one ref data (ACCT_RD) and one txn data (ACCT_TD).
ACCT_RD is like this
ACCT_ID
ACCT_NAME
1
abc
2
xyz
ACCT_TD is like this
ACCT_ID
DATE
VALUE
1
01-31-2020
4000.33
1
01-31-2021
2000.11
2
01-31-2020
5666.23
I want a query where I will pass the account id and date and it will return me data in format
ACCT_ID
NAME
DATE
VALUE
1
abc
01-31-2020
4000.33
1
abc
null
null
it could be that the ACCT_TD may not contain data (no rows) for all dates but ACCT_RD will always have the info.
I am trying a LEFT Join like
SELECT R.ACCT_ID, R.NAME, T.VALUE, T.DATE
FROM ACCT_RD R
LEFT JOIN ACCT_TD T ON R.ACCT_ID = T.ACCT_ID
WHERE R.ACCT_ID = 1
AND T.DATE IN ('01-31-2000','01-31-2020')
I am getting a row where I have data in both and no row where I don't have data in ACCT_TD.
Is it because in ACCT_TD no row exists for date '01-31-2000' and it is not a column for ACCT_RD?
How can I achieve what I am looking for?
It looks like you have the following requirements for the result set.
For any given ACCT_ID It must contain all the corresponding rows
(if any) from ACCT_TD
VALUE and DATE result set columns are equal to ACCT_TD.VALUE and ACCT_TD.DATE, if ACCT_TD.DATE is in a list of date parameters passed, and both NULL otherwise
If it's the correct understanding of requirements, then try this:
WITH
ACCT_RD (ACCT_ID, ACCT_NAME) AS
(
VALUES
(1, 'abc')
, (2, 'xyz')
)
, ACCT_TD (ACCT_ID, DATE, VALUE) AS
(
VALUES
(1, '01-31-2020', 4000.33)
, (1, '01-31-2021', 2000.11)
, (2, '01-31-2020', 5666.23)
)
SELECT
R.ACCT_ID, R.ACCT_NAME
, CASE WHEN T.DATE IN ('01-31-2000','01-31-2020') THEN T.DATE END AS DATE
, CASE WHEN T.DATE IN ('01-31-2000','01-31-2020') THEN T.VALUE END AS VALUE
FROM ACCT_RD R
LEFT JOIN ACCT_TD T ON R.ACCT_ID = T.ACCT_ID
WHERE R.ACCT_ID = 1;
The result is:
|ACCT_ID|ACCT_NAME|DATE |VALUE |
|-------|---------|----------|-------|
|1 |abc |01-31-2020|4000.33|
|1 |abc | | |
If you want to guarantee that one row is returned even when there is no match in the second table, just move the date filters to the on clause:
SELECT R.ACCT_ID, R.NAME, T.VALUE, T.DATE
FROM ACCT_RD R LEFT JOIN
ACCT_TD T
ON R.ACCT_ID = T.ACCT_ID AND
T.DATE IN ('01-31-2000', '01-31-2020')
WHERE R.ACCT_ID = 1;
The values will be NULL if there is no match.
Also, if your columns are really dates, then you should use proper date formats:
T.DATE IN ('2000-01-31', '2000-01-31')

SQL query for all possible combinations from table

I have a table as result of some calculations from SQL database and it looks like this:
[ID] [PAR1] [PAR2]
[A] [110] [0.5]
[B] [105] [1.5]
[C] [120] [2.0]
[D] [130] [3.0]
[E] [115] [5.5]
[F] [130] [6.5]
[G] [120] [7.0]
[H] [110] [7.5]
[I] [105] [8.0]
[J] [120] [9.0]
[K] [110] [9.5]
It's sorted by PAR2 - less means better result.
I need to find the best result of SUM PAR2 from 3 rows, where sum of PAR1 is minimum 350 (at least 350). For ex.:
combination of A+B+C give the the best result of sum PAR2 (0.5+1.5+2.0=4.0), but sum of PAR1: 110+105+120=335 <(350) - condition is not ok, can't use the result,
combination of A+B+D give the result of sum PAR2 (0.5+1.5+3.0=5.0), but sum of PAR1: 110+105+130=345 <(350)- condition is not ok, cant's use the result
combination of A+B+E give the result of sum PAR2 (0.5+1.5+5.5=7.5), but sum of PAR1: 110+105+115=330 <(350)- condition is not ok, cant's use the result
combination of A+B+F give the result of sum PAR2 (0.5+1.5+6.5=8.5), but sum of PAR1: 110+105+130=345 <(350)- condition is not ok, cant's use the result
(...)
combination of B+C+D give the result of sum PAR2 (1.5+2.0+3.0=6.5), and sum of PAR1: 105+120+130=355 >(350)- condition is ok!, so we have a winner with best result 6.5
It is an ASP.NET application, so I tried to get the table from database and use VB code behind to get the result, but this is a "manually" work using FOR..NEXT LOOP, takes a time. So it's not nice and good option for calculations like this and also too slow.
I am wondering if there is a better smooth and smart SQL code to get the result directly from SQL Query. Maybe some advanced math functions? Any ideas?
Thanks in advance.
I made some test using forpas solution, and yes, it works very good. But it takes to much time when i added a lot of WHERE conditions, because original table is very large. So I will try to find a solution for using temp tables in function (not procedures). Thank you all for your answers.
forpas, special thanks also for example and explanation, in this way you let me quikly understand your idea - this is master level ;)
You can use a double inner self-join like this:
select top 1 * from tablename t1
inner join tablename t2 on t2.id > t1.id
inner join tablename t3 on t3.id > t2.id
where t1.par1 + t2.par1 + t3.par1 >= 350
order by t1.par2 + t2.par2 + t3.par2
See the demo.
Results:
> ID | PAR1 | PAR2 | ID | PAR1 | PAR2 | ID | PAR1 | PAR2
> :- | ---: | :--- | :- | ---: | :--- | :- | ---: | :---
> A | 110 | 0.5 | C | 120 | 2.0 | D | 130 | 3.0
So the winner is A+C+D because:
110 + 120 + 130 = 360 >= 350
and the sum of PAR2 is
0.5 + 2.0 + 3.0 = 5.5
which is the minimum
Check this. I feel its accurate or close to your requiremnt-
WITH CTE (ID,PAR1,PAR2)
AS
(
SELECT 'A',110,0.5 UNION ALL
SELECT 'B',105,1.5 UNION ALL
SELECT 'C',120,2.0 UNION ALL
SELECT 'D',130,3.0 UNION ALL
SELECT 'E',115,5.5 UNION ALL
SELECT 'F',130,6.5 UNION ALL
SELECT 'G',120,7.0 UNION ALL
SELECT 'H',110,7.5 UNION ALL
SELECT 'I',105,8.0 UNION ALL
SELECT 'J',120,9.0 UNION ALL
SELECT 'K',110,9.5
)
SELECT B.AID,B.BID,B.CID,SUM_P2,SUM_P1
(
SELECT * , ROW_NUMBER() OVER (PARTITION BY CHAR_SUM ORDER BY CHAR_SUM) CS
FROM
(
SELECT ASCII(A.ID) + ASCII(B.ID)+ASCII(C.ID) CHAR_SUM,
A.ID AID,B.ID BID,C.ID CID,
(A.PAR2+B.PAR2+C.PAR2) AS SUM_P2,
(A.PAR1+B.PAR1+C.PAR1) AS SUM_P1
FROM CTE A
CROSS APPLY CTE B
CROSS APPLY CTE C
WHERE A.ID <> B.ID AND A.ID <> C.ID AND B.ID <> C.ID
AND (A.PAR1+B.PAR1+C.PAR1) >= 350
) A
)B
WHERE CS = 1
You might try to cross join the table with itself three times. This way you would have all the combination of three rows pivoted on a single row, thus making you able to apply the conditions required and picking the maximum value.
select t1.ID, t2.ID, t3.ID, t1.PAR2 + t2.PAR2 + t3.PAR2
from yourTable t1
cross join
yourTable t2
cross join
yourTable t3
where t1.ID < t2.ID and t2.ID < t3.ID and
t1.PAR1 + t2.PAR1 + t3.PAR1 >= 350
order by t1.PAR2 + t2.PAR2 + t3.PAR2 ASC
While this solution should technically work, cross joining tables is not ideal performance-wise, even more when doing it multiple times. If the size of the table is going to grow over time, and you have the option to apply the calculation at code level, I think it would be advisable to do so.
Edit
Changed the where clause including Serg's suggestion

merge adjacent repeated rows into one

I want to merge adjacent repeated rows into one ,
for example , I have a table demo with two columns ,
data | order
-------------
A | 1
A | 2
B | 3
B | 4
A | 5
I want the result to be :
A
B
A
How to achieve this by one select SQL query in oracle ?
please, try something like this
select *
from table t1
where not exists(select * from table t2 where t2.order = t1.order - 1 and t1.data = t2.data)
The answer suggested by Dmitry above is working in SQL, to make it work in oracle you need to do some modifications.
order is a reserved keyword you need to escape it as follows.
select
*
from
Table1 t1
where not exists(
select * from Table1 t2
where
t2."order" = t1."order" - 1
and
t1."data" = t2."data"
) order by "order"
Working Fiddle at http://sqlfiddle.com/#!4/cc816/3
You can group by a column
Take a look at http://docs.oracle.com/javadb/10.6.1.0/ref/rrefsqlj32654.html
Example from official oracle site:
SELECT AVG (flying_time), orig_airport
FROM Flights
GROUP BY orig_airport

Select min date after selected date

I have a schema that looks like this:
------------------------------------
ID | Time | Type | Description | obj
------------------------------------
And some data will be like
1 | 01/01/1900 01:01:01 AM | 1 | Start | O1
2 | 01/01/1900 01:01:02 AM | 1 | Start | O2
3 | 01/01/1900 01:01:03 AM | 2 | Stop | O1
4 | 01/01/1900 01:01:04 AM | 2 | Stop | O2
Notes:
The O1, O2 etc. is the ID of the object that the process operated on. It will be consistent between the start/stop times, but there will be multiple start/stop times for each object (the process will start and finish operating on a specific object multiple times, and my query will need to select records for each time the process processed that object)
The description says Start/Stop for the benefit of this question's clarity. In practice, it has all kinds of data that is parsed out.
So, what I need are the pairs of start times and stop times that are closest to each other. Stated another way: for every start time, I need the next closest stop time. So the result of a select statement for the sample data above (that only selected ids) would return:
(1, 3)
(2, 4)
What I've tried:
SELECT obj,
[Time] AS StartTime,
(SELECT MIN([TIME]) AS t
FROM TheTable
WHERE [Type] = 2
HAVING MIN([Time]) > StartTime) AS StopTime
FROM TheTable
WHERE [Type] = 1;
This obviously doesn't work as StartTime is unknown to the inner select.
Without the Having clause in the inner select, it runs but I get the same StopTime for all entries, as you would expect. Which is, of course, not what I need.
Is there any way that I can solve this?
SELECT t1.obj, t1.Time as Start, min(t2.Time) as Stop
FROM TheTable t1
LEFT OUTER JOIN TheTable t2
ON t1.obj = t2.obj and t2.Description = 'Stop' and t2.Time > t1.time
WHERE t1.Description = 'Start'
GROUP BY (t1.obj, t1.Time, t1.Description, t2.Description)
left outer join because there might be a start time and not yet a stop time
I am not sure why it doesn't work.
You can always use outer column reference in SubQ, you are missing a 'comma' and 'group by' BTW
SELECT obj,
[Time] AS StartTime,
(SELECT MIN([TIME]) AS t
FROM TheTable
WHERE [Type] = 2
and t1.obj = obj
HAVING t > StartTime) AS StopTime
FROM TheTable t1
WHERE [Type] = 1
group by obj,TIME;
EDIT:
I am not expert in SQL server and have no idea why the column alias is not working. This query works in other Dbs like Teradata which I was using. Anyhow, you can use table alias to workaround this.
SELECT obj,
[TIME] AS StartTime,
(SELECT MIN([TIME]) As [tt]
FROM TheTable
WHERE [Type] = 2
and t1.obj = obj
HAVING MIN([TIME]) > t1.TIME
) AS StopTime
FROM TheTable t1
WHERE [Type] = 1
group by obj,TIME;
SQLFiddle:
http://sqlfiddle.com/#!6/3a745/14
Now, it seems that column alias is not allowed in Having clause in SQL server:
SELECT obj,
min([tdate]) AS StartTime from thetable group by obj having starttime>5 ;
Invalid column name 'starttime'.: SELECT obj, min([tdate]) AS StartTime from thetable group by obj having starttime>5

Show data from table even if there is no data!! Oracle

I have a query which shows count of messages received based on dates.
For Eg:
1 | 1-May-2012
3 | 3-May-2012
4 | 6-May-2012
7 | 7-May-2012
9 | 9-May-2012
5 | 10-May-2012
1 | 12-May-2012
As you can see on some dates there are no messages received. What I want is it should show all the dates and if there are no messages received it should show 0 like this
1 | 1-May-2012
0 | 2-May-2012
3 | 3-May-2012
0 | 4-May-2012
0 | 5-May-2012
4 | 6-May-2012
7 | 7-May-2012
0 | 8-May-2012
9 | 9-May-2012
5 | 10-May-2012
0 | 11-May-2012
1 | 12-May-2012
How can I achieve this when there are no rows in the table?
First, it sounds like your application would benefit from a calendar table. A calendar table is a list of dates and information about the dates.
Second, you can do this without using temporary tables. Here is the approach:
with constants as (select min(thedate>) as firstdate from <table>)
dates as (select( <firstdate> + rownum - 1) as thedate
from (select rownum
from <table> cross join constants
where rownum < sysdate - <firstdate> + 1
) seq
)
select dates.thedate, count(t.date)
from dates left outer join
<table> t
on t.date = dates.thedate
group by dates.thedate
Here is the idea. The alias constants records the earliest date in your table. The alias dates then creates a sequence of dates. The inner subquery calculates a sequence of integers, using rownum, and then adds these to the first date. Note this assumes that you have on average at least one transaction per date. If not, you can use a bigger table.
The final part is the join that is used to bring back information about the dates. Note the use of count(t.date) instead of count(*). This counts the number of records in your table, which should be 0 for dates with no data.
You don't need a separate table for this, you can create what you need in the query. This works for May:
WITH month_may AS (
select to_date('2012-05-01', 'yyyy-mm-dd') + level - 1 AS the_date
from dual
connect by level < 31
)
SELECT *
FROM month_may mm
LEFT JOIN mytable t ON t.some_date = mm.the_date
The date range will depend on how exactly you want to do this and what your range is.
You could achieve this with a left outer join IF you had another table to join to that contains all possible dates.
One option might be to generate the dates in a temp table and join that to your query.
Something like this might do the trick.
CREATE TABLE #TempA (Col1 DateTime)
DECLARE #start DATETIME = convert(datetime, convert(nvarchar(10), getdate(), 121))
SELECT #start
DECLARE #counter INT = 0
WHILE #counter < 50
BEGIN
INSERT INTO #TempA (Col1) VALUES (#start)
SET #start = DATEADD(DAY, 1, #start)
SET #counter = #counter+1
END
That will create a TempTable to hold the dates... I've just generated 50 of them starting from today.
SELECT
a.Col1,
COUNT(b.MessageID)
FROM
TempA a
LEFT OUTER JOIN YOUR_MESSAGE_TABLE b
ON a.Col1 = b.DateColumn
GROUP BY
a.Col1
Then you can left join your message counts to that.