SQL SELECT multiple count in one query - sql

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)

Related

SELECT check the colum of the max row

Here my row with my first select:
SELECT
user.id, analytic_youtube_demographic.age,
analytic_youtube_demographic.percent
FROM
`user`
INNER JOIN
analytic ON analytic.user_id = user.id
INNER JOIN
analytic_youtube_demographic ON analytic_youtube_demographic.analytic_id = analytic.id
Result:
---------------------------
| id | Age | Percent |
|--------------------------
| 1 |13-17| 19,6 |
| 1 |18-24| 38.4 |
| 1 |25-34| 22.5 |
| 1 |35-44| 11.5 |
| 1 |45-54| 5.3 |
| 1 |55-64| 1.6 |
| 1 |65+ | 1.2 |
| 2 |13-17| 10 |
| 2 |18-24| 10 |
| 2 |25-34| 25 |
| 2 |35-44| 5 |
| 2 |45-54| 25 |
| 2 |55-64| 5 |
| 1 |65+ | 20 |
---------------------------
The max value by user_id:
---------------------------
| id | Age | Percent |
|--------------------------
| 1 |18-24| 38.4 |
| 2 |45-54| 25 |
| 2 |25-34| 25 |
---------------------------
And I need to filter Age in ['25-34', '65+']
I must have at the end :
-----------
| id |
|----------
| 2 |
-----------
Thanks a lot for your help.
Have tried to use MAX(analytic_youtube_demographic.percent). But I don't know how to filter with the age too.
Thanks a lot for your help.
You can use the rank() function to identify the largest percentage values within each user's data set, and then a simple WHERE clause to get those entries that are both of the highest rank and belong to one of the specific demographics you're interested in. Since you can't use windowed functions like rank() in a WHERE clause, this is a two-step process with a subquery or a CTE. Something like this ought to do it:
-- Sample data from the question:
create table [user] (id bigint);
insert [user] values
(1), (2);
create table analytic (id bigint, [user_id] bigint);
insert analytic values
(1, 1), (2, 2);
create table analytic_youtube_demographic (analytic_id bigint, age varchar(32), [percent] decimal(5, 2));
insert analytic_youtube_demographic values
(1, '13-17', 19.6),
(1, '18-24', 38.4),
(1, '25-34', 22.5),
(1, '35-44', 11.5),
(1, '45-54', 5.3),
(1, '55-64', 1.6),
(1, '65+', 1.2),
(2, '13-17', 10),
(2, '18-24', 10),
(2, '25-34', 25),
(2, '35-44', 5),
(2, '45-54', 25),
(2, '55-64', 5),
(2, '65+', 20);
-- First, within the set of records for each user.id, use the rank() function to
-- identify the demographics with the highest percentage.
with RankedDataCTE as
(
select
[user].id,
youtube.age,
youtube.[percent],
[rank] = rank() over (partition by [user].id order by youtube.[percent] desc)
from
[user]
inner join analytic on analytic.[user_id] = [user].id
inner join analytic_youtube_demographic youtube on youtube.analytic_id = analytic.id
)
-- Now select only those records that are (a) of the highest rank within their
-- user.id and (b) either the '25-34' or the '65+' age group.
select
id,
age,
[percent]
from
RankedDataCTE
where
[rank] = 1 and
age in ('25-34', '65+');

SQL - I have a 'People' table and an 'Account' table. How do I list all of the people in rows, and have a balance column for each account type?

I was having a hard time figuring out how to title this question, so my apologies in advance.
Here is my my situation:
I have one table that has just people in it. I have another table that has all the accounts, with a personID, accountType, and balance column. A person can have multiple accounts, there are different account types, and not everyone has all of the different account types.
How can I write a query where I list one person per row, and have a column for the balance of each "account type"?
Ideally, my query would look something like this:
PersonID | Account Type 1 Bal | Account Type 2 Bal | Account Type 3 Bal |
-------------------------------------------------------------------------
1 | $100 | null | null |
2 | null | $12 | $1300 |
3 | null | null | $5 |
4 | $150 | null | null |
5 | $65 | $300 | $45 |
I would assume I would use some sort of case statement, but I haven't been able to figure it out yet. Also, if they have multiple of the same account type, i assume I would just use sum() correct?
Thanks.
Something like this should work for postgres
select p.person_id,
(select sum(a.balance) from account a where a.person_id = p.person_id and a.account_type = 'Type1') type1_balance,
(select sum(a.balance) from account a where a.person_id = p.person_id and a.account_type = 'Type2') type2_balance,
(select sum(a.balance) from account a where a.person_id = p.person_id and a.account_type = 'Type3') type3_balance
from person p
sqlfiddle example
Check if this helps -
http://sqlfiddle.com/#!4/30ebb/10/0
CREATE TABLE person
("person_id" int, "name" varchar2(9))
;
INSERT ALL
INTO person ("person_id", "name")
VALUES (1, '''Abcd''')
INTO person ("person_id", "name")
VALUES (2, '''xyz''')
INTO person ("person_id", "name")
VALUES (3, '''jjjjj''')
INTO person ("person_id", "name")
VALUES (4, '''sfds''')
INTO person ("person_id", "name")
VALUES (5, '''temp''')
SELECT * FROM dual
;
CREATE TABLE accounts
("personID" int, "accountType" int, "balance" int)
;
INSERT ALL
INTO accounts ("personID", "accountType", "balance")
VALUES (1, 1, 100)
INTO accounts ("personID", "accountType", "balance")
VALUES (1, 2, 150)
INTO accounts ("personID", "accountType", "balance")
VALUES (2, 1, 20)
INTO accounts ("personID", "accountType", "balance")
VALUES (3, 1, 40)
INTO accounts ("personID", "accountType", "balance")
VALUES (3, 2, 440)
INTO accounts ("personID", "accountType", "balance")
VALUES (4, 1, 600)
INTO accounts ("personID", "accountType", "balance")
VALUES (5, 1, 43)
INTO accounts ("personID", "accountType", "balance")
VALUES (5, 2, 50)
SELECT * FROM dual
;
Query -
select * from (
select p."person_id", a."accountType", a."balance"
from person p, accounts a
where p."person_id" = a."personID"
)
pivot (sum("balance") for "accountType" in (1 as acc_type1_bal,2 as acc_type2_bal));
person_id ACC_TYPE1_BAL ACC_TYPE2_BAL
1 100 150
2 20 (null)
4 600 (null)
5 43 50
3 40 440
select personid
,sum(ac1bal) accounttype1bal
,sum(ac2bal) accounttype2bal
,sum(ac3bal) accounttype3bal
from (
select persontable.personid
,case when (accounttype=1) then bal end ac1bal
,case when (accounttype=2) then bal end ac2bal
,case when (accounttype=3) then bal end ac3bal
from persontable
left join accounttable on persontable.personid = accounttable.personid
)
group by personid

SQL aggregates over 3 tables

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 |

Query for customers who have made purchases on a common but discontinuous set of dates

RDMS: PostgreSQL 9.5.3
I have a table ('activity') of the following form:
customerID | date | purchaseID
-----------------------------------------
1 | 2016-01-01 | 1
2 | 2016-01-01 | 2
3 | 2016-01-01 | 3
2 | 2016-01-02 | 4
1 | 2016-01-03 | 5
2 | 2016-01-03 | 6
3 | 2016-01-03 | 7
1 | 2016-01-04 | 8
2 | 2016-01-04 | 9
3 | 2016-01-05 | 10
From this table, I want to find all customers who have made purchases on the same dates as customerID 1. The customers purchase history needs to completely overlap with customerID 1, but not necessarily be limited to it -- extra purchases outside of the dates are fine, but should not be returned in the final results.
The result on the above data should be:
customerID | date | purchaseID
-----------------------------------------
2 | 2016-01-01 | 2
2 | 2016-01-02 | 5
2 | 2016-01-03 | 8
At the moment, I'm solving this through a loop in the application code and then dropping all NULL results, so the actual SQL is:
SELECT customerID,
date,
purchaseID
FROM activity
WHERE customerID <> 1
AND date = %date%
where %date% is the is an iteration variable through all of the dates that customerID 1 has made purchases. This isn't an elegant solution, and extremely slow for large numbers of purchases (millions) or customers (tens of thousands). Any suggestions would be welcome.
Thanks for reading--
One method is to use a self-join and aggregation:
select a.customerid
from activity a join
activity a1
on a1.date = a.date and a1.customerid = 1
where a1.customerid <> a.customerid
group by a.customerID
having count(distinct a1.date) = (select count(distinct date) from activity where customerID = 1)
If you want the original records, you can use:
select a.*
from activity a
where a.customerId in (select a.customerid
from activity a join
activity a1
on a1.date = a.date and a1.customerid = 1
where a1.customerid <> a.customerid
group by a.customerID
having count(distinct a1.date) = (select count(distinct date) from activity where customerID = 1)
);
You can use the "contains" #> array operator:
with activity (customerID, date, purchaseID) AS (
values (1, '2016-01-01'::date, 1), (2, '2016-01-01', 2), (3, '2016-01-01', 3),
(2, '2016-01-02', 4), (1, '2016-01-03', 5), (2, '2016-01-03', 6),
(3, '2016-01-03', 7), (1, '2016-01-04', 8), (2, '2016-01-04', 9),
(3, '2016-01-05', 10))
select customerID
from activity
group by customerID
having customerID <> 1 AND
array_agg(date) #> array(select date from activity where customerID = 1)

Select group of rows that match all items in a list

Assume I have two tables:
cars – list of cars
carname | modelnumber | ...
passedtest – contains every test that a car passed:
id | carname | testtype | date | ...
1 | carA | A | 2000 |
2 | carB | C | 2000 |
3 | carC | D | 2001 |
4 | carA | C | 2002 |
Now, how can I select a car from the passedtest table that passed all tests (A, B, C, D)?
I tried the IN statement but it also matches cars that pass even one test. I am looking for a statement to match all values in a list across all rows.
How about this?
SELECT carname
FROM PassedTest
GROUP BY carname
HAVING COUNT(DISTINCT testtype) = 4
You can also use it as an inner statement for taking info from the cars table:
SELECT *
FROM cars
WHERE carname IN (
SELECT carname
FROM PassedTest
GROUP BY carname
HAVING COUNT(DISTINCT testtype) = 4
)
This type of problem is called Relational Division.
SELECT a.*
FROM Cars a
INNER JOIN
(
SELECT CarName
FROM PassedTest
WHERE testType IN ('A', 'B', 'C', 'D')
GROUP BY CarName
HAVING COUNT(*) = 4
) b ON a.CarName = b.CarName
if a UNIQUE constraint was not enforce on TestType for every CarName on table PassedTest a DISTINCT keyword is required on COUNT() so it will only count unique values.
SELECT a.*
FROM Cars a
INNER JOIN
(
SELECT CarName
FROM PassedTest
WHERE testType IN ('A', 'B', 'C', 'D')
GROUP BY CarName
HAVING COUNT(DISTINCT TestType) = 4
) b ON a.CarName = b.CarName
SQL of Relational Division
but if you are only interested on the CARNAME then you don't need to join the tables. Querying on table PassedTest will suit your needs.
SELECT CarName
FROM PassedTest
WHERE testType IN ('A', 'B', 'C', 'D')
GROUP BY CarName
HAVING COUNT(*) = 4
You want to perform relational division, an operation that is not implemented in SQL. Here is an example where we have a product-supplier table and a required-products table:
CREATE TABLE product_supplier (
product_id int NOT NULL,
supplier_id int NOT NULL,
UNIQUE (product_id, supplier_id)
);
INSERT INTO product_supplier (product_id, supplier_id) VALUES
(1, 1),
(2, 1),
(3, 1),
(1, 2),
(2, 2),
(3, 2),
(4, 2),
(2, 3),
(3, 3),
(4, 3);
CREATE TABLE reqd (
product_id int NOT NULL,
UNIQUE (product_id)
);
INSERT INTO reqd (product_id) VALUES
(1),
(2),
(3);
... and we want to find all suppliers that supply ALL required products and perhaps others. The result in the above example would be supplier 1 and 2.
The most straight forward solution is this:
SELECT product_supplier.supplier_id
FROM product_supplier
LEFT JOIN reqd ON product_supplier.product_id = reqd.product_id
GROUP BY product_supplier.supplier_id
HAVING COUNT(reqd.product_id) = (SELECT COUNT(*) FROM reqd);
+-------------+
| supplier_id |
+-------------+
| 1 |
| 2 |
+-------------+
And if we want to find all suppliers that supply ALL required products and no others (exact division/no remainder) then add one more condition to the above:
SELECT product_supplier.supplier_id
FROM product_supplier
LEFT JOIN reqd ON product_supplier.product_id = reqd.product_id
GROUP BY product_supplier.supplier_id
HAVING COUNT(reqd.product_id) = (SELECT COUNT(*) FROM reqd)
AND COUNT(product_supplier.product_id) = (SELECT COUNT(*) FROM reqd);
+-------------+
| supplier_id |
+-------------+
| 1 |
+-------------+
An alternate solution is to rephrase the problem: select suppliers where a required product does not exist that does not exist in the products supplied by the supplier. Hmmm:
SELECT DISTINCT supplier_id
FROM product_supplier AS ps1
WHERE NOT EXISTS (
SELECT *
FROM reqd
WHERE NOT EXISTS (
SELECT *
FROM product_supplier AS ps2
WHERE ps1.supplier_id = ps2.supplier_id AND ps2.product_id = reqd.product_id
)
);
+-------------+
| supplier_id |
+-------------+
| 1 |
| 2 |
+-------------+