SQL query with GROUP BY and HAVING - sql

I have a little problem in mi project, i'm trying to make a query on a single table but I'm not succeeding.
The table is this:
CREATE TABLE PARTITA(
COD_SFIDA VARCHAR (20) PRIMARY KEY,
DATA_P DATE NOT NULL,
RISULTATO CHAR (3) NOT NULL,
COD_DECK_IC VARCHAR (15),
COD_DECK_FC VARCHAR (15),
COD_EVT VARCHAR (15),
TAG_USR_IC VARCHAR (15),
TAG_USR_FC VARCHAR (15),
CONSTRAINT CHECK_RISULTATO CHECK (RISULTATO='0-0' OR RISULTATO='0-1' OR RISULTATO='1-0' OR RISULTATO='1-1'),
CONSTRAINT FK8 FOREIGN KEY (COD_DECK_IC, TAG_USR_IC) REFERENCES DECK (COD_DECK, TAG_USR) ON DELETE CASCADE,
CONSTRAINT FK17 FOREIGN KEY (COD_DECK_FC, TAG_USR_FC) REFERENCES DECK (COD_DECK, TAG_USR) ON DELETE CASCADE,
CONSTRAINT FK9 FOREIGN KEY (COD_EVT) REFERENCES TORNEO (COD_EVENTO) ON DELETE CASCADE
);
I would like to view the most used deck by each user.
this is the query I tried to do:
SELECT P.COD_DECK_FC, P.TAG_USR_FC, COUNT(P.COD_DECK_FC)
FROM PARTITA P
GROUP BY P.TAG_USR_FC, P.COD_DECK_FC
UNION
SELECT P.COD_DECK_IC, P.TAG_USR_IC, COUNT(P.COD_DECK_IC)
FROM PARTITA P
GROUP BY P.TAG_USR_IC, P.COD_DECK_IC
/
But I would like to view just the most used deck by each user and don't all the decks and how many times users used them.
How can I do?
I would like the query to show the tag_usr and the cod_deck that is used the most for all of this for each user
eg:
cod_deck tag_usr count(cod_deck)
------------- ----------- --------------
1 A1BE2 5
2 AE3NF 6
5 FNKJD 3
instead the previious query returns to me:
cod_deck tag_usr count(cod_deck)
------------- ----------- --------------
1 A1BE2 5
2 AE3NF 6
5 FNKJD 3
2 A1BE2 2
1 AE3NF 3
I just want that the query show me the users A1BE2 and AE3NF just one time, because the query have to select the most used deck of each user.

You don't want to select a field that you're counting. Try something like this:
SELECT P.COD_DECK_FC, P.TAG_USR_FC, COUNT(P.COD_SFIDA)
FROM PARTITA P
GROUP BY P.COD_DECK_FC, P.TAG_USR_FC
UNION
SELECT P.COD_DECK_IC, P.TAG_USR_IC, COUNT(P.COD_SFIDA)
FROM PARTITA P
GROUP BY P.COD_DECK_IC, P.TAG_USR_IC
That will list all of the combinations of COD_DECK_FC and TAG_USR_FC
and then number of times it appears in the table, and then do the same with COD_DECK_IC and TAG_USR_IC. It's not clear to me from your question exactly what you want, but I know that you shouldn't put a field in COUNT if you're selecting it.

If i understand correctly you need subquery with ranking function :
with t as (
select *, row_number() over (partition by cod_deck order by count desc) Seq
from (<union query>)
)
select *
from cte c
where seq = 1;

I think you want this:
with ct as (
select P.COD_DECK_FC as deck, P.TAG_USR_FC as usr, COUNT(P.COD_DECK_FC) as cnt
from partita p
group by P.TAG_USR_FC, P.COD_DECK_FC
union all
select P.COD_DECK_IC, P.TAG_USR_IC, COUNT(P.COD_DECK_IC)
from partita P
group by P.TAG_USR_IC, P.COD_DECK_IC
)
select ct.*
from (select ct.*,
row_number() over (partition by usr order by cnt desc) as seqnum
from ct
) ct
where seqnum = 1;
You can also shorten this using grouping sets:
select p.*
from (select coalesce(P.COD_DECK_FC, P.COD_DECK_IC) as deck,
coalesce(P.TAG_USR_FC, P.TAG_USR_IC) as usr,
count(*) as cnt,
row_number() over (partition by coalesce(P.TAG_USR_FC, P.TAG_USR_IC) order by count(*) desc) as seqnum
from partita p
group by grouping sets ( (P.TAG_USR_FC, P.COD_DECK_FC), P.TAG_USR_IC, P.COD_DECK_IC) )
) p
where seqnum = 1;

Related

SQL: create a view comparing different rows

I have data for movies that looks something like this:
cast_id | cast_name | movie_id
1 A 11
2 B 11
3 C 11
4 D 12
5 E 12
1 A 13
I want to create a view where I compare two different cast members so that I will start with something like this:
CREATE VIEW compare(cast_id_1, cast_id_2, num_movies);
SELECT * FROM compare LIMIT 1;
(1,2,2)
where I am looking at actor A and actor B, who have a total of 2 movies between the two of them.
Not sure how to compare the two different rows and my searchers so far have been unsuccessful. Any help is much appreciated!
That's a self-join:
create view myview as
select t1.cast_id cast_id_1, t2.cast_id cast_id_2, count(*) num_movies
from mytable t1
inner join mytable t2 on t2.movie_id = t1.movie_id and t1.cast_id < t2.cast_id
group by t1.cast_id, t2.cast_id
Thives generates all combinations of cast members that once appeared in the same movie, with the total number of movies. Join condition t1.cast_id < t2.cast_id is there to avoid "mirror" records.
You can then query the view. If you want members that have two common movies (which is actually not showing in your sample data...):
select * from myview where num_movies = 2
I'm thinking a procedure might be helpful. This stored procedure takes the 2 cast_id's and num_movies as input parameters. It selects the movie_id's of the movies the two cast_id's have appeared in together. Then based on whether or not that number exceeds the num_movies parameter: either 1) a list of movies (release dates, director, etc.) is returned, else the message 'Were not in 2 movies together' is returned.
drop proc if exists TwoMovieActors;
go
create proc TwoMovieActors
#cast_id_1 int,
#cast_id_2 int,
#num_movies int
as
set nocount on;
declare #m table(movie_id int unique not null,
rn int not null);
declare #rows int;
with
cast_cte as (
select *, row_number() over (partition by movie_id order by cast_name) rn
from movie_casts mc
where cast_id in(#cast_id_1, #cast_id_2))
insert #m
select movie_id, row_number() over (order by movie_id) rn
from cast_cte
where rn=2
select #rows=##rowcount;
if #rows<#num_movies
select concat('Were not in ', cast(#num_movies as varchar(11)), ' movies together');
else
select m.movie_id, mv.movie_name, mv.release_date, mv.director
from #m m
join movies mv on m.movie_id=mv.movie_id;
To execute it would be something like
exec TwoMovieActors 1, 2, 2;

Useage of aggregate function but without having clause

Im have the following two tables created:
create table partei(
id int not null primary key ,
name varchar(20),
vorsitzender varchar(20)
);
create table abgeordneter(
name varchar(20),
partei int references partei ,
wahlkreis varchar(20)
)
How can I change this Select-Statement:
SELECT a.Partei
FROM Abgeordneter a, Partei p
WHERE a.Partei = p.ID
GROUP BY a.Partei
HAVING COUNT(a.Name) < 5
Into a statement which doesn't use the having clause, but delivers exactly the same results? Is it even possible?
You can use a subquery an eliminate the JOIN:
SELECT Partei
FROM (SELECT a.Partei, COUNT(*) as cnt
FROM Abgeordneter ap
GROUP BY a.Partei
) a
WHERE cnt < 5;

SQL- How to select data from two different table?

I am working to select data from 2 different table but I can't figured out. If I use INNER JOIN it show noting. Any help are welcome and Thanks.
My First table:
CREATE TABLE P_N(
PN_ID int NOT NULL,
PN VARCHAR (1000),
primary key (PN_ID)
);
My second Table:
CREATE TABLE NAME (
NAME_ID VARCHAR(60) PRIMARY key,
NAME VARCHAR (40)
);
My select code :
SELECT DISTINCT NAME.NAME_ID, PN.PN_ID
FROM NAME
FULL JOIN P_N
ON PN.PN =NAME.NAME_ID;
If I use left or full Join this is the result:
NAME_ID PN_ID
nm0006300 NULL
nm0006400 NULL
nm0006500 NULL
nm0006600 NULL
nm0006700 NULL
AND if I use right join:
NAME_ID PN_ID
null 921691
null 921692
null 921693
null 921694
This is what I want the result to looks like For example:
NAME_ID PN_ID
nm0006300 921691
nm0006400 921692
nm0006500 921693
nm0006600 921694
You don't seem to have a JOIN key. You can add one with ROW_NUMBER():
SELECT n.NAME_ID, PN.PN_ID
FROM (SELECT n.*, ROW_NUMBER() OVER (ORDER BY NAME_ID) as seqnum
FROM NAME n
) n JOIN
(SELECT pn.*, ROW_NUMBER() OVER (ORDER BY PN) as seqnum
FROM P_N pn
) pn
ON PN.seqnum = n.seqnum;
try this
select DISTINCT NAME.NAME_ID, PN.PN_ID
from NAME,P_N as PN
where PN.PN =NAME.NAME_ID

PostgreSQL 9.3: Pivot table query

I want to show the pivot table(crosstab) for the given below table.
Table: Employee
CREATE TABLE Employee
(
Employee_Number varchar(10),
Employee_Role varchar(50),
Group_Name varchar(10)
);
Insertion:
INSERT INTO Employee VALUES('EMP101','C# Developer','Group_1'),
('EMP102','ASP Developer','Group_1'),
('EMP103','SQL Developer','Group_2'),
('EMP104','PLSQL Developer','Group_2'),
('EMP101','Java Developer',''),
('EMP102','Web Developer','');
Now I want to show the pivot table for the above data as shown below:
Expected Result:
Employee_Number TotalRoles TotalGroups Available Others Group_1 Group_2
---------------------------------------------------------------------------------------------------
EMP101 2 2 1 1 1 0
EMP102 2 2 1 1 1 0
EMP103 1 2 1 0 0 1
EMP104 1 2 1 0 0 1
Explanation: I want to show the Employee_Number, the TotalRoles which each employee has,
the TotalGroups which are present to all employees, the Available shows the employee available
in how many groups, the Others have to show the employee is available in other's also for which
the group_name have not assigned and finally the Group_Names must be shown in the pivot format.
SELECT * FROM crosstab(
$$SELECT grp.*, e.group_name
, CASE WHEN e.employee_number IS NULL THEN 0 ELSE 1 END AS val
FROM (
SELECT employee_number
, count(employee_role)::int AS total_roles
, (SELECT count(DISTINCT group_name)::int
FROM employee
WHERE group_name <> '') AS total_groups
, count(group_name <> '' OR NULL)::int AS available
, count(group_name = '' OR NULL)::int AS others
FROM employee
GROUP BY 1
) grp
LEFT JOIN employee e ON e.employee_number = grp.employee_number
AND e.group_name <> ''
ORDER BY grp.employee_number, e.group_name$$
,$$VALUES ('Group_1'::text), ('Group_2')$$
) AS ct (employee_number text
, total_roles int
, total_groups int
, available int
, others int
, "Group_1" int
, "Group_2" int);
SQL Fiddle demonstrating the base query, but not the crosstab step, which is not installed on sqlfiddle.com
Basics for crosstab:
PostgreSQL Crosstab Query
Special in this crosstab: all the "extra" columns. Those columns are placed in the middle, after the "row name" but before "category" and "value":
Pivot on Multiple Columns using Tablefunc
Once again, if you have a dynamic set of groups, you need to build this statement dynamically and execute it in a second call:
Selecting multiple max() values using a single SQL statement
You can use the crosstab function for this.
First of all you need to add the tablefunc extension if you haven't already:
CREATE EXTENSION tablefunc;
The crosstab functions require you to pass it a query returning the data you need to pivot, then a list of the columns in the output. (In other ways "tell me the input and the output format you want"). The sort order is important!
In your case, the input query is quite complicated - I think you need to do a load of separate queries, then UNION ALL them to get the desired data. I'm not entirely sure how you calculate the values "TotalGroups" and "Available", but you can modify the below in the relevant place to get what you need.
SELECT * FROM crosstab(
'SELECT employee_number, attribute, value::integer AS value FROM (with allemployees AS (SELECT distinct employee_number FROM employee) -- use a CTE to get distinct employees
SELECT employee_number,''attr_0'' AS attribute,COUNT(distinct employee_role) AS value FROM employee GROUP BY employee_number -- Roles by employee
UNION ALL
SELECT employee_number,''attr_1'' AS attribute,value from allemployees, (select count (distinct group_name) as value from employee where group_name <> '''') a
UNION ALL
SELECT employee_number,''attr_2'' AS attribute, COUNT(distinct group_name) AS value FROM employee where group_name <> '''' GROUP BY employee_number -- Available, do not know how this is calculate
UNION ALL
SELECT a.employee_number, ''attr_3'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- other groups. Use a LEFT JOIN to avoid nulls in the output
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name ='''' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_4'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 1
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_1'' GROUP BY employee_number) b on a.employee_number = b.employee_number
UNION ALL
SELECT a.employee_number, ''attr_5'' AS attribute,coalesce(value,0) AS value FROM allemployees a LEFT JOIN -- group 2
(SELECT employee_number,COUNT(*) AS value FROM employee WHERE group_name =''Group_2'' GROUP BY employee_number) b on a.employee_number = b.employee_number) a order by 1,2')
AS ct(employee_number varchar,"TotalRoles" integer,"TotalGroups" integer,"Available" integer, "Others" integer,"Group_1" integer, "Group_2" integer)

Simple Query to Grab Max Value for each ID

OK I have a table like this:
ID Signal Station OwnerID
111 -120 Home 1
111 -130 Car 1
111 -135 Work 2
222 -98 Home 2
222 -95 Work 1
222 -103 Work 2
This is all for the same day. I just need the Query to return the max signal for each ID:
ID Signal Station OwnerID
111 -120 Home 1
222 -95 Work 1
I tried using MAX() and the aggregation messes up with the Station and OwnerID being different for each record. Do I need to do a JOIN?
Something like this? Join your table with itself, and exclude the rows for which a higher signal was found.
select cur.id, cur.signal, cur.station, cur.ownerid
from yourtable cur
where not exists (
select *
from yourtable high
where high.id = cur.id
and high.signal > cur.signal
)
This would list one row for each highest signal, so there might be multiple rows per id.
You are doing a group-wise maximum/minimum operation. This is a common trap: it feels like something that should be easy to do, but in SQL it aggravatingly isn't.
There are a number of approaches (both standard ANSI and vendor-specific) to this problem, most of which are sub-optimal in many situations. Some will give you multiple rows when more than one row shares the same maximum/minimum value; some won't. Some work well on tables with a small number of groups; others are more efficient for a larger number of groups with smaller rows per group.
Here's a discussion of some of the common ones (MySQL-biased but generally applicable). Personally, if I know there are no multiple maxima (or don't care about getting them) I often tend towards the null-left-self-join method, which I'll post as no-one else has yet:
SELECT reading.ID, reading.Signal, reading.Station, reading.OwnerID
FROM readings AS reading
LEFT JOIN readings AS highersignal
ON highersignal.ID=reading.ID AND highersignal.Signal>reading.Signal
WHERE highersignal.ID IS NULL;
In classic SQL-92 (not using the OLAP operations used by Quassnoi), then you can use:
SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
FROM (SELECT id, MAX(Signal) AS MaxSignal
FROM t
GROUP BY id) AS g
JOIN t ON g.id = t.id AND g.MaxSignal = t.Signal;
(Unchecked syntax; assumes your table is 't'.)
The sub-query in the FROM clause identifies the maximum signal value for each id; the join combines that with the corresponding data row from the main table.
NB: if there are several entries for a specific ID that all have the same signal strength and that strength is the MAX(), then you will get several output rows for that ID.
Tested against IBM Informix Dynamic Server 11.50.FC3 running on Solaris 10:
+ CREATE TEMP TABLE signal_info
(
id INTEGER NOT NULL,
signal INTEGER NOT NULL,
station CHAR(5) NOT NULL,
ownerid INTEGER NOT NULL
);
+ INSERT INTO signal_info VALUES(111, -120, 'Home', 1);
+ INSERT INTO signal_info VALUES(111, -130, 'Car' , 1);
+ INSERT INTO signal_info VALUES(111, -135, 'Work', 2);
+ INSERT INTO signal_info VALUES(222, -98 , 'Home', 2);
+ INSERT INTO signal_info VALUES(222, -95 , 'Work', 1);
+ INSERT INTO signal_info VALUES(222, -103, 'Work', 2);
+ SELECT g.ID, g.MaxSignal, t.Station, t.OwnerID
FROM (SELECT id, MAX(Signal) AS MaxSignal
FROM signal_info
GROUP BY id) AS g
JOIN signal_info AS t ON g.id = t.id AND g.MaxSignal = t.Signal;
111 -120 Home 1
222 -95 Work 1
I named the table Signal_Info for this test - but it seems to produce the right answer.
This only shows that there is at least one DBMS that supports the notation. However, I am a little surprised that MS SQL Server does not - which version are you using?
It never ceases to surprise me how often SQL questions are submitted without table names.
WITH q AS
(
SELECT c.*, ROW_NUMBER() OVER (PARTITION BY id ORDER BY signal DESC) rn
FROM mytable
)
SELECT *
FROM q
WHERE rn = 1
This will return one row even if there are duplicates of MAX(signal) for a given ID.
Having an index on (id, signal) will greatly improve this query.
with tab(id, sig, sta, oid) as
(
select 111 as id, -120 as signal, 'Home' as station, 1 as ownerId union all
select 111, -130, 'Car', 1 union all
select 111, -135, 'Work', 2 union all
select 222, -98, 'Home', 2 union all
select 222, -95, 'Work', 1 union all
select 222, -103, 'Work', 2
) ,
tabG(id, maxS) as
(
select id, max(sig) as sig from tab group by id
)
select g.*, p.* from tabG g
cross apply ( select top(1) * from tab t where t.id=g.id order by t.sig desc ) p
We can do using self join
SELECT T1.ID,T1.Signal,T2.Station,T2.OwnerID
FROM (select ID,max(Signal) as Signal from mytable group by ID) T1
LEFT JOIN mytable T2
ON T1.ID=T2.ID and T1.Signal=T2.Signal;
Or you can also use the following query
SELECT t0.ID,t0.Signal,t0.Station,t0.OwnerID
FROM mytable t0
LEFT JOIN mytable t1 ON t0.ID=t1.ID AND t1.Signal>t0.Signal
WHERE t1.ID IS NULL;
select a.id, b.signal, a.station, a.owner from
mytable a
join
(SELECT ID, MAX(Signal) as Signal FROM mytable GROUP BY ID) b
on a.id = b.id AND a.Signal = b.Signal
SELECT * FROM StatusTable
WHERE Signal IN (
SELECT A.maxSignal FROM
(
SELECT ID, MAX(Signal) AS maxSignal
FROM StatusTable
GROUP BY ID
) AS A
);
select
id,
max_signal,
owner,
ownerId
FROM (
select * , rank() over(partition by id order by signal desc) as max_signal from table
)
where max_signal = 1;