SQL aggregates over 3 tables - sql

Well, this is annoying the hell out of me. Any help would be much appreciated.
I'm trying to get a count of how many project Ids and Steps there are. The relationships are:
Projects (n-1) Pages
Pages (n-1) Status Steps
Sample Project Data
id name
1 est et
2 quia nihil
Sample Pages Data
id project_id workflow_step_id
1 1 1
2 1 1
3 1 2
4 1 1
5 2 3
6 2 3
7 2 4
Sample Steps Data
id name
1 a
2 b
3 c
4 d
Expected Output
project_id name count_steps
1 a 3
1 b 1
2 c 2
2 d 1
Thanks!

An approach to meet the expected result. See it also at SQL Fiddle
CREATE TABLE Pages
("id" int, "project_id" int, "workflow_step_id" int)
;
INSERT INTO Pages
("id", "project_id", "workflow_step_id")
VALUES
(1, 1, 1),
(2, 1, 1),
(3, 1, 2),
(4, 1, 1),
(5, 2, 3),
(6, 2, 3),
(7, 2, 4)
;
CREATE TABLE workflow_steps
("id" int, "name" varchar(1))
;
INSERT INTO workflow_steps
("id", "name")
VALUES
(1, 'a'),
(2, 'b'),
(3, 'c'),
(4, 'd')
;
CREATE TABLE Projects
("id" int, "name" varchar(10))
;
INSERT INTO Projects
("id", "name")
VALUES
(1, 'est et'),
(2, 'quia nihil')
;
Query 1:
select pg.project_id, s.name, pg.workflow_step_id, ws.count_steps
from (
select distinct project_id, workflow_step_id
from pages ) pg
inner join (
select workflow_step_id, count(*) count_steps
from pages
group by workflow_step_id
) ws on pg.workflow_step_id = ws.workflow_step_id
inner join workflow_steps s on pg.workflow_step_id = s.id
order by project_id, name, workflow_step_id
Results:
| project_id | name | workflow_step_id | count_steps |
|------------|------|------------------|-------------|
| 1 | a | 1 | 3 |
| 1 | b | 2 | 1 |
| 2 | c | 3 | 2 |
| 2 | d | 4 | 1 |

Related

SQL SELECT multiple count in one query

I have these two tables:
and I want get this result:
How can I achieve this by using only one query?
I tried with join and count and group by but I cannot get it right.
I tried this already, but I cannot get it to work properly.
SELECT
coupon.*,
couponUsers.returned AS COUPON_TOTAL_USERS,
couponUses.returned AS COUPON_TOTAL_USES
FROM
coupon,
(SELECT
coupon.COUPON_CODE,
COUNT(redeemed.REDEEMED_USER) AS returned
FROM
coupon
JOIN
redeemed ON coupon.COUPON_CODE = redeemed.REDEEMED_CODE
GROUP BY
redeemed.REDEEMED_USER) couponUsers,
(SELECT
coupon.COUPON_CODE,
COUNT(redeemed.REDEEMED_CODE) AS returned
FROM
coupon
JOIN
redeemed ON coupon.COUPON_CODE = redeemed.REDEEMED_CODE
GROUP BY
redeemed.REDEEMED_CODE) couponUses
WHERE
coupon.COUPON_CODE = couponUsers.COUPON_CODE
AND coupon.COUPON_CODE = couponUses.COUPON_CODE
GROUP BY
coupon.COUPON_CODE
ORDER BY
coupon.COUPON_ID ASC
This is the build schema if you want to try it yourself in SQL fiddle or something like that..
CREATE TABLE IF NOT EXISTS `coupon`
(
`COUPON_ID` int(11) NOT NULL,
`COUPON_CODE` varchar(32) NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `coupon` (`COUPON_ID`, `COUPON_CODE`) VALUES
(1, "AAAAA"),
(2, "BBBBB"),
(3, "CCCCC"),
(4, "DDDDD"),
(5, "EEEEE");
CREATE TABLE IF NOT EXISTS `redeemed` (
`REDEEMED_ID` int(11) NOT NULL,
`REDEEMED_USER` varchar(32) NOT NULL,
`REDEEMED_CODE` varchar(32) NOT NULL
) DEFAULT CHARSET=utf8;
INSERT INTO `redeemed` (`REDEEMED_ID`, `REDEEMED_USER`, `REDEEMED_CODE`) VALUES
(1, "TOM", "AAAAA"),
(2, "PAULA", "BBBBB"),
(3, "TOBI", "CCCCC"),
(4, "JANA", "DDDDD"),
(5, "INGO", "EEEEE"),
(6, "TOM", "AAAAA"),
(7, "PETER", "EEEEE"),
(8, "JIM", "DDDDD"),
(9, "SARA", "AAAAA"),
(10, "TOBI", "CCCCC"),
(11, "PAULA", "AAAAA"),
(12, "TOM", "AAAAA"),
(13, "PAULA", "BBBBB"),
(14, "JIM", "DDDDD"),
(15, "JANA", "DDDDD");
i am trying this already a couple hours..
its time for some help ^^
You should be able to generate the wanted counts in a single table query:
select
redeemed_code
, count(*) as tot_uses
, count(distinct redeemed_user) as tot_users
from redeemed
group by redeemed_code
You can join this to the coupon table for final output, for example with a left join you would get all coupons listed in coupon even if none have been redeemed yet.
select
c.coupon_id
, c.coupon_code
, coalesce(d.tot_uses,0) as tot_uses
, coalesce(d.tot_users,0) as tot_users
from coupon as c
left join (
select
redeemed_code
, count(*) as tot_uses
, count(distinct redeemed_user) as tot_users
from redeemed
group by redeemed_code
) as d on c.coupon_code = d.redeemed_code
coupon_id
coupon_code
tot_uses
tot_users
1
AAAAA
5
3
2
BBBBB
2
1
3
CCCCC
2
1
4
DDDDD
4
2
5
EEEEE
2
2
db<>fiddle here
This is for PostgreSQL, but this query should work for you. You can do the GROUP BY on the redeemed table and join that with coupon to get the COUPON_ID. \
psql=# WITH redeemed_query AS (
SELECT
"REDEEMED_CODE",
COUNT(*) AS "TOTAL_USES",
COUNT(DISTINCT("REDEEMED_USER")) AS "TOTAL_USERS"
from redeemed GROUP BY "REDEEMED_CODE"
)
SELECT * FROM redeemed_query
INNER JOIN
coupon ON redeemed_query."REDEEMED_CODE" = coupon."COUPON_CODE";
REDEEMED_CODE | TOTAL_USES | TOTAL_USERS | COUPON_ID | COUPON_CODE
---------------+------------+-------------+-----------+-------------
AAAAA | 5 | 3 | 1 | AAAAA
BBBBB | 2 | 1 | 2 | BBBBB
CCCCC | 2 | 1 | 3 | CCCCC
DDDDD | 4 | 2 | 4 | DDDDD
EEEEE | 2 | 2 | 5 | EEEEE
(5 rows)

Roll up multiple rows into one when joining in SQL Server

I have a table, Foo
ID | Name
-----------
1 | ONE
2 | TWO
3 | THREE
And another, Bar:
ID | FooID | Value
------------------
1 | 1 | Alpha
2 | 1 | Alpha
3 | 1 | Alpha
4 | 2 | Beta
5 | 2 | Gamma
6 | 2 | Beta
7 | 3 | Delta
8 | 3 | Delta
9 | 3 | Delta
I would like a query that joins these tables, returning one row for each row in Foo, rolling up the 'value' column from Bar. I can get back the first Bar.Value for each FooID:
SELECT * FROM Foo f OUTER APPLY
(
SELECT TOP 1 Value FROM Bar WHERE FooId = f.ID
) AS b
Giving:
ID | Name | Value
---------------------
1 | ONE | Alpha
2 | TWO | Beta
3 | THREE | Delta
But that's not what I want, and I haven't been able to find a variant that will bring back a rolled up value, that is the single Bar.Value if it is the same for each corresponding Foo, or a static string something like '(multiple)' if not:
ID | Name | Value
---------------------
1 | ONE | Alpha
2 | TWO | (multiple)
3 | THREE | Delta
I have found some solutions that would bring back concatenated values (albeit not very elegant) 'Alpha' Alpha, Alpha', 'Beta, Gamma, Beta' &c, but that's not what I want either.
One method, using a a CASE expression and assuming that [Value] cannot have a value of NULL:
WITH Foo AS
(SELECT *
FROM (VALUES (1, 'ONE'),
(2, 'TWO'),
(3, 'THREE')) V (ID, [Name])),
Bar AS
(SELECT *
FROM (VALUES (1, 1, 'Alpha'),
(2, 1, 'Alpha'),
(3, 1, 'Alpha'),
(4, 2, 'Beta'),
(5, 2, 'Gamma'),
(6, 2, 'Beta'),
(7, 3, 'Delta'),
(8, 3, 'Delta'),
(9, 3, 'Delta')) V (ID, FooID, [Value]))
SELECT F.ID,
F.[Name],
CASE COUNT(DISTINCT B.[Value]) WHEN 1 THEN MAX(B.Value) ELSE '(Multiple)' END AS [Value]
FROM Foo F
JOIN Bar B ON F.ID = B.FooID
GROUP BY F.ID,
F.[Name];
You can also try below:
SELECT F.ID, F.Name, (case when B.Value like '%,%' then '(Multiple)' else B.Value end) as Value
FROM Foo F
outer apply
(
select SUBSTRING((
SELECT distinct ', '+ isnull(Value,',') FROM Bar WHERE FooId = F.ID
FOR XML PATH('')
), 2 , 9999) as Value
) as B

Recursive SQL with 1:n relation to another table

I have a problem that is giving me a headache:
We work with T-SQL (MS SQL-Server).
I have a ragged parent/child hierachy in one table. Each row in the table with the parent/child relation (T1) has multiple values in another table (T2).
My goal is to get the values from table T2 for each row of table T1, inluding those of it's ancenstors.
Here is an example:
T1 has the ragged parent child hierarchy.
ClassID | ParentclassID
____________________|___________________________
1 | NULL
--------------------|---------------------------
2 | 1
--------------------|---------------------------
3 | 2
-------------------|---------------------------
4 | 1
T2 has multiple values for each of the values from table T1
ClassID | FeatureID
____________________|___________________________
1 | A
--------------------|---------------------------
1 | B
--------------------|---------------------------
2 | C
--------------------|---------------------------
2 | D
--------------------|---------------------------
3 | E
--------------------|---------------------------
4 | F
My goal is the following Output:
ClassID | FeatureID
____________________|___________________________
1 | A
--------------------|---------------------------
1 | B
--------------------|---------------------------
2 | A
--------------------|---------------------------
2 | B
--------------------|---------------------------
2 | C
--------------------|---------------------------
2 | D
--------------------|---------------------------
3 | A
--------------------|---------------------------
3 | B
--------------------|---------------------------
3 | C
--------------------|---------------------------
3 | D
--------------------|---------------------------
3 | E
--------------------|---------------------------
4 | A
--------------------|---------------------------
4 | B
--------------------|---------------------------
4 | F
If it would only be the ragged hierarchy, I could solve this with an recursive cte. But it is the 1:n relation to the table T2 that is causing the problems.
Any suggestions will be highly appreciated.
Join on tree
declare #H table (id int primary key, par int);
insert into #H values
(1, NULL)
, (2, 1)
, (3, 2)
, (4, 1);
DECLARE #Feature AS TABLE
(
ClassID int,
FeatureID char(1)
)
INSERT INTO #Feature (ClassID, FeatureID) VALUES
(1, 'A'), (1, 'B'),
(2, 'C'), (2, 'D'),
(3, 'E'),
(4, 'F'),
(5, 'G');
with cte as
( select h.id, h.par, h.id as tree
from #H h
union all
select cte.id, cte.par, h.par
from cte
join #H H
on cte.tree = h.id
)
select * from cte
join #Feature f
on f.ClassID = cte.tree
where cte.tree is not null
order by cte.id, cte.par, cte.tree

Count Based on Columns in SQL Server

I have 3 tables:
SELECT id, letter
FROM As
+--------+--------+
| id | letter |
+--------+--------+
| 1 | A |
| 2 | B |
+--------+--------+
SELECT id, letter
FROM Xs
+--------+------------+
| id | letter |
+--------+------------+
| 1 | X |
| 2 | Y |
| 3 | Z |
+--------+------------+
SELECT id, As_id, Xs_id
FROM A_X
+--------+-------+-------+
| id | As_id | Xs_id |
+--------+-------+-------+
| 9 | 1 | 1 |
| 10 | 1 | 2 |
| 11 | 2 | 3 |
| 12 | 1 | 2 |
| 13 | 2 | 3 |
| 14 | 1 | 1 |
+--------+-------+-------+
I can count all As and Bs with group by. But I want to count As and Bs based on X,Y and Z. What I want to get is below:
+-------+
| X,Y,Z |
+-------+
| 2,2,0 |
| 0,0,2 |
+-------+
X,Y,Z
A 2,2,0
B 0,0,2
What is the best way to do this at MSSQL? Is it an efficent way to use foreach for example?
edit: It is not a duplicate because I just wanted to know the efficent way not any way.
For what you're trying to do without knowing what is inefficient with your current code (because none was provided), a Pivot is best. There are a million resources online and here in the stack overflow Q/A forums to find what you need. This is probably the simplest explanation of a Pivot which I frequently need to remind myself of the complicated syntax of a pivot.
To specifically answer your question, this is the code that shows how the link above applies to your question
First Tables needed to be created
DECLARE #AS AS TABLE (ID INT, LETTER VARCHAR(1))
DECLARE #XS AS TABLE (ID INT, LETTER VARCHAR(1))
DECLARE #XA AS TABLE (ID INT, AsID INT, XsID INT)
Values were added to the tables
INSERT INTO #AS (ID, Letter)
SELECT 1,'A'
UNION
SELECT 2,'B'
INSERT INTO #XS (ID, Letter)
SELECT 1,'X'
UNION
SELECT 2,'Y'
UNION
SELECT 3,'Z'
INSERT INTO #XA (ID, ASID, XSID)
SELECT 9,1,1
UNION
SELECT 10,1,2
UNION
SELECT 11,2,3
UNION
SELECT 12,1,2
UNION
SELECT 13,2,3
UNION
SELECT 14,1,1
Then the query which does the pivot is constructed:
SELECT LetterA, [X],[Y],[Z]
FROM (SELECT A.LETTER AS LetterA
,B.LETTER AS LetterX
,C.ID
FROM #XA C
JOIN #AS A
ON A.ID = C.ASID
JOIN #XS B
ON B.ID = C.XSID
) Src
PIVOT (COUNT(ID)
FOR LetterX IN ([X],[Y],[Z])
) AS PVT
When executed, your results are as follows:
Letter X Y Z
A 2 2 0
B 0 0 2
As i said in comment ... just join and do simple pivot
if object_id('tempdb..#AAs') is not null drop table #AAs
create table #AAs(id int, letter nvarchar(5))
if object_id('tempdb..#XXs') is not null drop table #XXs
create table #XXs(id int, letter nvarchar(5))
if object_id('tempdb..#A_X') is not null drop table #A_X
create table #A_X(id int, AAs int, XXs int)
insert into #AAs (id, letter) values (1, 'A'), (2, 'B')
insert into #XXs (id, letter) values (1, 'X'), (2, 'Y'), (3, 'Z')
insert into #A_X (id, AAs, XXs)
values (9, 1, 1),
(10, 1, 2),
(11, 2, 3),
(12, 1, 2),
(13, 2, 3),
(14, 1, 1)
select LetterA,
ISNULL([X], 0) [X],
ISNULL([Y], 0) [Y],
ISNULL([Z], 0) [Z]
from (
select distinct a.letter [LetterA], x.letter [LetterX],
count(*) over (partition by a.letter, x.letter order by a.letter) [Counted]
from #A_X ax
join #AAs A on ax.AAs = A.ID
join #XXs X on ax.XXs = X.ID
)src
PIVOT
(
MAX ([Counted]) for LetterX in ([X], [Y], [Z])
) piv
You get result as you asked for
LetterA X Y Z
A 2 2 0
B 0 0 2

How do I limit a query with distinct values in MariaDB

I have a table like this:
Id Num Some text
--------------------
1 1 ""
2 1 ""
3 2 ""
4 2 ""
5 2 ""
6 2 ""
7 3 ""
What I want is a query to select the first ten distinct nums, so if I want to get the two first nums I'll get the first six rows. I'm using MariaDB.
Try this approach:
select *
from Table1 as T1
join (
select distinct num
from Table1
order by num
limit 2 ) as T2
on T1.num = T2.num
;
In a fiddle here: http://sqlfiddle.com/#!9/1468ad/5
In MySQL you can use LIMIT. The caveat is that LIMIT is not directly supported in the IN sub-queries and therefore you must use a sub-query inside a IN sub-query:
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`Id` int, `Num` int, `Some text` varchar(2))
;
INSERT INTO Table1
(`Id`, `Num`, `Some text`)
VALUES
(1, 1, '""'),
(2, 1, '""'),
(3, 2, '""'),
(4, 2, '""'),
(5, 2, '""'),
(6, 2, '""'),
(7, 3, '""')
;
Query 1:
select * from Table1
where Num in (select Num FROM(select distinct Num from Table1 order by Num limit 2)a)
Results:
| Id | Num | Some text |
|----|-----|-----------|
| 1 | 1 | "" |
| 2 | 1 | "" |
| 3 | 2 | "" |
| 4 | 2 | "" |
| 5 | 2 | "" |
| 6 | 2 | "" |