Match SQL Id's and get and apply the matching value - php

I'm working on an add-on for a script. It's an old add-on written by someone else I used to know but then just left dead. In my interests of learning more php/sql, I've been working on it. Pretty much, it's a support ticket system.
Right now, there's a file which holds all convenient DB functions when they need to be called. I'm making a page that lists all of the tickets, and their status (New, Answered, so on...), but I'd like to add the name of the Department it was sent to. Admins can make departments, and then users can pick one of these departments when making a ticket. My problem is, however, that the person who left the code never really merged departments with tickets. Here's the ticket code I currently have:
static function ticket_list() {
return OW::getDbo()->queryForList("SELECT id,category_id,subject,requested_deletion,department_id,updated,status FROM ".SupportCenterDB::table_ticket()." ORDER BY updated DESC;");
}
"department_id" is the key factor here. There's a DB table for departments, and a call for "department_get" which is as follows:
static function department_get($id) {
$d=OW::getDbo();
$res=$d->queryForList("SELECT * FROM ".SupportCenterDB::table_department()." WHERE id='".$d->escapeString($id)."';");
return $res[0];
}
So, I call all the tickets as so:
$tickets_q=SupportCenterDB::ticket_list();
$tickets=array();
foreach($tickets_q as $data) {
array_push($tickets,array(
"text"=>$data["subject"],
"updated"=>$data["updated"],
"department"=>$data["department_id"],
"status"=>$this->get_ticket_status($data),
//"url_view"=>"/settings/tickets/view/${data["id"]}",
"url_view"=>OW::getRouter()->urlForRoute("supportcenter-manage-tickets-view",array("id"=>$data["id"])),
"url_delete"=>OW::getRouter()->urlForRoute("supportcenter-manage-tickets-delete",array("id"=>$data["id"])),
"request_deletion"=>$data["requested_deletion"],
));
}
$this->assign("items",$tickets);
This simply only shows the actual id of the selected department when I have the value displayed on the table. In this case, the department's text value is "Support" but the ID is "1". This code shows the department as the id, so as "1". I want to get it so the received $data of the department_id will match with the rest of my Department tables, so that I can actually display the Department text.
Basically, I want to get the value of department_id from each $tickets_q, and have the id match with the text of the corresponding department, so it displays the actual name and not the ID number.
Thanks for your help!

What you're looking for is an Inner Join. You can modify your original query to accomplish this in one statement. This also assumes you don't need the department_id, just the department_name. If you do need it, add it back into your list of fields to fetch.
static function ticket_list() {
$table_ticket = SupportCenterDB::table_ticket();
return OW::getDbo()->queryForList("SELECT id, category_id, subject,
requested_deletion, table_departments.text, updated, status FROM {$table_ticket}
JOIN table_departments ON table_departments.id = {$table_ticket}.department_id
ORDER BY updated DESC;");
}
Note: You should really look into using a prepared statement for the SQL query, that way you're not passing in a parameter directly into your select statement, even if it is defined right above it.
Update:
"department"=>$data["department_id"] needs to become "department"=>$data["text"]. To be cleaner, you could give the field an alias so you know what you're actually pulling.
SELECT id, category_id, ..., table_departments.text AS department_name, updated, status...
And then the line above becomes "department"=>$data["department_name"].

Related

How can I show results in a select input, based on values shown in a seperate table?

I have three tables, and I'm just looking for a way to make this work.
tbl_campaigns has the columns "id" and "campaign". This one is fairly straight forward, it's just campaign names with an ID number that is auto-incremented so they have unique IDs.
tbl_users has an "id" column so each user has a unique ID number, standard stuff.
tbl_permissions creates a new row whenever a new user is created. This means its "id" column has unique ID values that match to the ID of a user in 'tbl_users'. The columns have been named to match the ID value of a campaign each time a new one is created, for example, the column "campaign_1" is relevant to the campaign in 'tbl_campaigns' with the ID of 1. The idea is this table data is filled with either 1's or 0's.
If a row with the ID of 1 has the number 1 for the column "campaign_1", then the user with the ID of 1 is approved for the campaign with the ID of 1 in the campaign table. If it were 0 then they're not approved for it. The same logic applies for columns "campaign_2", "campaign_3" etc..
Anyways, the issue I'm having is displaying this information on a front-end, as I only want the user to be able to see the campaigns they are approved to run in a drop-down list. When the user is logged in it stores their User ID in a session, I'm not sure if there's a way around it with this method.
Is there any way to get around this? Please note I've done this in procedural PHP as I'm still in my early days, so if anyone has a solution along these lines it would be much appreciated. Sorry if it's a little confusing. I am aware it's a bit ham-fisted, but I just want it to work first.
I believe that your schema needs to be improved, as the table structure should not have to change every time that you add a new campaign.
keep tables tbl_campaigns and tbl_users as they are
create table tbl_permissions with 4 fields (id, user_id, campaign_id and permission)
To check if a user has permission use a query like this:
SELECT permission FROM tbl_permissions WHERE user_id = ? AND campaign_id = ?
So, every time you create a campaign add a corresponding record to the tbl_permissions table. No need to add a new column.
I think the best practice to do this is as follows:
- Create HTML to show to the user(if you don't have it, let me know so i can work on one you can use)
- Create JS archive that will be in charge of calling PHP file and show the result in your HTML(if you don't know how to make it let me know so i can help you)
- Create PHP file, this is going to be in charge of consulting your data base and give the result disired for your select (if you don't know how to make it, let me know)
It is pretty easy to make this work, let me know if you need more help.

Using special union functions inside Laravel Eloquent model

I've started developing a website using Laravel, and im pretty much finding everything through the official documentation and answers that I find here. However there is 1 thing that -even though i've found a way to do-, I have a feeling that could be done in another, more optimized way than the one I'm doing it right now. Let me explain.
For my website, I have a table called "players", which has data about some players extracted from a football game. Let's say the structure is like this:
ID (int, primary key, A_I)
GameID (int, unique) //what the game uses
PlayerName
Data (basically many different columns)
Since the purpose of my website is to allow users to do modifications on the game content, I also have another another table that I call "userplayers", which I use for doing modifications on the players that exist on the original table, or for adding new ones. The structure is like the "players" table, however with just one column added, called userID, which is to identify which modification belongs to which user.
ID (int, primary key, A_I)
GameID (int, unique together with userID)
PlayerName
Data (basically many different columns)
UserID (int, unique together with GameID)
If I add an entry on the userplayers table, if that entry has the same GameID as any entry on the players table (and the user that has created it is logged in), then on runtime it overwrites the players' table entry. If the GameID of the new entry doesnt exist on the original players table, then it just gets appended to it.
If the user is not logged in, then I just return the players table.
By using eloquent laravel model I can easily retrieve the players table for when the user is not logged in. However, I can't figure out an efficient way to return the whole DB + the user created content with just using core eloquent model functions.
In the past (without Laravel) I was using this DB query:
SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE players.gameid=userplayers.gameid AND userId=$userId) UNION (SELECT * FROM userplayers WHERE userId=$userId)) AS pl;
And the way I've "found" to do something like this in Laravel is by adding a local scope inside the Players model like this:
public function scopeIncludeCustom($query, $user)
{
return \DB::select('SELECT * FROM (SELECT *, NULL FROM players WHERE NOT EXISTS (SELECT * FROM userplayers WHERE userplayers.gameid=players.gameid AND userId='.$user.') UNION (SELECT * FROM userplayers WHERE userId='.$user.')) AS players_full);
}
However you can understand that this doesn't return the $query as intended by scopes, but just a php array, which im returning back, and I think that's not the correct way to do this. For example, when I'm just searching the players table (without using user created content), it takes a much much shorter time to return results than returning results with the custom content.
Any ideas are hugely appreciated.
So, after some days of researching my issue and possible solutions, I came up with this solution.
So, lets take this step by step.
I had a "Player" model, that fetched data from the "players" table.
I also had a "Userplayer" model, that fetched data from the "userplayers" table.
I had to create a relation between those 2 models. An entry in the "players" table may have many entries related to them in the "userplayers" table. So, in the Player Model I added this:
public function userplayers()
{
return $this->hasMany('App\Userplayer', 'gameid', 'gameid');
}
While in the Userplayer Model, I added this:
public function player()
{
return $this->belongsTo('App\Player', 'gameid', 'gameid');
}
When requesting data, the first step was to remove every row from the "players" table that had the same "GameId" as any row returned from the "userplayers" table, which also had a restriction that the "userId" in every row of this table must be a specific one.
This is the code that does this in my SearchPlayerController:
$orig_players = \App\Player::whereDoesntHave('userplayers', function ($query) use($user)
{
$query->where('userId', $user);
})->get();
At the same time, I need to get every row from the "userplayers" table that has the "userid" I want
$userplayers = \App\Userplayer::where([
['pesid', $search]
])->get();
Now, I just merge the 2 results (I can't use UNION because the data I fetch from the "players" table has one less column).
$players = $orig_players->merge($userplayers)
->sortBy('registeredName')
->take($limit);
And everything works perfectly fine, and a lot faster than before!

MySQL create a table from two existing table calculations

I'm making a php web page with Yii and MySQL. My problem is in database part.
I have two tables, "stock" and "given". They are like:
stock = id (primary key), name, amount (this is integer), details
and
given = id (from stock), name (from stock), amount (given amount, thus not same with stock), ...
I want to create a table "leftStock" which is the same format with "stock" but has different amount. It will be the number left in stock after given some. Since in different times, different amounts of the same item may be given, the given table's id and name is not unique. Thus i'll use "sum(given.amount)" but its not definite for me.
I thought i can use "create table...select" style coding but i could not construct its structure. Can anybody help me?
(Also can i use "view" in a Yii web page? Because i won't make any direct change on "leftStock" table, its being view may be feasible either.)
It should be done by creating a query too.
Try this, I dont test it yet:
SELECT
(SELECT SUM(a.amount - b.amount)
FROM stock AS a, given AS b
WHERE a.id = b.id AND a.id = id) AS `itemleft`, *
FROM stock

How to make a MYSQL query to include rows once of a column based on another?

Seems to be a simple question for someone who knows well SQL, but not for me.
Anyhow, here is a sample table:
Primary (and only) key is on id. So basically the scenario is as fallows. User may add images. Newly added images are inserted with comune_number value 0 avoiding duplicates (on file name = image via PHP). Images inserted can be assigned to a category from another table. The same image can be assigned to many categories, for each is inserted a new row with the category id (comune_number). Relation between the two tables is on comune_number.
I would like to show the images, with a checkbox checked for which is assigned already.
My question is simple. How to include all images once, but where the same image is already assigned, include that comune_number instead of 0. I don't care about the order. The result I would like to achieve is something like this:
I'm aware of GROUP BY, so if I try
mysql_query("SELECT * FROM banner WHERE `comune_number` = 0");
or
mysql_query("SELECT * FROM banner GROUP BY `image`");
I end up with the same (not wanted) result.
Most likely I have to combine two queries in one, but I can't figure out which and how.
Note1: I have tried many combinations in phpMyAdmin, based on my (little) knowledge and on what I found with Google (including Stackoverflow), but none of them resulted as shown.
Note2: MySQL version 5.1
Either MYSQL only or combined with PHP solutions are welcome.
EDIT
I need to keep the comune_number. So when I show the images, I need to know the id.
My queries must rely on comune_number, so when I need comune_number = 10, the result should be as on second image above.
EDIT2
It seems I wasn't made myself clear. So what I want, when user is watching category of id 10, show him all the images once, and mark the ones which were assigned to that category.
Another example is here. So if user is watching category (comune_number) of id=9, show every picture once, and mark the two assigned to it.
Based on your SQLFiddle and all the comments here is the updated query:
SELECT r.*
FROM (SELECT b.*
FROM banner b
WHERE b.comune_number = 9
UNION ALL
SELECT b2.*
FROM banner b2
WHERE b2.comune_number = 0
GROUP BY b2.image) r
GROUP BY r.image;
Live DEMO.
select * from
(SELECT * FROM banner order by
FIELD(comune_number,10,0))
abc group by `image`;
fiddle
Updated:
Query with filter condition:
select * from
(SELECT * FROM banner
where comune_number in (10,0) order by
FIELD(comune_number,10,0))
abc group by `image`;
an important tip. When you use GROUP BY all the field you put in your field list, must be in GRUOP BY clause or must be into an aggragate function. MySql has a strange behaviour, don't sign the error but if you try in another DBMS your query:
SELECT * FROM banner GROUP BY image
You have an error.
MySql applies for fields not present in group by an implicit limit 1.
Solution about your issue:
SELECT * FROM banner b1
WHERE b1.comune_number <= 'YOUR_CATEGORY'
AND NOT EXISTS
(SELECT 'X' FROM banner b2
where b2.image = b1.image
and b2.comune_number > b1.comune_number
AND b2.comune_number <= 'YOUR_CATEGORY')
EDIT
I've changed query, now I put a condition about input category. Where you find YOUR_CATEGORY put the value of category you see (i.e. 9).
I am not sure whether this is the exactly you need, but I think it can be helpful.Try this:
Select if(comune_number = 10, comune_number,0), id, image from table
You may change comune_number as per your convenience. Further nesting in IF condition is also possible.

Temporary Table and Left Joins not showing results as expected

I'm really hoping someone can help me with this. I have a number of product attribute types that users can select from to refine the products that are returned to them on screen. What I'm trying to do is, for each product attribute type, I want to list all attributes that relate to either the selected category or search term, then once they've made their selections, I still want to display each of the attributes that relate to the category or search term, but only display a clickable link if the product count for that particular attribute is greater than 1 and for those that have a product count of zero, I want to list them, but make them unclickable. An example of what I'm trying to achieve can be found on the ASOS website, in the left hand menu
http://www.asos.com/Women/Dresses/Cat/pgecategory.aspx?cid=8799#state=Rf961%3D3340%2C3341%40Rf-200%3D20&parentID=Rf-300&pge=0&pgeSize=20&sort=-1
Initially I tried using just joins to achieve this, but I wasn't able to do it, successfully. So I decided to create a temporary table for each attribute type which held a list of all the attributes that related to the main query and then created a refined query, with a left join. Here's my code:
CREATE TEMPORARY TABLE temp_table
SELECT su_types.id, type AS item FROM su_types
INNER JOIN su_typerefs ON su_types.id=su_typerefs.id
INNER JOIN su_pref ON su_typerefs.mykey = su_pref.mykey
WHERE wp_category_id =40 GROUP BY su_typerefs.id
$sudb->query($query);
if ($sudb->affected_rows > 0) {
SELECT temp_table.id,item,COUNT(su_typerefs.mykey) AS product_count FROM temp_table
LEFT JOIN su_typerefs ON temp_table.id=su_typerefs.id
LEFT JOIN su_pref ON su_typerefs.mykey = su_pref.mykey
LEFT JOIN su_stylerefs ON su_pref.mykey = su_stylerefs.mykey
LEFT JOIN su_productrefs ON su_pref.mykey = su_productrefs.mykey
WHERE wp_category_id =40 AND su_stylerefs.id in (91) AND su_productrefs.id in (54) AND su_typerefs.id in (159) GROUP BY su_typerefs.id
if ($itemresults = $sudb->query($query)) {
while($itemresult = $itemresults->fetch_array(MYSQLI_ASSOC)) {
$id=$itemresult['id'];
$item=$itemresult['item'];
$product_count=$itemresult['product_count'];
build_link($list_type, $item, $product_count, $id);
}
}
In the above example the first query selects all the product types that relate to a particular category, say dresses. And the second query is based on the refinements the user has made on the category, in this example this is product, product type and style. A user can also refine their search by colour, fit, fabric and design.
There are a couple of issues with this:
1) The number of results returned in the second query do not match the results of the first. Using the above as an example, I wish to list all products that relate to the chosen category, then using the second query return the product count for each of these products as I described above. So if the temporary table returns, trousers, jeans and skirts. I expected these three items to be displayed on screen based on the conditions applied in the second query, however my results may only show trousers and jeans, if there is not a match for skirts in the second query. I thought that using a left join would mean that all the results of the temporary table would be displayed.
2)Also I wonder if I'm doing this the most efficient way. I have a total of 8 attribute groups, and therefore need to do the above 8 times. If the user choses to refine the results using all 8 attribute groups then in addition to the temp table join, there will be a total of 9 joins for each type. It's taking a while to execute, is there a better way to do this? There are approximately 1/2 million products in the table, and this will probably be 5 times this, once my site goes live.
I really hope all that I have written makes sense and I'd really appreciate the stackoverflow community's help with this, if anyone can help. I apologise for the essay ;). Thanks in advance
To answer your first question; yes, a LEFT JOIN will indeed keep all data from the initial table. That, however, isn't the problem.
The reason why you lose empty categories, is most likely (I say this because I don't fully know your db structure) because of the where condition filtering out all results based on the data in the joined tables.
If for a category all items get filtered out (possibly including the NULL joined values), you will not get this category back from that query anymore. Also the GROUP BY is done on a joined column, that might also effectively wipe out your other categories.
As for the second question, you already state it's taking long; so it's probably not the way to go if you want things to work fast ;) (okay, obvious answer, low hanging fruit, etc). What you might want to do, is get a collection of keys from the filterable categories first, and use that data to select items.
This prevents that you have to join up your entire products table in a temp table (at least, that's what I think you're doing), which of course will take long with the given number of entries. Selecting a list of matching IDs from the given attributes also gives you the advance of using your indexes (more), which a temp-table probably won't have. If this is possible and feasible mainly depends on your schema's structure; but I hope it might lead you to the direction you want to go :)

Categories