Solving Logical Questions Using SQL - sql

I am trying to solve a problem for a fun work exercise showing that SQL can be used to solve it. It is a puzzle that goes as follows:
Successfully navigating the waters during sea voyages is a challenging task. A captain’s most important decision is selecting the right crew for the voyage. A mix of different skill sets are required to sail the ship efficiently, navigate to the destination, and fish for food along the way.
Table 1 shows a list of crew members that are available for you to hire for the voyage. Each crew member demands a salary for the voyage and has different skill levels of Fishing, Sailing, and Navigation.
In order for your journey to be successful, you must have a cumulative skill of 15 or more in each of the three skill categories from all of your chosen crew members. You may choose as many crew members as you like.
Question: What is the minimum achievable cost for the voyage?"
I would say I am what I would consider an intermediate to advanced (depending on the situation) SQL user.
Not asking for an answer per-say but I have thought about the best way to solve and I was first thinking using a WHILE loop in some way. I have create a table to hold the data and added a 'salary_ranking' column (below). I am curious if anyone has any tips or suggestions on routes to go? I would like to use something I have never used before but also am trying to get to the most efficient answer.
Here is the data (I added the last column):
NAME FISHING SAILING NAVIGATION SALARY SALARY_RANK
---------- ----------- ----------- ----------- ----------- -----------
Amy 3 5 1 46000 3
Bill 1 2 5 43000 2
Carl 3 4 2 47000 4
Dan 4 3 1 36000 1
Eva 4 2 2 43000 2
Fred 1 3 4 55000 5
Greg 3 1 5 68000 8
Henry 5 4 2 64000 7
Ida 3 3 3 60000 6
(9 rows affected)

This is a CTE version, where I first create test data, then run a recursive query, using a MaxID to prevent it doing all the permutations.
declare #t table(Id int, NAME varchar(10), FISHING int, SAILING int, NAVIGATION int, SALARY int)
insert #t values (1,'Amy',3,5,1,46000)
,(2,'Bill',1,2,5,43000 )
,(3,'Carl',3,4,2,47000)
,(4,'Dan',4,3,1,36000)
,(5,'Eva',4,2,2,43000)
,(6,'Fred',1,3,4,55000)
,(7,'Greg',3,1,5,68000)
,(8,'Henry',5,4,2,64000)
,(9,'Ida',3,3,3,60000 )
;with cte as (
select convert(varchar(1000),name) as crew, fishing, sailing, navigation, salary, ID as MaxID from #t
union all
select convert(varchar(1000),cte.crew+', '+ t.name), cte.fishing+t.fishing, cte.sailing+t.sailing, cte.navigation+t.navigation, cte.salary+t.salary, t.ID
from #t t
join cte on t.ID>cte.MaxID
)
select top 1 crew,fishing,sailing,navigation,salary
from cte
where fishing>=15 and sailing>=15 and navigation>=15
order by salary
result is:
crew fishing sailing navigation salary
Amy, Bill, Carl, Greg, Henry 15 16 15 268000

Related

Self JOIN to find the parent detail which matches with the row data -

I am trying to query in MS SQL and I can not resolve it. I have a table employees:
Id Name Surname FatherName MotherName WifeName Pincode isChild
-- ------- ------- ---------- ---------- -------- ------- -------
1 John Green James Sue null 101011 1
2 Michael Sloan Barry Lilly null 101011 1
3 Sally Green Andrew Molly Jemi 101011 1
4 Barry Sloan Soul Paul Lilly 101011 0
5 James Green Ned White Sue 101011 0
I want a query that selects rows where the father name and mother name of child matches with name and wife name. For the example table, where I want to return the result of rows where father and mother name matches the name and wife name column. For eg. id=1, where John's father name James and mother name Sue matches with id 5 which returns James as first name and Sue as wife name. So my query should return (this is my expected result)
Id Name Surname FatherName MotherName WifeName Pincode isChild
-- ------- ------- ---------- ---------- -------- ------- -------
5 James Green Ned White Sue 101011 0
4 Barry Sloan Soul Paul Lilly 101011 0
I tried with the below query but it checks for James only. How to change my query so that it checks all the names and returns the expected result.
select * FROM employees
where first_name like '%James%'
and wife_name like '%Sue%'
and pincode=101011;
Any tips on this will be really helpful. I am new to joins, need help on writing self join to get the result.
…
select *
from thetable as p -- the parent/father
where exists -- with one child at least
(
select *
from thetable as c
where c.fathername = p.name
and c.mothername = p.wifename
-- lastname?
)
Too long for a comment, but also not intended as a slam against what you are working with. Please take as constructive criticism.
Aside from VERY POOR DESIGN on the table content, getting that corrected before you get too deep into whatever you are working should be done first. A more typical design might be having a table of people. Now, to get the relationships you could do a couple ways. One is that on each individual person's record, you add 2 additional IDs. FatherID, MotherID. These IDs would join directly back to the child vs hard strings to match against. Take a surname like Smith or Jones. Then, look at the many instances of a "John Smith" may exist, yes a lot, and lower probability of finding a matching wife's name of Sue, Mary or whatever else name. But even that could lead to multiple possibilities. Yes, you are adding a PIN, but even a computer can generate a random pin of 1234.
By having the IDs, there is NO ambiguity of who the relationship is with.
If the data were slightly altered to something like
Id Name Surname FatherID MotherID SpouseID
-- ------- ------- ---------- ---------- --------
1 John Green 5 6 null
2 Michael Sloan 4 3 null
3 Lilly Sloan null null 4
4 Barry Sloan null null 3
5 James Green 9 10 6
6 Sue Green 7 8 5
7 Bill Jones null null 8
8 Martha Jones null null 7
9 Brian Green null null 10
10 Beth Smith-Green null null 9
So, in this modified example, you can see right away that ID#1 John Green has parents of Father (ID#5) is James and Mother (ID#6) is Sue. But even from this, James is a child to Father (ID#9) Brian and Mother (ID#10) Beth. This scenario is showing to a grand-parent level capacity and that each of James and Sue are also children but to their respective parents. Sue's parents of the Jones surname.
For Michael Sloan, parents of #4 Barry, and #3 Lilly.
And I additionally added a spouse ID. This prevents redundancy of people's names copied all over. Then you can query based on the child's parent's respective IDs to find out vs a hopeful name LIKE guess.
So, even though not solving a relatively simple query, fixing the underlying foundation of your database and is relations will, long-term, help ease your querying in the future.
Try this:
SELECT
T2.*
FROM Employee T1
JOIN Employee T2 ON T2.Name = T1.FatherName
AND T2.WifeName = T1.MotherName

Distributing Records Evenly From One Table to Another

I have 3 tables:
Users
-----
UserID (varchar)
Active (bit)
Refunds_Upload
--------------
BorrowerNumber (varchar)
Refunds
-------
BorrowerNumber
UserID
I first select all of the UserID values where Active = 1.
I need to insert the records from Refunds_Upload to Refunds but I need to insert the same (or as close as possible) number of records for each Active UserID.
For example, if Refunds_Upload has 20 records and the Users table has 5 people where Active = 1, then I would need to insert 4 records per UserID into table Refunds.
End Result would be:
BorrowerNumber UserID
105 Fred
110 Fred
111 Fred
115 Fred
120 Billy
122 Billy
123 Billy
125 Billy
130 Lucius
131 Lucius
133 Lucius
135 Lucius
138 Lucy
139 Lucy
140 Lucy
141 Lucy
142 Grady
143 Grady
144 Grady
145 Grady
Of course, it won't always come to an even number of records per User so I need to account for that as well.
First run this and check it returns something like what you want to insert, before you uncomment the insert and actually carry it out..
--INSERT INTO Refunds
SELECT
numbered_u.UserID,
numbered_ru.BorrowerNumber
FROM
(SELECT u.*, ROW_NUMBER() OVER(ORDER BY UserID) - 1 as rown, SUM(CAST(Active as INT)) OVER() as count_users FROM Users u WHERE active=1) numbered_u
INNER JOIN
(SELECT ru.*, ROW_NUMBER() OVER(ORDER BY BorrowerNumber) - 1 as rown, COUNT(*) OVER() as count_ru FROM Refund_Uploads ru) numbered_ru
ON
ROUND(CAST(numbered_ru.rown AS FLOAT) / (count_ru / count_users)) = numbered_u.rown
The logic:
We number every interesting (active=1) row in users and we also count them all. This should return us all 5 users, numbered 0 to 4 and with a ctr that is 5 on each row.
Then we join them to a similarly numbered list of Refund_Uploads (say 20). Similarly, those rows will be numbered 0 to 19 for mathematical reasons that become apparent later. We also count all these rows too
And we then join these two datasets together but the condition is a range of values rather than exact values. The logic is "refund_upload row number, divided by the_count_of_rows_there_should_be_per_user" (i.e. 0..19 / (20/5) ) = user_row_number. Hopefully thus refund rows 0 to 3, associate with user 0, refund rows 4 thru 7 associate with user 1.. etc
It's a little hard to debug without full data - I feel it might need a few +1 / -1 tweaks here and there.
I originally used FLOOR but switched to using ROUND, as I think this might work for distributing sets of numbers where there isn't a whole number of divisions in Refund/User e.g. your 240/13 example.. Hopefully some users will have 18 rows and some 19

Calculate average with assigned values SQL

Assume I have a table and it has the following columns:
assignment_id, employee.id, employee_salary and employee_performance
(assignment_id - int, employee.id - int, employee_salary - int, employee_performance - varchar).
The column employee_performance consists of the following values: excellent, good, average, bad, null.
I want to assign integers to the values and calculate average performance of an employee across different jobs. How can I do that?
For example, some employee has completed two assignments and his results are: excellent and good. I assign 10 to excellent and 9 to good and receive an average of 9.
assignment_id, employee.id, employee_salary employee_performance
1 1 100,000 excellent
2 1 100,000 excellent
3 1 100,000 good
4 4 50,000 good
5 3 75,000 null
Null means that an assignment is not yet completed.
I want to assign integers to employee_performance. For example, excellent - 10, good - 9, etc.
Result should:
eployee.id average_performance
1 9,7
3 9
epmployee.id = 4 is not included, as he does not have completed assignments.
You can use case. Something like this:
select employee_id,
avg(case when employee_performance = 'excellent' then 4.0
when employee_performance = 'good' then 3.0
when employee_performance = 'average' then 2.0
when employee_performance = 'bad' then 1.0
end) as avg_performance
from table
group by employee_id;
You can create a 2 column table that contains each performance (i.e. excellent, good, etc) and its corresponding value.
------------------------------
|performance | value|
------------------------------
|excellent | 10 |
------------------------------
|good | 9 |
------------------------------
Once you have that table you can do a simple join and group by to get the average value per employee.
select id, avg(pv.value)
from employees e
left join performance_values pv
on pv.performance = e.employee_performance
group by id

Recursive SQL Query, but not the usual kind of recursive

I have a set of tables that organize a group of people into teams.
Users (ID int PK, etc.)
Teams (ID int PK, etc.)
UsersToTeams (ID int PK, UserID int FK, TeamID int FK, TeamSupervisor bit not null)
There is no Parent ID in the table because users can be on any number of teams, and teams can have any number of supervisors. A user may be on six teams but only supervise two of them, and one or both of those supervised teams may have other supervisors in it. So my hierarchy looks more like a web than a tree.
I recognize that a recursive query may result in a circular reference. Assume the software is handling that for the moment.
The company hierarchy is described by a Supervisor supervising a team of Users, a Manager supervising a team of Supervisors, etc. So it's hierarchical, but not in the usual way.
I need a query which, given a UserID, will return the IDs of the users he supervises, down infinite levels. How might such a query go?
Example
Users (ID, Name)
1 Archie
2 Betty
3 Chuck
4 Dilton
5 Eddie
6 Fannie
User 1 is a Manager (level 3). Users 2 and 3 are Supervisors (level 2). Users 4, 5, 6 are Users (level 1).
Teams (ID, Name)
1 Team Alpha
2 Team Bravo
3 Sup Team
UsersToTeams (ID INT PK, UserID INT FK, TeamID INT FK, isSupervisor BIT)
1 1 3 1 -- Archie supervises Sup Team
2 2 3 0 -- Betty is a member of Sup Team
3 3 3 0 -- Chuck is a member of Sup Team
4 2 1 1 -- Betty supervises team Alpha
5 4 1 0 -- Dilton is a member of team Alpha
6 5 1 0 -- Eddie is a member of team Alpha
7 3 2 1 -- Chuck supervises Team Bravo
8 6 2 0 -- Fannie is a member of Team Bravo
Archie is a Manager, supervising a team of supervisors.
Betty is a Supervisor, supervising a team of users.
Chuck is a Supervisor, supervising a team of users.
Betty and Chuck are also on Archie's team, but do not supervise it.
Therefore:
If I pass in UserID 5 (Eddie), I should get back only 5, because Eddie doesn't supervise anyone.
If I pass in UserID 3 (Chuck), I should get back 3 and 6, because Fannie is on a team that Chuck supervises.
If I pass in UserID 1 (Archie), I should get back all UserIDs described here, because Betty and Chuck are on Archie's team, and everyone else is on either Betty's team or Chuck's team.
Sorry, I tried that SQL fiddle link, but after 15 minutes of "Building Schema" I lost hope for it.
You can do this with a recursive CTE.
First, select the user himself, and then recursively select all the user he's immediately supervising over:
declare #userID int = 1;
with u as (
select id from users where id = #userID
union all
select lacky.userID from u supervisor
join usersToTeams supervising on supervising.userID = supervisor.id and isSupervisor = 1
join usersToTeams lacky on lacky.teamID = supervising.teamID and lacky.isSupervisor = 0
)
select * from u
Here's the fiddle: http://www.sqlfiddle.com/#!3/525e1/3

Coalescing values in a column over a partition

I have chosen to ask this question via an example as I think it most clearly illustrates what I am trying to do.
Say I have the following table:
member number time
------ ----- -----
1 2 19:21
1 4 19:24
1 27 19:37
2 4 19:01
2 7 21:56
2 8 22:00
2 21 22:01
How can I obtain the following column?
member number new column
------ ----- ---------
1 2 2.4.27
1 4 2.4.27
1 27 2.4.27
2 4 4.7.8.21
2 7 4.7.8.21
2 8 4.7.8.21
2 21 4.7.8.21
EDIT(S):
I am using DB2 SQL.
There is not necessarily the same number of rows for each member.
The order is determined by time say.
depending on your version of db2, the LISTAGG() function may be available to you. i think it is included in any db2 version after 9.7.
example:
select
member,
number,
listagg(number,',') as new_column
from
tablename
group by
member
In Oracle this will do the job,
select a.member,a.number,b.newcol from table a,(select member,replace(wm_concat(number),',','.') newcol from test11 group by member)b where a.member=b.member;
I know it's bad form answering your own question but I have found this useful page:
https://www.ibm.com/developerworks/mydeveloperworks/blogs/SQLTips4DB2LUW/entry/aggregating_strings42?lang=en
Modifying the code there gives:
create table test (member int, number int, time_stamp time)`;
insert into test values
(1,2,'19:21'),
(1,4,'19:24'),
(1,27,'19:37'),
(2,4,'19:01'),
(2,7,'21:56'),
(2,8,'22:00'),
(2,21,'22:01');
select
member, substr(xmlcast(xmlgroup('.' || number as a order by time_stamp) as varchar(60)), 2)
from test
group by member