Select data with count of data from another table [duplicate] - mysql

This question already has answers here:
How can I get multiple counts with one SQL query?
(12 answers)
Closed 7 years ago.
I have two tables: `jobs' & 'clients'.
The clients table looks like this:
/----+------\
| id | name |
+----+------+
| 1 | Ben |
| 2 | Max |
\----+------/
And the jobs table:
/----+-----------+--------\
| id | client_id | status |
+----+-----------+--------+
| 1 | 1 | alpha |
| 2 | 1 | beta |
| 3 | 1 | beta |
| 4 | 2 | beta |
\----+-----------+--------/
I'm looking to create a statement that will return the name of the client along with the number of times each status appears, like this:
/------+-------+------\
| name | alpha | beta |
+------+-------+------+
| Ben | 1 | 2 |
| Max | 0 | 1 |
\------+-------+------/
I do not require there to be a 0 where no values exist though.
I have tried SELECT name, (SELECT status FROM jobs WHERE client_id = clients.id) FROM clients but it returns more than one row in the sub-query.

Here's an example with a solution: http://sqlfiddle.com/#!9/80593/3
create table clients (id int, name varchar(10));
insert into clients values (1, 'Ben'), (2, 'Max');
create table jobs (id int, client_id int, status varchar(10));
insert into jobs values (1, 1, 'alpha'), (2, 1, 'beta'), (3, 1, 'beta'), (4, 2, 'beta');
select
clients.name,
sum(case when jobs.status = 'alpha' then 1 else 0 end) alpha,
sum(case when jobs.status = 'beta' then 1 else 0 end) beta
from jobs
inner join clients
on jobs.client_id = clients.id
group by clients.name;

Related

How to get calculated data from one column in database

im new in sql. I cannot get data with format what i want in one step. Now i'm using more sql commands. I want to get all data in one command because i cant to connect them in subquery with group by. Somebodys can help me?
example of Table i have:
id
order_id
order_status
1
1
0
2
1
0
3
1
0
4
1
1
5
1
1
6
2
0
7
2
0
8
2
1
Table i want to have after sql query:
order_id
count
of
progress(%)
1
2
5
40
2
1
3
33
queries i use:
SELECT order_id, COUNT(status) as count
FROM `orders`
WHERE status = 1
GROUP by order_id;
SELECT order_id, COUNT(status) as of
FROM `orders`
GROUP by order_id;
SELECT order_id,
CAST((SELECT COUNT(status) FROM `orders` WHERE status = 1) /
(SELECT COUNT(status) FROM `orders`) *100 as int) AS progress FROM orders
group by order_id;
but last working properly only if i use where to single order id.
I want to make this data in one sql query to format i showed up.
Thanks a lot guys!
You don't need subqueries to do this, SQL's ordinary aggregate functions already work as you want with your group by clause:
SELECT order_id,
SUM(order_status) AS `count`,
COUNT(*) AS `of`,
SUM(order_status) / COUNT(order_status) * 100 as `progress`
FROM orders
group by order_id;
See example at http://sqlfiddle.com/#!9/d1799db/4/0
you need to use multiple subqueries
here's a query that I used and worked on your example on the onecompiler.com website
-- create
CREATE TABLE EMPLOYEE (
order_id INTEGER,
order_status INTEGER
);
-- insert
INSERT INTO EMPLOYEE VALUES (1,0 );
INSERT INTO EMPLOYEE VALUES (1, 0);
INSERT INTO EMPLOYEE VALUES (1, 0);
INSERT INTO EMPLOYEE VALUES (1, 1);
INSERT INTO EMPLOYEE VALUES (1,1 );
INSERT INTO EMPLOYEE VALUES (2, 0);
INSERT INTO EMPLOYEE VALUES (2, 0);
INSERT INTO EMPLOYEE VALUES (2, 1);
select *
from EMPLOYEE;
SELECT order_id, count, off , count/off
from(
select distinct order_id as order_id,
(select count(order_id) from EMPLOYEE C WHERE A.order_id=C.order_id AND order_status =1) as 'count',
(select count(order_id) from EMPLOYEE B WHERE A.order_id=B.order_id ) as 'off'
FROM EMPLOYEE A
) AA
;
You need to use sum and count with group by.
create table orders(
id int,
order_id int,
order_status int);
insert into orders values
(1,1,0),
(2,1,0),
(3,1,0),
(4,1,1),
(5,1,1),
(6,2,0),
(7,2,0),
(8,2,1);
select
order_id,
sum(order_status) count,
count(order_id) "of",
(100 * sum(order_status))
/ count(order_id) progress
from orders
group by order_id
order by order_id;
order_id | count | of | progress
-------: | ----: | -: | -------:
1 | 2 | 5 | 40.0000
2 | 1 | 3 | 33.3333
db<>fiddle here
i was described my problem without some details, w i want to join with other table but i see only record with status
oders_details
| id | order_describe | order_date |
|:----:|:--------------:|:----------:|
| 1 | sample 1 | 2022-02-28 |
| 2 | sample 2 | 2022-02-28 |
| 3 | sample 3 | 2022-03-01 |
| 4 | sample 4 | 2022-03-02 |
orders_status
| id | order_id |order_status|
|:---:|:---------------:|:----------:|
| 1 | 1 | 0 |
| 2 | 1 | 0 |
| 3 | 1 | 0 |
| 4 | 1 | 1 |
| 5 | 1 | 1 |
| 6 | 2 | 0 |
| 7 | 2 | 0 |
| 8 | 2 | 1 |
table i want after query
orders_view
| id |order_id|order_describe| order_date | count | of | progress |
|-----|--------|--------------|------------|-------|----|:--------:|
| 1 | 1 | sample 1 | 2022-02-28| 2 | 5 | 40 |
| 2 | 2 | sample 2 | 2022-02-28| 1 | 3 | 33 |
| 3 | 3 | sample 3 | 2022-03-01| null |null| null |
| 4 | 4 | sample 4 | 2022-03-02| null |null| null |
i want to get some hint what i have todo, to get finally table or view, not complete solution, to better understand sql lang

Aggregate "once-only" whether 1 or 2 rows in join

I'm trying to run an aggregate query where a join can find 0, 1 or 2 rows in the join table.
I want to aggregate "once-only" regardless of whether the join finds 1 or 2 matching rows.
Minimal example.
+--------------+--------+-----------+
| container_id | thing | alternate |
+--------------+--------+-----------+
| 1 | box | 0 |
| 1 | box | 1 |
| 1 | hat | 0 |
| 2 | monkey | 0 |
| 3 | monkey | 1 |
| 3 | chair | 1 |
+--------------+--------+-----------+
+--------------+------+
| container_id | uses |
+--------------+------+
| 1 | 3 |
| 2 | 1 |
| 3 | 2 |
+--------------+------+
You can see that 'box' is associated with container_id number 1 twice. Once with alternate=0 and once with alternate=1.
SELECT
thing, COUNT(DISTINCT ct.container_id) AS occurrencs, SUM(uses) AS uses
FROM
container_thing AS ct
INNER JOIN
container_usage AS cu ON cu.container_id = ct.container_id
GROUP BY
thing
gives:
+--------+------------+------+
| thing | occurrencs | uses |
+--------+------------+------+
| box | 1 | 6 |
| chair | 1 | 2 |
| hat | 1 | 3 |
| monkey | 2 | 3 |
+--------+------------+------+
but I really want is:
+--------+------------+------+
| thing | occurrencs | uses |
+--------+------------+------+
| box | 1 | 3 |
| chair | 1 | 2 |
| hat | 1 | 3 |
| monkey | 2 | 3 |
+--------+------------+------+
I want 3 as the value for uses in the first row because 'box' was in containers that were used a total of three times. Because of the 'alternate' column I get 6 for that value. Can I either join differently or group by differently or express in the SUM expression to only SUM once for each distinct thing regardless of the value of alternate?
(Note that a thing can appear in a container with alternate, without alternate or both.)
SQL necessary to set up the minimal example:
-- Set up db
CREATE DATABASE sumtest;
USE sumtest;
-- Set up tables
CREATE TABLE container (id INT PRIMARY KEY);
CREATE TABLE container_thing (container_id INT, thing NVARCHAR(10), alternate BOOLEAN);
CREATE TABLE container_usage (container_id INT, uses INT);
-- Insert data
INSERT INTO container (id) VALUES (1), (2), (3);
INSERT INTO container_thing (container_id, thing, alternate) VALUES (1, 'box', FALSE), (1, 'box', TRUE), (1, 'hat', FALSE), (2, 'monkey', FALSE), (3, 'monkey', TRUE), (3, 'chair', TRUE);
INSERT INTO container_usage VALUES (1, 3), (2, 1), (3, 2);
-- Query
SELECT thing, COUNT(DISTINCT ct.container_id) AS occurrencs, SUM(uses) AS uses FROM container_thing AS ct INNER JOIN container_usage AS cu ON cu.container_id = ct.container_id GROUP BY thing;
You can work around this by only selecting DISTINCT values of container_id and thing from container_thing in a derived table and JOINing that to container_usage:
SELECT thing, COUNT(ct.container_id) AS occurrences, SUM(uses) AS uses
FROM (SELECT DISTINCT container_id, thing
FROM container_thing) AS ct
INNER JOIN container_usage AS cu ON cu.container_id = ct.container_id
GROUP BY thing;
Output
thing occurrences uses
box 1 3
chair 1 2
hat 1 3
monkey 2 3
Demo on dbfiddle
If you want only the use .. then you should not perform the sum in join .. because the join produce T1xT2 rows for each macthing ON clause
where N is the number of row from table1 and M is the number of rows from table2 so in the case of box you have 2 x 1 with value 3 = 6.
for avoid this you should join container_usage with the subqiery for aggreated result for count of container_thing
select t.thing, t.count_container, cu.uses
from (
SELECT thing, container_id, COUNT(DISTINCT ct.container_id) count_container
FROM container_thing
GROUP BY thing, container_id
) t
inner join container_usage AS cu ON cu.container_id = t.container_id

update the link table

I'm validating following SQL approach from the community where I'm completely updating the user's roles. (Full update of the join table)
User
+----+-------+------+
| id | first | last |
+----+-------+------+
| 1 | John | Doe |
| 2 | Jane | Doe |
+----+-------+------+
Role
+----+----------+
| id | name |
+----+----------+
| 1 | admin |
| 2 | accounts |
| 3 | sales |
+----+----------+
UserRole
+--------+--------+
| userid | roleid |
+--------+--------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 2 |
| 2 | 3 |
+--------+--------+
My SQL approach -> first delete all, second insert all records
DELETE FROM UserRole WHERE userid = 1;
INSERT INTO UserRole(userid, roleid) VALUES(1, 2), (1, 3);
Is there a better way? I mean to do this in a single query possibly for these sorts of linking/join tables?
Edit
I think what I should have said to find an efficient SQL operation instead of a single query.
Here's another SQL
DELETE FROM UserRole WHERE user_id = 1 AND role_id NOT IN (2, 3);
INSERT INTO UserRole(user_id, role_id) VALUES(1, 2), (1, 3)
ON DUPLICATE KEY UPDATE user_id = VALUES(user_id), role_id = VALUES(role_id);
If you want to add all roles for all users into the userRoles table, you can delete from userRoles, then recreate table like below:
DECLARE #users TABLE ( userID INT, UserName NVARCHAR(MAX) )
DECLARE #roles TABLE ( roleID INT, RoleName NVARCHAR(MAX) )
DECLARE #userRoles TABLE ( userID INT, roleID INT )
INSERT INTO #users (userID,UserName) VALUES (1,'name1'),(2,'name2'),(3,'name3')
INSERT INTO #roles (roleID,RoleName) VALUES (1,'admin'),(2,'accounts'),(3,'sales')
INSERT INTO #userRoles (userID,roleID)
SELECT U.userID,R.roleID FROM #users U
FULL OUTER JOIN #roles R ON 1=1
ORDER BY userID
SELECT * FROM #userRoles
OUTPUT:
userID roleID
1 1
2 1
3 1
1 2
2 2
3 2
1 3
2 3
3 3

SubQuery returns one row when getting data from main query comma separated ids?

SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (e.topic_ids)) AS topics
FROM exam e
result :
topics = xyz topic
this query returns a single name of topic as result but when i use this :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE id IN (1,4)) AS topics
FROM exam e
result :
topics = xyz topic,abc topic
That works fine,and exam table had the same value in DB (comma separated topic ids = 1,4) as varchar type field.
is there any issue with datatype of field?
First, let me lecture you about how bad CSV in field is.
| id | topic_ids |
|----|-----------|
| 1 | a,b,c |
| 2 | a,b |
This, is how Satan look like in relational DB. Probably the worst, just after the
"lets put columns as line and use a recursive join to get everything back."
How it should be ?
exam
| id |
|----|
| 1 |
| 2 |
exam_topic
| exam_id | topic_id |
|---------|----------|
| 1 | a |
| 1 | b |
| 1 | c |
| 2 | a |
| 2 | b |
topic
| id |
|----|
| a |
| b |
| c |
Now, as awful as it may be, this is the "dynamic" alternative, using FIND_IN_SET() :
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE exam
(`id` int, `topic_ids` varchar(5))
;
INSERT INTO exam
(`id`, `topic_ids`)
VALUES
(1, 'a,b,c'),
(2, 'a,b'),
(3, 'b,c,d'),
(4, 'd')
;
CREATE TABLE topic
(`id` varchar(1), `topic_name` varchar(4))
;
INSERT INTO topic
(`id`, `topic_name`)
VALUES
('a', 'topA'),
('b', 'topB'),
('c', 'topC'),
('d', 'topD')
;
Query 1:
SELECT
e.*,
(
SELECT GROUP_CONCAT(topic_name)
FROM topic
WHERE FIND_IN_SET(id, e.topic_ids) > 0
) AS topics
FROM exam e
Results:
| id | topic_ids | topics |
|----|-----------|----------------|
| 1 | a,b,c | topA,topB,topC |
| 2 | a,b | topA,topB |
| 3 | b,c,d | topB,topC,topD |
| 4 | d | topD |

how to create a counting column for a particular item

I have the following schema (mysql)
create table test(
userid int(11) not null,
item varchar(15),
bookid int(11));
insert into test values ('1','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('1','book',NULL);
insert into test values ('2','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('1','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('3','book',NULL);
insert into test values ('1','book',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('3','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('2','book',NULL);
insert into test values ('2','journal',NULL);
insert into test values ('1','journal',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
insert into test values ('3','book',NULL);
whenever there is a book, I'm trying assign an auto increment beginning with 1 in the bookid column. For each user, the numbering begins again from 1. I know a way this can be done by creating a separate table. Is there a way I can avoid that and accomplish that using some sort of update query in this very table and update the column bookid? I am trying to get output similar to the following:
userid,item,bookid
'1','journal',NULL
'1','journal',NULL
'1','book',1
'2','book',1
'2','journal',NULL
'1','book',2
'2','journal',NULL
'3','book',1
'1','book',3
'1','journal',NULL
'3','journal',NULL
'1','journal',NULL
'2','journal',NULL
'2','book',2
'2','journal',NULL
'1','journal',NULL
'3','book',2
'3','book',3
'3','book',4
'3','book',5
I appreciate if someone could guide me on how to accomplish this?
Here's one idea...
drop table if exists test;
create table test
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,userid int not null
,item varchar(15) NOT NULL
);
insert into test (userid,item) values
(1,'journal')
,(1,'journal')
,(1,'book')
,(2,'book')
,(2,'journal')
,(1,'book')
,(2,'journal')
,(3,'book')
,(1,'book')
,(1,'journal')
,(3,'journal')
,(1,'journal')
,(2,'journal')
,(2,'book')
,(2,'journal')
,(1,'journal')
,(3,'book')
,(3,'book')
,(3,'book')
,(3,'book');
SELECT x.*
, COUNT(*) rank
FROM test x
JOIN test y
ON y.userid = x.userid
AND y.item = x.item
AND y.id <= x.id
GROUP
BY id
ORDER
BY userid
, item
, rank;
+----+--------+---------+------+
| id | userid | item | rank |
+----+--------+---------+------+
| 3 | 1 | book | 1 |
| 6 | 1 | book | 2 |
| 9 | 1 | book | 3 |
| 1 | 1 | journal | 1 |
| 2 | 1 | journal | 2 |
| 10 | 1 | journal | 3 |
| 12 | 1 | journal | 4 |
| 16 | 1 | journal | 5 |
| 4 | 2 | book | 1 |
| 14 | 2 | book | 2 |
| 5 | 2 | journal | 1 |
| 7 | 2 | journal | 2 |
| 13 | 2 | journal | 3 |
| 15 | 2 | journal | 4 |
| 8 | 3 | book | 1 |
| 17 | 3 | book | 2 |
| 18 | 3 | book | 3 |
| 19 | 3 | book | 4 |
| 20 | 3 | book | 5 |
| 11 | 3 | journal | 1 |
+----+--------+---------+------+
Note that MyISAM actually lets you use a composite PK in which part of that composite is an auto-incrementing id, but InnoDB prohinits this.
On larger datasets a query along these lines will likely be far more efficient...
SELECT id
, userid
, item
, IF(#userid=userid,IF(#item=item,#i:=#i+1,#i:=1),#i:=1) rank
, #userid := userid
, #item := item
FROM test
, (SELECT #userid = NULL,#item:='',#i:=1) vars
ORDER
BY userid,item,id;