My table of posts has a post ID and a reply_to columns, like this:
1 null
2 null
3 1
4 2
5 1
As you can see post 5 is a reply to a post 1 for example. I am trying to construct an SQL query that would sort the SELECT so that replies go after the post they refer to. Otherwise sorted by ID. There is no nesting, the parent post is is always a reply to NULL.
In this example the result would be:
1 null
3 1
5 1
2 null
4 2
I feel like I must do something with minimum of id, replyto, maybe:
SELECT id, replyto from posts ORDER BY LEAST(id, replyto)
But it puts all root posts on the top
+------+---------+
| id | replyto |
+------+---------+
| 1 | NULL |
| 2 | NULL |
| 3 | 1 |
| 5 | 1 |
| 4 | 2 |
+------+---------+
I just figured I can use CASE
SELECT * from posts
ORDER BY LEAST(
CASE WHEN ISNULL(replyto) THEN id ELSe replyto END, id
);
(Why I always find a solution just after I ask a question here)
Related
I´m playing with Laravel´s eloquent and a MYSQL database
I need to display a list of conversations
this is my table structure
messages:
- id
- from_id
- to_id
- message
- read
|----|-----------|---------|-------|------|
| id | message | from_id | to_id | read |
|----|---------------------|--------------|
| 1 | hello | 1 | 2 | 1 |
| 2 | hey | 2 | 1 | 0 |
| 3 | there | 2 | 1 | 0 |
| 4 | hi! | 3 | 1 | 1 |
|----|-----------|---------|-------|------|
users:
- id
- name
|-----------|
| id | name |
|-----------|
| 1 | john |
| 2 | mary |
| 3 | jack |
|-----------|
expected:
if I´m user 1 (it should show me the last message and the count of unread
|----|------|---------|--------|
| id | name | message | unread |
|----|------|---------|--------|
| 3 | mary | there | 2 |
| 4 | jack | hi! | 0 |
|----|------|-------- |--------|
I´m not sure if this is the right table structure for what I want to do but the SQL should be something like
SELECT users.name, messages.message
FROM users, messages
WHERE messages.from_id = 1 OR messages.to_id = 1
GROUP BY ??
ORDER BY messages.id DESC
I´m not sure how to GROUP because if I group by messages.from_id = 1 it will only show messages I sent and not messages I received. please help me thing :)
also how to handle the read status? for sure that read attribute is something like the to_id will set to 1. how to check that in the query as well?
THere's a couple of things I see here.
1. while aggregating your data to count the number of unread emails, you are still showing at least one message which indicates message text. In the expected response:
|----|------|---------|--------|
| id | name | message | unread |
|----|------|---------|--------|
| 3 | mary | there | 2 |
| 4 | jack | hi! | 0 |
|----|------|-------- |--------|
You have two unread messages for mary, however the message column says there, which is only the message of one the two unread emails.
I'm just wondering if that's on purpose.
However, to get you where you need to go,
you have the read column which appears binary in nature: 1 for messages read, 0 for messages not read.
That being said, you can utilize that logic to count the number of unread messages by using the algorithm: count(read) - sum(read).
the second thing is your joins. You have to join the tables together in order to establish names to user_ids. in your case, you are not using a join at all but filtering by field. if you want a list of from users, filtered by the to users, then you need to join the from_id to the user_id so you can get the name.
There you can group by name and at least get id, name, unread. as forth in this example:
SELECT users.name, count(read) - sum(read) as unread
FROM users, messages
WHERE users.id = messages.from_id and messages.to_id = 1 --to me
GROUP BY users.name
ORDER BY messages.from_id ASC
in laravel ORM, try this (I know I"m close, but I'm mobile and haven't been able to test it, so play around):
$users = DB::table('messages')
->select(DB::raw('users.name, count(read) - sum(read) as unread'))
->join('users', 'users.id', '=', 'messages.from_id')
->where('to_id', '=', 1)
->orWhere('from_id', '=', '1')
->groupBy('users.name')
->orderBy('messages.from_id','ASC')
->get();
Try this.
UPDATE 1:
to purposely get the "latest" message with the list:
select u.name, fm.message, count(m.unread) - sum(m.unread) as unread,
case when m.from_id = 1 then 'sent'
when m.to_id = 1 then 'received'
else null
end status
from messages m
join users u on u.id = m.from_id
left join (
Select max(id) as id, from_id, message from messages group by from_id
) fm on m.from_id = fm.from_id
where m.from_id = 1 or m.to_id = 1
group by u.name;
Eloquent:
DB::table('messages')
->select('u.name', 'fm.message', 'count(m.read) - sum(m.read) as unread', 'case when m.from_id = 1 then "sent" when m.to_id = 1 then "received" else null end status')
->join(users u', 'u.id', '=', 'u.from_id')
->leftjoin(DB::raw('Select max(id) as id, from_id, message from messages group by from_id', 'm.from_id', 'fm.from_id'))
->groupBy('users.name')
->orderBy('messages.from_id','ASC')
->get();
I have a table with the following layout, to store orders for users, and to remember which orders are being processed right now:
Sequence | User | Order | InProcess
---------+------+-------+----------
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 1 |
4 | 3 | 1 |
5 | 1 | 3 |
6 | 4 | 1 |
7 | 2 | 2 |
E.g., line 4 | 3 | 1 | means that the 4th order ever is for user 3, and it's his/her 1st order. Now I want to select the order which to process next. This has to be done according to the following criterias:
Older orders (with lower sequence numbers) are processed first.
Only one order is processed per user at once.
Once an order is selected as being processed it gets marked as InProcess.
Once an order is completed, it is deleted from this list.
So, after some time this may look like this:
Sequence | User | Order | InProcess
---------+------+-------+----------
1 | 1 | 1 | X
2 | 1 | 2 |
3 | 2 | 1 | X
4 | 3 | 1 | X
5 | 1 | 3 |
6 | 4 | 1 |
7 | 2 | 2 |
When now being asked for the next order to process, the answer would be the line with sequence number 6, since orders for users 1, 2 and 3 are already being processed, so no additional order for them may be processed. The question is: How do I get efficiently to this row?
Basically what I need is the SQL equivalent of
Of all orders, select the first order which is not in process, and whose user is not having an order already being processed.
The question is just how to tell this with SQL? BTW: I'm looking for a standard SQL solution, not DBMS-specific ways to go. However, if for whatever reason limiting the question to a specific DBMS, these are the ones I have to support (in this order):
PostgreSQL
MariaDB
MySQL
SQL Server
MongoDB
Any ideas?
I think captures your logic:
select t.*
from (select t.*, max(in_process) over (partition by user_id) as any_in_process
from t
) t
where any_in_process is null
order by sequence
fetch first 1 row only;
Fetching one row is database specific, but the rest is pretty generic.
You can get the next order to be processed by using the ROW_NUMBER() window function, as in:
select *
from (
select
*,
row_number() over(order by "order", "sequence") as as rn
from t
where "user" not in (
select "user" from t where inprocess = 'X'
)
) x
where rn = 1
Available in PostgreSQL, MariaDB 10.2, MySQL 8.0, SQL Server 2012.
I'm generating test data for a new database, and I'm having trouble populating one of the foreign key fields. I need to create a relatively large number (1000) of entries in a table (SurveyResponses) that has a foreign key to a table with only 6 entries (Surveys)
The database already has a Schools table that has a few thousand records. For arguments sake lets say it looks like this
Schools
+----+-------------+
| Id | School Name |
+----+-------------+
| 1 | PS 1 |
| 2 | PS 2 |
| 3 | PS 3 |
| 4 | PS 4 |
| 5 | PS 5 |
+----+-------------+
I'm creating a new Survey table. It will only have about 3 rows.
Survey
+----+-------------+
| Id | Col2 |
+----+-------------+
| 1 | 2014 Survey |
| 2 | 2015 Survey |
| 3 | 2016 Survey |
+----+-------------+
SurveyResponses simply ties a school to a survey.
Survey Responses
+----+----------+----------+
| Id | SchoolId | SurveyId |
+----+----------+----------+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | 3 | 1 |
| 4 | 4 | 3 |
| 5 | 5 | 2 |
+----+----------+----------+
Populating the SurveyId field is what's giving me the most trouble. I can randomly select 1000 Schools, but I haven't figured out a way to generate 1000 random SurveyIds. I've been trying to avoid a while loop, but maybe that's the only option?
I've been using Red Gate SQL Data Generator to generate some of my test data, but in this case I'd really like to understand how this can be done with raw SQL.
Here is one way, using a correlated subquery to get a random survey associated with each school:
select s.schoolid,
(select top 1 surveyid
from surveys
order by newid()
) as surveyid
from schools s;
Note: This doesn't seem to work. Here is a SQL Fiddle showing the non-workingness. I am quite surprised it doesn't work, because newid() should be a
EDIT:
If you know the survey ids have no gaps and start with 1, you can do:
select 1 + abs(checksum(newid()) % 3) as surveyid
I did check that this does work.
EDIT II:
This appears to be overly aggressive optimization (in my opinion). Correlating the query appears to fix the problem. So, something like this should work:
select s.schoolid,
(select top 1 surveyid
from surveys s2
where s2.surveyid = s.schoolid or s2.surveyid <> s.schoolid -- nonsensical condition to prevent over optimization
order by newid()
) as surveyid
from schools s;
Here is a SQL Fiddle demonstrating this.
I'm trying to write a SQL query that looks at a single MySQL DB table (exp_playa_relationships) which stores relationship data of CMS' posts and which data structure looks like this:
rel_id | parent_entry_id | parent_field_id | child_entry_id
-----------------------------------------------------------------
55 | 3 | 2 | 1
56 | 3 | 2 | 4
58 | 1 | 2 | 4
59 | 8 | 4 | 2
60 | 8 | 5 | 1
63 | 4 | 2 | 3
64 | 9 | 4 | 6
65 | 9 | 5 | 3
rel_id is unique, other columns are not.
I would like to generate the following out of the data above:
event_data_id | user_id | event_id
--------------------------------------
8 | 1 | 2
9 | 3 | 6
The parent_field_id value itself is discarded in the final output but is needed to figure out if the row's child_entry_id signifies a user_id or event_id.
parent_entry_id is the event_data_id.
So in plain english I would like to:
Filter rows that have a parent_field_id value of either 4 or 5
Out of those rows, I want to join all those that share the same parent_entry_id.
Return the parent_entry_id as event_data_id.
Return the child_entry_id as a user_id if parent_field_id of the same row is 5.
Return the child_entry_id as a event_id if parent_field_id of the same row is 4.
My current SQL query (not working) is this:
SELECT
t1.`parent_entry_id` AS event_data_id,
t1.`child_entry_id` AS user_id,
t1.`child_entry_id` AS event_id
FROM `exp_playa_relationships` AS t1
INNER JOIN `exp_playa_relationships` AS t2
ON t1.`parent_entry_id` = t2.`parent_entry_id`
WHERE t1.`parent_field_id` = 4 OR t1.`parent_field_id` = 5
What I cannot figure out specifically is how to avoid creating duplicates on the parent_entry_id (SQL creates 2 new rows per row) and how to return child_entry_id as either user_id or event_id based on the parent_field_id value.
Any ideas would be much appreciated.
You're sooo close:
SELECT t1.`parent_entry_id` AS event_data_id,
t1.`child_entry_id` AS user_id,
t2.`child_entry_id` AS event_id
FROM `exp_playa_relationships` AS t1
INNER JOIN `exp_playa_relationships` AS t2
ON t2.`parent_entry_id` = t1.`parent_entry_id`
AND t2.`parent_field_id` = 4
WHERE t1.`parent_field_id` = 5
Specifically, you're having to tell it which row-set to pull the relevant data from.
By the way, your current database design will cause you more of these types of headaches... I'd recommend pulling the information out into 'result' tables (unless that's what this is for?).
Here's a bit of sample context for my question below to help clarify what I'm asking...
The Schema
Users
- id
- name
Answers
- id
- user_id
- topic_id
- was_correct
Topics
- id
- name
The Data
Users
id | name
1 | Gabe
2 | John
Topics
id | name
1 | Math
2 | English
Answers
id | user_id | topic_id | was_correct
1 | 1 | 1 | 0
2 | 1 | 1 | 1
3 | 1 | 2 | 1
4 | 2 | 1 | 0
5 | 2 | 2 | 0
What I'd like to have, in a result set, is a table with one row per user, and two columns per topic, one that shows the sum of correct answers for the topic, and one that shows the sum of the incorrect answers for that topic. For the sample data above, this result set would look like:
My desired result
users.id | users.name | topic_1_correct_sum | topic_1_incorrect_sum | topic_2_correct_sum | topic_2_incorrect_sum
1 | Gabe | 1 | 1 | 1 | 0
2 | John | 0 | 1 | 0 | 1
Obviously, if there were more topics in the Topics table, I'd like this query to include new correct_sum and incorrect_sums for each topic that exists, so I'm looking for a way to write this without hard-coding topic_ids into the sum functions of my select clause.
Is there a smart way to magic this sort of thing with ARel?
Gabe,
What you're looking for here is a crosstab query. There are many approaches to writing this, unfortunately none that will be generic enough in SQL. AFAIK each database handles crosstabs differently. Another way of looking at this is as a "cube", something typically found in OLAP-type databases (as opposed to OLTP).
Its easily writeable in SQL, however will likely include some functions native to the database you're using. What DB are you using?
Your answers table looks like it needs to have 1,2,3,4,5 and not 1,1,1,1,1 as ids...