I have two tables:
CREATE TABLE instructions (
`id_instruction` INT(11),
`id_step` INT(11)
);
CREATE TABLE steps (
`id_instruction` INT(11),
`id_step` INT(11),
`val` VARCHAR(255)
);
One table contains instructions, another table contains steps. Each instruction may have many steps. Now, the data is:
INSERT INTO instructions (`id_instruction`, `id_step`) VALUES (1, 0), (1, 1), (1, 2);
INSERT INTO steps (`id_instruction`, `id_step`, `val` ) VALUES (1, 0, 'One'), (1, 0, 'Two'), (1, 0, 'Three'); /* step 0 */
INSERT INTO steps (`id_instruction`, `id_step`, `val` ) VALUES (1, 1, 'Five'), (1, 1, 'Six'), (1, 1, 'Seven'); /* step 1 */
INSERT INTO steps (`id_instruction`, `id_step`, `val` ) VALUES (1, 2, 'Eight'), (1, 2, 'Nine'), (1, 2, 'Ten'); /* step 2 */
For each instruction I want to have two concatenations - one which concatenates values from val column for the zero step, and another one which concatenates values from the same column for the largest step of the instruction. I know how to get the largest step and how to make a single group concatenation, but trying to do two concatenations, I get duplicates. Now, my query looks like this:
SELECT maxstep, i.id_instruction, i.id_step, GROUP_CONCAT(s.val) AS val_0
FROM instructions i
INNER JOIN (
SELECT MAX(id_step) AS maxstep, id_instruction FROM instructions i
GROUP BY i.id_instruction
) i2 ON i2.id_instruction = i.id_instruction
LEFT JOIN steps s ON s.id_instruction = i.id_instruction AND s.id_step = i.id_step
GROUP BY i.id_instruction, i.id_step
It just concatenates values per a pair instruction-step. But I want to have one more concatenation which would also concatenate values for the maxstep. The desired result should look like this:
| maxstep | id_instruction | val_0 | val_1 |
| 2 | 1 | One,Two, Three | Eight, Nine, Ten |
PS. I do join instead of just MAX and grouping, because I want to use its value in additional joining for further concatenation.
What you're trying to do is called pivoting. In MySQL there's no built-in function for this, but you can do it like this:
SELECT maxstep, id_instruction,
MAX(CASE id_step WHEN 0 THEN val END) AS val_0,
MAX(CASE id_step WHEN 1 THEN val END) AS val_1,
MAX(CASE id_step WHEN 2 THEN val END) AS val_2
FROM (
SELECT maxstep, i.id_instruction, i.id_step, GROUP_CONCAT(s.val) AS val
FROM instructions i
INNER JOIN (
SELECT MAX(id_step) AS maxstep, id_instruction FROM instructions i
GROUP BY i.id_instruction
) i2 ON i2.id_instruction = i.id_instruction
LEFT JOIN steps s ON s.id_instruction = i.id_instruction AND s.id_step = i.id_step
GROUP BY i.id_instruction, i.id_step
) sq
GROUP BY maxstep, id_instruction
Result:
maxstep id_instruction val_0 val_1 val_2
-----------------------------------------------------------------
2 1 One,Two,Three Five,Six,Seven Ten,Eight,Nine
By changing the query a little so that the inner join only gets the highest step and by setting the outer query to only take id_step=0 you can get what you want.
SELECT maxstep, i.id_instruction,GROUP_CONCAT(s.val) AS val_0, val_1
FROM instructions i
INNER JOIN (
SELECT MAX(ins.id_step) AS maxstep, ins.id_instruction, GROUP_CONCAT(st.val) as val_1 FROM instructions ins
LEFT JOIN steps st ON st.id_instruction = ins.id_instruction AND st.id_step = ins.id_step
where (ins.id_instruction, ins.id_step) in (select id_instruction, max(id_step) from instructions group by id_instruction)
GROUP BY ins.id_instruction, ins.id_step
order by maxstep, ins.id_instruction, st.val
)
i2 ON i2.id_instruction = i.id_instruction
LEFT JOIN steps s ON s.id_instruction = i.id_instruction AND s.id_step = i.id_step
where i.id_step=0
GROUP BY i.id_instruction, i.id_step;
Result from the query with extended data now looks like
| maxstep | id_instruction | val_0 | val_1 |
| 2 | 1 | One,Two,Three | Eight,Nine,Ten |
| 3 | 2 | One,Two,Three | 21,22,23 |
Related
I have a JSON_ARRAY of ids in the form of [1,3,...]. Each value represents an id to a value in another table.
Table: pets
id | value
1 | cat
2 | dog
3 | hamster
Table: pet_owner
id | pets_array
1 | [1, 3]
2 | [2]
3 | []
What I want to get when I query pet_owners is the following result:
Table: pet_owner
id | pets_array
1 | ["cat", "hamster"]
2 | ["dog"]
3 | []
How do I run a sub-select on each array element to get its value?
As JSON goes, it is always a pain to handle
When you need also all that have no pets, you must left Join the owner table
CREATE TABLE pet_owner (
`id` INTEGER,
`pets_array` JSON
);
INSERT INTO pet_owner
(`id`, `pets_array`)
VALUES
('1', '[1, 3]'),
('2', '[2]'),
('3', '[]');
CREATE TABLE pets (
`id` INTEGER,
`value` VARCHAR(7)
);
INSERT INTO pets
(`id`, `value`)
VALUES
('1', 'cat'),
('2', 'dog'),
('3', 'hamster');
SELECT
t1.id,
JSON_ARRAYAGG(
p.`value`
) AS pets_array
FROM(
SELECT *
FROM pet_owner ,
JSON_TABLE(
pet_owner.pets_array , "$[*]"
COLUMNS(IDs int PATH "$" NULL ON ERROR DEFAULT '0' ON EMPTY )
) AS J_LINK ) t1
LEFT JOIN pets p ON p.id =t1.IDs
GROUP BY
t1.id
;
id | pets_array
-: | :-----------------
1 | ["cat", "hamster"]
2 | ["dog"]
db<>fiddle here
A normalized Table would spare you to convert the data into usable columns.
You can join on json_contains(), then re-aggregate:
select po.id, json_arrayagg(p.value) as owners
from pet_owner po
left join pets p on json_contains(po.pets_array, cast(p.id as char))
group by po.id
Note that, unlike most (if not all!) other databases, MySQL does not guarantee the ordering of elements in an array generated by json_arrayagg(): that's just a fact we have to live with as of the current version.
I have to build one SQL SELECT query. Table structure is as follows:
[ID] [PHONE] [description] [DATE]
[1] [600898367] [main] [2016-01-23]
[2] [600898367] [] [2016-01-24]
[3] [600898367] [] [2016-01-26]
[4] [600898367] [] [2016-01-28]
[5] [662349093] [main] [2016-01-10]
[6] [662349093] [] [2016-01-21]
[7] [662349093] [] [2016-01-30]
[8] [662349093] [] [2016-01-31]
You have here different records grouped within the same telephone number. The first (the oldest) occurance is marked with [main] flag. There's no two identical numbers with [main] flag.
I want to select each [main] record and additionaly one youngest with the same phone number, so the result should give records 1,4,5,8.
Please help.
Use a WHERE clause to give you the records with the main flag. Use MAX to get the most recent record and JOIN to get the additional columns. Finally, do a UNION ALL to combine the result.
-- Get the main records first
SELECT *
FROM tbl
WHERE description = 'main'
UNION ALL
-- Get the most recent records
SELECT b.*
FROM (
SELECT
t.PHONE,
MAX(DATE) AS MaxDate
FROM tbl t
GROUP BY PHONE
) a
INNER JOIN tbl b -- Do a JOIN to get the additional columns
ON b.PHONE = a.PHONE
AND b.DATE = a.MaxDate
Try this;)
SQL Fiddle
MySQL 5.6 Schema:
CREATE TABLE table1
(`ID` int, `PHONE` int, `description` varchar(4), `DATE` varchar(11))
;
INSERT INTO table1
(`ID`, `PHONE`, `description`, `DATE`)
VALUES
(1, 600898367, 'main', '2016-01-23'),
(2, 600898367, NULL, '2016-01-24'),
(3, 600898367, NULL, '2016-01-26'),
(4, 600898367, NULL, '2016-01-28'),
(5, 662349093, 'main', '2016-01-10'),
(6, 662349093, NULL, '2016-01-21'),
(7, 662349093, NULL, '2016-01-30'),
(8, 662349093, NULL, '2016-01-31')
;
Query 1:
select t.*
from table1 t
inner join (
select `PHONE`, max(`DATE`) as `DATE` from table1 group by `PHONE`
) t1 on t.`PHONE` = t1.`PHONE` and (t.`DATE` = t1.`DATE` or t.`description` = 'main')
order by t.`ID`
Results:
| ID | PHONE | description | DATE |
|----|-----------|-------------|------------|
| 1 | 600898367 | main | 2016-01-23 |
| 4 | 600898367 | (null) | 2016-01-28 |
| 5 | 662349093 | main | 2016-01-10 |
| 8 | 662349093 | (null) | 2016-01-31 |
You can use the following query:
SELECT t1.*, t3.*
FROM mytable AS t1
LEFT JOIN (
SELECT PHONE, MAX(date) AS max_date
FROM mytable
GROUP BY PHONE
) AS t2 ON t1.PHONE = t2.PHONE
LEFT JOIN mytable AS t3 ON t1.PHONE = t3.PHONE AND t2.max_date = t3.`date`
WHERE t1.description = 'main'
With a GROUP BY we'll first group on both PHONE and description, thus getting 4 rows.
Next we'll create a comma separated set using GROUP_CONCAT. It can take an ORDER BY clause, to order the phone numbers by date.
Last we want to get the first item from the set, we can do this with SUBSTRING_INDEX.
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(`PHONE` ORDER BY `DATE`), ',', 1) AS `PHONE`,
description
FROM table1
GROUP BY `PHONE`, `description`;
See Fiddle
Try the following query..
SELECT
A.*
FROM
`old_young` A
INNER JOIN
(SELECT
MIN(`DATE`) AS Res
FROM
old_young
WHERE description = 'main'
GROUP BY PHONE
UNION
ALL
SELECT
MAX(`DATE`) AS Res
FROM
old_young
WHERE description = ''
GROUP BY PHONE) B
ON A.DATE = B.Res ;
Check the FIDDLE
Here's the SQLFiddle with schema and data.
I'm trying to sum 2 columns, one at parent level and the other at child level.
The current query I'm using gives me the right sum amount on child level, but doubles up the amount on parent level, due to another 1-many relationship involved on the child level.
Ugh... that's a terrible explanation - here's the English version:
Joe the salesman is involved in 2 sales.
For the 1st sale, he get's 2 sets of commissions, based on 2 different commission types. I'm trying to show Joe's total sale value, alongside the total value of his applicable splits. The split value total is fine, but sale value get's doubled up because I'm obviously, grouping/joining incorrectly (see the last example below).
This is fine:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl
where s.product_id = pr.id
and s.id = spl.sale_id
and sp.id = spl.sales_person_id
group by sp.id;
person_name | description | SplitValue
----------- ----------- | ----------
Joe | Widget 1 | 50
Sam | Widget 1 | 10
This is also yields the correct split and sale values, but now 3 rows are displayed for Joe (i.e 2nd row is a duplicate of the 1st one) - I only want to display Joe's "Widget 1" sale once, so not correct:
select sp.person_name, pr.description,
sum(s.sale_value) as SaleValue, sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id, spl.id;
person_name | description | SplitValue | SaleValue
----------- ----------- ---------- ---------
Joe | Widget 1 | 10 | 20
Joe | Widget 1 | 10 | 20
Joe | Widget 2 | 30 | 30
Sam | Widget 1 | 10 | 20
Now the duplicated row is gone, but Joe's SaleValue is incorrect - it should be 50, not 70:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue, sum(s.sale_value) as SaleValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id;
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 70
Sam | Widget 1 | 10 | 20
I.e. I'm after the query that will yield this result (i.e. Joe's correct SaleValue of 50):
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 50
Sam | Widget 1 | 10 | 20
Any help will be greatly appreciated!
UPDATE 1:
For clarity - here's the schema and test data from the fiddle:
CREATE TABLE product
(`id` int, `description` varchar(12))
;
INSERT INTO product
(`id`, `description`)
VALUES
(1, 'Widget 1'),
(2, 'Widget 2')
;
CREATE TABLE sales_person
(`id` int, `person_name` varchar(7))
;
INSERT INTO sales_person
(`id`, `person_name`)
VALUES
(1, 'Joe'),
(2, 'Sam')
;
CREATE TABLE sale
(`id` int, `product_id` int, `sale_value` int)
;
INSERT INTO sale
(`id`, `product_id`, `sale_value`)
VALUES
(1, 1, 20.00),
(2, 2, 30.00)
;
CREATE TABLE split_type
(`id` int, `description` varchar(6))
;
INSERT INTO split_type
(`id`, `description`)
VALUES
(1, 'Type 1'),
(2, 'Type 2')
;
CREATE TABLE sales_split_agreement
(`id` int, `sales_person_id` int, `split_type_id` int, `percentage` int)
;
INSERT INTO sales_split_agreement
(`id`, `sales_person_id`, `split_type_id`, `percentage`)
VALUES
(1, 1, 1, 50),
(2, 1, 2, 50),
(3, 2, 1, 50),
(4, 1, 1, 100)
;
CREATE TABLE sales_split
(`id` int, `sale_id` int, `sales_split_agreement_id` int, `sales_person_id` int, `split` int )
;
INSERT INTO sales_split
(`id`, `sale_id`, `sales_split_agreement_id`, `sales_person_id`, `split`)
VALUES
(1, 1, 1, 1, 10),
(2, 1, 2, 1, 10),
(3, 1, 3, 2, 10),
(4, 2, 4, 1, 30)
;
I think you were on to the right track, but I decided to restart and approach from the beginning. Getting the SplitValue for each person does not require all those tables. In fact, all you need are sales_split and sales_person, like this:
SELECT sp.person_name, SUM(ss.split) AS SplitValue
FROM sales_person sp
JOIN sales_split ss ON sp.id = ss.sales_person_id
GROUP BY sp.id;
Similarly, you can get the total sale value for each person with a join between sale, sales_split, and sales_person:
SELECT sp.person_name, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
JOIN sales_person sp ON sp.id = ss.sales_person_id
GROUP BY sp.id;
At this point, I realize you have an error in your expected results (for this data set). Joe does in fact have a sale value of 70, because sale id 1 (value 20), 2 (value 20), and 4 (value 30) add up to 70. However, I still think this query will help you out more than the one you have.
At this point, you can get the values for each sales_person_id by joining those two subqueries to the sales_person table. I took out the join to sales_person in the subqueries, as it became irrelevant now. It even makes the subqueries a little cleaner:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT ss.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
GROUP BY ss.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is an SQL Fiddle example.
EDIT: I understand now why Joe's actual sale price is 50, because he split twice on sale id 1. To work around this, I first got a list of distinct sales for each sales_person like this:
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split;
This way, there is only one row for sales_person_id = 1 and sale_id = 1. Then, it was easy enough to join that to the sale table and get the proper sales value for each sales_person:
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id;
The rest of my answer above still fits. I wrote one query to get SplitValue, and one query to get SaleValue, and I joined them together. So, all I have to do now is replace the subquery I just gave you, with the incorrect subquery from further up:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is the updated SQL Fiddle.
You mentioned in the comments that you shortened your data for brevity, which is fine. I am leaving my joins as they are, and I trust that it gives you enough direction that you can adjust them accordingly to match your proper structure.
*None of other available answers solved my problem
I have a table t like this
id,cc,count
'1','HN','22'
'1','US','18'
'1','VN','1'
'2','DK','2'
'2','US','256'
'3','SK','1'
'3','US','66310'
'4','UA','2'
'4','US','263'
'6','FR','7'
'6','US','84'
'9','BR','3'
I want to get the rows for ids with maximum count, like below:
id,cc,count
'1','HN','22'
'2','US','256'
'3','US','66310'
'4','US','263'
'6','US','84'
'9','BR','3'
My current code is like this but I am not getting the expected results:
SELECT t.* FROM t
JOIN (
SELECT
t.id,t.cc
,max(t.count) as max_slash24_count
FROM t
group by t.id,t.cc
) highest
ON t.count = highest.max_slash24_count
and t.cc = highest.cc
Can anybody help me out?
Remove CC column from group by. Try this.
SELECT t.* FROM t
JOIN (
SELECT
t.id
,max(t.count) as max_slash24_count
FROM t
group by t.id
) highest
ON t.count = highest.max_slash24_count
and t.id= highest.id
Try this:
create table t (id varchar(10), cc varchar(10), count varchar(10))
insert into t (id,cc,count) values ('1','HN','22');
insert into t (id,cc,count) values ('1','US','18');
insert into t (id,cc,count) values ('1','VN','1');
insert into t (id,cc,count) values ('2','DK','2');
insert into t (id,cc,count) values ('2','US','256');
insert into t (id,cc,count) values ('3','SK','1');
insert into t (id,cc,count) values ('3','US','66310');
insert into t (id,cc,count) values ('4','UA','2');
insert into t (id,cc,count) values ('4','US','263');
insert into t (id,cc,count) values ('6','FR','7');
insert into t (id,cc,count) values ('6','US','84');
insert into t (id,cc,count) values ('9','BR','3');
select *
from t
where exists (
select *
from t as t1
group by t1.id
having t1.id = t.id and max(t1.count) = t.count
)
Result
ID CC COUNT
-------------
1 HN 22
2 US 256
3 US 66310
4 US 263
6 US 84
9 BR 3
Check SQLFiddle
This question was answered a lot of times on SO. The query is as simple as this:
SELECT m.id, m.cc, m.count
FROM t m # "m" from "max"
LEFT JOIN t b # "b" from "bigger"
ON m.id = b.id # match a row in "m" with a row in "b" by `id`
AND m.count < b.count # match only rows from "b" having bigger count
WHERE b.count IS NULL # there is no "bigger" count than "max"
The real issue on your question is about the column types. If count is char (and not int) then the string comparison happens using the dictionary order, not the numeric order.
For example, if the third row reads:
'1','VN','123'
you might expect it to be selected in the output, because 123 is bigger than 22. This does not happen because, as string, '123' is smaller than '22'.
Even tho, this was already answered, using ROW_NUMBER functionality as in SQL Server is quite fun and interesting: please look at this query:
SELECT TT.Id, TT.cc, TT.count
FROM (
SELECT t.cc
, t.count
, #row_number:=CASE WHEN #Id=Id THEN #row_number+1 ELSE 1 END AS row_number
, #Id:=Id AS Id
FROM t, (SELECT #row_number:=0, #Id:='') AS temp
ORDER BY t.Id, t.count DESC
) AS TT
WHERE TT.row_number = 1
ORDER BY TT.Id;
It produces expected output:
| Id | cc | count |
|----|----|-------|
| 1 | HN | 22 |
| 2 | US | 256 |
| 3 | US | 66310 |
| 4 | US | 263 |
| 6 | US | 84 |
| 9 | BR | 3 |
SQLFiddle
I've taken test data from #Andrey Morozov
I want to do subquery with 3 tables and using where in multi values but I always get syntax error. I have to do reporting in Report Builder 3.0
Table A: record_id, Surname, Given Name
Table C: row_id, competency_code, competency_name
Table PC: link_id, record_id, row_id, attainment_date
I would like to join the tables into 1 table. One person will have some completion of competency_code and different with other person. the completion of competency_code based on the attainment_date. I also think to use iff function for attainment_date in competency_code value as complete/yes.
The table that I would like to create is:
Record_Id | Surname | GivenName | Code 1 | Code 2 | Code 3 | Code 4 | Code 5
01 | AA | AA | Complete | Complete | Complete | | Complete
02 | BB | BB | Complete | Complete | | Complete |
03 | CC | CC | | Complete | Complete | | Complete
here is the query that I tried to do.
select distinct a.id, a.surname, a.given_name
from all a
join
(
select pc.attainment_date
from personnel_competency pc
join
(
select c.code, c.name
from competency c)
competency c on (c.row_no = pc.linkid)
)
personnel_competency pc on (pc.id = a.id)
where c.code in ('ABC', 'BCD', 'ABE', 'DEA', 'DEF', 'POS', 'SAQ', 'LOP')
and pc.attainment_date < now()
order by a.record_id
My skill in SQL is very basic. Whether other ways to make the table like that?
Are you looking for a SQL to get your result. If so I think this is what you are looking for ..
It would help if you posted some sample data.
You can test it at
SQLFiddle
Here is the script ..
-- Generate schema and data
create table tableA (id int, surname varchar(30), given_name varchar(30));
create table tablePC (link_id int, id int, attainment_date datetime);
create table tableC (row_id int, competency_code varchar(20), Competency_name varchar(30));
insert into tableA (id, surname, given_name)
values (1, 'AA', 'AAgn')
, (2, 'BB', 'BBgn')
insert into tablePC (link_id, id, attainment_date)
values (1, 1, '2014-09-11')
, (2, 1, '2014-09-10')
, (3, 2, '2014-09-11')
insert into tableC (row_id, competency_code, Competency_name)
values (1, 'ABC', 'completed\Yes')
, (1, 'BCD', 'completed')
, (1, 'ABE', 'completed')
, (2, 'ABC', 'completed')
, (2, 'BCD', 'completed')
, (3, 'ABC', 'completed')
, (3, 'ABE', 'completed')
-- ===============
select *
from tableA TA
inner join tablePC PC
on TA.id = PC.id
inner join
(
select row_id, [ABC] as ABC, [BCD] as BCD, [ABE] as ABE
from tableC TC
pivot
(
max(Competency_name)
for Competency_code in ([ABC], [BCD], [ABE])
) as TCPVT
) TC
on PC.link_id = TC.row_id
where PC.attainment_date < GETDATE()