PHP+MSSQL JOIN two tables outputs nothing - "No tuples" - php

$conn=odbc_connect('mydatabase','','');
$sql="SELECT
Orders.OrderID,
Orders.OrderDate,
\"Order Details\".OrderID,
\"Order Details\".UnitPrice,
\"Order Details\".Quantity,
\"Order Details\".Discount,
FROM
\"Order Details\"
INNER JOIN
Orders
ON \"Order Details\".OrderID = Orders.OrderID";
$rs=odbc_exec($conn,$sql) or die("<p>".odbc_errormsg());
while (($row = odbc_fetch_array($rs)) !== false)
When I try to output the results of $rs nothing is returned. If I try to access fields from either Orders or \"Order Details\" separately it works fine but if I try to JOIN the two tables it outputs nothing.
Is this the correct way to SELECT a field from a table that has a space in its name when using MSSQL? It seems to work when I try "SELECT * FROM \"Order Details\"" but when I try to join the tables I have to specify "\Order Details\".OrderID and I think that might be where it's getting confused.
Apache error log returns a strange "odbc_fetch_array(): No tuples available at this result" message, but I'm not positive this is the actual problem as I've seen this error message pop up randomly for unrelated reasons. Still, I don't quite understand it so thought I should mention it.
Thanks!

Instead of using "/", use brackets.
"SELECT Orders.OrderID, [Order Details].UnitPrice
FROM [Order Details] INNER JOIN Orders ON [Order Details].OrderID = Orders.OrderID"

Related

Laravel DB:select() requires all columns in the group by

I need to run a custom query and I have tried all these methods
$sql = "SELECT acd.*, DATE_FORMAT(acd.cdate, '%d/%m/%Y') cdate_disp,
GROUP_CONCAT(CONCAT_WS(', ', car.type, car.session, crd.name) SEPARATOR '<br />') rd_names,
acb.booking_status my_booking_status
FROM app_data acd
INNER JOIN crmaccounts ca ON ca.id = acd.client_crm_id
LEFT JOIN crmaccount_rdids car ON car.account_id = ca.id
LEFT JOIN crmrd_ids crd ON crd.id = car.rd_id
LEFT JOIN app_bookings acb ON acb.call_ref_id = acd.call_ref AND acb.user_id = 12391
WHERE 1=1
AND acd.client_crm_id NOT IN (select account_id from bstaff WHERE user_id=12391)
GROUP BY acd.id
ORDER BY acd.cdate, ctiming";
DB:select($sql);
throws
Illuminate \ Database \ QueryException (42000)
SQLSTATE[42000]: Syntax error or access violation: 1055 'mydatabase.acd.call_ref' isn't in GROUP BY (SQL: ....)
DB::select($sql);
DB::raw($sql);
DB::select(DB::raw($sql));
//even with pdo
$sth = DB::getPdo()->prepare($sql);
$sth->execute();
$data = $sth->fetchAll(\PDO::FETCH_OBJ);
PDOException (42000)
SQLSTATE[42000]: Syntax error or access violation: 1055 'mydatabase.acd.call_ref' isn't in GROUP BY
It seems like the only way to get working is to list all the table columns in the group by which is doable but not convenient.
I am able to run the same query directly in the phpmyadmin. So I am not sure why when I run it through Laravel it asks me add all columns.
I have MariaDB installed and both Laravel and PhpMyAdmin are connecting to the same instance. Larvel version is 5.8.5.
Please take a look at full query as I asked question here too but couldn't find any answer - https://laracasts.com/discuss/channels/eloquent/query-runs-ok-in-phpmyadmin-but-dbselect-throws-exception
You should try disabling the strict mode in config/database.php, in mysql connection section.
This is not a Laravel issue, but a logical limitation by MySQL. When you add columns to your results set that are not in the GROUP BY clause, different values could be possible for these columns in the result set.
When executing queries in strict mode (which is on by default), MySQL will not allow this because it could lead to unexpected results. If you turn off strict mode MySQL will use the first result it finds for these columns.
You can turn off strict mode in your Laravel config files in config/database.php. However, it is recommended to change your query instead because of the unpredictability.
A full list of checks in strict mode can be found in the MySQL documentation: https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sql-mode-strict
I have installed Laravel 4.8 and also configured Auth. further more,I add two tables called as role and states and tried your pdo code - its perfectly works for me! Here, I put my code.
$sql = "SELECT *
FROM users u
INNER JOIN roles r ON r.id = u.role_id
LEFT JOIN states s ON s.id = u.state_id
WHERE 1=1
GROUP BY u.id
ORDER BY u.id ";
$sth = DB::getPdo()->prepare($sql);
$sth->execute();
$data = $sth->fetchAll(\PDO::FETCH_OBJ);
dd($data);
I am eager to know what is your db structure.

MySQL Query returning blank key

I have a MySQL Query which works fine except the field rnd_key is returned as null despite it having values. If I run a normal query without the join then the key is returned.
SELECT *, SUM(time_duration) as total_time
FROM rnd
LEFT JOIN time ON rnd.rnd_key = time.rnd_key
WHERE rnd.rnd_owner = $eng AND rnd.rnd_status != 90
AND (rnd.rnd_status != 80 OR rnd.rnd_status > '".$ninetyDate."')
GROUP BY rnd.rnd_key
Thanks
try two things:
First: try RIGHT JOIN instead of LEFT JOIN.
Second: try to give each of rnd_key columns diffrent alias names. duplicated names may cause such problem

Retrieving a table alias

I have several queries that have a same filter.
For example:
Filter:
$criteria = "AND id_student = 1 AND id_major = 2 ";
Query 1:
$query1 = "SELECT * FROM student AS t0
LEFT JOIN major AS t1 ON t0.id_major = t1.id
WHERE 1=1 ";
Query 2:
$query2 = "SELECT * FROM lecturer AS t0
LEFT JOIN major AS t1 ON t0.id_major = t1.id
LEFT JOIN student AS t2 ON t2.id_major = t1.id
WHERE 1=1 "
It will be run as
$query1.$criteria
and
$query2.$criteria
As you can see, this will yield a query error, since id_major is ambiguous.
My problem is, that this query is very big in number, so I prefer not to hard-code and change the $criteria and $query individually.
I've tried to change the $criteria into
$criteria = "AND student.id_student = 1 AND major.id_major = 2 ";
But it does no better, it gave me errors of "did you mean t0", "did you mean t2", etc.
So, can I do something like, get an alias of a table? So I can put it in the $criteria
$criteria = "AND (get-alias-code).id_student = 1 AND (get-alias-code).id_major = 2 ";
Or any other method?
I am not the primary developer of this program, just a bug-fixer so I cannot change much of this program (because those queries may affect another page).
Use the aliases you have for your tables already, and set the criteria to be
$criteria = "AND t0.`id_student`='1' AND t1.`id_major`='2' ";
I strongly recommend using backtics and ticks as well, it helps avoiding funny mistakes with table or field names like 'order' :)
Then all you need to ensure is that the table that needs the criteria applied gets the correct alias every time
Edit:
Hm...alternatively, you can remove aliasing from all queries.
I honestly don't think that there's any mysql feature to get the aliases for tables. You could run a string analyser code snippet to find them (knowing all table names it may not even be hard to do) but that looks like a very ugly solution IMHO.
I don't know away to handle it on sql but you can handle it with on array for example
$alias[1]["student"] = "t0"; // it means on query 1 your alias for student is t0
now you can use this variable on your filter
$criteria = "AND ".$alias[1]["student"].".id_student = 1 AND ".$alias[1]["major"].".id_major = 2 ";
I think it's one of fast ways to change your filter without changing all of queries.
hope It helps
Wrap the whole thing in an outer query like this:
"SELECT * FROM (".$query1.") a ".$criteria;
Assuming of course that the original query didn't create multiple columns with the same name, you can now reference them without the alias.

Using JOIN in NotORM

Been trying the following code
$notorm->table()->select("table.*, table2.column, table2.table3.column2");
from http://sql-cross-queries.freexit.eu/dibi/notorm to create a JOIN statement, but to no avail. Am I missing out on anything?
I have reports(timestamp, incident_id, location id), incident(incident_id, incident_desc, location_id), and location(location_id, location_name). I am trying to get timestamp, incident_name, and location_name by joining reports, incident, and location. So I used the following statement:
$notorm->reports()->select("reports.*, incident.incident_id, incident.location.location_id");
but it's not returning anything. The following statement, though:
$notorm->reports()->select("reports.*");
returns reports.
Try this, not 100% sure but worth a try.
$notorm->reports()->join("reports", "LEFT JOIN incident ON reports.incident_id = incident.incident_id")
->join("reports", "LEFT JOIN location ON reports.location_id = location.location_id")
->select("reports.timestamp, incident.incident_desc, location.location_name");
in your table definition, there must be a field name table2_id present

How to return multiple rows in a LEFT JOIN

I have a situation where lets say i'm trying to get the information about some food. Then I need to display all the information plus all the ingredients in that food.
With my query, i'm getting all the information in an array but only the first ingredient...
myFoodsArr =
[0]
foodDescription = "the description text will be here"
ratingAverage = 0
foodId = 4
ingredient = 1
ingAmount = 2
foodName = "Awesome Food name"
typeOfFood = 6
votes = 0
I would like to get something back like this...
myFoodsArr =
[0]
foodDescription = "the description text will be here"
ratingAverage = 0
foodId = 4
ingArr = {ingredient: 1, ingAmount: 4}, {ingredient: 3, ingAmount: 2}, {ingredient: 5, ingAmount: 1}
foodName = "Awesome Food name"
typeOfFood = 6
votes = 0
This is the query im working with right now. How can I adjust this to return the food ID 4 and then also get ALL the ingredients for that food? All while at the same time doing other things like getting the average rating of that food?
Thanks!
SELECT a.foodId, a.foodName, a.foodDescription, a.typeOfFood, c.ingredient, c.ingAmount, AVG(b.foodRating) AS ratingAverage, COUNT(b.foodId) as tvotes
FROM `foods` a
LEFT JOIN `foods_ratings` b
ON a.foodId = b.foodId
LEFT JOIN `foods_ing` c
ON a.foodId=c.foodId
WHERE a.foodId=4
EDIT:
Catcall introduced this concept of "sub queries" I never heard of, so I'm trying to make that work to see if i can do this in 1 query easily. But i just keep getting a return false. This is what I was trying with no luck..
//I changed some of the column names to help them be more distinct in this example
SELECT a.foodId, a.foodName, a.foodDescription, a.typeOfFood, AVG(b.foodRating) AS ratingAverage, COUNT(b.foodId) as tvotes
FROM foods a
LEFT JOIN foods_ratings b ON a.foodId = b.foodId
LEFT JOIN (SELECT fId, ingredientId, ingAmount
FROM foods_ing
WHERE fId = 4
GROUP BY fId) c ON a.foodId = c.fId
WHERE a.foodId = 4";
EDIT 1 more thing related to ROLANDS GROUP_CONCAT/JSON Idea as a solution 4 this
I'm trying to make sure the JSON string im sending back to my Flash project is ready to be properly parsed Invalid JSON parse input. keeps popping up..
so im thinking i need to properly have all the double quotes in the right places.
But in my MySQL query string, im trying to escape the double quotes, but then it makes my mySQL vars not work, for example...
If i do this..
GROUP_CONCAT('{\"ingredient\":', \"c.ingredient\", ',\"ingAmount\":', \"c.ingAmount\", '}')`
I get this...
{"ingredient":c.ingredient,"ingAmount":c.ingAmount},{"ingredient":c.ingredient,"ingAmount":c.ingAmount},{"ingredient":c.ingredient,"ingAmount":c.ingAmount}
How can i use all the double quotes to make the JSON properly formed without breaking the mysql?
This should do the trick:
SELECT food_ingredients.foodId
, food_ingredients.foodName
, food_ingredients.foodDescription
, food_ingredients.typeOfFood
, food_ingredients.ingredients
, AVG(food_ratings.food_rating) food_rating
, COUNT(food_ratings.foodId) number_of_votes
FROM (
SELECT a.foodId
, a.foodName
, a.foodDescription
, a.typeOfFood
, GROUP_CONCAT(
'{ingredient:', c.ingredient,
, ',ingAmount:', c.ingAmount, '}'
) ingredients
FROM foods a
LEFT JOIN foods_ing c
ON a.foodsId = c.foodsId
WHERE a.foodsId=4
GROUP BY a.foodId
) food_ingredients
LEFT JOIN food_ratings
ON food_ingredients.foodId = food_ratings.foodId
GROUP BY food_ingredients.foodId
Note that the type of query you want to do is not trivial in any SQL-based database.
The main problem is that you have one master (food) with two details (ingredients and ratings). Because those details are not related to each other (other than to the master) they form a cartesian product with each other (bound only by their relationship to the master).
The query above solves that by doing it in 2 steps: first, join to the first detail (ingredients) and aggregate the detail (using group_concat to make one single row of all related ingredient rows), then join that result to the second detail (ratings) and aggregate again.
In the example above, the ingredients are returned in a structured string, exactly like it appeared in your example. If you want to access the data inside PHP, you might consider adding a bit more syntax to make it a valid JSON string so you can decode it into an array using the php function json_decode(): http://www.php.net/manual/en/function.json-decode.php
To do that, simply change the line to:
CONCAT(
'['
, GROUP_CONCAT(
'{"ingredient":', c.ingredient
, ',"ingAmount":', c.ingAmount, '}'
)
, ']'
)
(this assumes ingredient and ingAmount are numeric; if they are strings, you should double quote them, and escape any double quotes that appear within the string values)
The concatenation of ingredients with GROUP_CONCAT can lead to problems if you keep a default setting for the group_concat_max_len server variable. A trivial way to mitigate that problem is to set it to the maximum theoretical size of any result:
SET group_concat_max_len = ##max_allowed_packet;
You can either execute this once after you open the connection to mysql, and it will then be in effect for the duration of that session. Alternatively, if you have the super privilege, you can change the value across the board for the entire MySQL instance:
SET GLOBAL group_concat_max_len = ##max_allowed_packet;
You can also add a line to your my.cnf or my.ini to set group_concat_max_lenght to some arbitrary large enough static value. See http://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_group_concat_max_len
One obvious solution is to actually perform two queries:
1) get the food
SELECT a.foodId, a.foodName, a.foodDescription, a.typeOfFood
FROM `foods` a
WHERE a.foodsId=4
2) get all of its ingredients
SELECT c.ingredient, c.ingAmount
FROM `foods_ing` c
WHERE c.foodsId=4
This approach has the advantage that you don't duplicate data from the "foods" table into the result. The disadvantage is that you have to perform two queries. Actually you have to perform one extra query for each "food", so if you want to have a listing of foods with all their ingredients, you would have to do a query for each of the food record.
Other solutions usually have many disadvantages, one of them is using GROUP_CONCAT function, but it has a tough limit on the length of the returned string.
When you compare MySQL's aggregate functions and GROUP BY behavior to SQL standards, you have to conclude that they're simply broken. You can do what you want in a single query, but instead of joining directly to the table of ratings, you need to join on a query that returns the results of the aggregate functions. Something along these lines should work.
select a.foodId, a.foodName, a.foodDescription, a.typeOfFood,
c.ingredient, c.ingAmount,
b.numRatings, b.avgRating
from foods a
left join (select foodId, count(foodId) numRatings, avg(foodRating) avgRating
from foods_ratings
group by foodId) b on a.foodId = b.foodId
left join foods_ing c on a.foodId = c.foodId
order by a.foodId

Categories