Tricky SQL select - mysql

I have a message table, this table contains the following columns :
id | fromUserId | toUserId | sentDate | readDate | message | subject | fromUserDeleted(bit) | toUserDeleted(bit)
Now I need to select first message within every conversionen no mather if its from or to the current user. What makes it more complex is that I need to exclude messages that are deleted by the current user, so if the message is from current user then the fromUserDeleted have to be 0 if the message is from another user then toUserDeleted needs to be 0.
I know that there already are a lot of examples on how to select first post within groups but this is a bit more complex. The current user might be in toUserId or in fromUserId and the same goes for the deleted column.
This is what I have tried :
SELECT
m1.*,
(SELECT count(*) FROM mail m3 WHERE m3.fromUserId=m1.fromUserId AND m3.toUserId=m1.toUserId AND m3.toUserDeleted = 0) as messageCount,
(SELECT count(*) FROM mail m4 WHERE m4.toUserId=15 AND m4.readDate is null AND m4.toUserDeleted=0) as messagesNotRead
FROM
mail m1
LEFT JOIN mail m2 ON
((m1.fromUserId = m2.fromUserId) and
m2.)
WHERE
m2.id IS NULL AND
(m1.toUserId = 15 OR m1.fromUserId = 15)
ORDER BY
m1.sentDate DESC;
And :
SELECT
*
FROM
mail m1
WHERE
((m1.fromUserId = 15 AND m1.fromUserDeleted=0) OR (m1.toUserId = 15 AND m1.toUserDeleted=0)) AND
m1.id = (SELECT m2.id FROM mail m2 WHERE m2.fromUserId = 15 OR m2.toUserId = 15 OR m2.fromUserId = m1.fromUserId OR m2.fromUserId = m1.toUserId OR m2.toUserId = m1.fromUserId OR m2.toUserId = m1.toUserId order By m2.sentDate desc limit 0,1)
Order by m1.sentDate DESC
None of these will do what I need. Pleas help!
It´s okay if a stored procedure needs to be created.
IMPORTANT : This i MySQL
EDIT 1:
Pleas see this for example : http://sqlfiddle.com/#!2/6a2ef/3

DOING UNPIVOT to get fromUSerId, toUserId under same column so that we can pick correct userId based on the sentDate.
SELECT T.*
FROM
(
SELECT *,
ROW_NUMBER() OVER ( PARTITION BY userVal ORDER BY sentDate DESC) as seq
FROM
(SELECT
*
FROM msg
WHERE (fromUserId = #userID AND fromUserDeleted = 0 )
OR (toUserId = #UserID AND toUserDeleted = 0)
)p
unpivot
( userVal for UserId in ( fromUserId, toUserId)
)as unpvt
) T
WHERE seq =1

Thanks for the feedback. Hopefully this will address your need:
DECLARE #UserID INT
SELECT #UserID = 15
SELECT *
FROM (
SELECT UserID, ConversationWithUserID, sentDate, readDate, [message], [subject], ROW_NUMBER() OVER (PARTITION BY ConversationWithUserID ORDER BY sentDate ASC) AS MessageOrder
FROM (
SELECT id, fromUserId AS UserID, toUserId AS ConversationWithUserID, sentDate, readDate, [message], [subject]
FROM dbo.mail
WHERE fromUserID = #UserID AND fromUserDeleted = 0
UNION
SELECT id, toUserId AS UserID, fromUserId AS ConversationWithUserID, sentDate, readDate, [message], [subject]
FROM dbo.mail
WHERE toUserId = #UserID AND toUserDeleted = 0
) x
) y
WHERE MessageOrder = 1
ORDER BY y.UserID, y.ConversationWithUserID

Related

How to skip rows from table in mysql

I have one table which has the following data, I want to skip row 3, just want to fetch OPEN status only once, I am using below query but it skipping wor 5 as well.
SELECT t.*
FROM emailEvent t
JOIN
( SELECT MIN(id) AS minid
FROM emailEvent WHERE email_id = 3
GROUP BY status
) AS grp
ON grp.minid = t.id
WHERE (t.email_id = 3)
I came up with this as a solution but not sure if there are any other best solution for this
SELECT t.*
FROM emailEvent t
WHERE t.status != "Open" and t.email_id = 3
UNION
(
SELECT et.*
FROM emailEvent et
WHERE et.status = "Open" and et.email_id = 3
ORDER BY et.createdAt DESC LIMIT 1
)
I want the result to be look like something like this
SELECT *
FROM
(
SELECT t.*,
min(id) over (partition by status) as min_id
FROM emailEvent t
WHERE (t.email_id = 3) -- only email 3
) AS dt
WHERE id = min_id -- only for 'Open' status
OR status <> 'Open'
For older releases not supporting Windowed Aggregates:
select *
from emailEvent
where email_id = 3
and
(
e.status <> 'Open'
or id in (select min(id) -- only for 'Open' status
from emailEvent
where status = 'Open'
and email_id = 3)
)
With NOT EXISTS:
select e.* from emailEvent e
where e.status <> 'Open'
or not exists (select 1 from emailEvent where status = e.status and id < e.id)

MySQL and/or query for advanced search between several tables

Now, I am using this query
SELECT h . * ,
(SELECT state
FROM house_name
WHERE name = h.house_name
AND purpose = h.purpose
AND district_id = (
SELECT id
FROM district
WHERE name = h.district )
LIMIT 1
) AS hstate,
(SELECT name
FROM district
WHERE id = (
SELECT district_id
FROM house_name
WHERE name = h.house_name
LIMIT 1 )
LIMIT 1
) AS origin_d,
(SELECT id
FROM house_name
WHERE name = h.house_name
AND purpose = h.purpose
AND district_id = (
SELECT id
FROM district
WHERE name = h.district )
LIMIT 1
) AS hnameid,
m.display_name
FROM house_detail h
LEFT JOIN members m ON m.id = h.member_id
WHERE
h.deleted =0
AND h.approve =0
AND
(SELECT state
FROM house_name
WHERE state = 'N'
AND name = h.house_name
AND purpose = h.purpose
AND district_id = (
SELECT id
FROM district
WHERE
name = h.district )
LIMIT 1
) IS NOT NULL
AND h.price <=1000000
OR h.price >=70000000
OR (
h.purpose = 'house'
AND h.rent >=100000
)
OR (
h.purpose = 'industry'
AND h.rent >=700000
)
OR h.rent <=5000
On the Where clause:
The h.approve can be 0 or 1 or 2
The h.deleted can be 0 or 1
The state can be Y or N
What I want to do is:
The h.deleted , h.approve and the state MUST be 0, 0 and N respectively
The h.rent and h.price may under a specific range.
And the current problem is it will still select some data where the h.deleted , h.approve and state may be 1 or 2 , 1 and Y. I think it's because there're some OR between them.
Is there any way to output my expected result ?
Set bracket around the following statements:
WHERE
name = h.district )
LIMIT 1
) IS NOT NULL
AND (h.price <=1000000
OR h.price >=70000000
OR (
h.purpose = 'house'
AND h.rent >=100000
)
OR (
h.purpose = 'industry'
AND h.rent >=700000
)
OR h.rent <=5000)
opening ( after the last AND and closing ) at the end... that should do

DB2 Show the latest row record from users, using the date and time

I have 100 records from 3 users. I want to show the most recent record from each user. I have the following query:
SELECT *
FROM Mytable
WHERE Dateabc = CURRENT DATE
AND timeabc =
(
SELECT MAX(timeabc)
FROM Mytable
)
It returns the most recent record for everyone, and I need it to return most recent record from every user.
Should the solution support both DB2 and mysql?
SELECT * FROM Mytable as x
WHERE Dateabc = CURRENT_DATE
AND timeabc = (SELECT MAX( timeabc ) FROM Mytable as y where x.user = y.user)
If it's only DB2 more efficient solutions exists:
SELECT * from (
SELECT x.*, row_number() over (partition by user order by timeabc desc) as rn
FROM Mytable as x
)
WHERE rn = 1
I assume somewhere in your table you have a userID...
select userID, max(timeabc) from mytable group by userID
SELECT *
FROM Mytable as a
WHERE Dateabc = CURRENT_DATE
AND timeabc =
(
SELECT MAX( timeabc )
FROM Mytable as b
WHERE a.uId = b.uId
)

mysql table with duplicate records

I have a table
email(email varchar(30),id integer(10),duplicated varchar(10))
with records
sai#gmail.com 101 null
kiran#gmail.com 102 null
sai123#gmail.com 103 null
sai#gmail.com 101 null
kiran#gmail.com 102 null
Now my question is i need to get "yes" in the duplicated column for the two duplicated records for the second time. so, the output table should be
sai#gmail.com 101 null
kiran#gmail.com 102 null
sai123#gmail.com 103 null
sai#gmail.com 101 yes
kiran#gmail.com 102 yes
Try this
update email set duplicated =
(case when (select count(*) from email x where x.email = e.email) > 1 then "yes" else null)
edited: this will update table
You can try this query for viewing:
select numerated.email, numerated.id, (case when cnt=1 OR numerated.rnum=grouped.min_rnum then null else "yes" end) as duplicated
from
(select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c order by id) as numerated
left join
(select email, id, min(rnum) as min_rnum, count(rnum) as cnt
from (select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c order by id) as numerated
group by email, id
) as grouped
on numerated.email=grouped.email and numerated.id=grouped.id
order by id;
Could you explain your situation in details? It looks like it needs another solution, not just SELECT query.
And try this one for updating:
update email u, (select #i:=0) urnum
set
id = id + (#i:=#i + 1) - #i,
duplicated = (
select duplicated from (
select
numerated.email,
numerated.id,
(case when cnt=1 OR numerated.rnum=grouped.min_rnum then null else "yes" end) as duplicated,
rnum
from
(select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c ) as numerated
left join
(select email, id, min(rnum) as min_rnum, count(rnum) as cnt
from (select #i := #i + 1 as rnum, email.* from email, (select #i:=0) as c ) as numerated
group by email, id
) as grouped
on numerated.email=grouped.email and numerated.id=grouped.id
order by rnum
) found_duplicates
where u.email=found_duplicates.email and u.id=found_duplicates.id and #i=found_duplicates.rnum
limit 1
)
;
It looks like it works, but you shouldn't rely on it.
If it is possible, you should do any of this:
1. change table structure - add unique field
2. change table filling logic - check uniqueness before inserting new row and insert it with proper 'duplicates' field value;
3. repopulate via temporary table like this:
CREATE TEMPORARY TABLE tmp_email AS <... 'SELECT' version of my query ...>;
TRUNCATE TABLE email;
INSERT INTO email SELECT * FROM tmp_email;

query to add incremental field based on GROUP BY

Have a table photos
photos.id
photos.user_id
photos.order
A) Is it possible via a single query to group all photos by user and then update the order 1,2,3..N ?
B) added twist, what if some of the photos already have an order value associated? Make sure that the new photos.order never gets repeated and fills in ant orders lower or higher than those existing (as best as possible)
My only thought is just to run a script on this and loop through it and re'order' everything?
photos.id int(10)
photos.created_at datetime
photos.order int(10)
photos.user_id int(10)
Right now data may look like this
user_id = 1
photo_id = 1
order = NULL
user_id = 2
photo_id = 2
order = NULL
user_id = 1
photo_id = 3
order = NULL
the desired result would be
user_id = 1
photo_id = 1
order = 1
user_id = 2
photo_id = 2
order = 1
user_id = 1
photo_id = 3
order = 2
A)
You can use a variable that increments with each row and resets with each user_ID to get the row count.
SELECT ID,
User_ID,
`Order`
FROM ( SELECT #r:= IF(#u = User_ID, #r + 1,1) AS `Order`,
ID,
User_ID,
#u:= User_ID
FROM Photos,
(SELECT #r:= 1) AS r,
(SELECT #u:= 0) AS u
ORDER BY User_ID, ID
) AS Photos
Example on SQL Fiddle
B)
My First solution was to just add Order to the sorting that adds the row number, therefore anything with an Order Gets sorted by its order first, but this only works if your ordering system has no gaps and starts at 1:
SELECT ID,
User_ID,
RowNumber AS `Order`
FROM ( SELECT #r:= IF(#u = User_ID, #r + 1,1) AS `RowNumber`,
ID,
User_ID,
#u:= User_ID
FROM Photos,
(SELECT #i:= 1) AS r,
(SELECT #u:= 0) AS u
ORDER BY User_ID, `Order`, ID
) AS Photos
ORDER BY `User_ID`, `Order`
Example using Order Field
ORDERING WITH GAPS
I have eventually found a way of maintaining the sort order even when there are gaps in the sequence.
SELECT ID, User_ID, `Order`
FROM Photos
WHERE `Order` IS NOT NULL
UNION ALL
SELECT Photos.ID,
Photos.user_ID,
Numbers.RowNum
FROM ( SELECT ID,
User_ID,
#r1:= IF(#u1 = User_ID,#r1 + 1,1) AS RowNum,
#u1:= User_ID
FROM Photos,
(SELECT #r1:= 0) AS r,
(SELECT #u1:= 0) AS u
WHERE `Order` IS NULL
ORDER BY User_ID, ID
) AS Photos
INNER JOIN
( SELECT User_ID,
RowNum,
#r2:= IF(#u2 = User_ID,#r2 + 1,1) AS RowNum2,
#u2:= User_ID
FROM ( SELECT DISTINCT p.User_ID, o.RowNum
FROM Photos AS p,
( SELECT #i:= #i + 1 AS RowNum
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY,
( SELECT #i:= 0) AS i
) AS o
WHERE RowNum <= (SELECT COUNT(*) FROM Photos P1 WHERE p.User_ID = p1.User_ID)
AND NOT EXISTS
( SELECT 1
FROM Photos p2
WHERE p.User_ID = p2.User_ID
AND o.RowNum = p2.`Order`
)
AND p.`Order` IS NULL
ORDER BY User_ID, RowNum
) AS p,
(SELECT #r2:= 0) AS r,
(SELECT #u2:= 0) AS u
ORDER BY user_ID, RowNum
) AS numbers
ON Photos.User_ID = numbers.User_ID
AND photos.RowNum = numbers.RowNum2
ORDER BY User_ID, `Order`
However as you can see this is pretty complicated. This works by treating those with an order value separately to those without. The top query just ranks all photos with no order value in order of ID for each user. The bottom query uses a cross join to generates a sequential list from 1 to n for each user ID (up to the number of entries for each User_ID). So with a data set like this:
ID User_ID Order
1 1 NULL
2 2 NULL
3 1 NULL
4 1 1
5 1 3
6 2 2
7 2 3
It would generate
UserID RowNum
1 1
1 2
1 3
1 4
2 1
2 2
2 3
It then uses NOT EXISTS to elimiate all combinations already used by Photos with a non null order, and ranked in order of RowNum partitioned by User_ID giving
UserID RowNum Rownum2
1 2 1
1 4 2
2 1 1
The RowNum2 value can then be matched with the rownum value achieved in the from subquery, giving the correct order value. Long winded, but it works.
Example on SQL Fiddle
Worked for me. I needed to increment version grouping by 4 fields (host, folder, fileName, status) and sort by 1 (downloadedAtTicks).
This is is my SELECT
SET #status := NULL;
SET #version := NULL;
SELECT
id,
host,
folder,
fileName,
status,
downloadedAtTicks,
version,
IF(IF(status IS NULL, 0, status) = #status, #version := #version + 1, #version := 0) AS varVersion,
#status := IF(status IS NULL, 0, status) AS varStatus
FROM csvsource
ORDER BY host, folder, fileName, status, downloadedAtTicks;
And this is my UPDATE
SET #status := NULL;
SET #version := NULL;
UPDATE
csvsource csv,
(SELECT
id,
IF(IF(status IS NULL, 0, status) = #status, #version := #version + 1, #version := 0) AS varVersion,
#status := IF(status IS NULL, 0, status) AS varStatus
FROM csvsource
ORDER BY host, folder, fileName, status, downloadedAtTicks) AS sub
SET
csv.version = sub.varVersion
WHERE csv.id = sub.id;