1 distinct row having max value - sql

This is the data I have
I need Unique ID(1 row) with max(Price). So, the output would be:
I have tried the following
select * from table a
join (select b.id,max(b.price) from table b
group by b.id) c on c.id=a.id;
gives the Question as output, because there is no key. I did try the other where condition as well, which gives the original table as output.

You could try something like this in SQL Server:
Table
create table ex1 (
id int,
item char(1),
price int,
qty int,
usr char(2)
);
Data
insert into ex1 values
(1, 'a', 7, 1, 'ab'),
(1, 'a', 7, 2, 'ac'),
(2, 'b', 6, 1, 'ab'),
(2, 'b', 6, 1, 'av'),
(2, 'b', 5, 1, 'ab'),
(3, 'c', 5, 2, 'ab'),
(4, 'd', 4, 2, 'ac'),
(4, 'd', 3, 1, 'av');
Query
select a.* from ex1 a
join (
select id, max(price) as maxprice, min(usr) as minuser
from ex1
group by id
) c
on c.id = a.id
and a.price = c.maxprice
and a.usr = c.minuser
order by a.id, a.usr;
Result
id item price qty usr
1 a 7 1 ab
2 b 6 1 ab
3 c 5 2 ab
4 d 4 2 ac
Explanation
In your dataset, ID 1 has 2 records with the same price. You have to make a decision which one you want. So, in the above example, I am showing a single record for the user whose name is lowest alphabetically.
Alternate method
SQL Server has ranking function row_number over() that can be used as well:
select * from (
select row_number() over( partition by id order by id, price desc, usr) as sr, *
from ex1
) c where sr = 1;
The subquery says - give me all records from the table and give each row a serial number starting with 1 unique to each ID. The rows should be sorted by ID first, then price descending and then usr. The outer query picks out records with sr number 1.
Example here: https://rextester.com/KZCZ25396

Related

Replace hard coded values with data from table

Currently, I have 3 affiliations hard-coded in a query. They serve as a heirarchy: 1 = Faculty, 2 = Staff, 3 = Student. If a user from the affiliations_tbl table has more than one affiliation (example: a Staff member who is also a Student), it will use their Staff affiliation since it is higher on the heirarchy that is defined with the partition by and decode().
SELECT x2.emplid,
scc_afl_code
FROM (SELECT x.emplid,
scc_afl_code,
row_number() over(partition BY x.emplid ORDER BY x.affil_order) r
FROM (SELECT t.emplid,
scc_afl_code,
DECODE(scc_afl_code,
'FACULTY',
1,
'STAFF',
2,
'STUDENT',
3,
999) affil_order
FROM affiliations_tbl t
WHERE t.scc_afl_code IN
(SELECT a.scc_afl_code
FROM affiliation_groups_tbl a
WHERE a.group = 'COLLEGE')) x) x2
WHERE x2.r = 1;
I have created a table that will store affiliation groups affiliation_groups_tbl so I can scale this by adding data to the table, rather than changing the hard-coded values in this query. Example: Instead of adding 'CONSULTANT', 4 to the decode() list, I would add it to the table, so I wouldn't have to modify the SQL.
scc_afl_code | group | group_name | sort_order
-------------+---------+------------+-----------
FACULTY | COLLEGE | Faculty | 1
STAFF | COLLEGE | Staff | 2
STUDENT | COLLEGE | Student | 3
I've already updated the latter half of the query to only select scc_afl_code that are in the COLLEGE_GROUP group. How can I properly update the first part of the query to use the table as a hierarchy?
Try a piece of code below instead decode in the select clause of your statement:
coalesce((
select g.sort_order
from affiliation_groups_tbl g
where g.scc_afl_code = t.scc_afl_code ), 999)
You can try like that
create table dictionary
(id number,
code varchar2(32),
name varchar2(32),
sort number);
insert into dictionary (id, code, name, sort) values (16, 'B', 'B name', 1);
insert into dictionary (id, code, name, sort) values (23, 'A', 'A name', 2);
insert into dictionary (id, code, name, sort) values (15, 'C', 'C name', 4);
insert into dictionary (id, code, name, sort) values (22, 'D', 'D name', 3);
select partition,
string,
decode(string, 'B', 1, 'A', 2, 'D', 3, 'C', 4, 999) decode,
row_number() over(partition by partition order by decode(string, 'B', 1, 'A', 2, 'D', 3, 'C', 4, 999)) ordering
from (select mod(level, 3) partition, chr(65 + mod(level, 5)) string
from dual
connect by level <= 8)
minus
-- Alternate --
select partition,
string,
nvl(t.sort, 999) nvl,
row_number() over(partition by partition order by nvl(t.sort, 999)) ordering
from (select mod(level, 3) partition, chr(65 + mod(level, 5)) string
from dual
connect by level <= 8) r
left join dictionary t
on t.code = r.string;

Count days between dates in two rows based on condition

I'm writing query which has to select few infos. Below table:
ID ID-Toner Quantity Location Order_date Send_date
1 2 1 55 20.01.2015 26.01.2015
2 2 1 41 22.02.2015 26.02.2015
3 2 1 35 23.02.2015 26.02.2015
4 5 1 77 25.02.2015 25.02.2015
5 2 1 55 25.02.2015 26.02.2015
I need to select all columns and additional column with number of days between two dates: Order_date and previous Order_date for location = ie.: 55.
Sample result should look like:
ID ID-Toner Quantity Location Order_date Send_date Number_of_days
1 2 1 55 20.01.2015 26.01.2015 0
5 2 1 55 25.02.2015 26.02.2015 36
How to select such a query?
updated after clarifications in the PO
let'say that it needs to do a sort of aggregation on data called ranking, that is a type of classification based on numbering in succession order tbale's rows.
In our case the order is given by the Orders dates.
This is a quite cross-dbms solution (date fields are suppased to be Datetime type and DATEDIFF is a function of MySql) so I think that you can adapt to your dbms quite easily.
You can try the sql on Sql Fiddle at http://sqlfiddle.com/#!9/290e9
Table
CREATE TABLE Orders
(`ID` int, `IDToner` int, `Quantity` int, `Location` int, `Order_date` Date, `Send_date` Date)
;
INSERT INTO Orders
(`ID`, `IDToner`, `Quantity`, `Location`, `Order_date`, `Send_date`)
VALUES
(1, 2, 1, 55, STR_TO_DATE('20.01.2015','%d.%m.%Y'), STR_TO_DATE('26.01.2015','%d.%m.%Y')),
(2, 2, 1, 41, STR_TO_DATE('22.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(3, 2, 1, 35, STR_TO_DATE('23.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y')),
(4, 5, 1, 77, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('25.02.2015','%d.%m.%Y')),
(5, 5, 1, 77, STR_TO_DATE('25.04.2015','%d.%m.%Y'), STR_TO_DATE('25.04.2015','%d.%m.%Y')),
(6, 5, 1, 77, STR_TO_DATE('25.06.2015','%d.%m.%Y'), STR_TO_DATE('25.06.2015','%d.%m.%Y')),
(7, 5, 1, 77, STR_TO_DATE('25.08.2015','%d.%m.%Y'), STR_TO_DATE('25.08.2015','%d.%m.%Y')),
(8, 2, 1, 55, STR_TO_DATE('25.02.2015','%d.%m.%Y'), STR_TO_DATE('26.02.2015','%d.%m.%Y'))
;
Query
SELECT
ID,
ID_Toner,
Quantity,
Location,
Order_date,
Send_date,
days_from_previous_order
FROM(
SELECT
current_ID AS ID,
current_IDToner AS ID_Toner,
current_Quantity AS Quantity,
current_Location AS Location,
current_Send_Date AS Send_date,
current_Order_Date AS Order_date,
previous_Order_Date,
COALESCE(DATEDIFF(current_Order_Date, previous_Order_Date),0) AS days_from_previous_order
FROM(
SELECT
TabOrdersRanking_currents.ID AS current_ID,
TabOrdersRanking_currents.IDToner AS current_IDToner,
TabOrdersRanking_currents.Quantity AS current_Quantity,
TabOrdersRanking_currents.Location AS current_Location,
TabOrdersRanking_currents.Send_Date AS current_Send_Date,
TabOrdersRanking_currents.Order_Date AS current_Order_Date,
TabOrdersRanking_previous.Order_Date AS previous_Order_Date
FROM(
SELECT Orders.*, #rank1 := #rank1 + 1 rank
FROM Orders
,(Select #rank1 := 0) r1
order by location, order_date
) TabOrdersRanking_currents
LEFT JOIN(
SELECT Orders.*, #rank2 := #rank2 + 1 rank
FROM Orders
,(Select #rank2 := 0) r2
order by location, order_date
) TabOrdersRanking_previous
on TabOrdersRanking_currents.Location = TabOrdersRanking_previous.Location
and TabOrdersRanking_currents.rank - TabOrdersRanking_previous.rank = 1
) TabOrdersSuccessionRanking
) TabWithDaysFromPrevious;

Check duplicates in sql table and replace the duplicates ID in another table

I have a table with duplicate entries (I forgot to make NAME column unique)
So I now have this Duplicate entry table called 'table 1'
ID NAME
1 John F Smith
2 Sam G Davies
3 Tom W Mack
4 Bob W E Jone
5 Tom W Mack
IE ID 3 and 5 are duplicates
Table 2
ID NAMEID ORDERS
1 2 item4
2 1 item5
3 4 item6
4 3 item23
5 5 item34
NAMEID are ID from table 1. Table 2 ID 4 and 5 I want to have NAMEID of 3 (Tom W Mack's Orders) like so
Table 2 (correct version)
ID NAMEID ORDERS
1 2 item4
2 1 item5
3 4 item6
4 3 item23
5 3 item34
Is there an easy way to find and update the duplicates NAMEID in table 2 then remove the duplicates from table 1
In this case what you can do is.
You can find how many duplicate records you have.
In Order to find duplicate records you can use.
SELECT ID, NAME,COUNT(1) as CNT FROM TABLE1 GROUP BY ID, NAME
This is will give you the count and you find all the duplicate records
and delete them manually.
Don't forget to alter your table after removing all the duplicate records.
Here's how you can do it:
-- set up the environment
create table #t (ID int, NAME varchar(50))
insert #t values
(1, 'John F Smith'),
(2, 'Sam G Davies'),
(3, 'Tom W Mack'),
(4, 'Bob W E Jone'),
(5, 'Tom W Mack')
create table #t2 (ID int, NAMEID int, ORDERS varchar(10))
insert #t2 values
(1, 2, 'item4'),
(2, 1, 'item5'),
(3, 4, 'item6'),
(4, 3, 'item23'),
(5, 5, 'item34')
go
-- update the referencing table first
;with x as (
select id,
first_value(id) over(partition by name order by id) replace_with
from #t
),
y as (
select #t2.nameid, x.replace_with
FROM #t2
join x on #t2.nameid = x.id
where #t2.nameid <> x.replace_with
)
update y set nameid = replace_with
-- delete duplicates from referenced table
;with x as (
select *, row_number() over(partition by name order by id) rn
from #t
)
delete x where rn > 1
select * from #t
select * from #t2
Pls, test first for performance and validity.
Let's use the example data
INSERT INTO TableA
(`ID`, `NAME`)
VALUES
(1, 'NameA'),
(2, 'NameB'),
(3, 'NameA'),
(4, 'NameC'),
(5, 'NameB'),
(6, 'NameD')
and
INSERT INTO TableB
(`ID`, `NAMEID`, `ORDERS`)
VALUES
(1, 2, 'itemB1'),
(2, 1, 'itemA1'),
(3, 4, 'itemC1'),
(4, 3, 'itemA2'),
(5, 5, 'itemB2'),
(5, 6, 'itemD1')
(makes it a bit easier to spot the duplicates and check the result)
Let's start with a simple query to get the smallest ID for a given NAME
SELECT
NAME, min(ID)
FROM
tableA
GROUP BY
NAME
And the result is [NameA,1], [NameB,2], [NameC,4], [NameD,6]
Now if you use that as an uncorrelated subquery for a JOIN with the base table like
SELECT
keep.kid, dup.id
FROM
tableA as dup
JOIN
(
SELECT
NAME, min(ID) as kid
FROM
tableA
GROUP BY
NAME
) as keep
ON
keep.NAME=dup.NAME
AND keep.kid<dup.id
It finds all duplicates that have the same name as in the result of the subquery but a different id + it also gives you the id of the "original", i.e. the smallest id for that name.
For the example it's [1,3], [2,5]
Now you can use that in an UPDATE query like
UPDATE
TableB as b
JOIN
tableA as dup
JOIN
(
SELECT
NAME, min(ID) as kid
FROM
tableA
GROUP BY
NAME
) as keep
ON
keep.NAME=dup.NAME
AND keep.kid<dup.id
SET
b.NAMEID=keep.kid
WHERE
b.NAMEID=dup.id
And the result is
ID,NAMEID,ORDERS
1, 2, itemB1
2, 1, itemA1
3, 4, itemC1
4, 1, itemA2 <- now has NAMEID=1
5, 2, itemB2 <- now has NAMEID=2
5, 6, itemD1
To eleminate the duplicates from tableA you can use the first query again.

How to return one row from group by multiple columns

I am trying to extract a list of unique customers from a database where some customers are listed more than once. The (almost) duplicate rows exist because customers have been moved from one division to another or because the customers have been registered with another address (or both).
So my challenge is in data that looks something like this:
ID Customer Division Address
-----------------------------------
1 A M X
1 A L X
2 B N Y
2 B N Z
3 C P W
3 C T S
I want my select statement to return one row for each customer (I dont care which one).
ID Customer Division Address
-----------------------------------
1 A M X
2 B N Y
3 C P W
I am using SQL Server 2008. I think I need to do a "GROUP BY" the last two columns but I don't know how to get just one row out of it.
I hope someone can help me!
(Yes, I know the problem should be solved at the source but unfortunately that is not possible within any reasonable time-frame...).
select ID, Customer,Division, Address from
(
SELECT
ID, Customer,Division, Address,
ROW_NUMBER() OVER (PARTITON BY Customer Order by Id) as RN
FROM T
) t1
WHERE RN=1
Try this one -
DECLARE #temp TABLE
(
ID INT
, Customer CHAR(1)
, Division CHAR(1)
, [Address] CHAR(1)
)
INSERT INTO #temp (ID, Customer, Division, [Address])
VALUES
(1, 'A', 'M', 'X'),
(1, 'A', 'L', 'X'),
(2, 'B', 'N', 'Y'),
(2, 'B', 'N', 'Z'),
(3, 'C', 'P', 'W'),
(3, 'C', 'T', 'S')
SELECT t.id
, t.Customer
, t.Division
, t.[Address]
FROM
(
SELECT *
, rn = ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY 1/0)
FROM #temp
) t
WHERE T.rn = 1
SELECT ID, Customer, Division = MAX(Division), [Address] = MAX([Address])
FROM #temp
GROUP BY ID, Customer
Output -
id Customer Division Address
----------- -------- -------- -------
1 A M X
2 B N Y
3 C P W
ID Customer Division Address
----------- -------- -------- -------
1 A M X
2 B N Z
3 C T W

In MYSQL, how can I select multiple rows and have them returned in the order I specified?

I know I can select multiple rows like this:
select * FROM table WHERE id in (1, 2, 3, 10, 100);
And I get the results returned in order: 1, 2, 3, 10, 100
But, what if I need to have the results returned in a specific order? When I try this:
select * FROM table WHERE id in (2, 100, 3, 1, 10);
I still get the results returned in the same order: 1, 2, 3, 10, 100
Is there a way to get the results returned in the exact order that I ask for?
(There are limitations due to the way the site is set up that won't allow me to ORDER BY using another field value)
the way you worded that I'm not sure if using ORDER BY is completely impossible or just ordering by some other field... so at the risk of submitting a useless answer, this is how you'd typically order your results in such a situation.
SELECT *
FROM table
WHERE id in (2, 100, 3, 1, 10)
ORDER BY FIELD (id, 2, 100, 3, 1, 10)
Unless you are able to do ORDER BY, there is no guaranteed way.
The sort you are getting is due to the way MySQL executes the query: it combines all range scans over the ranges defined by the IN list into a single range scan.
Usually, you force the order using one of these ways:
Create a temporary table with the value and the sorter, fill it with your values and order by the sorter:
CREATE TABLE t_values (value INT NOT NULL PRIMARY KEY, sorter INT NOT NULL)
INSERT
INTO t_values
VALUES
(2, 1),
(100, 1),
(3, 1),
(1, 1),
(10, 1);
SELECT m.*
FROM t_values v
JOIN mytable m
ON m.id = v.value
ORDER BY
sorter
Do the same with an in-place rowset:
SELECT m.*
FROM (
SELECT 2 AS value, 1 AS sorter
UNION ALL
SELECT 100 AS value, 2 AS sorter
UNION ALL
SELECT 3 AS value, 3 AS sorter
UNION ALL
SELECT 1 AS value, 4 AS sorter
UNION ALL
SELECT 10 AS value, 5 AS sorter
)
JOIN mytable m
ON m.id = v.value
ORDER BY
sorter
Use CASE clause:
SELECT *
FROM mytable m
WHERE id IN (1, 2, 3, 10, 100)
ORDER BY
CASE id
WHEN 2 THEN 1
WHEN 100 THEN 2
WHEN 3 THEN 3
WHEN 1 THEN 4
WHEN 10 THEN 5
END
You can impose an order, but only based on the value(s) of one or more columns.
To get the rows back in the order you specify in the example you would need to add a second column, called a "sortkey" whose values can be used to sort the rows in the desired sequence,
using the ORDER BY clause. In your example:
Value Sortkey
----- -------
1 4
2 1
3 3
10 5
100 2
select value FROM table where ... order by sortkey;