Does anyone know if it's possible to turn something like the table below.. While still being able to use a SELECT query.
+----+-----------+---------------+------------+
| id | listingId | value | identifier |
+----+-----------+---------------+------------+
| 1 | 1a | Alaskan Husky | race |
| 2 | 1a | High | activity |
| 3 | 1a | White | colour |
| 4 | 1b | Akita | race |
| 5 | 1b | Medium | activty |
| 6 | 1b | Grey | colour |
+----+-----------+---------------+------------+
To something like this, while still being able to use a select query..
+----+-----------+---------------+---------+--------+
| id | listingId | race | activty | colour |
+----+-----------+---------------+---------+--------+
| 1 | 1a | Alaskan Husky | High | White |
| 2 | 1b | Akita | Medium | Grey |
+----+-----------+---------------+---------+--------+
I also want to be able to search this new "table". Let's say, the user has picked a filter with something like this:
Race: Alaskan Husky
Activity: Medium, High
It should then return the listingId of 1a.
The reason being for this, is that I can't do a proper SELECT query, when all the values are placed in different rows..
As you can see, the listingId should be the variable that groups it all together, and makes the value in the identifier column, a new column.
The reason why I don't just use the second table as default, is because each listing can have different filters and filter groups. And I need to be able to select specific listings that meet a user specified filter.
Thanks.
You can use conditional aggregation :
select min(id) id, listingId,
max(case when [identifier] = 'race' then [value] end) race,
max(case when [identifier] = 'activity' then [value] end) activity,
max(case when [identifier] = 'colour' then [value] end) colour
from table t
group by listingId;
This might do the trick, just a few self joins. I think there a few ways you could accomplish this but for me this is pretty easy to read and maintain.
CREATE TABLE [dbo].[the_table](
[id] [int] NULL,
[listingId] [varchar](50) NULL,
[value] [varchar](50) NULL,
[identifier] [varchar](50) NULL
) ON [PRIMARY]
GO
INSERT INTO the_table (id, listingId, value, identifier)
VALUES (1, '1a', 'alaskan huskey', 'race'),
(2, '1a', 'high', 'activity'),
(3, '1a', 'white', 'colour'),
(4, '1b', 'akita', 'race'),
(5, '1b', 'medium', 'activity'),
(6, '1b', 'grey', 'colour')
SELECT * FROM
(SELECT a.id, a.listingID, a.value AS race, b.value as activity, c.value as colour
FROM the_table a
INNER JOIN the_table b
ON a.listingId = b.listingId
INNER JOIN the_table c
ON a.listingId = c.listingId
WHERE a.identifier = 'race'
AND b.identifier = 'activity'
AND c.identifier = 'colour') AS t
WHERE t.colour = 'white'
Output:
You can achieve it using Pivot
DECLARE #MyTable TABLE (Id INT, listingId VARCHAR(20),Value VARCHAR(20), identifier varchar(20))
INSERT INTO #MyTable VALUES
(1,'1a','Alaskan Husky' ,'race'),
(2,'1a','High' ,'activity'),
(3,'1a','White' ,'colour'),
(4,'1b','Akita' ,'race'),
(5,'1b','Medium','activity'),
(6,'1b','Grey' ,'colour')
SELECT *
FROM
(
SELECT listingId, identifier,Value FROM #MyTable)t
PIVOT(MIN(Value)
FOR identifier
IN (race,activity,colour)
)p
Related
Assessments table:
+---------------+-----------+---------------------+
| assessment_id | device_id | created_at |
+---------------+-----------+---------------------+
| 1 | 1 | 2022-07-15 20:03:03 |
| 2 | 2 | 2022-07-15 21:03:03 |
| 3 | 1 | 2022-07-15 22:03:03 |
| 4 | 2 | 2022-07-15 23:03:03 |
| 5 | 2 | 2022-07-15 23:03:03 |
+---------------+-----------+---------------------+
Results table:
+---------------+---------+--------+
| assessment_id | test | result |
+---------------+---------+--------+
| 1 | A | PASS |
| 2 | B | FAIL |
| 3 | A | FAIL |
| 4 | B | PASS |
| 5 | B | PASS |
+---------------+---------+--------+
Objective
I would like to return a row for each time the result of a test_id changes.
For example, Device 1 has Assessments 1 and 3. Test A in Assessment 1 was PASS, and Test A in Assessment 3 was FAIL, so we want to return this change as a row.
Device 2 has Assessments 2, 4, and 5. There was a test result change in Assessments 2 and 4 (Test B changed from FAIL to PASS), we want to return a row for this.
We do not want to return a row for Assessment 5 because Test B was PASS, and it was also PASS in Assessment 4. No change.
The resulting table would look like this:
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
| device_id | test_id | previous_assessment_id | previous_value | previous_value_date | next_assessment_id | next_value | next_value_date |
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
| 1 | A | 1 | PASS | 15/07/2022 20:03:03 | 3 | FAIL | 15/07/2022 22:03:03 |
| 2 | B | 2 | FAIL | 15/07/2022 21:03:03 | 4 | PASS | 15/07/2022 23:03:03 |
+-----------+---------+------------------------+----------------+----------------------+--------------------+------------+----------------------+
I've tried playing around with a couple of queries found here on SO, but they either took a long time and returned the wrong data, or didn't work at all. I don't think this is a duplicate because I'm using multiple tables, and every other question I've seen refers to a single table.
I've also looked at this SO question but could not get the helpful answer to apply to my situation.
I'm having some weird issue getting SQL Fiddle to work, but here is the test schema I've been tinkering with:
CREATE TABLE `assessments` (
`id` int,
`device_id` int,
`created_at` datetime
);
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (1, 1, '2022-07-09 22:56:00');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (2, 2, '2022-07-10 22:56:06');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (3, 1, '2022-07-11 22:56:11');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (4, 2, '2022-07-12 22:56:17');
INSERT INTO `so_assessments` (`id`, `device_id`, `created_at`) VALUES (5, 2, '2022-07-13 22:56:24');
CREATE TABLE `results` (
`assessment_id` int,
`test` enum('A','B'),
`result` enum('PASS','FAIL')
);
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (1, 'A', 'PASS');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (2, 'B', 'FAIL');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (3, 'A', 'FAIL');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (4, 'B', 'PASS');
INSERT INTO `results` (`assessment_id`, `test`, `result`) VALUES (5, 'B', 'PASS');
If you are using MySQL 8, Window functions can help. https://dev.mysql.com/doc/refman/8.0/en/window-function-descriptions.html
You can partition your results by device and test, and add a column that is the previous value of the result, then use the last row where the result differs from the previous value.
The following query creates a new column in your results with previous_value
SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
Yields the result:
+---------------+-----------+------+--------+----------------+------------------------+
| assessment_id | device_id | test | result | previous_value | previous_assessment_id |
+---------------+-----------+------+--------+----------------+------------------------+
| 1 | 1 | A | PASS | NULL | NULL |
| 3 | 1 | A | FAIL | PASS | 1 |
| 2 | 2 | B | FAIL | NULL | NULL |
| 4 | 2 | B | PASS | FAIL | 2 |
| 5 | 2 | B | PASS | PASS | 4 |
+---------------+-----------+------+--------+----------------+------------------------+
Which is a big part of the battle. Now we need to take that result and find the row for each device/test pair with the highest assessment_id, where result != previous_value.
The window is calculated after GROUP BY, ORDER BY, and even HAVING, so there's not much more that can be done in that query (that I have thought of) to narrow it down to the only the most recent entries for each device/test pair. So the above will have to be a subquery to get the final result.
Note: I am going to assume that if the result never changes, you want to show the first time that result was recorded. In other words, you want to count results with previous_value = NULL as a transition.
Here's a query that lists all the times the test result from a device/test pair changes:
SELECT * FROM
(SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY `device_id`, `test` ORDER BY `assessment_id`)
) AS t
WHERE result != `previous_value` OR `previous_value` IS NULL
gets the result (I left out previous_assesssment_id and the others for space):
+---------------+-----------+------+--------+----------------+
| assessment_id | device_id | test | result | previous_value |
+---------------+-----------+------+--------+----------------+
| 1 | 1 | A | PASS | NULL |
| 3 | 1 | A | FAIL | PASS |
| 2 | 2 | B | FAIL | NULL |
| 4 | 2 | B | PASS | FAIL |
+---------------+-----------+------+--------+----------------+
EDIT
That's the answer to the question. If the first time the value is set is not of interest, just delete the OR part of the WHERE clause. There rest of this answer is because I convinced myself the problem was to get the MOST RECENT time the value flipped. I'm leaving it here, but only for interest.
Carrying On
This is all the times the outcome was different than previous, plus the first time a result was recorded. Almost there.
It would be tempting at this point to add another window in the outer query to aggregate the rows from above and identify the correct rows. But at least in MySQL 8, nested windows are not supported.
But given that result, we can create a query using MAX() and GROUP BY that gives the assessment_ids of all the rows we ultimately want:
SELECT MAX(assessment_id)
FROM (
SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
) AS t
where result != previous_value OR previous_value IS NULL
GROUP BY device_id, test
Which yields:
+--------------------+
| MAX(assessment_id) |
+--------------------+
| 3 |
| 4 |
+--------------------+
Now we know exactly which rows we need; but we built all that data about the previous values, and now we need a way to join the result of that query with the result of the subquery.
Happily, MySQL 8 has a way to stash a query and use it multiple times, called Common Table Expressions, that use the WITH clause docs here. So we can create the table with all the fun data, then use it as a subquery to get the id's we ultimately want, and then join that right back with the results we just created:
WITH
transitions AS (SELECT
assessment_id,
device_id,
test,
result,
LAG (result) over w as `previous_value`,
LAG (assessment_id) over w as `previous_assessment_id`
FROM assessments join results using(assessment_id)
WINDOW w AS (PARTITION BY device_id, test ORDER BY assessment_id)
)
SELECT transitions.*
FROM transitions
JOIN (
SELECT MAX(assessment_id) as assessment_id
FROM transitions
WHERE result != previous_value OR previous_value IS NULL
GROUP BY device_id, test
) AS t2 using (assessment_id)
Giving us the final answer (with the other columns you can fill in):
+---------------+-----------+------+--------+----------------+------------------------+
| assessment_id | device_id | test | result | previous_value | previous_assessment_id |
+---------------+-----------+------+--------+----------------+------------------------+
| 3 | 1 | A | FAIL | PASS | 1 |
| 4 | 2 | B | PASS | FAIL | 2 |
+---------------+-----------+------+--------+----------------+------------------------+
The first part creates a data set that includes all the information about what came before each test. Then we write a query that gets the id's of the interesting rows in that query, then we join back to the original data set to fill in all the columns.
You can use windows functions to peek at values of previous or successive rows according to an ordering and grouping.
For example:
select *
from (
select
a.device_id,
r.test,
a.id as prev_assessment_id,
r.result as prev_result,
a.created_at as prev_value_date,
lead(a.id) over(partition by a.device_id
order by a.created_at) as next_assessment_id,
lead(r.result) over(partition by a.device_id
order by a.created_at) as next_result,
lead(a.created_at) over(partition by a.device_id
order by a.created_at) as next_value_date
from assessments a
join results r on r.assessment_id = a.id
) x
where prev_result <> next_result
Result:
device_id test prev_assessment_id prev_result prev_value_date next_assessment_id next_result next_value_date
---------- ----- ------------------- ------------ -------------------- ------------------- ------------ -------------------
1 A 1 PASS 2022-07-09 22:56:00 3 FAIL 2022-07-11 22:56:11
2 B 2 FAIL 2022-07-10 22:56:06 4 PASS 2022-07-12 22:56:17
See running example at db<>fiddle.
Note: Your setup queries (that I used) include different data, compared to your expected result.
I've a table like this:
CREATE TABLE `base_build_floor` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`build_no` varchar(64) NOT NULL,
`build_name` varchar(64) DEFAULT NULL,
`floor_no` varchar(64) DEFAULT NULL,
`floor_name` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
)
and insert some data:
INSERT INTO `base_build_floor` VALUES ('41', 'BUILD40210011', 'A', null, null);
INSERT INTO `base_build_floor` VALUES ('42', 'BUILD40210012', 'B', null, null);
INSERT INTO `base_build_floor` VALUES ('43', 'BUILD40210013', 'C', null, null);
INSERT INTO `base_build_floor` VALUES ('44', 'BUILD40210013', 'C', 'FLOOR40210002', 'C1');
INSERT INTO `base_build_floor` VALUES ('45', 'BUILD40210013', 'C', 'FLOOR40210003', 'C2');
INSERT INTO `base_build_floor` VALUES ('46', 'BUILD40210012', 'B', 'FLOOR40210004', 'B1');
the table is about a build-floor table, first you should make a building, then, a building can has no or some floors. the A building has no floor, the B building has one floor named B1, the C building has two floors named C1 and C2, I want to get the result as below:
41 BUILD40210011 A null null
44 BUILD40210013 C FLOOR40210002 C1
45 BUILD40210013 C FLOOR40210003 C2
46 BUILD40210012 B FLOOR40210004 B1
it means that, if a building has no floors, then get it, while if a building has any one floor, the building itself should not be got, so how to write the mysql?I've tried to use Subquery but doesn't work
I've try like this :
SELECT
b.*
FROM
base_build_floor b
WHERE
b.floor_no IS NOT NULL
OR (
b.floor_no IS NULL
AND b.build_no NOT IN (
SELECT
GROUP_CONCAT(nostr)
FROM
(
SELECT
concat("'", f.build_no, "'") as nostr
FROM
base_build_floor f
WHERE
f.floor_no IS NOT NULL
GROUP BY
f.build_no
) t
)
)
but I get all the data
With NOT EXISTS:
select t.* from base_build_floor t
where t.floor_no is not null
or not exists (
select 1 from base_build_floor
where build_no = t.build_no and floor_no is not null
)
See the demo.
Results:
| id | build_no | build_name | floor_no | floor_name |
| --- | ------------- | ---------- | ------------- | ---------- |
| 41 | BUILD40210011 | A | | |
| 44 | BUILD40210013 | C | FLOOR40210002 | C1 |
| 45 | BUILD40210013 | C | FLOOR40210003 | C2 |
| 46 | BUILD40210012 | B | FLOOR40210004 | B1 |
This query would be much simpler if you had normalized tables. Ideally, you would have a buildings table with building id, no, and name, and a floors table with building id, floor no, and floor name. Then you could just join the two tables. Since that's not the case, we can basically extract the building and floor sub-tables from the main one and join them like this:
SELECT
b.build_no,
b.build_name,
f.floor_no,
f.floor_name
FROM
(SELECT DISTINCT build_no, build_name
FROM base_build_floor) b
LEFT OUTER JOIN
(SELECT *
FROM base_build_floor
WHERE floor_no IS NOT NULL) f ON b.build_no = f.build_no
I want to match id's of column 1 from column 2.
_________________________________
| uid | profile_id | status |
|------|-------------|-----------
| 1 | 2 | checked |
| 2 | 1 | checked |
| 3 | 4 | unchecked|
| 4 | 1 | unchecked|
| 4 | 3 | checked |
| 1 | 4 | checked |
...
This is my table. I want to show the result of same values that match from id1 to id2 and status is checked. Following is the output:
__________________________
| uid | profile_id | status |
|------|-------------|-----------
| 1 | 2 | checked |
| 2 | 1 | checked |
...
Because id1 1 check the id2 2 and vice versa.
I done the following code.
SELECT
`aa`.`uid` AS `uid`,
`aa`.`profile_id` AS `profile_id`,
`aa`.`match_type` AS `match_type`
FROM
(
`matched_profiles` `aa`
left join `matched_profiles` `ab` on(
(`aa`.`uid` = `ab`.`profile_id`)
)
)
where
(
(`aa`.`uid` = `ab`.`profile_id`)
and (`ab`.`uid` = `aa`.`profile_id`)
);
but above code also show me the unchecked result.
Great question. Because of the structure of the data int this table, you can do this by joining the table to itself. You will have two sets of data, replicas of one another.
You want to make sure there is a two-way match between uid and profile_id.
You also want the status for both directions to be checked
SELECT
a.*
FROM matched_profiles a
inner join matched_profiles a2 on
a.uid = a2.profile_id
and a.profile_id = a2.uid
and a.status = 'checked'
and a2.status = 'checked';
You have a kind of explicit/inexplicit join going on. I reformatted to be completely explicit for clarity. You just need to add a filter to make sure all status values are checked.
Below is the SQL to build your test schema, which is nice to provide when asking these questions for SO users.
create table matched_profiles (
uid int,
profile_id int,
status varchar(18)
);
insert into matched_profiles
values
(1, 2, 'checked'),
(2, 1, 'checked'),
(3, 4, 'unchecked'),
(4, 1, 'unchecked'),
(4, 3, 'checked'),
(1, 4, 'checked');
So, lets say I have a table called "imports" that looks like this:
| id | importer_id | total_m | total_f |
|====|=============|=========|=========|
| 1 | 1 | 100 | 200 |
| 1 | 1 | 0 | 200 |
And I need the query to return it pivoted or transposed (rows to columns) in this way:
| total_m | sum(total_m) |
| total_f | sum(total_f) |
I can't think on a way to do this without using another table (maybe a temporary table?) and using unions, but there should be a better way to this anyway (maybe with CASE or IF?).
Thanks in advance.
select 'total_m', sum(total_m) from imports
union
select 'total_f', sum(total_f) from imports
http://sqlfiddle.com/#!9/fc1c0/2/0
You can "unpivot" by first expanding the number of rows, which is done below by cross joining a 2 row subquery. Then on each of those rows use relevant case expression conditions to align the former columns to the new rows ("conditional aggregates").
SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE imports
(`id` int, `importer_id` int, `total_m` int, `total_f` int)
;
INSERT INTO imports
(`id`, `importer_id`, `total_m`, `total_f`)
VALUES
(1, 1, 100, 200),
(1, 1, 0, 200)
;
Query 1:
select
*
from (
select
i.importer_id
, concat('total_',cj.unpiv) total_type
, sum(case when cj.unpiv = 'm' then total_m
when cj.unpiv = 'f' then total_f else 0 end) as total
from imports i
cross join (select 'm' as unpiv union all select 'f') cj
group by
i.importer_id
, cj.unpiv
) d
Results:
| importer_id | total_type | total |
|-------------|------------|-------|
| 1 | total_f | 400 |
| 1 | total_m | 100 |
I have a table with attributes to a product:
product_attributes
+------------+--------------+-------------+------+
| product_id | attribute_id | language_id | text |
+------------+--------------+-------------+------+
| 1 | 2 | 2 | bla |
| 1 | 2 | 3 | blo |
| 1 | 3 | 2 | foo |
| 1 | 4 | 3 | bar |
+------------+--------------+-------------+------+
and I have another table called product with multiple product_ids that do not exist in the product_attributes table.
I would like to copy all data from product_attributes where product_id = 1 and insert them for ALL the ids in the product table.
This is what I had so far but it does not work.
INSERT INTO `product_attribute`(`product_id`, `attribute_id`, `language_id`, `text`) SELECT (SELECT 'product_id' FROM `product`), `attribute_id`, `language_id`, `text` FROM `product_attribute` WHERE `product_id` = '1';
It says the subquery returns more than 1 row (which it does, that's the point), I just don't know how to solve this with 2 subsets.
use cross join :
[1] solution :
INSERT INTO `product_attributes`(`product_id`, `attribute_id`,`language_id`, `text`)
select product_id,v.attribute_id,v.language_id,v.text
from product
cross join (SELECT attribute_id, language_id, text
FROM product_attributes
WHERE product_id = '1') v ;
[2] solution :
INSERT INTO `product_attributes`(`product_id`, `attribute_id`,`language_id`, `text`)
select b.product_id,`attribute_id`, `language_id`, `text`
from product_attributes a
CROSS join product b
use above sql query it is perfect and it has no error.
your sql :
INSERT INTO `product_attribute`(`product_id`, `attribute_id`, `language_id`, `text`)
SELECT (SELECT 'product_id' FROM product), attribute_id, language_id, text
FROM product_attribute
WHERE product_id = '1';
Here,in your sql (SELECT 'product_id' FROM product) returns
more than one value it is not possible in co-related sql.
when inner query behaves as a field then at a time it has only one value .
(SELECT 'product_id' FROM `product`)
In this query should be return more than one row.
I think this query should be not required.