GROUP BY with MAX and UNION, or JOIN? - sql

How to obtain from this table date_departure and date_arrival for each travel according visiting_order
select * from step;
id_step | id_travel | id_port | visiting_order | date_arrival | date_departure
---------+-----------+---------+----------------+--------------+----------------
1 | 1 | 1 | 0 | | 2021-01-12
2 | 1 | 2 | 1 | 2021-05-20 | 2021-05-22
3 | 1 | 3 | 2 | 2021-07-27 |
4 | 2 | 4 | 0 | | 2021-02-13
5 | 2 | 5 | 1 | 2021-02-27 |
6 | 3 | 7 | 0 | | 2022-01-12
7 | 3 | 6 | 1 | 2022-05-27 |
like this :
id_travel | date_departure | date_arrival
------------+----------------+--------------
1 | 2021-01-12 | 2021-07-27
2 | 2021-02-13 | 2021-02-27
3 | 2022-01-12 | 2022-05-27
?
My first intention was to take both columns and UNION them
(SELECT id_travel, date_departure FROM step WHERE visiting_order = 0
GROUP BY id_travel, date_departure)
UNION
(SELECT A.id AS id_travel, A.arr_date AS date_arrival FROM
(SELECT id_travel, MAX(visiting_order), date_arrival
FROM step GROUP BY id_travel
) AS A(id, ord, arr_date)
);
and first select is ok
id_travel | date_departure
-----------+----------------
1 | 2021-01-12
2 | 2021-02-13
3 | 2022-01-12
but second one return an error
ERROR: column "step.date_arrival" must appear in the GROUP BY clause or be used in an aggregate function

Seems like this can just be:
SELECT id_travel
, min(date_departure) AS date_departure
, max(date_arrival) AS date_arrival
FROM step
GROUP BY 1
ORDER BY 1;
Certainly works with your sample data.

SELECT DISTINCT step.id_travel, sub_query1.date_departure, sub_query.date_arrival
FROM step
INNER JOIN
(SELECT step.id_travel, MAX(step.date_arrival) as date_arrival
FROM step
GROUP BY step.id_travel
)
AS sub_query ON (sub_query.id_travel = step.id_travel)
INNER JOIN
(SELECT id_travel, date_departure FROM step WHERE visiting_order = 0 GROUP BY id_travel, date_departure)
AS sub_query1 ON (sub_query1.id_travel = step.id_travel)
ORDER BY id_travel;

Related

Combine 2 tables into 1 (shared common column)

I believe my problem appeared somewhere on this site (of course they are resolved) but those solutions did not work for my case.
I have:
Table 1
isbn | in_count
123 | 3
124 | 1
Table 2
isbn | out_count
125 | 1
126 | 1
My expected result:
isbn | in_count | out_count
123 | 3 | 0
124 | 1 | 0
125 | 0 | 1
126 | 0 | 1
I've tried with the sql:
SELECT * FROM
(SELECT o.isbn, COUNT(*) AS out_count FROM out_tbl o GROUP BY o.isbn) t1
FULL OUTER JOIN
(SELECT i.isbn, COUNT(*) AS in_count FROM in_tbl i GROUP BY i.isbn) t2
ON t1.isbn = t2.isbn;
But what I got in return was:
isbn | in_count | isbn | out_count
123 | 3 | null | null
124 | 1 | null | null
null | null | 125 | 1
null | null | 126 | 1
Please help with this. Really appreciate!
SELECT T.isbn,T.in_count,0 AS out_count
FROM TABLE_1 AS T
UNION ALL
SELECT X.isbn,0,X.out_count
FROM TABLE_2 AS X

Query to show top 3 records per user where the user has submitted a minimum of 3?

I have in a table in MS SQL with multiple entries per user. I am trying to get the top 3 entries by date for each user. I have a query that returns returns the maximum top 3 entries per user but is also returning users which have submitted 2 or 1 entries. I have a join with another table only to get the email address. I would like it to return only the entries by john and dave as they have 3 entries. If they have more than 3 just return the top 3 by submitmonth.
select * from (
select m.Email, q.submitmonth, q.A2, q.A7, q.C7, q.C8, q.C16, q.F9, q.F10, q.G4, q.H1, q.H2, q.J2, q.J13, q.K18, q.N1, q.P6,
row_number() over (partition by q.userid order by q.submitmonth desc) as Submitted
from dbo.submission q
left join dbo.users m
on q.UserId = m.UserId ) ranks
where Submitted < 4
this returns
| Email | submitmonth | A2 | A7 | Submitted
| | | | |
| john#yahoo.com | 01/08/2020 | 2 | 4 | 1
| john#yahoo.com | 01/07/2020 | 8 | 8 | 2
| john#yahoo.com | 01/06/2020 | 2 | 1 | 3
| bob#gmail.com | 01/08/2020 | 1 | 3 | 1
| bob#gmail.com | 01/07/2020 | 9 | 7 | 2
| pete#yahoo.co.uk | 01/08/2020 | 8 | 5 | 1
| dave#gmail.com | 01/06/2020 | 3 | 6 | 1
| dave#gmail.com | 01/04/2020 | 5 | 6 | 2
| dave#gmail.com | 01/02/2020 | 1 | 6 | 3
Thanks for your help.
Add the count window function and then filter on it.
select *
from (
select m.Email, q.submitmonth, q.A2, q.A7, q.C7, q.C8, q.C16, q.F9, q.F10, q.G4, q.H1, q.H2, q.J2, q.J13, q.K18, q.N1, q.P6
, row_number() over (partition by q.userid order by q.submitmonth desc) as Submitted
, count(*) over (partition by q.userid) TotalSubmitted
from dbo.submission q
left join dbo.users m on q.UserId = m.UserId
) ranks
where Submitted < 4 and TotalSubmitted >= 3

SQL Server Get all Birthday Years

I have a table in SQL Server that is Composed of
ID, B_Day
1, 1977-02-20
2, 2001-03-10
...
I want to add rows to this table for each year of a birthday, up to the current birthday year.
i.e:
ID, B_Day
1,1977-02-20
1,1978-02-20
1,1979-02-20
...
1,2020-02-20
2, 2001-03-10
2, 2002-03-10
...
2, 2019-03-10
I'm struggling to determine what the best strategy for accomplishing this. I thought about recursively self-joining, but that creates far too many layers. Any suggestions?
The following should work
with row_gen
as (select top 200 row_number() over(order by name)-1 as rnk
from master..spt_values
)
select a.id,a.b_day,dateadd(year,rnk,b_day) incr_b_day
from dbo.t a
join row_gen b
on dateadd(year,b.rnk,a.b_day)<=getdate()
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=0d06c95e1914ca45ca192d0d192bd2e0
You can use recursive approach :
with cte as (
select t.id, t.b_day, convert(date, getdate()) as mx_dt
from table t
union all
select c.id, dateadd(year, 1, c.b_day), c.mx_dt
from cte c
where dateadd(year, 1, c.b_day) < c.mx_dt
)
select c.id, c.b_day
from cte c
order by c.id, c.b_day;
Default recursion is 100, you can add query hint for more recursion option (maxrecursion 0).
If your dataset is not too big, one option is to use a recursive query:
with cte as (
select id, b_day bday0, b_day, 1 lvl from mytable
union all
select
id,
bday0,
dateadd(year, lvl, bday0), lvl + 1
from cte
where dateadd(year, lvl, bday0) <= getdate()
)
select id, b_day from cte order by id, b_day
Demo on DB Fiddle:
id | b_day
-: | :---------
1 | 1977-02-20
1 | 1978-02-20
1 | 1979-02-20
1 | 1980-02-20
1 | 1981-02-20
1 | 1982-02-20
1 | 1983-02-20
1 | 1984-02-20
1 | 1985-02-20
1 | 1986-02-20
1 | 1987-02-20
1 | 1988-02-20
1 | 1989-02-20
1 | 1990-02-20
1 | 1991-02-20
1 | 1992-02-20
1 | 1993-02-20
1 | 1994-02-20
1 | 1995-02-20
1 | 1996-02-20
1 | 1997-02-20
1 | 1998-02-20
1 | 1999-02-20
1 | 2000-02-20
1 | 2001-02-20
1 | 2002-02-20
1 | 2003-02-20
1 | 2004-02-20
1 | 2005-02-20
1 | 2006-02-20
1 | 2007-02-20
1 | 2008-02-20
1 | 2009-02-20
1 | 2010-02-20
1 | 2011-02-20
1 | 2012-02-20
1 | 2013-02-20
1 | 2014-02-20
1 | 2015-02-20
1 | 2016-02-20
1 | 2017-02-20
1 | 2018-02-20
1 | 2019-02-20
1 | 2020-02-20
2 | 2001-03-01
2 | 2002-03-01
2 | 2003-03-01
2 | 2004-03-01
2 | 2005-03-01
2 | 2006-03-01
2 | 2007-03-01
2 | 2008-03-01
2 | 2009-03-01
2 | 2010-03-01
2 | 2011-03-01
2 | 2012-03-01
2 | 2013-03-01
2 | 2014-03-01
2 | 2015-03-01
2 | 2016-03-01
2 | 2017-03-01
2 | 2018-03-01
2 | 2019-03-01
2 | 2020-03-01

Group-by if all field results are true

I have this query:
SELECT
tbl_ord.ord.table,
tbl_ord.ord.n_ord,
player.confirm
FROM
tbl_ord
INNER JOIN
tbl_players ON tbl_ord.player_id = tbl_players.player_ids
WHERE
id_shop = 3
ORDER BY
n_ord ASC
result:
+-----------+-------+---------+
| ord_table | n_ord | confirm |
+-----------+-------+---------+
| 10 | 2 | 1 |
| 10 | 2 | 0 |
| 8 | 3 | 1 |
| 8 | 3 | 1 |
| 4 | 5 | 1 |
| 4 | 5 | 1 |
+-----------+-------+---------+
I'd like get only result with all confirmed users by group-by on ord_table
+-----------+-------+---------+
| ord_table | n_ord | confirm |
+-----------+-------+---------+
| 4 | 5 | 1 |
| 8 | 3 | 1 |
+-----------+-------+---------+
Thanks!
You can probably use not exists:
SELECT o.ord_table, o.ord.n_ord,
1 as confirm
FROM tbl_ord o
WHERE NOT EXISTS (SELECT 1
FROM tbl_players p
WHERE o.player_id = p.player_ids AND
?.id_shop = 3 AND -- not sure what table this comes from
p.confirm = 0
)
ORDER BY o.n_ord ASC;
The advantage of this approach is that it avoids aggregating at the outer level. This, in turn, means that it can make better use of indexes, including using an index to potentially avoid sorting.
Use HAVING:
SELECT
tbl_ord.ord.table,
tbl_ord.ord.n_ord,
MIN(player.confirm)
FROM tbl_ord
INNER JOIN tbl_players ON tbl_ord.player_id = tbl_players.player_ids
WHERE id_shop = 3
GROUP BY tbl_ord.ord.table, tbl_ord.ord.n_ord,
HAVING MIN(player.confirm) = 1
ORDER BY n_ord ASC

Counting on multiple columns

I have a table like this:
+------------+---------------+-------------+
|store_number|entrance_number|camera_number|
+------------+---------------+-------------+
| 1 | 1 | 1 |
| 1 | 1 | 2 |
| 2 | 1 | 1 |
| 2 | 2 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 1 |
| 4 | 1 | 1 |
| 4 | 1 | 2 |
| 4 | 2 | 1 |
| 4 | 3 | 1 |
+------------+---------------+-------------+
In summary the stores are numbered 1 and up, the entrances are numbered 1 and up for each store, and the cameras are numbered 1 and up for each entrance.
What I want to do is count how many how many entrances in total, and how many cameras in total for each store. Producing this result from the above table:
+------------+---------------+-------------+
|store_number|entrances |cameras |
+------------+---------------+-------------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 1 |
| 4 | 3 | 4 |
+------------+---------------+-------------+
How can I count on multiple columns to produce this result?
You can do this with a GROUP BY and a COUNT() of each item:
Select Store_Number,
Count(Distinct Entrance_Number) as Entrances,
Count(Camera_Number) As Cameras
From YourTable
Group By Store_Number
From what I can tell from your expected output, you're looking for the number of cameras that appear, whilst also looking for the DISTINCT number of entrances.
This will work as well,
DECLARE #store TABLE
( store_number INT,entrance_number INT,camera_number INT)
INSERT INTO #store VALUES(1,1,1),(1,1,2),(2,1,1),(2,2,1),
(2,2,2),(3,1,1),(4,1,1),(4,1,2),(4,2,1),(4,3,1)
SELECT AA.s store_number, BB.e entrances,AA.c cameras FROM (
SELECT s,COUNT(DISTINCT c) c FROM ( SELECT store_number s,
CONVERT(VARCHAR,store_number) + CONVERT(VARCHAR,entrance_number) +
CONVERT(VARCHAR,camera_number) c FROM #store ) A GROUP BY s ) AA
LEFT JOIN
( SELECT s,COUNT(DISTINCT e) e FROM ( SELECT store_number s,
CONVERT(VARCHAR,store_number) + CONVERT(VARCHAR,entrance_number) e
FROM #store ) B GROUP BY s ) BB ON AA.s = BB.s
Hope it helped. :)