Update duplicate rows only with a MAX function in SQL - sql

I have a table like this, where, suppose for the sake of an example, NAME is a unique identifier.
NAME AGE VALUE
Jack Under 65 3
Jack 66-74 5
John 66-74 7
John Over 75 9
Gill 25-35 11
Some NAMEs have more than one AGE, which is undesirable, as this is due to dirtiness of the data.
My aim is to update the duplicates only to have one AGE within each NAME. The desired output is thus:
NAME AGE VALUE
Jack Under 65 3
Jack Under 65 5
John 66-74 7
John 66-74 9
Gill 25-35 11
Something like this UPDATE statement should work, but it doesn't.
UPDATE table t1
SET t1.age=MAX(t1.age)
WHERE EXISTS (SELECT COUNT(t2.AGE)
FROM table t2
WHERE t1.NAME=t2.NAME
GROUP BY t2.NAME
HAVING COUNT(t2.AGE) > 1)
SQL Error: ORA-00934: group function is not allowed here
Second issue
Even if I got the above statement to work, there is a second issue. The idea there is to use the MAX (or MIN) function on strings to set the same value for all repeats within a group.
But unfortunately, this too would not quite work as desired. For consistency, ideally an age would default to the lowest age group. But because MAX/MIN compare alphabetic order on strings, this would give, e.g.:
"66-74" and "Under 65" => MAX="Under 65" -- Lowest
"66-74" and "Over 75" => MAX="Over 75" -- Highest
There are only four age groups, would it be possible to specify a custom order?
NB1: I am using Oracle SQL.
NB2: I do not mind if there is a way to achieve the result using a SELECT instead of an UPDATE statement.
Reproducible example
SELECT 'Jack' as NAME, 'Under 65' as AGE, 3 as VALUE from dual
UNION ALL
SELECT 'Jack' as NAME, '66-74' as AGE, 5 as VALUE from dual
UNION ALL
SELECT 'John' as NAME, '66-74' as AGE, 7 as VALUE from dual
UNION ALL
SELECT 'John' as NAME, 'Over 75' as AGE, 9 as VALUE from dual
UNION ALL
SELECT 'Gill' as NAME, '25-35' as AGE, 11 as VALUE from dual

You can define custom order with case when clause and then use analytic max(). This worked for given examples:
update t1 set age = (
select max(age) keep (dense_rank last
order by case when age = 'Over 75' then 1
when age = '66-74' then 2
when age = 'Under 65' then 3
when age = '25-35' then 4
end)
from t1 tx where tx.name = t1.name )

Related

how to select SQL , age 20 first , and then all the rest from table PERSONS?

My table contains 113 people.
48 of them are 20 years old. Now I am just selecting all people like
select * from persons
this will get me all persons, but 20 yr old are not the first 48 people.
I need the 20 yr old to be first 48 in 113 results.
something like
20 year ols ( 48 of them ), after that ..... all the rest in the table
How can I query this using PostgreSQL.
EDIT : there are age less than 20 too. after getting the first 48 , 20 yr olds, I dont care rest of the order I am getting the 48 to 113 people.
Just use order by :
select *
from persons
order by age
You can use asc or desc but because default is asc you do not need to put it in your example.
select *
from persons
order by age desc
After the comment from OP here is the new code(I do not know why but my firs assumption was that the value 20 is the lowest possible value... bad assumption):
select *
from persons
order by case when age = 20 then 1 else 2 end
OR
select *
from persons
order by (age = 20) desc
Here is a demo
If 20 is not your minimum age, you can use the CASE statement inside the ORDER BY clause, like this:
SELECT
*
FROM
persons
ORDER BY
CASE WHEN age = 20 THEN 0
ELSE 1
END ASC

Oracle: union all query 1 and query 2 want to minus some rows if query 1 have rowdata

my query as below , i want to minus some rows from query1 when query2 have rowdata , but i don't know how to do:
my query:
with query1 as(
select wm_concat(linkman_name) name,
wm_concat(phone_num) phone,
t.org_id
from (
select linkman_name, phone_num, LINK_ORG_ID, org_id
from TD_SM_LINKMAN
where STATE = '2'
and (LINK_ORG_ID is null or LINK_ORG_ID = '')) t
group by t.org_id) ,
query2 as(
select wm_concat(linkman_name) name,
wm_concat(phone_num) phone,
org_id
from (select linkman_name, phone_num, LINK_ORG_ID, org_id
from TD_SM_LINKMAN
where STATE = '2'
and (LINK_ORG_ID = '55')) t
group by org_id)
select *
from query1
union all
select *
from query2 minus
-- this doesn't work ,i want to minus the rowdata from query 1 when query1.org_id = query2.org_id. the query2 is marked as outer query column.
(select * from query1 where query1.ORG_ID = query2.ORG_ID)
;
sample table
name phone link_org_id org_id
lily 133 1
ming 144 1
hao 333 2
jane 1234 55 2
bob 666 3
herry 555 3
query 1 result:
name phone org_id
lily,ming 133,144 1
hao 333 2
bob,herry 666,555 3
query 2 result:
name phone org_id
jane 1234 2
such like this , jane selected by query2 and hao selected by query 1 . All of them are from a same org which org_id =2 . but i don't need hao ,i just need jane. how to do?
i means if query2 can find result , then no need query1's result. but if query2 can't find any data, then i need query1's data.
The way it is now, you'll first have to split names (and phones) into rows, and then apply set operators (UNION, MINUS) to such a data.
Which means that you shouldn't use WM_CONCAT at all; at least, not at the beginning, because
first you concatenate data
then you'd have to split it back into rows
UNION / MINUS sets
Doing useless job in the first 2 steps.
I'd suggest you to UNION / MINUS data first, then aggregate them using WM_CONCAT. By the way, which database version do you use? WM_CONCAT is a) undocumented, b) doesn't even exist in latest Oracle database versions so you'd rather switch to LISTAGG, if possible.

SQLite query to get table based on values of another table

I am not sure what title has to be here to correctly reflect my question, I can only describe what I want.
There is a table with fields:
id, name, city
There are next rows:
1 John London
2 Mary Paris
3 John Paris
4 Samy London
I want to get a such result:
London Paris
Total 2 2
John 1 1
Mary 0 1
Samy 1 0
So, I need to take all unique values of name and find an appropriate quantity for unique values of another field (city)
Also I want to get a total quantity of each city
Simple way to do it is:
1)Get a list of unique names
SELECT DISTINCT name FROM table
2)Get a list of unique cities
SELECT DISTINCT city FROM table
3)Create a query for every name and city
SELECT COUNT(city) FROM table WHERE name = some_name AND city = some_city
4)Get total:
SELECT COUNT(city) FROM table WHERE name = some_name
(I did't test these queries, so maybe there are some errors here but it's only to show the idea)
As there are 3 names and 2 cities -> 3 * 2 = 6 queries to DB
But for a table with 100 cities and 100 names -> 100 * 100 = 10 000 queries to DB
and it may take a lot of time to do.
Also, names and cities may be changed, so, I can't create a query with predefined names or cities as every day it's new ones, so, instead of London and Paris it may be Moscow, Turin and Berlin. The same thing with names.
How to get such table with one-two queries to original table using sqlite?
(sqlite: I do it for android)
You can get the per-name results with conditional aggregation. As for the total, unfortunately SQLite does not support the with rollup clause, that would generate it automatically.
One workaround is union all and an additional column for ordering:
select name, london, paris
from (
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
) t
order by prio, name
Actually the subquery might not be necessary:
select name, sum(city = 'London') london, sum(city = 'Paris') paris, 1 prio
from mytable
group by name
union all
select 'Total', sum(city = 'London'), sum(city = 'Paris'), 0
from mytable
order by prio, name
#GMB gave me the idea of using group by, but as I do it for SQLite on Android, so, the answer looks like:
SELECT name,
COUNT(CASE WHEN city = :london THEN 1 END) as countLondon,
COUNT(CASE WHEN city = :paris THEN 1 END) as countParis
FROM table2 GROUP BY name
where :london and :paris are passed params, and countLondon and countParis are fields of the response class

How to select rows with condition? sql, select sentence

I have table like this:
NAME IDENTIFICATIONR SCORE
JOHN DB 10
JOHN IT NULL
KAL DB 9
HENRY KK 3
KAL DB 10
HENRY IP 9
ALI IG 10
ALI PA 9
And with select sentence I want that my result would be like only those names whose scores are 9 or above. So basically it means, that, for exaple, Henry cannot be selected, because he has score under the value of 9 in one line , but in the other he has the score of 3 (null values also should be emitted).
My newtable should look like this:
NAME
KAL
ALI
I'm using a sas program. THANK YOU!!
The COUNT of names will be <> COUNT of scores if there is a missing score. Requesting equality in the having clause will ensure no person with a missing score is in your result set.
proc sql;
create table want as
select distinct name from have
group by name
having count(name) = count(score) and min(score) >= 9;
here the solution
select name
from table name where score >= 9
and score <> NULL;
Select NAME from YOUR_TABLE_NAME name where SCORE > 9 and score is not null
You can do aggregation :
select name
from table t
group by name
having sum(case when (score < 9 or score is null) then 1 else 0 end) = 0;
If you want full rows then you can use not exists :
select t.*
from table t
where not exists (select 1
from table t1
where t1.name = t.name and (t1.score < 9 or t1.score is null)
);
You seem to be treated NULL scores as a value less than 9. You can also just use coalesce() with min():
select name
from have
group by name
having min(coalesce(score, 0)) >= 9;
Note that select distinct is almost never useful with group by -- and SAS proc sql probably does not optimize it well.

SQL Server: pivoting without aggregation on a table with two columns

This is a question on a test. I have a table with two columns. I want to pivot on one of them and output the other.
Table structure:
(Name varchar(10), Age int)
I need output with age values as columns and Names listed below each age value.
From searching, I only see examples where there is at least one other column that is used to "group by" for want of a better term. In other words, there is a common factor in each row of the output. My problem does not have this property.
I tried:
SELECT
[agevalue1], [agevalue2], [agevalue3], [agevalue4]
FROM
(SELECT Name, Age FROM MyClass) AS SourceTable
PIVOT
(MAX(Name)
FOR Age IN ([agevalue1], [agevalue2], [agevalue3], [agevalue4])
) AS PivotTable;
I specified agevalue* as a string, i.e. in quotes. I got the column headings alright but a row of NULLS below them.
P.S.: The solution does not need to use pivot but I couldn't think of an alternative approach.
Sample Data:
Name Age
Bob 11
Rick 25
Nina 30
Sam 11
Cora 16
Rachel 25
Desired output:
11 16 25 30
Bob Cora Rick Nina
Sam NULL Rachel NULL
Try this :
with tab as
(
Select 'A' Name, 10 Age union all
Select 'B',11 union all
Select 'c',10 union all
Select 'D',11 union all
Select 'E',11 union all
Select 'F',11
)
select distinct
Age
, stuff((
select ',' + g.Name
from tab g
where g.age = g1.age
order by g.age
for xml path('')
),1,1,'') as Names_With_Same_Age
from tab g1
group by g1.age,Name
To group these together in one row:
11 16 25 30
Bob Cora Rick Nina
and separate them from another set, like:
11 16 25 30
Sam NULL Rachel NULL
they must have something different between each row, since doing a MAX(Name) would get you only one Name for each Age.
This query creates a number that links a particular Age to a row number and then pivots the result. As you said, the PIVOT will group by all columns not referenced in the PIVOT function, so it will group by this row indexer, separating the values like you wanted.
;WITH IndexedClass AS
(
SELECT
M.Name,
M.Age,
-- The ordering will determine which person goes first for each Age
RowIndexer = ROW_NUMBER() OVER (PARTITION BY M.Age ORDER BY M.Name)
FROM
MyClass AS M
)
SELECT
P.[11],
P.[16],
P.[25],
P.[30]
FROM
IndexedClass AS I
PIVOT (
MAX(I.Name) FOR I.Age IN ([11], [16], [25], [30])
) AS P