SQL Query to return tier based values - sql

I have a database with a table for tier based pricing depending on the quantity bought example: (1-10) is $5, (11-15) is $10, 16 is $15, and 17-20 is $20
The table is structured in this way:
number int,
cost int
an example of the table:
number | cost
1 | 1
2 | 1
3 | 1
4 | 2
5 | 2
6 | 2
7 | 3
8 | 4
9 | 7
10 |7
Is there any way for me to write a query so that i can get these numbers returned in the format min, max, and cost for example running the query on the example above would return:
min|max|cost
-----|-----|----
1 | 10 | 5
11 |15 | 10
16 |16 | 15
17 |20 | 20
Also I am not sure if this is the best structure for such a table. Any and all help is appreciated. Thanks!

Try this. Its rather messy. Just tried it using my Server Management Studio.
Update: For better readability.
SELECT Mininum.Min, Maximum.Max, Mininum.Cost
FROM
(
SELECT MIN([Number]) as 'Min', Cost
FROM [TestDB].[dbo].[Testing]
GROUP BY Cost
) as [Mininum]
INNER JOIN
(
SELECT MAX([Number]) as 'Max', Cost
FROM [TestDB].[dbo].[Testing]
GROUP BY Cost
) as [Maximum]
ON Mininum.Cost = Maximum.Cost

Related

Take sum of a concatenated column SQL

I want to use post and pre revenue of an interaction to calculate net revenue. Sometimes there are multiple customers in an interaction. The data is like:
InteractionID | Customer ID | Pre | Post
--------------+-------------+--------+--------
1 | ab12 | 10 | 30
2 | cd12 | 40 | 15
3 | de12;gh12 | 15;30 | 20;10
Expected output is to take sum in pre and post call to calculate net
InteractionID | Customer ID | Pre | Post | Net
--------------+---------------+--------+-------+------
1 | ab12 | 10 | 30 | 20
2 | cd12 | 40 | 15 | -25
3 | de12;gh12 | 45 | 30 | -15
How do I get the net revenue column?
The proper solution is to normalize your relational design by adding a separate table for customers and their respective pre and post.
While stuck with the current design, this would do it:
SELECT *, post - pre AS net
FROM (
SELECT interaction_id, customer_id
,(SELECT sum(x::numeric) FROM string_to_table(pre, ';') x) AS pre
,(SELECT sum(x::numeric) FROM string_to_table(post, ';') x) AS post
FROM tbl
) sub;
db<>fiddle here
string_to_table() requires at least Postgres 14.
You did not declare your Postgres version, so I assume the current version Postgres 14.
For older versions replace with regexp_split_to_table() or unnest(string_to array)).

SQL Filter unique results

I am trying to get an SQL statement that will output a unique part number eg no duplicates
However I want the type as Purchased is the default and when there isnt a Purchased part it defults back to Manufactured. NOTE all parts can be purchased
The result I require is to only show unique part numbers
e.g. 1 to 10 and Purchased is the default Type
Table1
Part_number | Type | Cost
Part 1 | Manufactured | £1.00
Part 1 | Purchased | £0.56
Part 2 | Manufactured | £1.26
Part 2 | Purchased | £0.94
Part 3 | Manufactured | £0.36
Part 3 | Purchased | £0.16
Part 4 | Manufactured | £1.00
Part 4 | Purchased | £1.50
Part 5 | Manufactured | £1.65
Part 6 | Manufactured | £1.98
Part 7 | Manufactured | £0.15
Part 8 | Manufactured | £0.45
Part 9 | Manufactured | £1.20
Part 9 | Purchased | £0.80
Part 10| Manufactured | £1.00
This is the result I am hoping to get back
Part_number | Type | Cost
Part 1 | Purchased | £0.56
Part 2 | Purchased | £0.94
Part 3 | Purchased | £0.16
Part 4 | Purchased | £1.50
Part 5 | Manufactured | £1.65
Part 6 | Manufactured | £1.98
Part 7 | Manufactured | £0.15
Part 8 | Manufactured | £0.45
Part 9 | Purchased | £0.80
Part 10| Manufactured | £1.00
I have tried loads of different techniques but am not getting the result.
I am guessing that I will need to create temp tables that are filtered and then join the tables together but I really don't know.
Any help will be apricated
You could also just grab the first row in each group by sorting them. This would make it easier when there are other columns of data to bring back.
with data as (
select *, row_number() over (
partition by part_number
order by case when t.type = Purchased then 1 else 2 end) as rn
from t
)
select * from data where rn = 1;
If there are other types this would work as well although you would want to tweak it if there are more than two per part.
One method uses not exists. Assuming you have at most two rows per part:
select t.*
from t
where t.type = 'Purchased' or
(t.type = 'Manufactured' and
not (exists (select 1 from t t2 where t2.part_number = t.part_number and t2.type = 'Purchased')
)
);
There are other fun ways to handle this. For instance, an aggregation approach:
select part_number,
max(type) as type,
coalesce(max(case when type = 'Purchased' then cost end),,
max(cost)
) as cost
from t
group by part_number;

Convert rows to columns for a Report

Using instructions found here I've tried to create a crosstab query to show historical data from three previous years and I would like to output it in a report.
I've got a few complications that are making this difficult and I'm having trouble getting the data to show correctly.
The query it is based on is structured like this:
EmpID | ReviewYearID | YearName | ReviewDate | SelfRating | ManagerRating | NotSelfRating |
1 | 5 | 2013 | 01/09/2013 | 3.5 | 3.5 | 3.5 |
1 | 6 | 2014 | 01/09/2014 | 2.5 | 2.5 | 2.5 |
1 | 7 | 2015 | 01/09/2015 | 4.5 | 4.5 | 4.5 |
2 | 6 | 2014 | 01/09/2014 | 2.0 | 2.0 | 2.0 |
2 | 7 | 2015 | 01/09/2015 | 2.0 | 2.0 | 2.0 |
3 | 7 | 2015 | 01/09/2015 | 5.0 | 5.0 | 5.0 |
[Edit]: Here is the SQL for the base query. It is combining data from two tables:
SELECT tblEmployeeYear.EmployeeID AS EmpID, tblReviewYear.ID AS ReviewYearID, tblReviewYear.YearName, tblReviewYear.ReviewDate, tblEmployeeYear.SelfRating, tblEmployeeYear.ManagerRating, tblEmployeeYear.NotSelfRating
FROM tblReviewYear INNER JOIN tblEmployeeYear ON tblReviewYear.ID = tblEmployeeYear.ReviewYearID;
[/Edit]
I would like a crosstab query that transposes the columns/rows to show historical data for up to 3 previous years (based on review date) for a specific employee. The end result would look something like this for Employee ID 1:
Year | 2015 | 2014 | 2013 |
SelfRating | 4.5 | 2.5 | 3.5 |
ManagerRating | 4.5 | 2.5 | 3.5 |
NotSelfRating | 4.5 | 2.5 | 3.5 |
Other employees would have less columns since they don't have data for previous years.
I'm having issues with filtering it down to a specific employee and sorting the years by their review date (the name isn't always a reliable way to sort them).
In the end I'm looking to use this as the data for a report.
If there is a different way than a crosstab query to accomplish this I would be okay with that as well.
Thanks!
You need a column for all the rating types, not an individual column for each type. If you can't redesign the table, I would suggest creating a new one for your purposes. The below uses a union to add in that type column referred to above. You create a column and hardcode the value (SelfRating, ManagerRating, etc):
SELECT * INTO EmployeeRatings
FROM (SELECT tblEmployeeYear.EmployeeId AS EmpId, ReviewYearId, "SelfRating" AS Category, SelfRating AS Score
FROM tblEmployeeYear
WHERE SelfRating Is Not Null
UNION ALL
SELECT tblEmployeeYear.EmployeeId, ReviewYearId, "ManagerRating", ManagerRating
FROM tblEmployeeYear
WHERE ManagerRating Is Not Null
UNION ALL
SELECT tblEmployeeYear.EmployeeId, ReviewYearId, "NotSelfRating", NotSelfRating
FROM tblEmployeeYear
WHERE NotSelfRating Is Not Null)
Then use the newly created table in place of tblEmployeeYear. Note that I use Year([ReviewDate]) which will return only the year. Also, since it looks like it may be possible to have more than one of each review type per year, I averaged the Score for the year.
TRANSFORM Avg(Score)
SELECT EmpId, Category
FROM (SELECT EmpId, Category, ReviewDate, Score
FROM tblReviewYear
INNER JOIN EmployeeRatings
ON tblReviewYear.ID = EmployeeRatings.ReviewYearID) AS Reviews
GROUP BY EmpId, Category
PIVOT Year([ReviewDate]);

Randomly Populating Foreign Key In Sample Data Set

I'm generating test data for a new database, and I'm having trouble populating one of the foreign key fields. I need to create a relatively large number (1000) of entries in a table (SurveyResponses) that has a foreign key to a table with only 6 entries (Surveys)
The database already has a Schools table that has a few thousand records. For arguments sake lets say it looks like this
Schools
+----+-------------+
| Id | School Name |
+----+-------------+
| 1 | PS 1 |
| 2 | PS 2 |
| 3 | PS 3 |
| 4 | PS 4 |
| 5 | PS 5 |
+----+-------------+
I'm creating a new Survey table. It will only have about 3 rows.
Survey
+----+-------------+
| Id | Col2 |
+----+-------------+
| 1 | 2014 Survey |
| 2 | 2015 Survey |
| 3 | 2016 Survey |
+----+-------------+
SurveyResponses simply ties a school to a survey.
Survey Responses
+----+----------+----------+
| Id | SchoolId | SurveyId |
+----+----------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 1 |
| 4 | 4 | 3 |
| 5 | 5 | 2 |
+----+----------+----------+
Populating the SurveyId field is what's giving me the most trouble. I can randomly select 1000 Schools, but I haven't figured out a way to generate 1000 random SurveyIds. I've been trying to avoid a while loop, but maybe that's the only option?
I've been using Red Gate SQL Data Generator to generate some of my test data, but in this case I'd really like to understand how this can be done with raw SQL.
Here is one way, using a correlated subquery to get a random survey associated with each school:
select s.schoolid,
(select top 1 surveyid
from surveys
order by newid()
) as surveyid
from schools s;
Note: This doesn't seem to work. Here is a SQL Fiddle showing the non-workingness. I am quite surprised it doesn't work, because newid() should be a
EDIT:
If you know the survey ids have no gaps and start with 1, you can do:
select 1 + abs(checksum(newid()) % 3) as surveyid
I did check that this does work.
EDIT II:
This appears to be overly aggressive optimization (in my opinion). Correlating the query appears to fix the problem. So, something like this should work:
select s.schoolid,
(select top 1 surveyid
from surveys s2
where s2.surveyid = s.schoolid or s2.surveyid <> s.schoolid -- nonsensical condition to prevent over optimization
order by newid()
) as surveyid
from schools s;
Here is a SQL Fiddle demonstrating this.

SQL Query - Mathematical Query across 2 tables

I think the Title is a bit off what I need, so if someone thinks of a better title please don't hesitate to change it.
My issue is this: I have a TIMESHEET table, and a RATES table.
The TIMESHEET table looks like this:
|RATE-ID|OT |DAY1|DAY2|DAY3|DAY4|DAY5|DAY6|DAY7|DAY8|DAY9|DAY10|DAY11|DAY12|DAY13|DAY14|
----------------------------------------------------------------------------------------
| 1 |1 | 2 | 3 | 4 | 5 | 6 | 0 | 0 | 7 | 8 | 1 | 2 | 0 | 5 | 6 |
| 5 |1.5| 6 | 5 | 4 | 3 | 2 | 0 | 0 | 1 | 0 | 7 | 6 | 4 | 3 | 2 |
The RATES table looks like this:
|ID| RATE |
|1 | 50.00 |
|2 | 30.00 |
....
|5 | 100.00|
So then what I do in terms of a query (once off, so no worries about SQL injections) is this:
SELECT sum(DAY1)+sum(DAY2)+sum(DAY3)+sum(DAY4)+sum(DAY5)+sum(DAY6)+sum(DAY7)+sum(DAY8)+sum(DAY9)+sum(DAY10)+sum(DAY11)+sum(DAY12)+sum(DAY13)+sum(DAY14) as TOTALHOURS, OT, RATES.RATE from TIMESHEET INNER JOIN RATES on TIMESHEETS.RATE-ID = RATES.ID
Works fantastic - 3 seperate columns that tell me the total hours, the OT multiplier, and the rate. Now comes the part I'm struggling with. I can't seem to multiply the TOTALHOURS column against anything. I can do the simple OT * RATE as NEWRATE and get it down to 2 columns: TOTALHOURS and NEWRATE.
My question is, how do I multiply these two dynamically named columns to get the result?
i.e the first one would be 49 (sum of line 1) * 1 (OT for line 1) * 50 (Rate for line 1) = 2450
Any help is greatly appreciated.
Cheers,
Dee.
If you want a single line query without the hassles of running subqueries, temp tables, CTE's, group by clauses, or what have you, just try cleaning up the query a bit like this:
SELECT sum((DAY1+DAY2+DAY3+DAY4+DAY5+DAY6+DAY7+DAY8+DAY9+DAY10+DAY11+DAY12+DAY13+DAY14) * RATE * OT) as TOTALL FROM from TIMESHEET INNER JOIN RATES on TIMESHEETS.RATE-ID = RATES.ID
SQL has a wide variety of ways to accomplish the same result. Try the different ones and find the type that suits you and the application well.