DISCLAIMER: I'm still new to this website so I'm still learning the etiquette of the site, I apologize for any errors. Also, I previously posted a questions similar to this but some fantastic people recommended I rework my database to the current format. This was a great help however it was one step forward and one step back. I have an improved database but my question now continues to stand with a few minor tweaks.
To elaborate, I'm currently building an app that has the user create an account and login. Their information that they provided is saved into my database. My database contains two tables, one holds the users information, and one holds the users inventory, both are generated upon the completion of a create account GUI. For this question, only the second table is necessary. This table has three columns, the first is the users username, the second is their inventory slot number, and the third is the item id for the item that is in that slot. When the user creates an account, forty rows are created in this table, in each row their username remains constant. However, the slot number increments from one to forty and the item id column defaults to zero. Here is a visual representation:
Now to get to my code, when the user clicks a button, a random method gets called which sets an int variable which is current named "i" to a specific number. This number is the ID of an item in my app. At this point the user is prompted with two buttons that ask whether they want to keep the item or discard it. If they decide to keep the item I need it to be added to their inventory in the database. This is where my question comes into play. My app knows which user is logged in because when someone properly logs in the app sets their username (which is a primary key) to a global string variable which the rest of the app can user. so it knows which user to update but I need it to check through each of the rows in order, and if it finds a row with a zero in the ItemID column, it will update it to what the variable "i" currently is and end the query.
This is my current code, I'm very new to SQL but I'm trying to teach myself, I apologize if this offends you (because it's so bad):
EDIT: I've updated my code to this new query however I get an error that states java.sql.SQLException: You can't specify target table 'userinv' for update in FROM clause
try{
//get connection to database
Connection con = DataBaseConnect.getConnection();
//create a statement
PreparedStatement addInfo = con.prepareStatement("UPDATE userinv SET "
+ "ItemID = "+i+" "
+ "WHERE Username = '"+LoginController.userLog+"' "
+ "AND Slot = ("
+ "SELECT MIN(Slot) FROM userinv "
+ "WHERE ItemID = 0 "
+ "AND Username = '"+LoginController.userLog+"')");
//process result set
addInfo.executeUpdate();
}
catch(Exception e){
e.printStackTrace();
}
at this point I know it needs to update the userinv table and I know it needs to do this where the users username is but I'm not sure how to write the code in between. Does anyone have any ideas?
This works in Oracle and should work for MySql:
update userinv set itemid = 815
where username = 'test'
and slot = (
select min(slot) from userinv
where itemid = 0
and username = 'test'
)
For more complex cases where you need the first row according to some ordering, but can't express this as a minimum this approach works on Oracle:
update userinv set itemid = 815
where username = 'test'
and slot = (
select slot from (
select count(*) over (partition by username order by slot) cnt,
slot
from userinv
where itemid = 0
and username = 'test'
) where cnt = 1
)
It uses analytic functions so it won't work on MySql, but there is an article how to fake them in MySQL.
With analytic functions, this should also work (didn't try, so it does contain typos and stuff)
update (
select count(*) over (partition by username order by slot) cnt,
u.*
from userinv u
where itemid = 0
and username = 'test'
order by slot
)
set itemid = 815
where cnt = 1
This accesses the table only once, which should be way faster when your table is huge.
Good evening guys,
I'm a newbie to web programming and I need your help to solve a problem inherent to SQL query.
The database engine I'm using is MySQL and I access it via PHP, here I'll explain a simplified version of my database, just to fix ideas.
Let's suppose to work with a database containing three tables: teams, teams_information, attributes. More precisely:
1) teams is a table containing some basic information about italian football teams (soccer, not american football :D), it is formed by three fields: 'id' (int, primary key), 'name' (varchar, team name), nickname (Varchar, team nickname);
2) attributes is a table containing a list of possible information about a football team, such as city (the city where team plays its home match), captain (team captain's fullname), f_number (number of fans) and so on. This table is formed by three fields: id (int, primary key), attribute_name (varchar, an identifier for the attribute), attribute_desc (text, an explanation of the meaning of attribute). Each record of this table represents a single possible attribute of a football team;
3) teams_information is a table where some information, about teams listed in team table, are available. This table contains three fields: id (int, primary key), team_id (int, a foreign key which identifies a team), attribute_id (int, a foreign key which identifies one of the attributes listed in attributes table), attribute_value (varchar, the value of the attribute). Each record represents a single attribute of a single team. In general, different teams will have a different number of information, so for some teams a large number of attributes will be available while for other teams only a small number of attributes will be available.
Note that relation between teams and teams_information is one to many and the same relation exists between attributes and teams_information
Well, given this model my purpose is to realize a grid (maybe with ExtJS 4.1) to show user the list of italian football team, each record of this grid will represent a single football team and will contain all possible attributes: some fields may be empty (because, for considered team, the correspondent attribute is unknown), while the others will contain the values stored in teams_information table (for the considered team).
According to the above grid's field are: id, team_name and a number of fields to represent all the different attributes listed in 'attributes' table.
My question is: can I realize such a grid by using a SINGLE SQL query (maybe a proper SELECT query, to fetch all data I need from database tables) ?
Can anyone suggest me how to write a similar query (if it exists) ?
Thanks in advance for helping me.
Regards.
Enrico.
The short answer to your question is no, there is no simple construct in MySQL to achieve the result set you are looking for.
But it is possible to carefully (painstakingly) craft such a query. Here is an example, I trust you will be able to decipher it. Basically, I'm using correlated subqueries in the select list, for each attribute I want returned.
SELECT t.id
, t.name
, t.nickname
, ( SELECT v1.attribute_value
FROM team_information v1
JOIN attributes a1
ON a1.id = v1.attribute_id AND a1.attribute_name = 'city'
WHERE v1.team_id = t.id ORDER BY 1 LIMIT 1
) AS city
, ( SELECT v2.attribute_value
FROM team_information v2 JOIN attributes a2
ON a2.id = v2.attribute_id AND a2.attribute_name = 'captain'
WHERE v2.team_id = t.id ORDER BY 1 LIMIT 1
) AS captain
, ( SELECT v3.attribute_value
FROM team_information v3 JOIN attributes a3
ON a3.id = v3.attribute_id AND a3.attribute_name = 'f_number'
WHERE v3.team_id = t.id ORDER BY 1 LIMIT 1
) AS f_number
FROM teams t
ORDER BY t.id
For 'multi-valued' attributes, you'd have to pull each instance of the attribute separately. (Use the LIMIT to specify whether you are retrieving the first one, the second one, etc.)
, ( SELECT v4.attribute_value
FROM team_information v4 JOIN attributes a4
ON a4.id = v4.attribute_id AND a4.attribute_name = 'nickname'
WHERE v4.team_id = t.id ORDER BY 1 LIMIT 0,1
) AS nickname_1st
, ( SELECT v5.attribute_value
FROM team_information v5 JOIN attributes a5
ON a5.id = v5.attribute_id AND a5.attribute_name = 'nickname'
WHERE v5.team_id = t.id ORDER BY 1 LIMIT 1,1
) AS nickname_2nd
, ( SELECT v6.attribute_value
FROM team_information v6 JOIN attributes a6
ON a6.id = v6.attribute_id AND a6.attribute_name = 'nickname'
WHERE v6.team_id = t.id ORDER BY 1 LIMIT 2,1
) AS nickname_3rd
I use nickname as an example here, because American soccer clubs frequently have more than one nickname, e.g. Chicago Fire Soccer Club has nicknames: 'The Fire', 'La Máquina Roja', 'Men in Red', 'CF97', et al.)
NOT AN ANSWER TO YOUR QUESTION, BUT ...
Have I mentioned numerous times before, how much I dislike working with EAV database implementations? What should IMO be a very simple query turns into an overly complicated beast of a potentially light dimming query.
Wouldn't it be much simpler to create a table where each "attribute" is a separate column? Then queries to return reasonable result sets would look more reasonable...
SELECT id, name, nickname, city, captain, f_number, ... FROM team
But what really makes me shudder is the prospect that some developer is going to decide that the LDQ should be "hidden" in the database as a view, to enable the "simpler" query.
If you go this route, PLEASE PLEASE PLEASE resist any urge you may have to store this query in the database as a view.
I'm going to take a slightly different route. Spencer's answer is fantastic, and it addresses the issue quite well, but there's still a large underlying problem.
The data that you are trying to display on the site is over-normalized in the database. I won't elaborate, since, again, Spencer's answer highlights the issue pretty well.
Rather, I'd like to recommend a solution that denormalizes the data a bit.
Convert all of your Team data into a single table with many columns. (If there is Player data that isn't covered in the question, that would be a second table, but I'll gloss over that for now.)
Sure, you'll have a whole bunch of columns, and a lot of the columns might be NULL for a lot of the rows. It's not normalized, and it's not pretty, but here's the huge advantage that you gain.
Your query becomes:
SELECT * FROM Teams
That's it. That gets displayed right to the website and you are done. You might have to go out of your way to realize this schema, but it would be totally worth the time investment.
I think what you're saying is that you want the rows in the attributes table to appear as columns in the result recordset. If this is correct, then then in SQL you would use PIVOT.
A quick search on SO seems to indicate that there is no PIVOT equivalent in MySql.
I wrote a simple PHP script to generalize spencer's idea to solve my issue.
Here's the code:
<?php
require_once('includes/db.config.php'); //this file performs connection to mysql
/*
* Following function requires a table name ($table)
* and a number of service fields ($num). Given those parameters
* it returns the number of table fields (excluding service fields).
*/
function get_fields_number($table,$num,$conn)
{
$query = "SELECT * FROM $table";
$result = mysql_query($query,$conn);
return mysql_num_fields($result)-$num; //remember there are $num service fields
}
/*
* Following function requires a table name ($table) and an array
* containing a list of service fields names. Given those parameters,
* it returns the list of field names. That list is contained within an array and
* service fields are excluded.
*/
function get_fields_name($table,$service,$conn)
{
$query = "SELECT * FROM $table";
$result = mysql_query($query,$conn);
$name = array(); //Array to be returned
for ($i=0;$i<mysql_num_fields($result);$i++)
{
if(!in_array(mysql_field_name($result,$i),$service))
{
//currently selected field is not a service field
$name[] = mysql_field_name($result,$i);
}
}
return $name;
}
//Below $conn is db connection created in 'db.config.php'
$query = "SELECT `name` FROM `detail_arg` WHERE visibility = 0";
$res = mysql_query($query,$conn);
if($res===false)
{
$err_msg = mysql_real_escape_string(mysql_error($conn));
echo "{success:false,data:'".$err_msg."'}";
die();
}
$arg = array(); //list of argument names
while($row = mysql_fetch_assoc($res))
{
$arg[] = $row['name'];
}
//Following function writes the select subquery which is
//necessary to build a column containing a single attribute.
function make_subquery($attribute) //$attribute contains attribute name
{
$query = "";
$query.="(SELECT incident_detail.arg_value ";
$query.="FROM incident_detail ";
$query.="INNER JOIN detail_arg ";
$query.="ON incident_detail.arg_id = detail_arg.id AND detail_arg.name='".$attribute."' ";
$query.="WHERE incident.id = incident_detail.incident_id) ";
$query.="AS $attribute";
return $query;
}
/*
echo make_subquery("date"); //debug code
*/
$subquery = array(); //list of subqueries
for($i=0;$i<count($arg);$i++)
{
$subquery[] = make_subquery($arg[$i]);
}
$query = "SELECT "; //final query containing subqueries
$fields = get_fields_name("incident",array("id","visibility"),$conn);
//list of 'incident' table's fields
for($i=0;$i<count($fields);$i++)
{
$query.="incident.".$fields[$i].", ";
}
//insert the subqueries
$sub = implode($subquery,", ");
$query .= $sub;
$query.=" FROM incident ORDER BY incident.id";
echo $query;
?>
I've got 3 dataset objects that are nested with each other using entity set objects. I am selecting the data like this
var newList = from s in MainTable
from a in s.SubTable1 where a.ColumnX = "value"
from b in a.Detail where b.Name = "searchValue"
select new {
ID = s.ID,
Company = a.CompanyName,
Name = b.Name,
Date = s.DueDate
Colour = b.Colour,
Town = a.Town
};
and this works fine, but the trouble is there are many records in the Detail object-list/table for each Name value so I get a load of duplicate rows and thus I only want to display one record per b.Name. I have tried putting
group s by b.Name into g
before the select, but then this seems to stop the select enabling me to select the columns I want (there are more, in practice). How do I use the group command in this circumstance while still keeping the output rows in a "flat" format?
Appending comment as answer to close question:-
Of course that if you group your results, you cant get select a column of a child, thats because there may be more than one childs and you have to specify an aggregate column for example the sum,max etx –
I have say three tables.
Projects
Id
Name
Categories
Id
Name
ProjectCategories
Id
ProjectId
CategoryId
I now have an array of strings which represents categories the user has elected to search on. I now need to find all the projects that have those category attached to them.
I tried;
string[] searchTerms = new string[2];
searchTerms[0] = "paint";
searchTerms[1] = "painting";
IQueryable<Project> projects = (from category in dc.ProjectCategories
where searchTerms.Any(val => category.Category.Name.Contains(val))
select category.Project).Distinct();
But I get the error;
{"Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains() operator."}
Unless I'm mistaking your meaning, which is possible, I think you can just do a contains with the searchTerms
where searchTerms.Contains( category.Category.Name )
this will translate into SQL as
... WHERE Name IN ('paint','painting') ...
I am very frustrated from linq to sql when dealing with many to many relationship with the skip extension. It doesn't allow me to use joinned queries. Not sure it is the case for SQL server 2005 but I am currently using SQL Server 2000.
Now I consider to write a store procedure to fetch a table that is matched by two tables e.g. Album_Photo (Album->Album_Photo<-Photo) and Photo table and only want the Photos data so I match the Album's ID with Album_Photo and use that ID to match the photo. In the store procedure I am just fetch all the joinned data. After that in the linq to sql, I create a new Album object.
e.g.
var albums = (from r in result
where (modifier_id == r.ModifierID || user_id == r.UserID)
select new Album() {
Name = r.Name,
UserID = r.UserID,
ModifierID = r.ModifierID,
ID = r.ID,
DateCreated = r.DateCreated,
Description = r.Description,
Filename = r.Filename
}).AsQueryable();
I used the AsQueryable to get the result as a IQueryable rather than IEnumerable. Later I want to do something with the collection, it gives me this error:
System.InvalidOperationException: The query results cannot be enumerated more than once.
It sounds like you have a situation where the query has already executed by the time you are want to filter it later in your code.
Can you do something like...
var albums = (blah blah blah).AsQueryable().Where(filterClause) when you have enough info to process
what happens if you try albums.where(filter) later on in the code? Is this what you are trying?