range sql query result range - sql-server

ID | NAME | AGE | ADDRESS | SALARY |
+----+----------+-----+-----------+----------+
| 1 | Ramesh | 32 | Ahmedabad | 2000.00 |
| 2 | Khilan | 25 | Delhi | 1500.00 |
| 3 | kaushik | 23 | Kota | 2000.00 |
| 4 | Chaitali | 25 | Mumbai | 6500.00 |
| 5 | Hardik | 27 | Bhopal | 8500.00 |
| 6 | Komal | 22 | MP | 4500.00 |
| 7 | Muffy | 24 | Indore | 10000.00
I want to show result below using ms sql server
SALARY
-------------------
0 - 1500 (1)
1500 - 3000(2)
3000 - 4500(1)
4500-6000(0)
6000-7500(0)
7500 - 9000(1)
9000-10500(1)

This is something of a pain, because you want the 0 counts. Here is one method:
with ranges as (
select 0 as low, 1500 as high union all
select 1500, 3000 union all
select 3000, 4500 union all
. . .
)
select r.low, r.high, count(t.salary)
from ranges r left join
t
on t.salary >= r.low and
t.salary < r.high
group by r.low, r.high
order by r.low;
If you really want, you can use string manipulations to put the range as a single character column. In general, I prefer to have them as two separate columns.

Related

How to check a specific range value is filled in SQL with Window functions?

I am working on a project in which we should evaluate suppliers and in this database I have this table EvaluationGrade:
+------+---------------------+------------+-----------+
| Id | EvaluationMethodId | FromScore | ToScore |
+------+---------------------+------------+-----------+
| 1 | 2 | 1 | 20 |
| 2 | 2 | 21 | 50 |
| 3 | 2 | 51 | 70 |
| 4 | 2 | 71 | 100 |
| 5 | 3 | 1 | 20 |
| 6 | 3 | 31 | 40 |
+------+---------------------+------------+-----------+
This table categorize scores and I am gonna be sure for EvaluationMethodId=2 scope values fill 1 to 100 (just like sample above).
I am looking for something like this:
+---------------------+------------+
| EvaluationMethodId | Sum |
+---------------------+------------+
| 2 | 100 |
| 3 | 30 |
+---------------------+------------+
This is the way I attempted:
WITH myUpdate
AS (SELECT emg.Id,emg.EvaluationMethodId,
SUM(emg.ToGrade - emg.FromGrade) + 1 AS SumScope
FROM generalsup.EvaluationMethodGrading emg
GROUP BY emg.Id,emg.EvaluationMethodId)
SELECT myUpdate.EvaluationMethodId, SUM(myUpdate.SumScope) AS SumScopeAll
FROM myUpdate
GROUP BY myUpdate.EvaluationMethodId;
But I use window function that put less overhead on server.
Since there is no case of overlaps in the scores, you can do it with group by EvaluationMethodId and sum():
select EvaluationMethodId, sum(ToScore - FromScore + 1) [Sum]
from EvaluationMethodGrading
group by EvaluationMethodId
See the demo.
Results:
> EvaluationMethodId | Sum
> -----------------: | --:
> 2 | 100
> 3 | 30

SQL Server - get values from X months ago according to columndata

Let's say I have the following table (data is completely fiction):
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck
1 | 2017-12 | 900 | Jack | Ill | -
2 | 2018-01 | 900 | Jack | Ill | 1
3 | 2018-02 | 900 | Jack | Ill | 2
4 | 2018-03 | 900 | Jack | Healthy | 1
5 | 2017-02 | 901 | Bill | Ill | -
6 | 2017-03 | 901 | Bill | Ill | 1
7 | 2017-05 | 901 | Bill | Healthy | 1
For each record, I would like to see the previous status that person had X months ago since last check (column MonthsAgoSinceLastCheck). Notice that MonthDate can skip months.
So in this case, the result would be
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck | PreviousSatus
1 | 2017-12 | 900 | Jack | Ill | - | -
2 | 2018-01 | 900 | Jack | Ill | 1 | Ill
3 | 2018-02 | 900 | Jack | Ill | 2 | Ill
4 | 2018-03 | 900 | Jack | Healthy | 1 | Ill
5 | 2017-02 | 901 | Bill | Healthy | - | -
6 | 2017-03 | 901 | Bill | Healthy | 1 | Healthy
7 | 2017-05 | 901 | Bill | Ill | 2 | Healthy
Any sugestions/tips? I tried to do this with CTE's and self-joins but failed on both.
It's way easier to use full dates than year and months separately. The first thing you should do is generate a full date from your year + month. Then just self join with previous month, depending on the last check.
;WITH DataWithDates AS
(
SELECT
T.ID,
MonthDate = CONVERT(DATE, T.MonthDate + '-01'),
T.PersonID,
T.Name,
T.Status,
T.MonthsAgoSinceLastCheck
FROM
YourTable AS T
)
SELECT
D.ID,
D.MonthDate,
D.PersonID,
D.Name,
D.Status,
D.MonthsAgoSinceLastCheck,
PreviousStatus = N.Status
FROM
DataWithDates AS D
LEFT JOIN DataWithDates AS N ON
D.PersonID = N.PersonID AND
N.MonthDate = DATEADD(MONTH, -1 * D.MonthsAgoSinceLastCheck, D.MonthDate)
I'm assuming your MonthDate has values for all rows, otherwise the conversion will fail. I'm also assuming that your - values for MonthsAgoSinceLastCheck are actually NULL.
try this:
select *,LAG(Status) OVER(Partition by Name Order by MonthDate,Id) AS PreviousSatus
from tab1
order by id
SQl Fiddle:http://sqlfiddle.com/#!18/04407/4

SQL: Taking Column From a Row Picked By Aggregate Function in View

I have three SQL tables: Release (which represents a release of a movie), Media (which represents the individual pieces of recordable media in those releases; i.e. for Blu-ray/DVD combos, there will be two rows in Media, one Blu-ray and one DVD, that point back to the same row in Release) and MediaType (which defines Blu-ray, DVD, VHS, etc.). There's a one-to-many relationship for Release/Media and MediaType/Media, with Media being on the "many" side of both relationships. I have a view for Release, vRelease, which contains aggregate functions, such as a COUNT that shows how many media are associated with that release. This is what I have for this view so far:
SELECT dbo.Release.ReleaseID
,dbo.Release.Name
,CASE WHEN Release.Compilation = 0 THEN 'No' WHEN Release.Compilation = 1 THEN 'Yes' END AS Compilation
,dbo.Release.Owner
,CASE WHEN Release.LentOut = 0 THEN 'No' WHEN Release.LentOut = 1 THEN 'Yes' END AS LentOut
,COUNT(dbo.Media.ReleaseID) AS NumberOfMedia
,MIN(dbo.Media.MediaID) AS FirstMediaID
,MIN(dbo.MediaType.Name) AS FirstMediaType
FROM dbo.MediaType INNER JOIN
dbo.Media ON dbo.MediaType.MediaTypeID = dbo.Media.MediaTypeID RIGHT OUTER JOIN
dbo.Release ON dbo.Media.ReleaseID = dbo.Release.ReleaseID
GROUP BY dbo.Release.ReleaseID, dbo.Release.Name, dbo.Release.Compilation, dbo.Release.Owner, dbo.Release.LentOut
You'll notice that I've also included two other aggregate columns: FirstMediaID grabs the ID of the media associated with that release that appears first in the Media table (i.e. if a release has two DVDs associated with it, it gets one with the lower ID value). This column on its own isn't useful; what I want to do is then, in turn, get the MediaType that that Media is associated with. In other words, I want a column that shows the MediaType of the first Media that is attached to each Release. The column after that, FirstMediaType, is supposed to do that, but it instead gets the MediaType among all of the Media associated with the Release and picks the one that is alphabetically first - which means that Blu-ray will always be prioritized over DVD (which is fine), but Audio CD will always be prioritized over everything else (which is not fine).
How do I get the FirstMediaType column in this view to get the MediaType of the Media identified in FirstMediaID?
UPDATE: Here are the tables, their columns and some sample rows.
A couple from Release:
+-----------+----------------------------------------+-------+-------------+---------+
| ReleaseID | Name | Owner | Compilation | LentOut |
+-----------+----------------------------------------+-------+-------------+---------+
| 2 | Alice in Wonderland | NULL | 0 | 0 |
| 6 | 4 Film Favorites - Family Comedies | NULL | 1 | 0 |
| 8 | Aladdin | NULL | 0 | 0 |
| 463 | Harry Potter and the Half-Blood Prince | NULL | 0 | 1 |
| 534 | Spirited Away | Ryan | 0 | 0 |
| 571 | The Original Christmas Classics | NULL | 1 | 0 |
+-----------+----------------------------------------+-------+-------------+---------+
Compilation indicates a release that has more than one movie in it.
Corresponding entries in Media:
+---------+-------------+-------------------------------------------------------------------------------------+-----------+
| MediaID | MediaTypeID | Name | ReleaseID |
+---------+-------------+-------------------------------------------------------------------------------------+-----------+
| 2 | 2 | Movie | 2 |
| 3 | 1 | Movie | 2 |
| 12 | 1 | Space Jam; Looney Tunes: Back in Action | 6 |
| 13 | 1 | Funky Monkey; Osmosis Jones | 6 |
| 17 | 3 | Movie | 8 |
| 620 | 1 | Movie | 463 |
| 726 | 1 | Movie | 534 |
| 807 | 1 | Rudolph the Red-Nosed Reindeer; Cricket on the Hearth | 571 |
| 808 | 1 | Frosty the Snowman; Frosty Returns | 571 |
| 809 | 1 | Santa Claus is Comin' to Town!; Mr. Magoo's Christmas Carol; The Little Drummer Boy | 571 |
| 810 | 4 | Tracks 1-7 | 571 |
+---------+-------------+-------------------------------------------------------------------------------------+-----------+
First few in MediaType:
+-------------+--------------+
| MediaTypeID | Name |
+-------------+--------------+
| 1 | DVD Disc |
| 2 | Blu-ray Disc |
| 3 | VHS |
| 4 | Audio CD |
+-------------+--------------+
The corresponding entries in vRelease SHOULD be this:
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
| ReleaseID | Name | Compilation | Owner | LentOut | NumberOfMedia | FirstMediaID | FirstMediaType |
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
| 2 | Alice in Wonderland | No | NULL | No | 2 | 2 | Blu-ray Disc |
| 6 | 4 Film Favorites - Family Comedies | Yes | NULL | No | 2 | 12 | DVD Disc |
| 8 | Aladdin | No | NULL | No | 1 | 17 | VHS |
| 463 | Harry Potter and the Half-Blood Prince | No | NULL | Yes | 1 | 620 | DVD Disc |
| 534 | Spirited Away | No | Ryan | No | 1 | 726 | DVD Disc |
| 571 | The Original Christmas Classics | Yes | NULL | No | 4 | 807 | DVD Disc |
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
But it's actually this:
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
| ReleaseID | Name | Compilation | Owner | LentOut | NumberOfMedia | FirstMediaID | FirstMediaType |
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
| 2 | Alice in Wonderland | No | NULL | No | 2 | 2 | Blu-ray Disc |
| 6 | 4 Film Favorites - Family Comedies | Yes | NULL | No | 2 | 12 | DVD Disc |
| 8 | Aladdin | No | NULL | No | 1 | 17 | VHS |
| 463 | Harry Potter and the Half-Blood Prince | No | NULL | Yes | 1 | 620 | DVD Disc |
| 534 | Spirited Away | No | Ryan | No | 1 | 726 | DVD Disc |
| 571 | The Original Christmas Classics | Yes | NULL | No | 4 | 807 | Audio CD |
+-----------+----------------------------------------+-------------+-------+---------+---------------+--------------+----------------+
It's that last one that's the problem.
I ended up finding a simple way to do what I wanted. It isn't as fancy as Used_By_Already's answer (which did end up working, as far as I could tell) and probably breaks a SQL Best Practices rule somewhere, but it's much easier to understand and maintain - at least for my newbie brain.
Since the problem was trying to get the view to use an aggregate column it calculated in a join, I just split the two-step action over two views. vReleasePre has all of the columns I outlined in my original query except for FirstMediaType. vRelease now simply takes all of the columns from vReleasePre and adds FirstMediaType, which takes its value from a join at the end: LEFT OUTER JOIN dbo.vMedia ON dbo.vReleasePre.FirstMediaID = dbo.vMedia.MediaID, where vMedia is a view with all the columns from Media, plus the MediaType column (I already had vMedia lying around).
Since this database is being used in an ASP.NET MVC web application via Entity Framework, and EF has been pretty strange about what it will and won't accept into the data model, I figure that a simple, if roundabout, solution is probably going to be my best option.
vReleasePre:
SELECT dbo.Release.ReleaseID
,dbo.Release.Name
,CASE WHEN Release.Compilation = 0 THEN 'No' WHEN Release.Compilation = 1 THEN 'Yes' END AS Compilation
,dbo.Release.Owner
,CASE WHEN Release.LentOut = 0 THEN 'No' WHEN Release.LentOut = 1 THEN 'Yes' END AS LentOut
,COUNT(dbo.Media.ReleaseID) AS NumberOfMedia
,MIN(dbo.Media.MediaID) AS FirstMediaID
FROM dbo.MediaType INNER JOIN
dbo.Media ON dbo.MediaType.MediaTypeID = dbo.Media.MediaTypeID RIGHT OUTER JOIN
dbo.Release ON dbo.Media.ReleaseID = dbo.Release.ReleaseID
GROUP BY dbo.Release.ReleaseID, dbo.Release.Name, dbo.Release.Compilation, dbo.Release.Owner, dbo.Release.LentOut
vRelease:
SELECT dbo.vReleasePre.ReleaseID
,dbo.vReleasePre.Name
,dbo.vReleasePre.Compilation
,dbo.vReleasePre.Owner
,dbo.vReleasePre.LentOut
,dbo.vReleasePre.NumberOfMedia
,dbo.vMedia.MediaType
FROM dbo.vReleasePre LEFT OUTER JOIN
dbo.vMedia ON dbo.vReleasePre.FirstMediaID = dbo.vMedia.MediaID
A very convenient technique that returns whole rows associated with needs such as "First", "Last", "Earliest", "Latest" is to use row_number() over(). Here you want the "first media type", so it is relevant here.
As you will see in the following query joining the [Media] table is replaced with a subquery that includes a row number calculation. Here we partition by ReleaseID and order by MediaID, so, for each ReleaseID the first row will be the one with the lowest MediaID value. Then in the join to this derived table an extra condition is added to only consider rows with a row number of 1.
Proposed Query
SELECT
r.ReleaseID
, m.MediaID
, mt.MediaTypeID
, mt.name MediaName
, r.Name
, CASE
WHEN r.Compilation = 0 THEN 'No'
WHEN r.Compilation = 1 THEN 'Yes'
END AS compilation
, r.Owner
, CASE
WHEN r.LentOut = 0 THEN 'No'
WHEN r.LentOut = 1 THEN 'Yes'
END AS lentout
FROM dbo.Release r
INNER JOIN (
SELECT
Media.*
, ROW_NUMBER() OVER(PARTITION BY ReleaseID
ORDER BY MediaID) AS rn
FROM dbo.Media
) m ON r.ReleaseID = m.ReleaseID and rn = 1
INNER JOIN dbo.MediaType mt ON m.MediaTypeID = mt.MediaTypeID
Result
| ReleaseID | MediaID | MediaTypeID | MediaName | Name | compilation | Owner | lentout |
|-----------|---------|-------------|--------------|----------------------------------------|-------------|--------|---------|
| 2 | 2 | 2 | Blu-ray Disc | Alice in Wonderland | No | (null) | No |
| 6 | 12 | 1 | DVD Disc | 4 Film Favorites - Family Comedies | Yes | (null) | No |
| 8 | 17 | 3 | VHS | Aladdin | No | (null) | No |
| 463 | 620 | 1 | DVD Disc | Harry Potter and the Half-Blood Prince | No | (null) | Yes |
| 534 | 726 | 1 | DVD Disc | Spirited Away | No | Ryan | No |
| 571 | 807 | 1 | DVD Disc | The Original Christmas Classics | Yes | (null) | No |
Demo available at SQLFiddle
The easiest way would be to add another join to your MediaType table on FirstMediaId = MediaType.MediaId
;WITH data AS (
SELECT dbo.Release.ReleaseID
,dbo.Release.Name
,CASE WHEN Release.Compilation = 0 THEN 'No' WHEN Release.Compilation = 1 THEN 'Yes' END AS Compilation
,dbo.Release.Owner
,CASE WHEN Release.LentOut = 0 THEN 'No' WHEN Release.LentOut = 1 THEN 'Yes' END AS LentOut
,COUNT(dbo.Media.ReleaseID) AS NumberOfMedia
,MIN(dbo.Media.MediaID) AS FirstMediaID
FROM dbo.MediaType
INNER JOIN dbo.Media
ON dbo.MediaType.MediaTypeID = dbo.Media.MediaTypeID
RIGHT OUTER JOIN dbo.Release
ON dbo.Media.ReleaseID = dbo.Release.ReleaseID
GROUP BY dbo.Release.ReleaseID, dbo.Release.Name, dbo.Release.Compilation, dbo.Release.Owner, dbo.Release.LentOut
)
SELECT data.ReleaseId
,data.Name
,data.Compilation
,data.Owner
,data.LentOut
,data.NumberOfMedia
,data.FirstMediaId
,MediaType.Name as FirstMediaName
FROM data
LEFT OUTER JOIN dbo.MediaType
ON data.FirstMediaId = MediaType.MediaTypeId
for the newbie brain, this is the subquery I used
SELECT
ROW_NUMBER() OVER(PARTITION BY ReleaseID
ORDER BY MediaID) AS rn
, Media.*
FROM dbo.Media
and this is what it does (see the rn column)
| rn | MediaID | MediaTypeID | Name | ReleaseID |
|----|---------|-------------|-------------------------------------------------------------------------------------|-----------|
| 1 | 2 | 2 | Movie | 2 |
| 2 | 3 | 1 | Movie | 2 |
| 1 | 12 | 1 | Space Jam; Looney Tunes: Back in Action | 6 |
| 2 | 13 | 1 | Funky Monkey; Osmosis Jones | 6 |
| 1 | 17 | 3 | Movie | 8 |
| 1 | 620 | 1 | Movie | 463 |
| 1 | 726 | 1 | Movie | 534 |
| 1 | 807 | 1 | Rudolph the Red-Nosed Reindeer; Cricket on the Hearth | 571 |
| 2 | 808 | 1 | Frosty the Snowman; Frosty Returns | 571 |
| 3 | 809 | 1 | Santa Claus is Comin' to Town!; Mr. Magoo's Christmas Carol; The Little Drummer Boy | 571 |
| 4 | 810 | 4 | Tracks 1-7 | 571 |
Now keep only those rows with 1 in the rn column:
| rn | MediaID | MediaTypeID | Name | ReleaseID |
|----|---------|-------------|-------------------------------------------------------|-----------|
| 1 | 2 | 2 | Movie | 2 |
| 1 | 12 | 1 | Space Jam; Looney Tunes: Back in Action | 6 |
| 1 | 17 | 3 | Movie | 8 |
| 1 | 620 | 1 | Movie | 463 |
| 1 | 726 | 1 | Movie | 534 |
| 1 | 807 | 1 | Rudolph the Red-Nosed Reindeer; Cricket on the Hearth | 571 |
Then join just those rows to Releases and MediaType
Bingo
= the wanted result.
Not hard, really not hard. You really will want to learn about those window functions because they can solve heaps of problems.

SQL percentage of total items by group

I have the following pseudo-table which shows product orders:
+---------+------+--------------+---------------+
| OrderID | Year | PriorityCode | ShippedOnTime |
+---------+------+--------------+---------------+
| 1 | 2014 | A | Y |
| 2 | 2014 | B | Y |
| 3 | 2014 | A | N |
| 4 | 2015 | C | Y |
| 5 | 2015 | B | Y |
| 6 | 2015 | A | N |
| 7 | 2015 | A | N |
| 8 | 2015 | B | N |
| 9 | 2015 | C | Y |
| 10 | 2015 | C | Y |
+---------+------+--------------+---------------+
I need to find a way to query to find percentages of ShippedOntime grouped by PriorityCode, not as a total number of rows. For Example:
PriorityCode: A - Total 4, 1 was shipped on time = 25%
PriorityCode: B - Total 3, 2 were shipped on time = 33.3%
PriorityCode: C - Total 3, 3 were shipped on time = 100%
+--------------+------------+
| PriorityCode | Percentage |
+--------------+------------+
| A | 25 |
| B | 33.3 |
| C | 100 |
+--------------+------------+
Been looking into using the Over() function and then Grouping the results, but cant seem to figure it out.
Id like to also be able to group it by year also, but small steps!
You can use conditional statements inside a group by function, such as count or sum to achieve the desired output:
select PriorityCode, sum(case when ShippedOnTime="Y" then 1 else 0 end)/count(*) * 100 as percentage
from table
group by PriorityCode
This should do the trick:
SELECT
Year,
PriorityCode,
CAST(SUM(CASE WHEN ShippedOnTime = 'Y'
THEN 100.0 ELSE 0
END)
/ COUNT(*) as DECIMAL(4,1)) Percentage
FROM yourtable
GROUP BY
PriorityCode, Year

SQL Server Join temp tables and Pivot

I have two tables with data
TABLE 1:
Counts of service for weekdays
-------------------------------------------------------
| Day of Wk| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
| 1 | 50 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 0 | 75 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 89 | 0 | 0 | 0 | 0 |
TABLE 2:
Table 2 with Production Totals by for selected services (Prod A - C) by weekday
-------------------------------------------------------
| Day of Wk| Date | Prod_A | Prod_B | Prod_C |
| 1 | 2015-01-01 | 4000 | 8000 | 9000 |
| 2 | 2015-01-14 | 3000 | 7000 | 8000 |
| 3 | 2015-01-05 | 2000 | 2000 | 5000 |
I need a query to produce this table:
Service counts of Weekdays = Total Service counts of table 1 in a column
-------------------------------------------------------
| Day of Wk | Service_Count | Prod_A | Prod_B | Prod_C |
| Sun | 50 | 4000 | 8000 | 9000 |
| Mon | 75 | 3000 | 7000 | 8000 |
| Tue | 89 | 2000 | 2000 | 5000 |
I am pretty new to pivoting and not sure if pivot is way to go for this or not.
In this recordset the Day of Wk column represents the day of the week, i.e. 1=Sunday
Not sure but looks simple to me.Sorry if I have not understood your question.
select t1.days_of_Week,(t1.sun+t1.mon+t1.tue+t1.wed+t1.thr+t1.fri+t1.sat)
Service_Count, t2.Prod_A,t2.Prod_B,t2.Prod_C
from Table1 t1,Table2 t2
where t1.days_of_week = t2.days_of_Week;
If you are trying to produce what you shared then the following query should do the job... #Swazzy
SELECT unpiv.[Day] AS Days_of_Week, unpiv.[Count] AS Service_Count,t2.Prod_A,t2.Prod_B,t2.Prod_C
FROM ( SELECT t.* FROM Table1 t) d
UNPIVOT
(
[Count]
for [Day] in (Sun, Mon, Tue, Wed, Thurs, Fri)
) unpiv
INNER JOIN Table2 t2 ON t2.Days_of_week = unpiv.days_of_week
WHERE unpiv.[Count] > 0

Resources