MySQL accessing two tables in single query - mysql

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 = ?

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;

Add user defined value to a column in an sql query

I have an SQL query:
select DISTINCT shortname_chn from dim_channel;
The query returns me data for example:
| shortname_chn (VARCHAR) |
|__________________________|
| MTV |
| National Geographic|
| Discovery |
| ARY News |
How can I manipulate the SQL query so that I can add an additional row to the returned rows.
Following is the result I wish to get after running some query:
| shortname_chn (VARCHAR) |
|__________________________|
| MTV |
| National Geographic|
| Discovery |
| ARY News |
| ALL |
Where the last row "ALL" is user defined, not present in the database.
In the above mentioned regard, I researched and came across this question : How to add a user defined column with a single value to a SQL query but it targets the problem of adding a whole new column.
select DISTINCT shortname_chn from dim_channel
UNION
SELECT 'ALL'
You can simply do something like this by UNIONing with a query that returns your fake row, e.g.:
SELECT DISTINCT
shortname_chn
FROM dim_channel
UNION ALL
SELECT 'ALL' AS shortname_chn

Order the rows of a MySQL result based on a "next_id" field

I'm currently working with a database table that is structured as follows:
______________________________
| id | content | next_id |
|------|-----------|-----------|
| 1 | (value) | 4 |
| 2 | (value) | 1 |
| 3 | (value) | (NULL) |
| 4 | (value) | 3 |
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The value of the next_id field defines the id of the row of data that should follow it. A value of NULL means that no row follows it.
Is there a way I can query the database in such a way that in the resulting rows will be ordered using this method? For example, in the case I gave above, the rows should be returned ordered so that the ids are in this order: 2, 1, 4, 3. I'm looking for a solution that can do this regardless of the number of rows in this sequence.
I know that it is possible to reorder the results after retrieving them from the database (using the programming language I'm working with), but I'm hoping that there is a way that I can do it in SQL.
I can't see a solution without as many self-joins as you have rows. Instead I would build a nested set out of it in a temp table using push down stack algorithm and then retrieve a full tree.
I've got something that's close.
/*one select to init the #next variable to the first row*/
select #next:= id from table1 order by isnull(next_id) asc, next_id asc limit 1;
select distinct a.id, a.next_id from table1 b
inner join
(
select #rank:= id as id, #next:= next_id as next_id from table1
where id = #next
) a
on (b.id = b.id);
This outputs
+----+---------+
| id | next_id |
+----+---------+
| 2 | 1 |
| 1 | 4 |
And then stops. If only I could find a way for it to continue....
Anyway this sort of force feeding values into a query is dodgy enough when doing ranking, let alone this sort of stuff, so maybe I'm going down a dead end.

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...

SQL concatenate rows query

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)