SQL-Get highest record from group at another table - sql

I have read several answers to related questions but none of them can be applied to this case.
I have a table TableA where several groups are listed, with their score:
GROUP|SCORE
Blue | 0
Green| 0
Red | 0
Orange| 0
On another table TableB, I have the parts of each group and their individual score (status), which can have three different values:
- G (Good)
- A (Average)
- B (Bad)
So tableB is:
GROUP|PART|STATUS
Blue | 3H2| A
Blue | 4NQ| G
Blue | W9X| A
Green| 65D| G
Red | 73F| B
Red | 91G| A
I need to Update the score on TableA in the following way:
If the best status between the parts of the group is G, group score is 3
If the best status between the parts of the group is A, group score is 2
If the best status between the parts of the group is B, group score is 1
I have been a couple of days going around this and I can't find a solution. Thank you guys. Btw, I am using Access 2013.

As I have already mentioned in the comments: Don't store the score redundantly; it is implicit in tableB. And to get your database straight introduce a status table:
STATUS DESCRIPTION SCORE
G Good 3
A Avarage 2
B Bad 1
If you want to select the score for each color group use this query for instance:
select b.colorgroup, max(s.score) as maxscore
from tableb as b
join status as s on s.status = b.status
group by b.colorgroup;
Two alternative ways to write the same query:
select
colorgroup,
(
select max(score)
from status as s
where s.status = b.status
) as maxscore
from tableb as b;
and
select b.colorgroup, s.maxscore
from tableb as b
join
(
select status, max(score) as maxscore
from status
group by status
) as s on s.status = b.status;
(BTW: I called your group colorgroup because GROUP is a reserved name in SQL.)
UPDATE You say you cannot add a table to the database. So you must evaluate the score in the query itself unfortunately. In standard SQL you would use CASE WHEN, which MS Access doesn't feature. MS Access provides IIF instead:
select
colorgroup,
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb
group by colorgroup;
If you even must use the column in tableA and store redundantly, use:
update tablea as a
set score =
(
select
max(iif(status = 'G', 3, iif(status = 'A', 2, 1))) as maxscore
from tableb as b
where b.colorgroup = a.colorgroup
);

In SQL-Server you could do in following:
QUERY
update a
set a.SCORE = MaxSTATUS
from #a a
join (select GROUP_, MAX(case when b.STATUS_ = 'G' then 3
when b.STATUS_ = 'A' then 2
when b.STATUS_ = 'B' then 1
end) MaxSTATUS
from #b b
group by GROUP_
) b ON a.GROUP_ = b.GROUP_
select * from #a
SAMPLE DATA
CREATE TABLE #a
(
GROUP_ NVARCHAR(60),
SCORE INT
)
INSERT INTO #a VALUES
('Blue',0)
,('Green',0)
,('Red',0)
,('Orange',0)
CREATE TABLE #b
(
GROUP_ NVARCHAR(60),
PART NVARCHAR(60),
STATUS_ NVARCHAR(60),
)
INSERT INTO #b VALUES
('Blue','3H2','A')
,('Blue','4NQ','G')
,('Blue','W9X','A')
,('Green','65D','G')
,('Red','73F','B')
,('Red','91G','A')
OUPUT
GROUP_ SCORE
Blue 3
Green 3
Red 2
Orange 0

Related

Get the sum of details table

I need to retrieve a master record together the sum of its details. Here are the definitions of two tables:
Master table (Master)
ID
Name
Example record:
10 Product
Details table (Details)
master_id
type (values can only be red or blue)
quantity
Example records:
10 red 2
10 red 5
10 red 6
10 blue 7
10 blue 9
I need to have such results:
10 Product 13 (sum for red) 16 (sum for blue)
The database is SQL Server 2017. How can I write a SINGLE query without using a stored procedure?
---UPDATE---
Based on the input from Venkataraman R, here is the solution:
SELECT m.id, m.name ,
SUM(CASE when type='red' then quantity end) as redsum,
SUM(CASE when type='blue' then quantity end) as bluesum
from mydetails as t
inner join mymaster as m
on m.id = t.master_id
GROUP BY m.id, m.name
You can use CONCAT function with group by to get results as single statement.
DECLARE #table table(master_id int, typev varchar(10), quantity int)
DECLARE #master table(id int, name varchar(50))
insert into #master values(10, 'Product10')
insert into #table values
(10,'red', 2),
(10,'red', 5),
(10,'red', 6),
(10,'blue', 7),
(10,'blue', 9);
SELECT m.name ,
SUM(CASE when typev='red' then quantity end) as redsum,
SUM(CASE when typev='blue' then quantity end) as bluesum
from #table as t
inner join #master as m
on m.id = t.master_id
GROUP BY m.name
+-----------+--------+---------+
| name | redsum | bluesum |
+-----------+--------+---------+
| Product10 | 13 | 16 |
+-----------+--------+---------+
If I get you correctly then you are looking for pivot, in that case you can use case expressions as following
select
master_id,
sum(case when type = 'red' then quantity end) as sum_of_red,
sum(case when type = 'blue' then quantity end) as sum_of_blue
from yourTable
group by
master_id
If you simply wants total quantity by type then use the following
select
master_id,
type,
sum(quantity) as total_quantity
from yourTable
group by
master_id,
type
Just use a group by
Select master_id, type, sum(quantity)
From details group by master_id, type
And use pivot to show each product in one line
With data_cte as
(Select master_id, type, sum(quantity)
From details group by master_id, type)
Select * from data_cte pivot (
For type in ('red', 'black'))

sql multiple filter all conditions in a group

I have a table with 3 fields:
id order date
1 1 null
1 2 not null
1 3 null
2 1 null
2 2 null
2 3 null
2 4 not null
3 1 null
I need the "id" in which:
ALL the "order" in (1,2,3)
and
ALL the "date" is null (so it is id 2)
I've tried as follows:
where order in (1,2,3) and date is null
but it returns both id 2 and id 1 (I'm expecting id 2 only).
Thanks for helps.
ID 3 should be also included. It satisfies your condition.
SELECT distinct id
FROM tab1 aa
WHERE aa.order IN (1, 2, 3) AND aa.data IS NULL
AND NOT exists(SELECT 1
FROM tab1 bb
WHERE ((bb.order IN (1, 2, 3) AND bb.data IS NOT NULL)
OR
(bb.order NOT IN (1, 2, 3) AND bb.data IS NULL))
AND aa.id = bb.id);
If you don't want ID 2 cause it has order in ID 4, then relax the last condition in:
bb.order NOT IN (1, 2, 3)
without the check in the date.
If with
ALL the "order" in (1,2,3)
you mean that there should be an order for 1, an order for 2 and an order for 3, then you should add and exists in the query to check this, like
and exists (select 1 form tab1 cc where aa.id = cc.id and cc.order = 1 and cc.data is not null)
and so on.
You want a result per ID, so GROUP BY it. You are only interested in order 1, 2, and 3, so use a WHERE clause. You only want IDs with all three orders and no date set. You can check this after aggregation in the HAVING clause.
select id
from mytable
where "order" in (1,2,3)
group by id
having count(*) = 3 -- all three IDs
and count("date") = 0; -- no non-null date
Rextester demo: http://rextester.com/SEE91944
(I surmise the table's unique key is id + order. Otherwise you'd have to COUNT(DISTINCT "order") and maybe to check null dates differently. As both order and date are SQL words, I am using quotes on them. You should avoid such names.)
I'm not sure what database are you using so I tried to right a query that should work with most. Have a look:
select * from
(select
id,
SUM(case
when [order] = 1 and [date] is null then 1
when [order] = 2 and [date] is null then 1
when [order] = 3 and [date] is null then 1
else 0
end) score
from test
group by id) scores
where score = 3
http://sqlfiddle.com/#!18/e4334/9
Sorry but none of above solved my question.
I solved it using "where not exists" (excluding the unwanted)
thanks to you all for your efforts.
If I've correctly interpreted your question, the following SQL does the work.
It returns id values of records for which all order who have 1, 2 or 3 value have null value for the date field:
SELECT DISTINCT id
FROM t
WHERE
order IN (1, 2, 3) AND
date IS NULL AND
id NOT IN (
SELECT id
FROM t
WHERE
order IN (1, 2, 3) AND
date IS NOT NULL
)

SQL Select with Priority

I need to select top 1 most valid discount for a given FriendId.
I have the following tables:
DiscountTable - describes different discount types
DiscountId, Percent, Type, Rank
1 , 20 , Friend, 2
2 , 10 , Overwrite, 1
Then I have another two tables (both list FriendIds)
Friends
101
102
103
Overwrites
101
105
I have to select top 1 most valid discount for a given FriendId. So for the above data this would be sample output
Id = 101 => gets "Overwrite" discount (higher rank)
Id = 102 => gets "Friend" discount (only in friends table)
Id = 103 => gets "Friend" discount (only in friends table)
Id = 105 => gets "Overwrite" discount
Id = 106 => gets NO discount as it does not exist in neither Friend and overwrite tables
INPUT => SINGLE friendId (int).
OUTPUT => Single DISCOUNT Record (DiscountId, Percent, Type)
Overwrites and Friend tables are the same. They only hold list of Ids (single column)
Having multiple tables of identical structure is usually bad practice, a single table with ID and Type would suffice, you could then use it in a JOIN to your DiscountTable:
;WITH cte AS (SELECT ID,[Type] = 'Friend'
FROM Friends
UNION ALL
SELECT ID,[Type] = 'Overwrite'
FROM Overwrites
)
SELECT TOP 1 a.[Type]
FROM cte a
JOIN DiscountTable DT
ON a.[Type] = DT.[Type]
WHERE ID = '105'
ORDER BY [Rank]
Note, non-existent ID values will not return.
This will get you all the FriendIds and the associate discount of the highest rank. It's an older hack that doesn't require using top or row numbering.
select
elig.FriendId,
min(Rank * 10000 + DiscountId) % 10000 as DiscountId
min(Rank * 10000 + Percent) % 10000 as Percent,
from
DiscountTable as dt
inner join (
select FriendId, 'Friend' as Type from Friends union all
select FriendId, 'Overwrite' from Overwrites
) as elig /* for eligible? */
on elig.Type = dt.Type
group by
elig.FriendId
create table discounts (id int, percent1 int, type1 varchar(12), rank1 int)
insert into discounts
values (1 , 20 , 'Friend', 2),
(2 , 10 , 'Overwrite', 1)
create table friends (friendid int)
insert into friends values (101),(102), (103)
create table overwrites (overwriteid int)
insert into overwrites values (101),(105)
select ids, isnull(percent1,0) as discount from (
select case when friendid IS null and overwriteid is null then 'no discount'
when friendid is null and overwriteid is not null then 'overwrite'
when friendid is not null and overwriteid is null then 'friend'
when friendid is not null and overwriteid is not null then (select top 1 TYPE1 from discounts order by rank1 desc)
else '' end category
,ids
from tcase left outer join friends
on tcase.ids = friends.friendid
left join overwrites
on tcase.ids = overwrites.overwriteid
) category1 left join discounts
on category1.category=discounts.type1

Select Condition Based on Multiple rows

I have a Table Like this:
TableA
----------------------------
ID - Name - PatID
1 A 10
2 B 10
3 A 11
4 A 12
5 B 13
I want to select All Such PatID Which has Name=A and Name = B.
So i should only get 10 as result.
What should be query for this?
You should be able to use the following query to get the result:
select patid
from tablea
where name in ('A', 'B')
group by patid
having count(distinct name) = 2;
See SQL Fiddle with Demo
If you need information from the A group and the B group you could also do it like this:
SELECT AGroup.ID AS AId, BGroup.ID AS BId
FROM TableA AGroup
JOIN TableB BGroup
ON AGroup.Name = 'A'
AND BGroup.Name = 'B'
AND AGroup.PatID = BGroup.PatID
This also retains duplicates if you have more than one entry, for Name = A and PatID = 10 for example.

How to do this data transformation

This is my input data
GroupId Serial Action
1 1 Start
1 2 Run
1 3 Jump
1 8 End
2 9 Shop
2 10 Start
2 11 Run
For each activitysequence in a group I want to Find pairs of Actions where Action1.SerialNo = Action2.SerialNo + k and how may times it happens
Suppose k = 1, then output will be
FirstAction NextAction Frequency
Start Run 2
Run Jump 1
Shop Start 1
How can I do this in SQL, fast enough given the input table contains millions of entries.
tful, This should produce the result you want, but I don't know if it will be as fast as you 'd like. It's worth a try.
create table Actions(
GroupId int,
Serial int,
"Action" varchar(20) not null,
primary key (GroupId, Serial)
);
insert into Actions values
(1,1,'Start'), (1,2,'Run'), (1,3,'Jump'),
(1,8,'End'), (2,9,'Shop'), (2,10,'Start'),
(2,11,'Run');
go
declare #k int = 1;
with ActionsDoubled(Serial,Tag,"Action") as (
select
Serial, 'a', "Action"
from Actions as A
union all
select
Serial-#k, 'b', "Action"
from Actions
as B
), Pivoted(Serial,a,b) as (
select Serial,a,b
from ActionsDoubled
pivot (
max("Action") for Tag in ([a],[b])
) as P
)
select
a, b, count(*) as ct
from Pivoted
where a is not NULL and b is not NULL
group by a,b
order by a,b;
go
drop table Actions;
If you will be doing the same computation for various #k values on stable data, this may work better in the long run:
declare #k int = 1;
select
Serial, 'a' as Tag, "Action"
into ActionsDoubled
from Actions as A
union all
select
Serial-#k, 'b', "Action"
from Actions
as B;
go
create unique clustered index AD_S on ActionsDoubled(Serial,Tag);
create index AD_a on ActionsDoubled(Tag,Serial);
go
with Pivoted(Serial,a,b) as (
select Serial,a,b
from ActionsDoubled
pivot (
max("Action") for Tag in ([a],[b])
) as P
)
select
a, b, count(*) as ct
from Pivoted
where a is not NULL and b is not NULL
group by a,b
order by a,b;
go
drop table ActionsDoubled;
SELECT a1.Action AS FirstActio, a2.Action AS NextAction, COUNT(*) AS Frequency
FROM Activities a1 JOIN Activities a2
ON (a1.GroupId = a2.GroupId AND a1.Serial = a2.Serial + #k)
GROUP BY a1.Action, a2.Action;
The problem is this: Your query has to go through EVERY row regardless.
You can make it more manageable for your database by tackling each group separately as separate queries. Especially if the size of each group is SMALL.
There's a lot going on under the hood and when the query has to do a scan of the entire table, this actually ends up being many times slower than if you did small chunks which effectively cover all million rows.
So for instance:
--Stickler for clean formatting...
SELECT
a1.Action AS FirstAction,
a2.Action AS NextAction,
COUNT(*) AS Frequency
FROM
Activities a1 JOIN Activities a2
ON (a1.groupid = a2.groupid
AND a1.Serial = a2.Serial + #k)
WHERE
a1.groupid = 1
GROUP BY
a1.Action,
a2.Action;
By the way, you have an index (GroupId, Serial) on the table, right?