I have the following two postgresql tables:
table: daily
id date close symbol_id
1 2016-05-01 80 65
2 2016-05-01 75 67
3 2016-05-01 95 45
4 2016-05-02 11 65
5 2016-05-02 48 67
6 2016-05-02 135 45
7 2016-05-03 18 65
8 2016-05-03 82 67
9 2016-05-03 107 45
10 2016-05-04 29 65
table: symbol
id symbol
65 abc
67 xyz
45 jkl
I need to select all symbols where the close value is less than 100 for the latest date for each symbol. As per the example, not all symbols will have the same latest date.
The following query gives me correct data when I do not use the WHERE clause:
SELECT DISTINCT ON (daily.symbol_id) symbol.symbol, daily.close, daily.date
FROM daily JOIN symbol ON daily.symbol_id = symbol.id
--WHERE daily.close < 100
ORDER BY daily.symbol_id, daily.date DESC
Result:
symbol close date
abc 29 2016-05-04
xyz 82 2016-05-03
jkl 107 2016-05-03
The problem comes when I uncomment the WHERE clause. The desired result is for the symbol jkl to be removed from the list because the value for close for that symbol on its latest date is not < 100. However this is what happens:
symbol close date
abc 29 2016-05-04
xyz 82 2016-05-03
jkl 95 2016-05-01
You can move your existing query to a subquery and then filter with where criteria.
select *
from (
select distinct on (d.symbol_id) s.symbol, d.close, d.date
from daily d
join symbol s on d.symbol_id = s.id
order by daily.symbol_id, daily.date desc
) t
where close < 100
Here's another similar option using a windows function such as row_number:
select *
from (
select d.symbol_id, s.symbol, d.close, d.date,
row_number() over (partition by d.symbol_id order by d.date desc) rn
from daily d
join symbol s on d.symbol_id = s.id
) t
where rn = 1 and close < 100
Code not tested, just to demonstrate idea
First you make a query to get the latest date of every symbol. Then make a join to filter out rows that are not latest which you can safely apply the close < 100 where clause.
SELECT DISTINCT ON(symbol) * FROM (
SELECT MAX(d1.date) latest FROM daily d1 GROUP BY d1.symbol_id
INNER JOIN daily d2 ON latest = d2.date AND d1.symbol_id = d2.symbol_id) t
WHERE close <100
Related
I have a problem where i need to fetch 2 specific records with 2 different value and find the difference between their amount. This needs to be done for each device.
Lets take the following table as example
DevID reason amount DateTime
--------------------------------------------------
99 5 84 18-12-2016 18:10
99 0 35 18-12-2016 18:11
99 0 80 18-12-2016 18:12
99 0 34 18-12-2016 18:15
23 5 36 18-12-2016 18:16
23 4 22 18-12-2016 18:17
23 1 22 18-12-2016 18:18
23 2 22 18-12-2016 18:19
99 2 11 18-12-2016 18:20
99 8 50 18-12-2016 18:21
99 0 23 18-12-2016 18:22
99 5 06 18-12-2016 18:25
99 8 12 18-12-2016 18:30
So my reason of interest is 5 and 8. 5 is device logon and 8 is logout and other numbers refer to other things.
I want to fetch records with device logon reason(5) and the next device logout(8) and find the difference in its amount value so in the table above for device 99, amount for reason 5 is 84 and the logout event(8) is 50, so the difference is 34 which if greater than 10 i need list that device.
(please note there is another case of 5 and 8 for the same record, the difference is not greater than 5) but the first set has diff greater than 10 so we need to display that device id
So the expected output for the above is
DevID
-------
99
i was thinking of join
Join table A which has all records with 5(sorted by deviceid,date) and table B which has all records of with 8 and then subtract their amounts and display the records with value greater than 10.
Not sure if join is the way to go? any simpler/fast solution?
You can use LEAD function to match logon time with logout:
WITH cte AS (
SELECT devid
, reason
, amount
, LEAD(reason) OVER (PARTITION BY devid ORDER BY datetime) AS next_reason
, LEAD(amount) OVER (PARTITION BY devid ORDER BY datetime) AS next_amount
FROM t
WHERE reason IN (5, 8)
)
SELECT *, amount - next_amount AS diff
FROM cte
WHERE reason = 5 -- logon
AND next_reason = 8 -- next event is a logout
AND amount - next_amount >= 10 -- difference of current and next
You can get the next "8" value using window functions. Then join and filter:
select t.*,
(t.value - t8.value) as diff
from (select t.*,
min(case when reason = 8 then datetime end) over (partition by devid order by datetime desc) as next_8_datetime
from t
) t left join
t t8
on t8.devid = t.devid and
t8.datetime = t.next_8_datetime and
t8.reason = 8
where t.reason = 5
order by diff desc
limit 1;
Good day community.
I'm having a hard time trying to figure out a way to achieve the results I try to get. As im not very skilled with SQL queries, I start to lose my mind. What I'm trying to do is to find the highest and lowest grade on a particular test, but I also wish to get the ID or the row number (they are matching) of the rows where the MAX() and MIN() were found.
The table "Results" looks like this:
ResultID|Test_UK|Test_US|TestUK_Scr|TestUS_Scr|TestTakenOn
1 1 3 85 14 2018-11-22 00:00:00.000
2 3 1 41 94 2018-11-23 00:00:00.000
3 2 4 71 54 2018-11-24 00:00:00.000
4 4 2 51 52 2018-12-25 00:00:00.000
5 6 3 74 69 2018-12-01 00:00:00.000
6 3 6 83 57 2018-12-02 00:00:00.000
7 7 4 91 98 2018-12-03 00:00:00.000
8 4 7 88 22 2018-12-04 00:00:00.000
9 5 8 41 76 2018-12-08 00:00:00.000
10 8 5 37 64 2018-12-09 00:00:00.000
The results I get when I run my query...
TestID|TopScore|LowScore|LastDateTestTaken
1 94 85 2018-11-23 00:00:00.000
2 71 52 2018-11-25 00:00:00.000
3 83 14 2018-12-02 00:00:00.000
4 98 51 2018-12-04 00:00:00.000
5 64 41 2018-12-09 00:00:00.000
6 74 57 2018-12-02 00:00:00.000
7 91 22 2018-12-04 00:00:00.000
8 76 37 2018-12-09 00:00:00.000
This is the queries I'm working on.
This query returns the results mentioned above
WITH
-- Combine the results of UK and US tests
Combined_Results_Both_Tests AS(
select ResultID as resultID, Test_UK as TestID, Test_UK_Scr as TestScore, TestTakenOn as TestDate from Results
union all
select ResultID as resultID, Test_US as TestID, Test_US_Scr as TestScore, TestTakenOn as TestDate from Results),
--Gets TOP and WORST results of the tests, LastDateTaken (Needs to add ResultID!)
Get_Best_and_Worst_Results_And_LastTestDate AS(
SELECT TestID ,max(TestScore) AS TopScore ,min(TestScore) AS LowScore ,max(TestDate) AS LastDateTestTaken
FROM Combined_Results_Both_Tests
GROUP BY TestID)
--Final query execution
SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate
I've tried to achieve my desired results with something like this, which doesn't work and is also very inefficient. What I mean that it doesn't work, it is filled with dublicates, whenever the match is found on US and UK tests.
--Gets ReslutID of Min and Max values
Get_ResultID_Of_Results AS(
SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate A
CROSS APPLY
(SELECT ResultID FROM Results res
WHERE (A.TestID = res.Test_UK AND A.TopScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_US AND A.TopScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_UK AND A.LowScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_US AND A.LowScore = res.Test_UK_Scr) OR
(A.TestID = res.Test_UK AND A.TopScore = res.Test_US_Scr) OR
(A.TestID = res.Test_US AND A.TopScore = res.Test_US_Scr) OR
(A.TestID = res.Test_UK AND A.LowScore = res.Test_US_Scr) OR
(A.TestID = res.Test_US AND A.LowScore = res.Test_US_Scr)) D)
SELECT * FROM Get_ResultID_Of_Results
This is the results I'm trying to achieve (extra columns that would state where Max value and Min value was found) that would state the ResultID from Results table. Also, the row numbers match the ResultIDs in the table.
TestID|TopScore|LowScore|LastDateTestTaken |MaxValueLocID|MinValueLocID|
1 94 85 2018-11-23 00:00:00.000 2 1
2 71 52 2018-11-25 00:00:00.000 3 4
3 83 14 2018-12-02 00:00:00.000 6 1
4 98 51 2018-12-04 00:00:00.000 7 4
5 64 41 2018-12-09 00:00:00.000 10 9
6 74 57 2018-12-02 00:00:00.000 5 6
7 91 22 2018-12-04 00:00:00.000 7 8
8 76 37 2018-12-09 00:00:00.000 9 10
Asking for any help with the solution, theoretical or even practical. Thank you!
If I follow correctly, you want to unpivot the data and aggregate:
select v.testid, max(v.score), min(v.score) max(v.TestTakenOn)
from results r cross apply
(values (Test_UK, TestUK_Scr, TestTakenOn),
(Test_US, TestUS_Scr, TestTakenOn)
) v(testid, score, TestTakenOn)
group by v.testid;
Then you can modify this using window functions:
select v.testid, max(v.score), min(v.score) max(v.TestTakenOn),
max(case when seqnum_desc = 1 then resultid end) as resultid_max,
max(case when seqnum_asc = 1 then resultid end) as resultid_min
from (select r.resultid, v.*,
row_number() over (partition by v.testid order by v.score asc) as seqnum_asc,
row_number() over (partition by v.testid order by v.score desc) as seqnum_desc
from results r cross apply
(values (Test_UK, TestUK_Scr, TestTakenOn),
(Test_US, TestUS_Scr, TestTakenOn)
) v(testid, score, TestTakenOn)
) v
group by v.testid;
with allScores (TestId, Score, TestTakenOn, valueLoc) as
(
select [Test_UK], [TestUK_Scr],[TestTakenOn], ResultId from scores
union all
select [Test_US], [TestUS_Scr],[TestTakenOn], ResultId from scores
),
maxMin (TestId, MaxScore, MinScore, LastTestDate) as (
select TestId, Max(score), Min(score), Max(TestTakenOn)
from allScores
group by TestId
)
select mm.*, a1.valueLoc as MaxValueLoc, a2.ValueLoc as MinValueLoc
from maxMin mm
inner join allScores a1
on mm.TestId = a1.TestId and mm.MaxScore = a1.score
inner join allScores a2
on mm.TestId = a2.TestId and mm.MinScore = a2.score;
DBFiddle demo
I wrote this query and it seems to be working to gather correct results, however, it also takes a VERY long time. I'm just wondering if there is a way to make it more efficient?
(I understand that it's inefficient because it is creating data tables and joining them back together - I just don't know how to get around it, specifically with the CASE issue included).
I'm working in Excel with an ODBC connection to AS400. The question marks allow user-entered parameters within Excel cells.
with W as
(
select yr as YEAR, pd as PERIOD, sum(amt1 + amt2 + amt3 + amt4) as SALES
from TABLE
group by yr, pd
order by yr desc, pd desc
), X as
(
select yr as YEAR, pd as PERIOD, sum(amt1 + amt2 + amt3 + amt4) as BSALES
from TABLE
where type = 'B'
group by yr, pd
order by yr desc, pd desc
), Y as
(
select
CASE WHEN pd = 1 THEN yr - 1 ELSE yr END as YEAR,
CASE WHEN pd = 1 THEN 12 ELSE pd - 1 END as PERIOD,
SUM(OM) as MODOM
from TABLE
group by yr, pd
order by yr desc, pd desc
), Z as
(
select
CASE WHEN pd = 1 THEN yr - 1 ELSE yr END as YEAR,
CASE WHEN pd = 1 THEN 12 ELSE pd - 1 END as PERIOD,
SUM(BOM) as BMODOM
from TABLE
where type = 'B'
group by yr, pd
order by yr desc, pd desc
)
select w.YEAR, w.PERIOD, w.SALES, x.BSales, y.MODOM, z.BMODOM
from w inner join x
on w.YEAR = x.YEAR and w.PERIOD = x.PERIOD
inner join y
on w.YEAR = y.YEAR and w.PERIOD = y.PERIOD
inner join z
on w.YEAR = z.YEAR and w.PERIOD = z.PERIOD
where w.YEAR between ? and ? and w.PERIOD between ? and ?
order by YEAR desc, PERIOD desc
I had to change the code slightly for privacy purposes, but I believe this all relays correctly.
Example Data:
Yr Pd Type Amt OM
18 2 A 45 181
18 2 B 33 163
18 2 A 40 153
18 1 B 39 136
18 1 B 24 142
18 1 B 53 143
18 1 A 41 186
18 1 A 78 197
17 12 A 98 139
17 12 A 54 159
17 12 B 78 181
17 12 B 45 101
17 11 A 28 134
17 11 A 77 192
17 11 A 75 110
17 11 B 60 135
17 11 B 83 170
17 10 B 72 114
17 10 A 26 118
17 10 A 95 111
17 9 A 12 112
17 9 B 14 171
Example Results
Yr Pd Sales Bsales MODOM BMODOM
18 2 118 33 804 421
18 1 235 116 580 282
17 12 275 123 741 305
17 11 323 143 343 114
17 10 193 72 283 171
Note that I need SALES at a TOTAL Level, and then at a TYPE B level. I also need OM at a TOTAL Level, and then also a Type B level - HOWEVER - I need to offset by one period. So the MODOM is for 17-10 is reflection of the OM total for period 17-9 in the table. (I hope that make sense).
EDIT I have this backward. The MODOM for 17-10 would actually reflect the OM value for 17-11, not the other way. Corrected EXPECTED RESULTS.
Yr Pd Sales Bsales MODOM BMODOM
18 2 118 33 0 0
18 1 235 116 497 163
17 12 275 123 804 421
17 11 323 143 580 282
17 10 193 72 741 305
So using Conditional aggregation you can at least get rid of 2 of your common table expressions which will help. Not ordering until your final presentation query instead of in each common table expression will help too. Using an actual date e.g. First of First Month of a Period could help eliminate what the potentially costly case expressions when defining previous period.
There are a few ways of writing this but this will give you an example of conditional aggregation.
And note the LEFT JOIN rather than INNER because your 1st period will always drop off your query if you use INNER
WITH PeriodSales AS(
SELECT
yr as YEAR
,pd AS PERIOD
,SUM(amt) as SALES
,SUM(CASE WHEN type = 'B' THEN amt END) as BSALES
FROM
Table
GROUP BY
yr
,pd
)
, PreviousPeriod AS
(
SELECT
CASE WHEN pd = 12 THEN yr + 1 ELSE yr END as YEAR
,CASE WHEN pd = 12 THEN 1 ELSE pd + 1 END as PERIOD
,SUM(OM) as MODOM
,SUM(CASE WHEN type = 'B' THEN OM END) as BMODOM
FROM
Table
GROUP BY
CASE WHEN pd = 12 THEN yr + 1 ELSE yr END
,CASE WHEN pd = 12 THEN 1 ELSE pd + 1 END
)
SELECT
ps.YEAR
,ps.PERIOD
,ps.SALES
,ps.BSALES
,pp.MODOM
,pp.BMODOM
FROM
PeriodSales ps
LEFT JOIN PreviousPeriod pp
ON ps.YEAR = pp.YEAR
AND ps.PERIOD = pp.PERIOD
ORDER BY
ps.YEAR DESC
,ps.PERIOD DESC
Per your edits, to align to the "Previous Period" to get to the OM amounts you want you will actually want to ADD a period not subtract one in the example I used. I have tested this and it does work. There can be many other factors for performance that we cannot discover without knowing more about the tables and execution plans etc.
For school I need to make a function on an auction website. For this I need to join a couple of tables in a VIEW. This worked just fine, until I needed to add a filter for price range. Seems easy enough but the query result needs to allow a NULL when no bid has been placed.
The Statement for the View:
SELECT I.itemID, I.title, I.startPrice, B.highestBid, Cfi.category, I.endDate
FROM dbo.Items AS I INNER JOIN dbo.category_for_item AS Cfi ON V.itemID = Vir.itemID
LEFT OUTER JOIN dbo.Bid AS B ON V.itemID = B.itemID
This would get the following Table:
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 1234 Alfa 25 NULL PC 2018-09-22
3 5678 Bravo 9 20 Console 2018-07-03
4 5678 Bravo 9 15 Console 2018-07-03
5 5678 Bravo 9 NULL Console 2018-07-03
6 9876 Charlie 84 100 Stamps 2018-06-14
7 9876 Charlie 84 90 Stamps 2018-06-14
8 9876 Charlie 84 85 Stamps 2018-06-14
9 9876 Charlie 84 NULL Stamps 2018-06-14
10 1470 Delta 98 100 Fashion 2018-06-15
11 1470 Delta 98 99 Fashion 2018-06-15
12 1470 Delta 98 NULL Fashion 2018-06-15
13 9631 Echo 56 65 Cars 2018-06-25
14 9631 Echo 56 NULL Cars 2018-06-25
15 7856 Foxtrot 98 NULL Dolls 2018-12-26
After looking around for answers I got a query for joining the VIEW on itself with only showing the highest bid instead of all bids:
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH AS VW
INNER JOIN (SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
GROUP BY itemID) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid
This gave the next results:
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 1470 Delta 98 100 Fashion 2018-06-15
5 9631 Echo 56 65 Cars 2018-06-25
As I expected the result only showed the items with at least one bid on them. I tried added one extra condition on the subQuery and Joining RIGHT OUTER to make sure I would not get doubles of an itemID.
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH AS VW
RIGHT OUTER JOIN (SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
WHERE highestBid > 0 OR highestBid IS NULL
GROUP BY itemID) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid
This gave the following results (did not add result 5 - 1199 because it is all the same as result 4, this would happen in the actual table not the example table from above):
itemID title startPrice highestBid category endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 NULL NULL NULL NULL NULL NULL
1200 1470 Delta 98 100 Fashion 2018-06-15
1201 9631 Echo 56 65 Cars 2018-06-25
While this is technicly allowing a NULL in the colums I need to get a result in the likes of :
itemID title startPrice highestBid catgory endDate
1 1234 Alfa 25 26 PC 2018-09-22
2 5678 Bravo 9 20 Console 2018-07-03
3 9876 Charlie 84 85 Stamps 2018-06-14
4 1470 Delta 98 100 Fashion 2018-06-15
5 9631 Echo 56 65 Cars 2018-06-25
6 7856 Foxtrot 98 NULL Dolls 2018-12-26
How do I get the desired result, or is it just impossible?
Also if the query could be written better, please say so.
Thanks in advance.
Solve the problem using a left join:
SELECT VW.itemID, VW.title, VW.startPrice, VW.highestBid, VW.category, VW.endDate
FROM VW_SEARCH VW LEFT JOIN
(SELECT itemID, MAX(highestBid) AS MaxBid
FROM VW_SEARCH
GROUP BY itemID
) VJ
ON VW.itemID = VJ.itemID AND VW.highestBid = VJ.MaxBid;
Or, use the ANSI-standard ROW_NUMBER() function:
select vw.*
from (select vw.*,
row_number() over (partition by itemID
order by highestBid nulls last
) as seqnum
from vw_search vw
) vw
where seqnum = 1;
This guarantees one row per item.
Note: Not all databases support NULLS LAST. This may not even be necessary, but you can also implement it using a case expression.
Can you give the definition of the view at least? Maybe the table definition too.
I would go only with subquery as because identity column :
select vw.*
from vw_search vw
where id = (select vm1.id
from vw_search vm1
where vm.itemID = vw1.itemID and vm1.highestBid is not null
order by vm1.highestBid desc
limit 1
);
However, some DBMS has not support LIMIT clause such as SQL Server if so, then you can use TOP clause instead.
I want to show the date field can not group.
My Query:
SELECT DAY(T1.UI_CreateDate) AS DATEDAY, SUM(1) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1 WHERE T1.UI_BR_BO_ID = 45
GROUP BY DAY(T1.UI_CreateDate)
Result:
DATEDAY TOTALCOUNT
----------- -----------
15 186
9 1
3 2
26 481
21 297
27 342
18 18
30 14
4 183
25 553
13 8
22 469
16 1
17 28
20 331
28 90
14 33
8 1
But i want to show the full date...
Example result:
DATEDAY TOTALCOUNT
----------- -----------
15/06/2015 186
9/06/2015 1
3/06/2015 2
26/06/2015 481
21/06/2015 297
27/06/2015 342
18/06/2015 18
30/06/2015 14
4/06/2015 183
25/06/2015 553
13/06/2015 8
22/06/2015 469
16/06/2015 1
17/06/2015 28
20/06/2015 331
28/06/2015 90
14/06/2015 33
8/06/2015 1
I want to see the results...
I could not get a kind of results...
How can I do?
Thanx!
How about just casting to date to remove any time component:
SELECT CAST(T1.UI_CreateDate as DATE) AS DATEDAY, COUNT(*) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1
WHERE T1.UI_BR_BO_ID = 45
GROUP BY CAST(T1.UI_CreateDate as DATE)
ORDER BY DATEDAY;
SUM(1) for calculating the count does work. However, because SQL has the COUNT(*) function, it seems a bit awkward.
So you can group by DAY(T1.UI_CreateDate) or use full date for grouping. But these are different . As both these dates '2015-04-15' and '2015-12-15' result in same DAY value of 15.
Assuming you want to group on DAY rather than date please try the below version of query:
SELECT DISTINCT
T1.UI_CreateDate as DATEDAY,
count(1) over (PARTITION BY DAY(T1.UI_CreateDate) ) AS TOTALCOUNT
FROM mydb.dbo.LP_UseImpression T1 WHERE T1.UI_BR_BO_ID = 45
sql fiddle for demo: http://sqlfiddle.com/#!6/c3337/1