Select top 2 rows different from each other - sql

I have this table
| date | sum |
|--------------|-------|
| 2015-02-19 | 10000 |
| 2015-02-19 | 10000 |
| 2015-02-20 | 15000 |
| 2015-02-20 | 15000 |
| 2015-02-21 | 18000 |
| 2015-02-21 | 18000 |
I want to select top 2 rows from the table, but only different ones, meaning my result should return 2015-02-20 and 2015-02-21.
SELECT TOP 2 distinct date
FROM stock
Using this gives me an error:
Incorrect syntax near the keyword 'distinct'.
Help would be highly appreciated.

You can try like this
select top 2 * from
(
select distinct date FROM stock
)

Try something like:
SELECT TOP 2 date
FROM stock
GROUP BY date

I think Distinct and Top should switch places in your query:
SELECT DISTINCT TOP 2 date FROM stock ORDER BY date DESC

try
select distinct top 2 date from stock

You can use GROUP BY:
SELECT TOP 2 date
FROM stock
GROUP BY date
ORDER BY date DESC
Sample result:
DATE
2015-02-21
2015-02-20
See result in SQL Fiddle.

Try this :
WITH cte AS
( SELECT distinct date ,
ROW_NUMBER() OVER (PARTITION BY date
ORDER BY date DESC
)
AS rn
FROM stock
)
SELECT date
FROM cte
WHERE rn <= 3
ORDER BY rn ;

Try this:
SELECT TOP 2 date FROM stock group by date

Related

Create all months list from a date column in ORACLE SQL

CREATE TABLE dates(
alldates date);
INSERT INTO dates (alldates) VALUES ('1-May-2017');
INSERT INTO dates (alldates) VALUES ('1-Mar-2018');
I want to generate all months beginning between these two dates. I am very new to Oracle SQL. My solution is below, but it is not working properly.
WITH t1(test) AS (
SELECT MIN(alldates) as test
FROM dates
UNION ALL
SELECT ADD_MONTHS(test,1) as test
FROM t1
WHERE t1.test<= (SELECT MAX(alldates) FROM date)
)
SELECT * FROM t1
The result I want should look like
Test
2017-02-01
2017-03-01
...
2017-12-01
2018-01-01
2018-02-01
2018-03-01
You made a typo and wrote date instead of dates but you also need to make a second change and use ADD_MONTHS in the recursive query's WHERE clause or you will generate one too many rows.
WITH t1(test) AS (
SELECT MIN(alldates)
FROM dates
UNION ALL
SELECT ADD_MONTHS(test,1)
FROM t1
WHERE ADD_MONTHS(test,1) <= (SELECT MAX(alldates) FROM dates)
)
SELECT * FROM t1
Which outputs:
| TEST |
| :-------- |
| 01-MAY-17 |
| 01-JUN-17 |
| 01-JUL-17 |
| 01-AUG-17 |
| 01-SEP-17 |
| 01-OCT-17 |
| 01-NOV-17 |
| 01-DEC-17 |
| 01-JAN-18 |
| 01-FEB-18 |
| 01-MAR-18 |
However, a more efficient query would be to get the minimum and maximum values in the same query and then iterate using these pre-found bounds:
WITH t1(min_date, max_date) AS (
SELECT MIN(alldates),
MAX(alldates)
FROM dates
UNION ALL
SELECT ADD_MONTHS(min_date,1),
max_date
FROM t1
WHERE ADD_MONTHS(min_date,1) <= max_date
)
SELECT min_date AS month
FROM t1
db<>fiddle here
Update
Oracle 11gR2 has bugs handling recursive date queries; this is fixed in later Oracle versions but if you want to use SQL Fiddle and Oracle 11gR2 then you need to iterate over a numeric value and not a date. Something like this:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE dates(
alldates date);
INSERT INTO dates (alldates) VALUES ('1-May-2017');
INSERT INTO dates (alldates) VALUES ('1-Mar-2018');
Query 1:
WITH t1(min_date, month, total_months) AS (
SELECT MIN(alldates),
0,
MONTHS_BETWEEN(MAX(alldates),MIN(alldates))
FROM dates
UNION ALL
SELECT min_date,
month+1,
total_months
FROM t1
WHERE month+1<=total_months
)
SELECT ADD_MONTHS(min_date,month) AS month
FROM t1
Results:
| MONTH |
|----------------------|
| 2017-05-01T00:00:00Z |
| 2017-06-01T00:00:00Z |
| 2017-07-01T00:00:00Z |
| 2017-08-01T00:00:00Z |
| 2017-09-01T00:00:00Z |
| 2017-10-01T00:00:00Z |
| 2017-11-01T00:00:00Z |
| 2017-12-01T00:00:00Z |
| 2018-01-01T00:00:00Z |
| 2018-02-01T00:00:00Z |
| 2018-03-01T00:00:00Z |
You seem to want a recursive CTE. That syntax would be:
WITH CTE(min_date, max_date) as (
SELECT MIN(alldates) as min_date, MAX(alldates) as max_date
FROM dates
UNION ALL
SELECT add_months(min_date, 1), max_date
FROM CTE
WHERE min_date < max_date
)
SELECT min_date
FROM CTE;
Here is a db<>fiddle.
You just made a typo: date instead of dates:
WITH t1(test) AS (
SELECT MIN(alldates) as test
FROM dates
UNION ALL
SELECT ADD_MONTHS(test,1) as test
FROM t1
WHERE t1.test<= (SELECT MAX(alldates) FROM dateS) -- fixed here
)
SELECT * FROM t1

Count distinct dates within timestamp by Month SQL

I would like to count the number of distinct dates within each month.
I have a data set that looks like this:
TIMESTAMP
------------------
2017-10-25 14:39:51
2017-10-25 15:00:51
2017-11-10 02:39:42
2018-09-24 14:39:55
2018-09-25 13:25:01
2019-02-12 12:23:44
...
So my expected output would be:
year_month | count
2017-10 2
2018-09 2
2019-02 1
I have tried the following code so far, but it is returning incorrect results:
WITH F AS(
SELECT concat(YEAR(TIMESTAMP), MONTH(TIMESTAMP)) AS year_month
FROM tbl
WHERE TYPE = 'Site'
AND TO_SITE = 'location'
)
SELECT count(year_month), year_month
FROM F
GROUP BY year_month
I do not need to worry about the time of day. I just want to count the distinct days in each month. Thank you in advance for your help.
In SQL Server, I would recommend one of these approaches:
select year(timestamp), month(timestamp), count(distinct convert(date, timestamp)
from t
group by year(timestamp), month(timestamp);
Or:
select format(timestamp, 'yyyy-MM'), count(distinct convert(date, timestamp))
from t
group by format(timestamp, 'yyyy-MM');
I see no need for subqueries or CTEs.
from your code I assume you are using a SQL Server then you can do something like this
with cte as
(
select
left(convert(varchar, myCol,112),6) as yyyy_mm,
convert(date, myCol) as date
from myTable
)
select
yyyy_mm,
count(distinct date) as count
from cte
group by
yyyy_mm
output:
| yyyy_mm | count |
*-----------------*
| 201710 | 1 |
| 201711 | 1 |
| 201809 | 2 |
| 201902 | 1 |
Using date_trunc() in PostgreSQL it can simply be:
SELECT date_trunc('month', timestamp)
, count(DISTINCT date_trunc('day', timestamp))
FROM tbl
GROUP BY 1;
Various performance optimizations possible, depending on details of the setup.

get the id based on condition in group by

I'm trying to create a sql query to merge rows where there are equal dates. the idea is to do this based on the highest amount of hours, so that i in the end gets the corresponding id for each date with the highest amount of hours. i've been trying to do with a simple group by, but does not seem to work, since i CANT just put a aggregate function on id column, since it should be based the hours condition
+------+-------+--------------------------------------+
| id | date | hours |
+------+-------+--------------------------------------+
| 1 | 2012-01-01 | 37 |
| 2 | 2012-01-01 | 10 |
| 3 | 2012-01-01 | 5 |
| 4 | 2012-01-02 | 37 |
+------+-------+--------------------------------------+
desired result
+------+-------+--------------------------------------+
| id | date | hours |
+------+-------+--------------------------------------+
| 1 | 2012-01-01 | 37 |
| 4 | 2012-01-02 | 37 |
+------+-------+--------------------------------------+
If you want exactly one row -- even if there are ties -- then use row_number():
select t.*
from (select t.*, row_number() over (partition by date order by hours desc) as seqnum
from t
) t
where seqnum = 1;
Ironically, both Postgres and Oracle (the original tags) have what I would consider to be better ways of doing this, but they are quite different.
Postgres:
select distinct on (date) t.*
from t
order by date, hours desc;
Oracle:
select date, max(hours) as hours,
max(id) keep (dense_rank first over order by hours desc) as id
from t
group by date;
Here's one approach using row_number:
select id, dt, hours
from (
select id, dt, hours, row_number() over (partition by dt order by hours desc) rn
from yourtable
) t
where rn = 1
You can use subquery with correlation approach :
select t.*
from table t
where id = (select t1.id
from table t1
where t1.date = t.date
order by t1.hours desc
limit 1);
In Oracle you can use fetch first 1 row only in subquery instead of LIMIT clause.

SQL Find Last Entry Closest to a Date

I am trying to filter the last entry in a table closet to a defined date and I am having difficulties. Any input is greatly appreciated. Thanks! I am running Microsoft SQL Server 2008.
Table:
code | account | date | amount
1 | 1234 | 2016-02-28 | 500
2 | 1234 | 2016-03-01 | 650
3 | 1234 | 2016-03-05 | 842
4 | 7890 | 2016-02-28 | 500
5 | 7890 | 2016-03-30 | 550
I want to select only entries with a date closest to March 31 ('2016-03-31'). In this example, the entry closest to 2016-03-31 for account 1234 is entry #3 and the entry closest to 2016-03-31 for account 7890 is entry #5. In other words, I want the last entry for all accounts equal to or before a date.
3 | 1234 | 2016-03-05 | 842
5 | 7890 | 2016-03-30 | 550
Most DBMSes (including MS SQL Server) support Analytical Functions:
select *
from
(
select *,
row_number() -- create a ranking
over (partition by account -- for each account
order by date desc) as rn -- based on descending dates
from tab
where date <= date '2016-03-31'
) dt
where rn = 1 -- return the row with the "closest" date
Since no DBMS is specified, here's a kind of hacky way to do this in SQL Server. It grabs the record just before and just after the specified date:
select * from (
select top(1) * FROM mytable
where date >= '2016-03-31' order by date asc
) t1
union
select * from (
select top(1) * FROM mytable
where date <= '2016-03-31' order by date desc
) t2
This should do what you want, and it should be easy enough to understand to don't need further explanation:
select t.*
from your_table t
join (
select account, max(date) as date
from your_table
where date <= '2016-03-31'
group by account
) as subquery on t.account = subquery.account and t.date = subquery.date
Edit: for SQL Server it might be better to use an analytical function (like row_number)

Remove duplicate rows query result except for one in Microsoft SQL Server?

How would I delete all duplicate month from a Microsoft SQL Server Table?
For example, with the following syntax I just created:
SELECT * FROM Cash WHERE Id = '2' AND TransactionDate between '2014/07/01' AND '2015/02/28'
and the query result is:
+----+-------------------------+
|Id | TransactionDate |
+----+-------------------------+
| 2 | 2014-07-22 00:00:00.000 |
| 2 | 2014-08-09 00:00:00.000 |
| 2 | 2014-08-25 00:00:00.000 |
| 2 | 2014-08-29 00:00:00.000 |
| 2 | 2015-01-27 00:00:00.000 |
| 2 | 2015-01-28 00:00:00.000 |
+----+-------------------------+
How would I remove duplicates month which is only return any 1 value for any 1 month each, like this result:
+----+-------------------------+
|Id | TransactionDate |
+----+-------------------------+
| 2 | 2014-07-22 00:00:00.000 |
| 2 | 2014-08-09 00:00:00.000 |
| 2 | 2015-01-27 00:00:00.000 |
+----+-------------------------+
You can do it with the help of ROW_NUMBER.
This will tell you which are the rows you are going to keep
SELECT id,transactionDate, ROW_NUMBER() OVER ( PARTITION BY YEAR(TransactionDate ),MONTH(TransactionDate ) ORDER BY TransactionDate ) firstTrans
FROM Cash
WHERE Id = '2' AND
TransactionDate between '2014/07/01' AND '2015/02/28'
You can delete the other rows with a CTE.
with myCTE (id,transactionDate, firstTrans) AS (
SELECT id,transactionDate, ROW_NUMBER() OVER ( PARTITION BY YEAR(TransactionDate ),MONTH(TransactionDate ) ORDER BY TransactionDate ) firstTrans
FROM Cash
WHERE Id = '2' AND
TransactionDate between '2014/07/01' AND '2015/02/28'
)
delete from myCTE where firstTrans <> 1
Will only keep one transaction for each month of each year.
EDIT:
filter by the row_number and will only return the rows you want
select id, transactionDate from (SELECT id,transactionDate, ROW_NUMBER() OVER ( PARTITION BY YEAR(TransactionDate ),MONTH(TransactionDate ) ORDER BY TransactionDate ) firstTrans
FROM Cash
WHERE Id = '2' AND
TransactionDate between '2014/07/01' AND '2015/02/28') where firstTrans = 1
When you run this query you will get the highest Id for each month in each year.
SELECT MAX(<IdColumn>) AS Id, YEAR(<DateColumn>) AS YE, MONTH(<DateColumn>) AS MO FROM <YourTable>
GROUP BY YEAR(<DateColumn>), MONTH(<DateColumn>)
If needed, for example, you can late delete rows that their Id is not in this query.
Select only the first row per month
SELECT *
FROM Cash c
WHERE c.Id = '2'
AND c.TransactionDate between '2014/07/01' AND '2015/02/28'
AND NOT EXISTS ( SELECT 'a'
FROM Cash c2
WHERE c2.Id = c.Id
AND YEAR(c2.TransactionDate) * 100 + MONTH(c2.TransactionDate) = YEAR(c.TransactionDate) * 100 + MONTH(c.TransactionDate)
AND c2.TransactionDate < c.TransactionDate
)