Related
There are two tables:
$inner_query =
"SELECT A.*, ROWNUM AS RN, TO_CHAR(A.last_newsletter_modify, 'DD/MM/YYYY') AS LAST_NEWSLETTER_MODIFY2
FROM ".$db_schema_name."newsletter_subscription A,
".$db_schema_name."newsletter_type B,
".$db_schema_name."newsletter_subtyp_profile C
WHERE A.id_subscription = C.id_subscription AND
C.id_type = B.id_type
ORDER BY e_mail";
If I run this query for id_subscription 734 it displays 3 times.
How can to display it just once?
Short answer: you are getting one row per newsletter_subtyp_profile. Subscription 734 is linked to three newsletter types, hence three rows of output.
You have three tables, not two, and the question would be clearer if you included full descriptions and sample data, and also got rid of the irrelevant PHP aspect and focussed on the SQL.
With some detective work, I make it this:
create table newsletter_subscription
( id_subscription integer primary key
, last_newsletter_modify date
, e_mail varchar2(50) not null );
create table newsletter_type
( id_type integer primary key
, description varchar2(40) not null unique );
create table newsletter_subtyp_profile
( id_subscription references newsletter_subscription
, id_type references newsletter_type
, constraint nsp_pk primary key (id_type,id_subscription) );
insert into newsletter_subscription values (600, date '2017-01-10', 'someone#somewhere.net');
insert into newsletter_subscription values (734, date '2017-02-05', 'someone#somewhereelse.net');
insert into newsletter_subscription values (800, date '2017-03-01', 'nobody#nowherewhere.net');
insert into newsletter_type values (1, 'Type One');
insert into newsletter_type values (2, 'Type Two');
insert into newsletter_type values (3, 'Type Three');
insert into newsletter_subtyp_profile values (734, 1);
insert into newsletter_subtyp_profile values (734, 2);
insert into newsletter_subtyp_profile values (734, 3);
Now run your query (I shortened the select list to simplify the output, and added b.description - a dummy column as I don't know what other columns you have on newsletter_type):
select a.id_subscription, a.e_mail
, to_char(a.last_newsletter_modify, 'DD/MM/YYYY') as last_newsletter_modify2
, b.description
from newsletter_subscription a,
newsletter_type b,
newsletter_subtyp_profile c
where a.id_subscription = c.id_subscription and
c.id_type = b.id_type
order by a.e_mail, c.id_type;
ID_SUBSCRIPTION E_MAIL LAST_NEWSLETTER_MODIFY2 DESCRIPTION
--------------- -------------------------- ----------------------- ---------------------
734 someone#somewhereelse.net 05/02/2017 Type One
734 someone#somewhereelse.net 05/02/2017 Type Two
734 someone#somewhereelse.net 05/02/2017 Type Three
btw the logic would be clearer if you used mnemonic aliases such as sub instead of a for newsletter_subscription, and also used standard ANSI joins and lost the uppercase:
select sub.id_subscription, sub.e_mail
, to_char(sub.last_newsletter_modify, 'DD/MM/YYYY') as last_newsletter_modify
, typ.description
from newsletter_subscription sub
join newsletter_subtyp_profile pro on pro.id_subscription = sub.id_subscription
join newsletter_type typ on typ.id_type = pro.id_type
where sub.id_subscription = 734
order by sub.e_mail, pro.id_type;
You can use window function row_number to get one row per id_subscription with max (or min id_type if you change the order by clause in the function to order by id_type):
$inner_query = "SELECT *
FROM (
SELECT A.*,
ROWNUM AS RN,
TO_CHAR(A.last_newsletter_modify, 'DD/MM/YYYY') AS LAST_NEWSLETTER_MODIFY2,
row_number() over (
partition by C.id_subscription
order by C.id_type desc nulls last
) as seqnum
FROM ".$db_schema_name."newsletter_subscription A
JOIN ".$db_schema_name."newsletter_subtyp_profile C
on A.id_subscription = C.id_subscription
JOIN ".$db_schema_name."newsletter_type B
on C.id_type = B.id_type
) t
WHERE seqnum = 1
ORDER BY e_mail";
Also notice that I have replace the old comma based join with widely recommended explicit JOIN syntax.
Two tables, with a left join. For ease table 1 and table 2.
Table 1 contains a list of people and their current status, table 2 is all of their "invites". All im trying to do as part of the join is show in a list all the current "people" and then the LATEST invite status (from table 2) so return a single row from table 2.
I have everything working... but its duplicating for example if a person has had multiple invites it will put them twice on the list. I just want to limit it to
$sql = "SELECT table1.fieldname as table1fielname table2.fieldname [more fields]
FROM xxx
LEFT JOIN xxx on table1.sharedid=table2.sharedid
WHERE XXX LIMIT 1 ";`
Obvioulsy the limit 1 doesnt do what its supposed to. I have tried adding additional select statements in brackets but being honest it just breaks everything and im not an expert at all.
I'm not an expert too but I'll try. Have you tried to use DISTINCT?
For exemple:
SELECT DISTINCT column_name1,column_name2
FROM table_name; [...]
It normally delete double matches.
Here are the links:
http://www.w3schools.com/sql/sql_distinct.asp
https://www.techonthenet.com/oracle/distinct.php
Give example data. And use good table and column names. For example:
(this returns all rows that satisfy the join):
WITH people(ppl_id,ppl_name,status) AS (
SELECT 1,'Arthur','active'
UNION ALL SELECT 2,'Tricia','active'
), invites(ppl_id,inv_id,inv_date) AS (
SELECT 1,1, DATE '2017-01-01'
UNION ALL SELECT 1,2, DATE '2017-01-07'
UNION ALL SELECT 1,3, DATE '2017-01-08'
UNION ALL SELECT 2,1, DATE '2017-01-01'
UNION ALL SELECT 2,2, DATE '2017-01-08'
)
SELECT
*
FROM people
JOIN invites USING(ppl_id)
ORDER BY 1
;
ppl_id|ppl_name|status|inv_id|inv_date
1|Arthur |active| 1|2017-01-01
1|Arthur |active| 3|2017-01-08
1|Arthur |active| 2|2017-01-07
2|Tricia |active| 2|2017-01-08
2|Tricia |active| 1|2017-01-01
But we want only 'Arthur' with '2017-01-08' and 'Tricia' with '2017-01-08'.
With any database that supports ANSI 99, you could try with a temporary table containing the newest invitation date per "people id", and join that temporary table with the invitations table. We call that table newest_invite_date, and, apparently, it does what we expect it to do:
WITH people(ppl_id,ppl_name,status) AS (
SELECT 1,'Arthur','active'
UNION ALL SELECT 2,'Tricia','active'
), invites(ppl_id,inv_id,inv_date) AS (
SELECT 1,1, DATE '2017-01-01'
UNION ALL SELECT 1,2, DATE '2017-01-07'
UNION ALL SELECT 1,3, DATE '2017-01-08'
UNION ALL SELECT 2,1, DATE '2017-01-01'
UNION ALL SELECT 2,2, DATE '2017-01-08'
), newest_invite_date(ppl_id,inv_date) AS (
SELECT ppl_id,MAX(inv_date)
FROM invites
GROUP BY ppl_id
)
SELECT
people.ppl_id
, people.ppl_name
, people.status
, newest_invite_date.inv_date
FROM people
JOIN newest_invite_date USING(ppl_id)
ORDER BY 1
;
ppl_id|ppl_name|status|inv_date
1|Arthur |active|2017-01-08
2|Tricia |active|2017-01-08
Is this what you were looking for?
Happy playing ...
Marco the Sane
I have a table, gainfinal, which consists of three columns: countrycode, year and values.
I want to calculate z values for all the rows.
I did a simple query like this.
SELECT (
`values` - STDDEV( `values` )
) / AVG( `values` ) AS zvalue
FROM `gainfinal`
But the query returned only one row. How can I perform same thing for all the rows?
How about this?
DECLARE #stdev AS FLOAT, #avg AS FLOAT
SELECT #stdev = STDEV([values]), #avg = AVG([values])
FROM gainfinal
SELECT
countrycode
, year
, [values]
, ([values] - #stdev) / #avg AS zvalue
FROM gainfinal
You need a subquery to get the aggregated values:
SELECT (gf.`values` - stats.average) / stats.s AS zvalue
FROM `gainfinal` gf cross join
(select sttdev(values) as s, avg(values) as average from gainfinal) stats;
In most databases, your original query would generate an error, because it is mixing aggregated columns and non-aggregated columns in a query with no group by. The solution is a separate subquery that does the calculation.
I have 2 tables, one of which lists items, the other of which keeps track of when items changed in price.
price_table:
price_id int
item_id int
price_amount float
change_date date // this date is stored as Y-m-d, without a time
and
item_table:
item_id int
item_name char(40)
item_description char(40)
I know that the following finds the item_ids changed at the current date:
SELECT item_id
FROM price_table
WHERE change_date = "$today"
when
$today = date('Y-m-d');
I then run another query for EACH item_id returned (this is why I think my method is inefficient)
SELECT card_price
FROM price_table
WHERE item_id
ORDER BY change_date ASC
And get the last 2 values from the PHP array, comparing them to get the difference in price. My test had an inner join to return the item_name as well, but I've removed that to simplify the question. Is there a better way to do this? And if so, can it be expanded to use the last 2 or 3 days as the change criteria instead?
This is what triggers are for, I'm also going to talk about DATE_SUB and date intervals
Using triggers will allow you to keep the current price in the price table and keep historical prices in a separate table.
For instance, consider creating a new table called price_table_old
price_table_old:
price_id int
item_id int
price_amount float
change_date date
On the price_table, create an 'update' trigger...
insert into price_table_old ( `item_id`, `price_amount` , `change_date ` )
VALUES ( OLD.`item_id` , OLD.`price_amount` , NOW() )
To help you with trigger syntax, I copied the trigger I used for a staff table below
When ever someone changes a price, the price_table_old table is automatically updated.
From here, things are a little more logical to get the current price and comparisons of previous prices.
You could run a query like....
SELECT
price_table.item_id ,
price_table.price_amount ,
(price_table.price_amount - price_table_old.price_amount ) AS pricediff ,
price_table_old.change_date
FROM
price_table
LEFT JOIN
price_table_old ON price_table_old.item_id = price_table.item_id
ORDER BY price_table.item_id , price_table_old.change_date DESC
or stick a where in there to zone in on a specific item and/or date range and/or a limit to get the last (say) 3 price changes.
For instance, to get a list of price changes for the last 3 days on all items, you could use a statement like this
SELECT
price_table.item_id ,
price_table.price_amount ,
(price_table.price_amount - price_table_old.price_amount ) AS pricediff ,
price_table_old.change_date
FROM
price_table
LEFT JOIN
price_table_old ON price_table_old.item_id = price_table.item_id
WHERE
price_table_old.change_date > ( DATE_SUB( NOW() , INTERVAL 3 DAY ))
ORDER BY price_table.item_id , price_table_old.change_date DESC
Here is my staff table trigger...
CREATE TRIGGER `Staff-CopyOnUpdate` BEFORE UPDATE ON `staff`
FOR EACH ROW insert into staff_old
( `sid` , `title`, `firstname` , `surname` , `type`,
`email` , `notify`, `actiondate`, `action` )
VALUES
( OLD.`sid` , OLD.`title`, OLD.`firstname` , OLD.`surname` , OLD.`type`,
OLD.`email` , OLD.`notify`, NOW() , 'updated' )
Right now I'm creating an online game where I list the last transfers of players.
The table that handles the history of players, has the columns history_join_date and history_end_date.
When history_end_date is filled, it means that player left a club, and when it is like the default (0000-00-00 00:00:00) and history_join_date has some date it means player joined the club (in that date).
Right now, I've the following query:
SELECT
player_id,
player_nickname,
team_id,
team_name,
history_join_date,
history_end_date
FROM
players
INNER JOIN history
ON history.history_user_id = players.player_id
INNER JOIN teams
ON history.history_team_id = teams.team_id
ORDER BY
history_end_date DESC,
history_join_date DESC
LIMIT 7
However, this query returns something like (filtered with PHP above):
(22-Aug-2012 23:05): Folha has left Portuguese Haxball Team.
(22-Aug-2012 00:25): mancini has left United.
(21-Aug-2012 01:29): PatoDaOldSchool has left Reign In Power.
(22-Aug-2012 23:37): Master has joined Born To Win.
(22-Aug-2012 23:28): AceR has joined Born To Win.
(22-Aug-2012 23:08): Nasri has joined Porto Club of Haxball.
(22-Aug-2012 18:53): Lloyd Banks has joined ARRIBA.
PHP Filter:
foreach ($transfers as $transfer) {
//has joined
if($transfer['history_end_date']<$transfer['history_join_date']) {
$type = ' has joined ';
$date = date("d-M-Y H:i", strtotime($transfer['history_join_date']));
} else {
$type = ' has left ';
$date = date("d-M-Y H:i", strtotime($transfer['history_end_date']));
}
As you can see, in the transfers order, the date is not being followed strictly (22-Aug => 21-Aug => 22-Aug).
What am I missing in the SQL?
Regards!
The issue is you are ordering based upon two different values. So your results are ordered first by history_end_date, and when the end dates are equal (i.e. when it is the default value), they are then ordered by history_join_date
(Note that your first results are all ends, and then your subsequent results are all joins, and each subset is properly ordered).
How much control do you have over this data structure? You might be able to restructure the history table such that there is only a single date, and a history type of JOINED or END... You might be able to make a view of joined_date and end_date and sort across that...
From what you have in the question I made up the following DDL & Data:
create table players (
player_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
player_nickname VARCHAR(255) NOT NULL UNIQUE
);
create table teams (
team_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
team_name VARCHAR(255) NOT NULL UNIQUE
);
create table history (
history_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
history_user_id INT NOT NULL, history_team_id INT NOT NULL,
history_join_date DATETIME NOT NULL,
history_end_date DATETIME NOT NULL DEFAULT "0000-00-00 00:00:00"
);
insert into players VALUES
(1,'Folha'),
(2,'mancini'),
(3,'PatoDaOldSchool'),
(4,'Master'),
(5,'AceR'),
(6,'Nasri'),
(7,'Lloyd Banks');
insert into teams VALUES
(1,'Portuguese Haxball Team'),
(2,'United'),
(3,'Reign In Power'),
(4,'Born To Win'),
(5,'Porto Club of Haxball'),
(6,'ARRIBA');
insert into history VALUES
(DEFAULT,1,1,'2012-08-01 00:04','2012-08-22 23:05'),
(DEFAULT,2,2,'2012-08-21 19:04','2012-08-22 00:25'),
(DEFAULT,3,3,'2012-08-19 01:29','2012-08-21 01:29'),
(DEFAULT,4,4,'2012-08-22 23:37',DEFAULT),
(DEFAULT,5,4,'2012-08-22 23:28',DEFAULT),
(DEFAULT,6,5,'2012-08-22 23:08',DEFAULT),
(DEFAULT,7,6,'2012-08-22 18:53',DEFAULT);
SOLUTION ONE - History Event View
This is obviously not the only solution (and you'd have to evaluate options as they suit your needs, but you could create a view in MySQL for your history events and join to it and use it for ordering similar to the following:
create view historyevent (
event_user_id,
event_team_id,
event_date,
event_type
) AS
SELECT
history_user_id,
history_team_id,
history_join_date,
'JOIN'
FROM history
UNION
SELECT
history_user_id,
history_team_id,
history_end_date,
'END'
FROM history
WHERE history_end_date <> "0000-00-00 00:00:00";
Your select then becomes:
SELECT
player_id,
player_nickname,
team_id,
team_name,
event_date,
event_type
FROM players
INNER JOIN historyevent
ON historyevent.event_user_id = players.player_id
INNER JOIN teams
ON historyevent.event_team_id = teams.team_id
ORDER BY
event_date DESC;
Benefit here is you can get both joins and leaves for the same player.
SOLUTION TWO - Pseudo column. use the IF construction to pick one or the other column.
SELECT
player_id,
player_nickname,
team_id,
team_name,
history_join_date,
history_end_date,
IF(history_end_date>history_join_date,history_end_date,history_join_date) as order_date
FROM
players
INNER JOIN history
ON history.history_user_id = players.player_id
INNER JOIN teams
ON history.history_team_id = teams.team_id
ORDER BY
order_date DESC;
Building from #Barmar's answer, you can also use GREATEST() to pick the greatest of the arguments. (MAX() is a grouping function... not actually what you're looking for)
I think what you want is:
ORDER BY MAX(history_join_date, history_end_date)