mysql converting multiple rows into columns in a single row - mysql

i have a details table with columns:
user_id int
code int
value int
And i want to build a summary table that looks like:
user_id int
valueA int
valueB int
In the details table, valueA would correspond to say, code 5, and valueB would correspond to say, code 6, so i'm looking for something like:
insert into summary (user_id,valueA,valueB) VALUES ( SELECT ??? from details );
The problem of course is that i'm looking at multiple rows from the "details" table to populate one row in the "summary" table.
Eg, if i had the following rows in details:
1 5 100
1 6 200
2 5 1000
2 6 2000
I want to end up with the following in the summary table:
1 100 200
2 1000 2000
Any ideas?

MySQL doesn't have PIVOT/UNPIVOT syntax, which leaves you to use a combination of GROUP BY and CASE expressions:
INSERT INTO SUMMARY
(user_id,valueA,valueB)
SELECT d.user_id,
MAX(CASE WHEN d.code = 5 THEN d.value ELSE NULL END),
MAX(CASE WHEN d.code = 6 THEN d.value ELSE NULL END),
FROM DETAILS d
GROUP BY d.user_id

insert into summary (user_id,valueA,valueB)
SELECT a.user_id, a.value, b.value
from details a
join details b on a.user_id = b.user_id
WHERE a.code = 5 and b.code = 6;
beware: you will end up with multiple summary columns if user_id+code is not unique.
EDIT:
insert into summary (user_id,valueA,valueB)
select u.user_id, ifnull(a.value,0), ifnull(b.value,0)
from (select distinct user_id from details /* where code in (5,6) */) u
left join details a on a.user_id = u.user_id and a.code = 5
left join details b on b.user_id = u.user_id and b.code = 6

If you have a manageable set of codes (say just 5 and 6) you could do something like this:
SELECT details.user_id, code5.value, code6.value
FROM details JOIN
(SELECT user_id, value FROM details WHERE code = 5) AS code5 USING(user_id)
JOIN
(SELECT user_id, value FROM details WHERE code = 6) AS code6 USING(user_id);
You may need to modify your JOINs depending on if your codes are not required as 1 to 1 relationship (i.e. LEFT JOINs).
If you have a large set of codes, I would look into a cursor runs a similar query above over a result set of your codes or using a different technology, (i.e. PHP script).

Related

sql calculate present students in class on multiple rows

in barcodes table i have two column witch they are enter, exit like with student attendance record,
in this table when student entered to class, i add new row in table and enter value is 1 and exit is 0 like with login
after exiting from classroom i check latest row of this student and if enter is 1 then i add new row in table with 1 value for exit like with logout
now i want to calculate counting all students witch enter column is 1 and exit value is 0 to get all present students in class
SAMPLE DATA:
in this DB-FIDDLE i have sample data and into that i have 2 present student that session_id of them is 1, they are 123451,123452 students, but my sql command as second part is incorrect and that return one present student:
(
select count(*)
from barcodes b
where b.session_id = s.id
group by session_id, barcode
having sum(exit) = 0
) as present
for example:
select s.id, s.session_name, s.session_type, s.date_time,
(
select count(*)
from barcodes b where b.session_id = s.id
) as barcode_count ,
(
select count(*)
from barcodes b
where b.session_id = s.id
group by session_id, barcode
having sum(exit) = 0
) as present
from sessions s;
If I get what you want to do right, you can add another, outer aggregation.
...
(SELECT sum(x.count)
FROM (SELECT count(*) count
FROM barcodes b
WHERE b.session_id = s.id
GROUP BY b.session_id,
b.barcode
HAVING sum(b.enter) <> 0
AND sum(b.exit) = 0) x) present
...
But I think there's a much simpler way to get what you want by just taking the sum of enter minus exit for the session. Since any row with exit = 1 also comes with enter = 1, we need to double the exit before subtraction though.
...
(SELECT sum(b.enter - 2 * b.exit)
FROM barcodes b
WHERE b.session_id = s.id) present
...
If you can trust the data, something like this could work:
select
s.id,
s.session_name,
s.session_type,
s.date_time,
count(*) as barcode_count,
sum(exit = 0) - sum(exit = 1) as present
from sessions s
left join barcodes b on b.session_id = s.id
group by s.id, s.session_name, s.session_type, s.date_time
db-fiddle
You can read sum(exit = 0) - sum(exit = 1) as (number of entries) - (number of exits). If 4 students entered a class and 2 left the class, I would expect 2 students (4 - 2) to be still in the class.

Select excluding a field for a particular value in mysql query in same column

Perhaps the statement is not very clear, so here you go.
We have a mysql table where we have columns like this:
id(key) user_id action
1 4832 receive_content
2 4832 click_icon
3 4832 click_image
4 4447 receive_push
5 4447 click_url
I want to write a select statement where we will select user_id and action for same user_id where action is receive_content but the final output doesn't contain receive_content So the output is :
user_id action
4832 click_icon
4832 click_image
I have been trying case but it also selects the receive_content field which I want to exclude.
You can use an EXISTS clause to check that a user_id has a receive_content action, and then also check that the action for each row is not receive_content:
SELECT *
FROM actions
WHERE action != 'receive_content'
AND EXISTS (SELECT *
FROM actions a2
WHERE action = 'receive_content'
AND a2.user_id = actions.user_id)
Output:
id user_id action
2 4832 click_icon
3 4832 click_image
Demo on dbfiddle
try using inner query:
select a.user_id, a.action
from table a
join table b on b.user_id = (select b2.user_id from table b2 where b2.action = "receive_content" and b.user_id = b2.user_id)
where a.action <> "receive_content"
You must join the table with himself , after filter row you want to keep , and select columns you want .
The solution can be this query :
select a.user_id,a.action
from actions a join actions b on a.user_id = b.user_id and a.id !=b.id
where
b.action='receive_content' and a.action != 'receive_content'

repeated rows in json_agg() in query with 2 lateral joins

I have a strange result when performing a lateral join on a query
I have the following table structure
task->id
comment -> id , taskId, comment
tasklink -> taskId, type, userid
with a single task record (id 10), 1 comment record ("row1", "a test comment") and 5 tasklink records (all with taskid 10)
I expected this query
select task.id,
json_agg(json_build_object('id',c.id, 'user',c.comment)) as comments,
json_agg(json_build_object('type',b.type, 'user',b.userid)) as users
FROM task
left join lateral (select c.* from comment c where task.id = c.taskid) c on true
left join lateral (select b.* from taskuserlink b where task.id = b.taskid) b on true
where task.id = 10
GROUP BY task.id ;
to return
id | comments | users
---------------------------------------------------------------------
10 "[{"id":"row1","user":"a test comment"}]" "[{"type":"updatedBy","user":1},"type":"closedBy","user":5},"type":"updatedBy","user":5},"type":"createdBy","user":5},{"type":"ownedBy","user":5}]"
instead, I got this
id | comments | users
10 "[{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"},{"id":"row1","user":"a test comment"}]" "[{"type":"updatedBy","user":1},{"type":"closedBy","user":5},{"type":"updatedBy","user":5},{"type":"createdBy","user":5},{"type":"ownedBy","user":5}]"
ie , for every link row, the comment row is duplicated
I am thinking that I am missing something really obvious, but as I have only just started using Postgres (and sql ) I'm a little stumped
I would appreciate some guidance on where I'm going wrong
Move the aggregates into subqueries:
select id, comments, users
from task t
left join lateral (
select json_agg(json_build_object('id',c.id, 'user',c.comment)) as comments
from comment c
where t.id = c.taskid
) c on true
left join lateral (
select json_agg(json_build_object('type',b.type, 'user',b.userid)) as users
from taskuserlink b
where t.id = b.taskid
) b on true
DbFiddle.

Cross Referencing Multiple Tables

What I was trying to do is to get data from multiple tables, supposed that I have the following results in my query:
The numbers in the column ticket_item_type represents certain table. For example, 2 is for tbl_company and 3 is for tbl_lease. Then the details represents the id of a certain record in that table.
Suppose that I want to get the title of those records using ticket_item_type and details. Is it possible to embed it to the results? Or should I make separate queries for each.
I know JOIN, but I is it only for single table?
Here's my MYSQL query for the image above:
SELECT *
FROM
(SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN
(SELECT id
FROM tickets
WHERE hs_customer='1'
AND ticket_status = 'dispatch_reviewed')
AND ticket IN
(SELECT ticket
FROM ticket_items
WHERE ticket_item_type = 5
AND details = '159')) AS TB1
WHERE ticket_item_type IN (3,
2,
8)
You could try something like this:
SELECT
TB1.*,
CASE
WHEN TB1.ticket_item_type = 2 THEN t2.title
WHEN TB1.ticket_item_type = 3 THEN t3.title
WHEN TB1.ticket_item_type = 8 THEN t8.title
ELSE 'NA'
END as title
FROM
(
SELECT *
FROM ticket_items
WHERE hs_customer = 1
AND ticket IN (SELECT id FROM tickets WHERE hs_customer='1' AND ticket_status = 'dispatch_reviewed')
AND ticket IN (SELECT ticket FROM ticket_items WHERE ticket_item_type = 5 AND details = '159')
) AS TB1
LEFT JOIN tbl_company t2 ON TB1.details = t2.id
LEFT JOIN tbl_lease t3 ON TB1.details = t3.id
LEFT JOIN tbl_next t8 ON TB1.details = t8.id
WHERE ticket_item_type IN (3, 2, 8)
However, this is not a design that I would prefer. Without looking at details of your database it's going to be hard to write a query to cover multiple types of ticket_item_type. I hope this query works for you, though.

mysql SELECT EXISTS on multiple tables

Have tables: person,person_ip
Both tables have pid column as a primary key, in table person there is column state_id, in table person_ip there is column ip.
Want to discover if specified IP address is assigned to person with state_id is not equal to 2. But always got result 1, even if state_id is 0, 1 or 2. Always got 0 only if ip address is not listed at all. What am I doing wrong?
SELECT EXISTS (
SELECT person_ip.PID
FROM person_ip,person
WHERE person.PID=person_ip.PID
AND person.state_id NOT IN (2)
AND person_ip.ip='10.11.12.13'
)
this seems like a simple join.. unless i'm missing something
select person.*
from person
inner join person_ip
on person.pid = person_ip.pid
where person.state_id <> 2
and person_ip.ip_address = '10.0.0.1'
If you want to exclude the ip_address if it has been assigned to any user with state = 2, even if it has also been assigned to a user without state = 2, then try:
select max(i)
from (
select *
from (
select 1 as i
from dual
where not exists (
select 1
from person p
inner join person_ip pi
on p.pid = pi.pid
where p.state_id = 2
and pi.ip_address = '10.0.0.1'
)
) q
union
select 0
) qq
(dual is a system table that can be used as a sort of stub table)
here's a fiddle showing both versions
update after some actual sleep
Okay, so the above query is a little.. out there. Back in the real world, this one is probably more appropriate:
select count(case when p1.state_id = 2 then 1 end)
from person p1
inner join person_ip pi1
on p1.pid = pi1.pid
where pi1.ip_address = '10.0.0.1'
group by pi1.ip_address;
This will return 1 or more if your ip_address has been used by someone with a state_id of 2, and 0 if it has never been used by someone with a state_id of 2.
It will return nothing if the ip has never been used.
this fiddle has all three of the above queries.
SELECT IF(COUNT(*)>0,1,0)
FROM person
INNER JOIN person_ip
ON person.pid = person_ip.pid
AND person_ip.ip_address = '10.0.0.1'
WHERE person.state_id <> 2