SELECT aggregate values with distinct as columns - sql-server

Looking for assistance in writing a SQL Query to return data in a particular format. My table has the following structure
Table name: ExpiryTable
FRUIT | EXPIRY
--------+--------------
Apple | Monday
Apple | Wednesday
Banana | Tuesday
Orange | Monday
Orange | Tuesday
Pear | Monday
Pear | Tuesday
Pear | Wednesday
The output I need would show the following, where the fruits go across the result set as columns. This means that this week there are 4 fruits, next week there could be 7 fruits, so it has to factor in that the columns may expand or collapse.
| Apple | Banana | Orange | Pear
----------+--------+---------+---------+-------
Monday | 1 | 0 | 1 | 1
Tuesday | 0 | 1 | 1 | 1
Wednesday | 1 | 0 | 0 | 1
With regards to the rows, the days will actually never change. There will only ever be a Monday, Tuesday and Wednesday in the expiry cycle.
I do have another table which contains the complete list of available fruits sold, if that helps with the SQL statement in any way.
Table name: FruitMaster
Fruit
-------
Apple
Orange
Pear
Watermelon
Banana
Orange
Rockmelon
Tangerine
Kiwi-fruit
All I have thought of to tackle this is selecting the distinct expiry from ExpiryTable and then LEFT JOIN the aggregate of each fruit, however I am not sure how I would factor in where this week there is 4 fruits expiring, and next week there is more or less. If it was always a fixed list of Fruits I could get way with this technique, but alas its not.
Any assistance would really help me in getting started on this one.

You may use pivoting logic here:
SELECT
EXPIRY,
COUNT(CASE WHEN FRUIT = 'Apple' THEN 1 END) AS Apple,
COUNT(CASE WHEN FRUIT = 'Banana' THEN 1 END) AS Banana,
COUNT(CASE WHEN FRUIT = 'Orange' THEN 1 END) AS Orange,
COUNT(CASE WHEN FRUIT = 'Pear' THEN 1 END) AS Pear
FROM yourTable
GROUP BY
EXPIRY;

Related

Join tables by Column name

I've got a table with vehicles mark and sales date. I need to take a query to take how many different vehicles has been sold in this year, separated by months, for example:
| January | February | March | April |..............
----------------------------------------------------------
mark1 | | 1 | 5 | |..............
mark2 | 45 | | | 7 |..............
mark3 | 12 | 11 | 5 | 3 |..............
The original table is:
mark | soldDate
----------------------------
mark1 | 01/07/2020
mark2 | 04/07/2020
mark1 | 05/07/2020
mark3 | 06/07/2020
If i want to take how many different vehicles has been sold i use this query:
SELECT mark, COUNT(mark) WHERE FORMAT(soldDate, 'MMMM') = 'january' GROUP BY mark
How can i divide the data in every single month?
With conditional aggregation:
select mark,
count(case when month(soldDate) = 1 then 1 end) as January,
count(case when month(soldDate) = 2 then 1 end) as February,
...........................................................
where year(soldDate) = 2020
group by mark
SELECT Mark, DATENAME(MONTH, DATEADD(MONTH, MONTH(SalesDate) - 1, '1900-01-01')) M, COUNT(*) COUNT
FROM VehicleSales
WHERE YEAR(SalesDate) = '2020'
GROUP BY Mark, MONTH(SalesDate)
Order by Mark, M

Finding consecutive rows above a certain value

I have a sports statistical database. In one of the tables, I have the game-by-game stats for each player.
PK, PlayerID, OpponentID, GameID, Points, Rebounds, etc...
I would like to know how to return queries like, most consecutive games with at least 20 points or consecutive games with 10 rebounds, etc... (I have many other tables where this applies as well, just using this as an example.)
GameID is in chronological order, so that would be the way to determine consecutive games.
I assume this involves CTEs but I am not well-versed in that subject.
You are looking for queries that implement solutions for gaps-and-island problems.
Your question is quite generic, so let me give you an example of a such query, say: find the player(s) with the most consecutive 20+ point games; also find out the first and last game of the series, and the top/low points.
Here is a query for that purpose:
select top 1 with ties
PlayerID,
min(GameID) first_game,
max(GameID) last_game,
min(Points) min_points,
max(Points) max_points,
count(*) consecutive_games
from (
select
s.*,
row_number() over(partition by PlayerID order by GameID) rn,
sum(case when Points >= 20 then 1 else 0 end) over(partition by PlayerID order by GameID) sm
from PlayerStats s
) x
where Points >= 20
group by PlayerID, rn - sm
order by consecutive_games desc;
This works by doing a conditional sum ordered by game, that increments for each game above 20 points, and comparing it to the game sequence. When the difference between the sum and the game sequence changes, a new group of games starts; the rest is just aggregation. You can run the subquery idenpendently to see what it returns; you can also remove the top 1 clause to see the whole list of +20 points games series).
With this sample data:
PlayerID | GameID | Points
-------: | -----: | -----:
1 | 1 | 10
1 | 2 | 25
1 | 3 | 24
1 | 4 | 32
1 | 5 | 2
1 | 6 | 27
1 | 7 | 42
1 | 8 | 32
1 | 9 | 21
1 | 10 | 20
The query returns:
PlayerID | first_game | last_game | min_points | max_points | consecutive_games
-------: | ---------: | --------: | ---------: | ---------: | ----------------:
1 | 6 | 10 | 20 | 42 | 5
You should be able to apply the same logic for other stats.
Demo on DB Fiddle

Return a column to notify row change of another column

I have this table:
|----fruit----|
|-------------|
|--Apples--|
|--Apples--|
|--Apples--|
|--Apples--|
|-bananas-|
|-bananas-|
|-oranges-|
|--plums---|
|--plums---|
I have the below script:
case when
[fruit] = [fruit] then '0'
else [fruit]
end
what I want to do is return two columns.
1 is the fruit column and the 2nd is a column that shows when the fruit column changes to the next fruit, so i have something like the following:-
|----fruit----||fruit change|
|-------------||----------------|
|--Apples--||-------0-------|
|--Apples--||-------0-------|
|--Apples--||-------0-------|
|--Apples--||-------0-------|
|-bananas-||--bananas--|
|-bananas-||-------0-------|
|-oranges-||---oranges---|
|--plums---||----plums----|
|--plums---||-------0-------|
|--plums---||-------0-------|
|--plums---||-------0-------|
|--plums---||-------0-------|
|--mango---||---mango---|
|--mango---||-------0-------|
How do i return the column corresponding to the change in fruit as my script above doesn't allow me to specify what I need it to do.
You can use lag as below:
Select fruit,case when fruit <> lag(fruit) over(order by id) then fruit else '0' end as fruitChange
from #data
Output as below:
+---------+-------------+
| fruit | fruitChange |
+---------+-------------+
| Apples | 0 |
| Apples | 0 |
| Apples | 0 |
| Apples | 0 |
| bananas | bananas |
| bananas | 0 |
| oranges | oranges |
| plums | plums |
| plums | 0 |
+---------+-------------+
Version with first row display fruit, not 0.
-- create table
create table #data (id int identity(1,1), fruit nvarchar(20))
go
-- insert
insert into #data
values ('apple'),('apple'),('apple'),('ban'),('ban'),('orange'),('orange'),('orange'),('orange'),('lime'),('lime'),('lime'),('lime'), ('steak')
go
-- select, note first row will give you fruit as well
Select *,
case when fruit <> lag(fruit) over(order by id)
or lag(fruit) over(order by id) is null then fruit
else '0'
end as value
from #data
Output:
id fruit value
1 apple apple
2 apple 0
3 apple 0
4 ban ban
5 ban 0
6 orange orange
7 orange 0
8 orange 0
9 orange 0
10 lime lime
11 lime 0
12 lime 0
13 lime 0
14 steak steak

Cleaning up old record to a specific date: How to select the old record?

I posted a question here, which I now need to perform. I edited it a few times to match the current requirement, and now I think i will make it clearer as a final solution for me as well.
My table:
Items | Price | UpdateAt
1 | 2000 | 02/02/2015
2 | 4000 | 06/04/2015
1 | 2500 | 05/25/2015
3 | 2150 | 07/05/2015
4 | 1800 | 07/05/2015
5 | 5540 | 08/16/2015
4 | 1700 | 12/24/2015
5 | 5200 | 12/26/2015
2 | 3900 | 01/01/2016
4 | 2000 | 06/14/2016
As you can see, this is a table that keeps items' price as well as their old price before the last update.
Now I need to find the rows which :
UpdateAt is more than 1 year ago from now
Must have updated price at least once ever since
Aren't the most up-to-date price
Why those conditions? Because I need to perform a cleanup on that table off of those records that older than 1 year, while still maintain the full item list.
So with those conditions, the result from the above table should be :
Items | Price | UpdateAt
1 | 2000 | 02/02/2015
2 | 4000 | 06/04/2015
4 | 1800 | 07/05/2015
The update at 02/02/2015 of item 1 should be selected, while the update no. 2 at 05/25/2015, though still over 1 year old, should not because it is the most up-to-date price for item 1.
Item 3 isn't in the list because it never been updated, hence its price remain the same until now so i don't need to clean it up.
At first i think it wouldn't be so hard, and i think I've already had an answer but as I proceed, it isn't something that easy anymore.
#Tim Biegeleisen provided me with an answer in the last question, but it doesn't select the items which price doesn't change over the year at all, which i'm having to deal with now.
I need a solution to effectively clean up the table - it isn't necessary to follow 3 conditions above if it can produce the same result as I need : Records that needs to be deleted.
try this,
DECLARE #Prices TABLE(Items INT, Price DECIMAL(10,2), UpdateAt DATETIME)
INSERT INTO #Prices
VALUES
(1, 2000, '02/02/2015')
,(2, 4000, '06/04/2015')
,(1, 2500, '05/25/2015')
,(3, 2150, '07/05/2015')
,(4, 1800, '07/05/2015')
,(5, 5540, '08/16/2015')
,(4, 1700, '12/24/2015')
,(5, 5200, '12/26/2015')
,(2, 3900, '01/01/2016')
,(4, 2000, '06/14/2016')
SELECT p.Items, p.Price, p.UpdateAt
FROM #Prices p
LEFT JOIN ( SELECT
p1.Items,
p1.UpdateAt,
ROW_NUMBER() OVER (PARTITION BY p1.Items ORDER BY p1.UpdateAt DESC) AS RowNo
FROM #Prices p1
) AS hp ON hp.Items = p.Items
AND hp.UpdateAt = p.UpdateAt
WHERE hp.RowNo > 1 -- spare one price for each item at any date
AND p.UpdateAt < DATEADD(YEAR, -1, GETDATE()) -- remove only prices older than a year
the result is:
Items Price UpdateAt
----------- --------------------------------------- -----------------------
1 2000.00 2015-02-02 00:00:00.000
2 4000.00 2015-06-04 00:00:00.000
4 1800.00 2015-07-05 00:00:00.000
This query will return the dataset you're looking for:
SELECT t1.Items, t1.Price, t1.UpdateAt
FROM
(
SELECT
t2.Items,
t2.Price,
t2.UpdateAt,
ROW_NUMBER() OVER (PARTITION BY t2.Items ORDER BY t2.UpdateAt DESC) AS rn
FROM [Table] AS t2
) AS t1
WHERE t1.rn > 1
AND t1.UpdateAt < DATEADD(year, -1, GETDATE())

SQL Query for Date Range, multiple start/end times

A table exists in Microsoft SQL Server with record ID, Start Date, End Date and Quantity.
The idea is that for each record, the quantity/total days in range = daily quantity.
Given that a table containing all possible dates exists, how can I generate a result set in SQL Server to look like the following example?
EX:
RecordID | Start Date | End Date | Quantity
1 | 1/1/2010 | 1/5/2010 | 30000
2 | 1/3/2010 | 1/9/2010 | 20000
3 | 1/1/2010 | 1/7/2010 | 10000
Results as
1 | 1/1/2010 | QTY (I can do the math easy, just need the dates view)
1 | 1/2/2010 |
1 | 1/3/2010 |
1 | 1/4/2010 |
1 | 1/3/2010 |
2 | 1/4/2010 |
2 | 1/5/2010 |
2 | 1/6/2010 |
2 | 1/7/2010 |
2 | 1/8/2010 |
2 | 1/9/2010 |
3 | 1/1/2010 |
3 | 1/2/2010 |
3 | 1/3/2010 |
3 | 1/4/2010 |
3 | 1/5/2010 |
3 | 1/6/2010 |
3 | 1/7/2010 |
Grouping on dates I could get then get the sum of quantity on that day however the final result set can't be aggregate due to user provided filters that may exclude some of these records down the road.
EDIT
To clarify, this is just a sample. The filters are irrelevant as I can join to the side to pull in details related to the record ID in the results.
The real data contains N records which increases weekly, the dates are never the same. There could be 2000 records with different start and end dates... That is what I want to generate a view for. I can right join onto the data to do the rest of what I need
I should also mention this is for past, present and future data. I would love to get rid of a temporary table of dates. I was using a recursive query to get all dates that exist within a 50 year span but this exceeds MAXRECURSION limits for a view, that I cannot use.
Answer
select RecordId,d.[Date], Qty/ COUNT(*) OVER (PARTITION BY RecordId) AS Qty
from EX join Dates d on d.Date between [Start Date] and [End Date]
ORDER BY RecordId,[Date]
NB: The below demo CTEs use the date datatype which is SQL Server 2008 the general approach should work for SQL2005 as well though.
Test Case
/*CTEs for testing purposes only*/
WITH EX AS
(
SELECT 1 AS RecordId,
cast('1/1/2010' as date) as [Start Date],
cast('1/5/2010' as date) as [End Date],
30000 AS Qty
union all
SELECT 2 AS RecordId,
cast('1/3/2010' as date) as [Start Date],
cast('1/9/2010' as date) as [End Date],
20000 AS Qty
),Dates AS /*Dates Table now adjusted to do greater range*/
(
SELECT DATEADD(day,s1.number + 2048*s2.number,'1990-01-01') AS [Date]
FROM master.dbo.spt_values s1 CROSS JOIN master.dbo.spt_values s2
where s1.type='P' AND s2.type='P' and s2.number <= 8
order by [Date]
)
select RecordId,d.[Date], Qty/ COUNT(*) OVER (PARTITION BY RecordId) AS Qty
from EX join Dates d on d.Date between [Start Date] and [End Date]
ORDER BY RecordId,[Date]
Results
RecordId Date Qty
----------- ---------- -----------
1 2010-01-01 6000
1 2010-01-02 6000
1 2010-01-03 6000
1 2010-01-04 6000
1 2010-01-05 6000
2 2010-01-03 2857
2 2010-01-04 2857
2 2010-01-05 2857
2 2010-01-06 2857
2 2010-01-07 2857
2 2010-01-08 2857
2 2010-01-09 2857
I think you can try this.
SELECT [Quantities].[RecordID], [Dates].[Date], SUM([Quantity])
FROM [Dates]
JOIN [Quantities] on [Dates].[Date] between [Quantities].[Start Date] and [End Date]
GROUP BY [Quantities].[RecordID], [Dates].[Date]
ORDER BY [Quantities].[RecordID], [Dates].[Date]

Resources