SQL concatenate rows query - mysql

Say we have a table
table posts
+---------+-----------+--------------------------------+
| postId | title | status | bodyText |
+---------+-----------+--------------------------------+
| 1 | Hello! | deleted | A deleted post! |
| 2 | Hello 2! | deleted | Another one! |
| 3 | New 1 | new | A new one! |
| 4 | New 2 | new | A new one again! |
Can we, in SQL, retrieve a concatenation of a field across rows, by issuing a single query, not having to do the join up in a loop in our back-end code?
Something like
select title from posts group by status ;
Should give a result like
+---------+--------------------+
| deleted | Hello!, Hello 2! |
| new | New 1, New 2 |

If you use MySQL then you can use GROUP_CONCAT:
SELECT status, GROUP_CONCAT(title)
FROM posts
GROUP BY status

In MySQL:
SELECT status, GROUP_CONCAT(title SEPARATOR ', ')
FROM posts
GROUP BY
status
In PostgreSQL:
SELECT status,
ARRAY_TO_STRING(
ARRAY(
SELECT title
FROM posts pi
WHERE pi.status = po.status
))
FROM posts po
GROUP BY
status

You didn't indicate a particular SQL engine.
In Firebird (from 2.1) you can use the LIST() function. Take a look at: link text
It's an aggregate function to do exactly what you need.
I guess it exists in other engines (LIST in Sybase SQL Anywhere, GROUP_CONCAT in MySQL)

Related

How to select both sum value of all rows and values in some specific rows?

I have a record table and its comment table, like:
| commentId | relatedRecordId | isRead |
|-----------+-----------------+--------|
| 1 | 1 | TRUE |
| 2 | 1 | FALSE |
| 3 | 1 | FALSE |
Now I want to select newCommentCount and allCommentCount as a server response to the browser. Is there any way to select these two fields in one SQL?
I've tried this:
SELECT `isRead`, count(*) AS cnt FROM comment WHERE relatedRecordId=1 GROUP BY `isRead`
| isRead | cnt |
| FALSE | 2 |
| TRUE | 1 |
But, I have to use a special data structure to map it and sum the cnt fields in two rows to get allCommentCount by using an upper-layer programming language. I want to know if I could get the following format of data by SQL only and in one step:
| newCommentCount | allCommentCount |
|-----------------+-----------------|
| 2 | 3 |
I don't even know how to describe the question. So I got no any search result in Google and Stackoverflow. (Because of My poor English, maybe)
Use conditional aggregation:
SELECT SUM(NOT isRead) AS newCommentCount, COUNT(*) AS allCommentCount
FROM comment
WHERE relatedRecordId = 1;
if I under stand you want show sum of newComments Count and all comments so you can do it like
SELECT SUM ( CASE WHEN isRead=false THEN 1 ELSE 0 END ) AS newComment,
Count(*) AS AllComments From comments where relatedRecord=1
also you can make store procedure for it.
To place two result sets horizontally, you can as simple as use a subquery for an expression in the SELECT CLAUSE as long as the number of rows from the result sets match:
select (select count(*) from c_table where isread=false and relatedRecordId=1 ) as newCommentCount,
count(*) as allCommentCount
from c_table where relatedRecordId=1;

Display only one row for values that appear multiple times

I have multiple rows with the same name in this table, and I want to show only one of row of each. For example, with the following data:
| name | number |
+------+--------+
| exe | 1 |
| exe | 10 |
| exe | 2 |
| bat | 1 |
| exe | 3 |
| bat | 4 |
I would like to see the following results:
| name | number |
+------+--------+
| exe | 16 |
| bat | 5 |
How can I achieve this result?
Duplicate response: My question only have 1 table, the JOIN ..ON command creates confusion in understanding, i think this simple question can help many guys!
Try something like this:
SELECT t.`name`, SUM(t.`number`) AS `number`
FROM mytable t
GROUP BY t.`name`
ORDER BY `number` DESC
let the database return the result you want, rather than mucking with returning a bloatload of rows, and collapsing them on the client side. There's plenty of work for the client to do without doing what the database can do way more efficiently.
You can use an aggregation function for this:
SELECT name, SUM(number) AS total
FROM myTable
GROUP BY name;
Here is a reference on aggregate functions, and here is an SQL Fiddle example using your sample data.

MySQL accessing two tables in single query

Here's a simplified version of my troubles. 3 tables, the first (transit) will be used in upcoming procedures and functions, the second (products) will hold stationary data about products, the third (userWatchList) will hold user-specific data related to products.
TABLE: transit
+---------+------+
| ranking | data |
+---------+------+
| | |
+---------+------+
TABLE: products
+----+------+-----------------+
| ID | data | importantnumber |
+----+------+-----------------+
| 1 | c | 10 |
| 2 | u | 20 |
| 3 | t | 20 |
| 4 | u | 40 |
+----+------+-----------------+
TABLE: userWatchList
+---------+----+
| ranking | ID |
+---------+----+
| 1 | 2 |
| 2 | 1 |
| 3 | 4 |
| 4 | 3 |
+---------+----+
I need to insert into "transit" the data and ranking of rows that are within the needed ranking range and the data of which meets certain requirements.
I now want the ranking and data of a product, that has an importantnumber value of 20.
Say the allowed ranking range was between 1 and 2, SELECT * FROM transit at the end of the desired process would output:
+---------+------+
| ranking | data |
+---------+------+
| 1 | 'u' |
+---------+------+
Say the allowed ranking range was between 1 and 3, SELECT * FROM transit at the end of the desired process would output:
+---------+------+
| ranking | data |
+---------+------+
| 1 | 'u' |
| 4 | 't' |
+---------+------+
My vision of a possible solution...
To make sure the ranking falls within the needed range, I thought I might use dynamic SQL:
SET #IDsRetrieveStmt = CONCAT("SELECT group_concat(ID SEPARATOR ',') INTO #IDsStr FROM userWatchList WHERE ranking BETWEEN ', #rankingmin,' AND ', #rankingmax,';');
PREPARE stmt FROM #IDsRetrieveStmt;
EXECUTE stmt;
Now. To add ranking value to those fields... what should i do? I imagine one option is somewhere along the lines of:
SET #fetch_data_stmt = CONCAT('INSERT INTO transit (data, ranking) SELECT data, ( **** ) FROM products WHERE ID IN ( ', #IDsStr, ') AND importantnumber=20;');
PREPARE stmt FROM #fetch_data_stmt;
EXECUTE stmt;
** some unknown magic here that fetches ranking from a row with the same ID from 'products' table. This could be SELECT ranking FROM userWatchList WHERE ID=ID, but as you see, the ID part will probably create a conflict. Also, it seems a bit ineffective to run a new SELECT query with every inserted row.
I am sure there is a more effective way of doing this that I haven't heard of yet.
What's the best way of achieving this? Thanks in advance!
The first, and most important, part of the answer is the query that generates the data you want. You need to join the two tables together and use your criteria as conditions in the query:
select ranking, data
from userWatchList u
join product p on p.ID = u.ID
where ranking between ? and ?
and importantnumber = ?
Of course substituting ? with your criteria.
The next part of the answer is more advice. Unless there's an extremely compelling reason to do so, don't create a table to hold the data output from this query, because it's derived data that is out of date the instant it's created, unless you put in complicated database infrastructure (triggers) to keep it fresh.
Instead, create a view, that's like a table to a client (an application), but is actually a query under the hood:
create view transit as
select ranking, data, importantnumber
from userWatchList u
join product p on p.ID = u.ID
Then to use:
select ranking, data
from transit
where ranking between ? and ?
and importantnumber = ?

activerecord ruby row with max value in mysql table

I need to select from MySQL table table1 (it's shown below) all records with different 'foreign_row_id' values and group them by maximum datetime value. For example, from the table below I should select rows with id=2 and id=3. And after this I have to join the result with table with phrase_id's.
In my project I use only Ruby and ActiveRecord without Rails.
+----+---------------------+----------------+--------------+
| id | datetime | foreign_row_id | other_fields |
+----+---------------------+----------------+--------------+
| 1 | 2013-05-02 17:36:15 | 1 | 1 |
| 2 | 2013-05-02 17:36:53 | 1 | 1 |
| 3 | 2013-05-03 00:00:00 | 2 | 3 |
+----+---------------------+----------------+--------------+
Here my ruby code:
#result= Model1.joins(:foreign_row).
where(:user_id => user_id).
order(:datetime).
reverse_order.
select('table1.*, foreign_row.*').
maximum(:datetime, :group => :foreign_row_id).
And it gives me only one record, without grouping by id and joining: {"1":"2013-05-02T17:36:53+09:00"}.
What should I change in the my code to get all rows?
I solved this by parts, first I get a SQL sentence that would solve problem:
SELECT * FROM (SELECT * FROM `models` ORDER BY `datetime` desc) m GROUP BY `foreign_row_id`
And then I built that query with Arel:
model_table = Model1.arel_table
subquery = model_table.project(Arel.sql('*')).order('`datetime` desc').as('m')
query = model_table.project(Arel.sql('*')).from(subquery).group('`foreign_row_id`')
Finally you can run that query:
Model1.find_by_sql query.to_sql
I added some back ticks because fields I tested with were SQL reserved words, I think you can omit them.

MySQL - COUNT before INSERT in one query

Hey all, I am looking for a way to query my database table only once in order to add an item and also to check what last item count was so that i can use the next number.
strSQL = "SELECT * FROM productr"
After that code above, i add a few product values to a record like so:
ID | Product | Price | Description | Qty | DateSold | gcCode
--------------------------------------------------------------------------
5 | The Name 1 | 5.22 | Description 1 | 2 | 09/15/10 | na
6 | The Name 2 | 15.55 | Description 2 | 1 | 09/15/10 | 05648755
7 | The Name 3 | 1.10 | Description 3 | 1 | 09/15/10 | na
8 | The Name 4 | 0.24 | Description 4 | 21 | 09/15/10 | 658140
i need to count how many times it sees gcCode <> 'na' so that i can add a 1 so it will be unique. Currently i do not know how to do this without opening another database inside this one and doing something like this:
strSQL2 = "SELECT COUNT(gcCode) as gcCount FROM productr WHERE gcCode <> 'na'
But like i said above, i do not want to have to open another database query just to get a count.
Any help would be great! Thanks! :o)
There's no need to do everything in one query. If you're using InnoDB as a storage engine, you could wrap your COUNT query and your INSERT command in a single transaction to guarantee atomicity.
In addition, you should probably use NULL instead of na for fields with unknown or missing values.
They're two queries; one is a subset of the other which means getting what you want in a single query will be a hack I don't recommend:
SELECT p.*,
(SELECT COUNT(*)
FROM PRODUCTR
WHERE gccode != 'na') AS gcCount
FROM PRODUCTR p
This will return all the rows, as it did previously. But it will include an additional column, repeating the gcCount value for every row returned. It works, but it's redundant data...