How can I avoid a sub-query? - sql

This is my table:
ID KEY VALUE
1 alpha 100
2 alpha 500
3 alpha 22
4 beta 60
5 beta 10
I'm trying to retrieve a list of all KEY-s with their latest values (where ID is in its maximum):
ID KEY VALUE
3 alpha 22
5 beta 10
In MySQL I'm using this query, which is not effective:
SELECT temp.* FROM
(SELECT * FROM t ORDER BY id DESC) AS temp
GROUP BY key
Is it possible to avoid a sub-query in this case?

Use an INNER JOIN to join with your max ID's.
SELECT t.*
FROM t
INNER JOIN (
SELECT ID = MAX(ID)
FROM t
GROUP BY
key
) tm ON tm.ID = t.ID
Assuming the ID column is indexed, this is likely as fast as its going to get.

here is the mysql documentation page that discusses this topic.
it presents three distinct options.
the only one that doesn't involve a sub query is:
SELECT t1.id, t1.k, t1.value
FROM t t1
LEFT JOIN t t2 ON t1.k = t2.k AND t1.id < t2.id
WHERE t2.k IS NULL;

There's page in the manual explaining how to do this

Related

How to use Lateral join in Oracle?

I am trying to use Lateral Join in Oracle(Oracle Database 11g Release 11.2.0.1.0) but it is giving some error. I have followed this link
https://oracle-base.com/articles/12c/lateral-inline-views-cross-apply-and-outer-apply-joins-12cr1#lateral-inline-views
and applied it on the same data but still it isn't working. Can someone figure out the issue?
SELECT department_name, employee_name
FROM departments d,
LATERAL(SELECT employee_name FROM employees e WHERE e.department_id = d.department_id)
ORDER BY 1, 2;
Adding further details for clarify why I need a lateral join:
I have a table e.g
ID Length
1 20
2 50
3 30
4 40
5 20
6 80
and I want to add another column of the sum of the length of records that have ID less than current row's ID i.e
ID Length Sum
1 20 NULL
2 50 20
3 30 70
4 40 100
5 20 140
6 80 160
With the Lateral JOIN it could have be very simple for example
select A.ID,A.length,Sum from Table A,
Lateral (select sum(B.length) as Sum from Table B where B.id<A.id);
So is there is any alternative to this?
The LATERAL does not work because it is introduced from version 12.
As GMB say.
This is one aproach to the problem you have:
SELECT t1.id, t3.name
FROM test t1
left join (select id, name from test t2) t3
on t1.id = t3.id
order by 2, 1
Here is a DEMO
Or maybe you wanted something like this:
select
t1.id,
t1.name
from test t1
where t1.name in (select t2.name
from test t2
where t2.id = t1.id)
order by 1, 2;
If none of the above approaches does not help you (it is not what you wanted), then there is another way. You can "enable" LATERAl in your old Oracle 11 version like this:
alter session set events '22829 trace name context forever';
You see, this option/feature did existed in older versions but it was not "enabled".
Here is a DEMO showing that your statement on your example data first trhows an error and then after this alter session command, it works.
If you want a query that will give you a resul as in your question and will work on 11g then you can use this:
select ID, Length, LAG(ACUM) OVER (order by ID) sum
from (SELECT ID
, length
, Sum(length) OVER (ORDER BY id) as ACUM
FROM Table1
group by ID, length)
And the same thing can be done in a "more easy" way:
SELECT id,
length,
(SELECT Sum(length)
FROM Table1 b
WHERE a.id > b.id) ACUM
FROM Table1 a
Here is the demo where you can see this query returns the same ersults.
Hope this helps.
Your query is fine, however the LATERAL JOIN syntax was added in Oracle 12.1, so it is not available for 11.2 version, that you are running.
For your use case, you can use a regular join:
SELECT d.department_name, e.employee_name
FROM department d
INNER JOIN employee e ON e.department_id = d.department_id
ORDER BY 1, 2

Deleting equal number of records with positive and negative values in a table

I have a table having multiple negative and positive values, i want to delete only those number of records from table which are having negative values and have the same positive values . I'm not sure how to explain this scenario...
I will give a brief example-
I have a table with 6 records in which 2 records are with negative value and 4 record with positive
Name | number
A | 1
A |-1
A | 1
A |-1
A | 1
A | 1
So here i want to delete equal number of records of negative value and positive value
so my output should be
Name | Number
A | 1
A | 1
By using Row_number
;WITH CTE AS (
select *,ROW_NUMBER()OVER(PARTITION BY number ORDER BY (SELECT NULL)) -1 RN from Table1 )
Select Name, number from CTE WHERE RN NOT IN (1,0)
The following query assumes that your table has either a column called id which is either a primary key or some other means to order your records. Without any order, your question cannot be answered, and in fact the data sample you showed us would have no meaning, since internally records have no order in a SQL database.
WITH cte1 AS (
SELECT t1.id, t1.number, SUM(t2.number) as sum
FROM yourTable t1
INNER JOIN yourTable t2 on t1.id >= t2.id
GROUP BY t1.id, t1.number
)
WITH cte2 AS (
SELECT MAX(id) AS cutoff
FROM cte1
WHERE sum = 0
)
SELECT t.*
FROM yourTable t
WHERE t.id > (SELECT cutoff FROM cte2)
Note that I used the old school way of computing a running sum because you never told us the version of SQL Server which you are using. Hence, I didn't want to make assumptions about what you have available.
declare #negvalrecs int = (select COUNT(*) from tab where Number < 0)
delete
from tab
where Number < 0
delete top (#negvalrecs)
from tab
where Number > 0
Thanks for all your inputs!
I have a solution for it. We will be needing row number function for it.
--Providing row number to rows
select *,row_number () over (partition by name,number order by name) R into #1 from Table
--Taking negative values
select * into #2 from #1 where number<0
--Now Deleting those records from the main table by joining this table
delete #1 from #1 a inner join #2 b on a.name=b.name and a.number=b.number and a.r<=b.r
delete #1 from #1 a inner join #2 b on a.name=b.name and a.number=-(b.number) and a.r<=b.r
Hope it helps!
I recently encountered a similar problem and this is how I resolved it.
I also had records in table where there we no negatives for a given name the union all is to bring such records.
SELECT t1.name, t1.number
FROM table t1
LEFT OUTER JOIN
(SELECT name, number FROM table where number < 0) t2
ON
t1.name = t2.name and t1.number = t2.number
WHERE t1.number > 0 and t2.number IS NOT NULL
UNION ALL
SELECT t1.name, t1.number
FROM table t1
LEFT OUTER JOIN
(SELECT name, number FROM table where number < 0) t2
ON
t1.name = t2.name
WHERE t1.number > 0 and t2.number IS NULL;`
Try this,
delete from table_name
where substring(ltrim(rtrim(number)),1,1)='-'

SQL - get max result

Assume there is a table name "test" below:
name value
n1 1
n2 2
n3 3
Now, I want to get the name which has the max value, I have some solution below:
Solution 1:
SELECT TOP 1 name
FROM test
ORDER BY value DESC
solution 2:
SELECT name
FROM test
WHERE value = (SELECT MAX(value) FROM test);
Now, I hope use join operation to find the result, like
SELECT name
FROM test
INNER JOIN test ON...
Could someone please help and explain how it works?
If you are looking for JOIN then
SELECT T.name, T.value
FROM test T
INNER JOIN
( SELECT T1.name, T1.value ,
RANK() OVER (PARTITION BY T1.name ORDER BY T1.value) N
FROM test T1
WHERE T1.value IN (SELECT MAX(t2.value) FROM test T2)
)T3 ON T3.N = 1 AND T.name = T3.name
FIDDLE DEMO
or
select name, value
from
(
select name, value,
row_number() over(order by value desc) rn
from test
) src
where rn = 1
FIDDLE DEMO
First, note that solutions 1 and 2 could give different results when value is not unique. If in your test data there would be an additional record ('n4', 3), then solution 1 would return either 'n3' or 'n4', but solution 2 would return both.
A solution with JOIN will need aliases for the table, because as you started of, the engine would say Ambiguous column name 'name'.: it would not know whether to take name from the first or second occurrence of the test table.
Here is a way to complete the JOIN version:
SELECT t1.name
FROM test t1
LEFT JOIN test t2
ON t2.value > t1.value
WHERE t2.value IS NULL;
This query takes each of the records, and checks if any records exist that have a higher value. If not, the first record will be in the result. Note the use of LEFT: this denotes an outer join, so that records from t1 that have no match with t2 -- based on the ON condition -- are not immediately rejected (as would be the case with INNER): in fact, we want to reject all the other records, which is done with the WHERE clause.
A way to understand this mechanism, is to look at a variant of the query above, which lacks the WHERE clause and returns the values of both tables:
SELECT t1.value, t2.value
FROM test t1
LEFT JOIN test t2
ON t2.value > t1.value
On your test data this will return:
t1.value t2.value
1 2
1 3
2 3
3 (null)
Note that the last entry would not be there if the join where an INNER JOIN. But with the outer join, one can now look for the NULL values and actually get those records in the result that would be excluded from an INNER JOIN.
Note that this query will give the same result as solution 2 when there are duplicate values. If you want to have also only one result like with solution 1, it suffices to add TOP 1 after SELECT.
Here is a fiddle.
Alternative with pure INNER JOIN
If you really want an INNER join, then this will do it. Again the TOP 1 is only needed if you have non-unique values:
SELECT TOP 1 t1.name
FROM test t1
INNER JOIN (SELECT Max(value) AS value FROM test) t2
ON t2.value = t1.value;
But this one really is very similar to what you did in solution 2. Here is fiddle for it.

Comparing different rows in PostgreSQL for each Id

Few columns in my table looks like
Id Code date latest
1 T 2014-10-04 0
2 B 2014-10-19 0
2 B 2014-10-26 0
1 S 2014-10-05 0
1 T 2014-10-06 0
1 T 2014-10-08 1
2 P 2014-10-27 1
I am tracking all changes made by each ID. if there is any change, I insert new row and update the latest value column.
What I want is for each Id, I should be able to find last code where latest is 0. Also, that code should not be equal to existing code(latest = 1) So for id = 1, answer cannot be
Id Code
1 T
as for id = 1 T is existing code (latest = 1).
So ideally my output should look like:
Id Code
1 S
2 B
I think I can get the latest value for code for each id where latest = 0.
But how do I make sure that it should not be equal to existing code value (latest = 1)
Works in Postgres:
SELECT DISTINCT ON (t0.id)
t0.id, t0.code
FROM tbl t0
LEFT JOIN tbl t1 ON t1.code = t0.code
AND t1.id = t0.id
AND t1.latest = 1
WHERE t0.latest = 0
AND t1.code IS NULL
ORDER BY t0.id, t0.date DESC;
I use the combination of a LEFT JOIN / IS NULL to remove siblings of rows with latest = 1. There are various ways to do this:
Select rows which are not present in other table
Details for DISTINCT ON:
Select first row in each GROUP BY group?
Version with CTE and 2x LEFT JOIN
Since Redshift does not seem to support DISTINCT ON:
WITH cte AS (
SELECT t0.*
FROM tbl t0
LEFT JOIN tbl t1 ON t1.code = t0.code
AND t1.id = t0.id
AND t1.latest = 1
WHERE t0.latest = 0
AND t1.id IS NULL
)
SELECT c0.id, c0.code
FROM cte c0
LEFT JOIN cte c1 ON c1.id = c0.id
AND c1.date > c0.date
WHERE c1.id IS NULL
ORDER BY c0.id;
SQL Fiddle showing both.
I think the following does what you want:
select t.*
from (select distinct on (code) id, code
from table t
where latest = 0
order by code, date desc
) t
where not exists (select 1 from table t2 where t2.id = t.id and t2.code = t.code and t2.latest = 1);
I believe you should have a data for the current version and you should create another table where you would store previous revisions, having foreign key to the Id. Your Id does not fulfill the general expectations for a column with such a name. So, ideally, you would:
create a table Revisions(Id, myTableId, core, date, revision), where Id would be auto_increment primary key and myTableId would point to the Id of the records (1 and 2 in the example)
migrate the elements into revision: insert into Revisions(myTableId, core, date, revision) select Id, core, date latest from MyTable where latest = 0
update the migrated records: update Revisions r1 set r1.revision = (select count(*) from revisions r2 where r2.date < r1.date)
remove the old data from your new table: delete from MyTable where latest = 0
drop your latest column from MyTable
From here, you will be always able to select the penultimate version, or second to last and so on, without problems. Note, that my code suggestions might be of wrong syntax in postgreSQL, as I have never used it, but the idea should work there as well.

Self join for two same rows with one different column

Hi I have table with the following data
A B bid status
10 20 1 SUCCESS_1
10 20 1 SUCCESS_2
10 30 2 SUCCESS_1
10 30 2 SUCCESS_2
Now I want to print or count above rows based on SUCCESS_1 and SUCCESS_2. I created the following query but it does not work it just returns one row by combining two rows.
select * from tbl t1 join tbl t2 on
on (t1.A=t2.A and t1.B=t2.B and
(t1.Status = 'SUCCESS_1' and t2.Status = 'SUCCESS_2')
where t1.bid= 1
I want output as the following for the above query
A B bid status
10 20 1 SUCCESS_1
10 20 1 SUCCESS_2
I am new to SQL please guide. Thanks in advance.
If you need to do the join for some reason (e.g. your database does not let you select everything if you group by 1 column, because it wants everything projected to either be grouped or be an aggregate), you could do the following:
select t1.*
from tbl t1 join tbl t2
on (t1.A=t2.A and t1.B=t2.B and t1.Status = 'SUCCESS_1' and t2.Status = 'SUCCESS_2')
where t1.bid= 1
union all select t2.*
from tbl t1 join tbl t2
on (t1.A=t2.A and t1.B=t2.B and t1.Status = 'SUCCESS_1' and t2.Status = 'SUCCESS_2')
where t1.bid= 1
order by 1,2,3,4
Your original query is pulling back all the data in one row, but this one pulls back the two rows that make that resulting join row separately.
SELECT * FROM `tbl1` WHERE `bid`=1 GROUP BY `status`