How to write this MySql query on Laravel Controller? - mysql

This is my query on MySql and I want to write it on Laravel. How to write this MySql query on Laravel Controller? Beacuse i try to write it on laravel and the result is different from MySql
SET #row_num=0;
SELECT #row_num:=#row_num+1,
model.name, date,
sum(a), sum(b),
SUM(SUM(b)) OVER(PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW),
IF(#row_num>2, SUM(SUM(b)/3) OVER(PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) , NULL)
FROM table INNER JOIN model ON table.id_model = model.id
WHERE id_model = 1
GROUP BY date

Step 0: Indent that query so it's easier to work with
I used https://codebeautify.org/sqlformatter for this. The result was the following:
SET
#row_num = 0;
SELECT
#row_num := #row_num + 1,
model.name,
date,
sum(a),
sum(b),
SUM(
SUM(b)
) OVER(
PARTITION BY model.name
ORDER BY
date ROWS BETWEEN 2 PRECEDING
AND CURRENT ROW
),
IF(
#row_num > 2,
SUM(
SUM(b)/ 3
) OVER(
PARTITION BY model.name
ORDER BY
date ROWS BETWEEN 2 PRECEDING
AND CURRENT ROW
),
NULL
)
FROM
table
INNER JOIN model ON table.id_model = model.id
WHERE
id_model = 1
GROUP BY
date
Step 1: Identify different statements.
Your query (or queries) have 2 statements. a SET statement and a SELECT statement.
Step 2: Translate the statements to Query Builder if possible.
Start with the shortest/easier one first
Step 2.1: Translate the SET Statement.
To run a statement where we don't care about the results (just that it runs), DB::statement() can be used. This will mostly be copying the SQL.
DB::statement('SET #row_num = 0');
Step 2.2: Translate the SELECT statement.
For complex SQL queries, the easiest way to translate them is to run them with tinker and check if they are the same when using toSql().
Create a temporary .php file: touch myquery.php.
Write your query work-in-progress in your temp php file, finish with ->toSql(); and dump the results.
Run it through tinker: php artisan tinker myquery.php
Keep working on the query in your temp php file
Repeat steps 3 and 4 until the query is the one you want.
Again, we can start with the easy parts.
SQL
Query Builder
INNER JOIN model ON table.id_model = model.id
join('model', 'table.id_model', 'model.id')
WHERE id_model = 1
where('id_model', 1)
GROUP BY date
groupBy('date')
Now the query should look like this:
$query = DB::table('table')
->join('model', 'table.id_model', 'model.id')
->where('id_model', 1)
->groupBy('date')
->toSql();
As for the columns selected; many of them are aggregate functions so the use of selectRaw() is needed. As the name implies, you pretty much pass raw SQL to it. You can put it all in a single selectRaw() but I prefer to use one per column for readability.
As a result, the final query looks like this:
$query = DB::table('table')
->selectRaw('#row_num := #row_num + 1')
->selectRaw('model.name')
->selectRaw('date')
->selectRaw('sum(a)')
->selectRaw('sum(b)')
->selectRaw('SUM(SUM(b)) OVER (PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)')
->selectRaw('IF(row_num > 2, SUM(SUM(b)/3) OVER (PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), NULL)')
->join('model', 'table.id_model', 'model.id')
->where('id_model', 1)
->groupBy('date')
->toSql();
And there, it should be done.
Step 3: Cleanup
Step 3.1: Copy the queries to your Controller or wherever you need to use them.
DB::statement('SET #row_num = 0');
$results = DB::table('table')
->selectRaw('#row_num := #row_num + 1')
->selectRaw('model.name')
->selectRaw('date')
->selectRaw('sum(a)')
->selectRaw('sum(b)')
->selectRaw('SUM(SUM(b)) OVER (PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)')
->selectRaw('IF(row_num > 2, SUM(SUM(b)/3) OVER (PARTITION BY model.name ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), NULL)')
->join('model', 'table.id_model', 'model.id')
->where('id_model', 1)
->groupBy('date')
->get();
Step 3.2: Delete temp php file.

You can try DB::statement() or DB::unprepared()

Related

sub query returns more than 1 - issue with passing values into subquery

I am running the following query which keep stating that more then one row is given:
select filestorage.id
from `filestorage`
where (SELECT LEFT(filestorage_inst_data.value, length(filestorage_inst_data.value - 1)) as seconds
FROM filestorage_inst_data
WHERE filestorage_inst_data.parameter = "Time" AND filestorage_inst_data.filestorage_id = filestorage.id) <= 3600
For some reason, the only very first value is passed into the subquery. Also, if I do set a limit within the subquery than the data is fetched fine, it's just I don't see why query would fetch multiple results?
Try this:
SELECT filestorage.id
FROM filestorage f
WHERE EXISTS(SELECT 1 FROM filestorage_inst_data fid
WHERE fid.parameter = 'Time'
AND fid.filestorage_id = f.id
AND CAST(LEFT(fid.value, length(fid.value - 1)) AS UNSIGNED) <= 3600)
You have to pass a specific one row when giving a select statement on where clause. select clause that, the one you using on where clause must return one unique row. for example.
"SELECT * FROM user WHERE role_id=(SELECT role_id FROM user_role WHERE role_name='Admin');"

CTE returning error

I wrote a CTE to remove non numeric values from a data set, then get a count of numeric values within a range.
WITH dtr
AS ( SELECT resultlevel r
FROM dbo.Result
WHERE DrugID = 'AMP'
AND ISNUMERIC(ResultLevel) = 1
AND AuditStamp > '1/1/2016'
AND DeleteFlag = 0
)
SELECT COUNT(*)
FROM dtr
WHERE CONVERT(INT, r) BETWEEN 50 AND 75
This returns an error in SMS
Msg 245, Level 16, State 1, Line 2
Conversion failed when converting the varchar value 'PND ' to data type int.
This error is completely possible without the 'dtr' query in the CTE.
When I rewrite this, instead of a CTR, but a TEMP table, it works.
SELECT resultlevel r
INTO #d
FROM dbo.Result
WHERE DrugID = 'AMP'
AND ISNUMERIC(ResultLevel) = 1
AND AuditStamp > '1/1/2016'
AND DeleteFlag = 0
SELECT COUNT(*)
FROM #d
WHERE CONVERT(INT, r) BETWEEN 50 AND 75
So my questions is why?? I have always thought a CTE was like creating a TEMP table.
TEST DATA
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (result char(5))
insert into #temp (result) values
('1'),('A'),('>2'),('PEN ') ,('#3'),('-2'),('-33')
;with isnum AS (
SELECT result
FROM #temp
WHERE ISNUMERIC(result) = 1)
--Selecting from the CTE yields 1, -2, and -33 all of which can be converted to INT
--Running the query with the where clause causes the conversion error
SELECT
result,
ISNUMERIC(result)
FROM isnum
--WHERE CONVERT(INT,result) > 1
In SQL Server there is Logical Processing Order of the SELECT statement, which determines when the objects defined in one step are made available to the clauses in subsequent steps:
FROM
ON
JOIN
WHERE
GROUP BY
WITH CUBE or WITH ROLLUP
HAVING
SELECT
DISTINCT
ORDER BY
TOP
This is how your query is going to be proccesed and your query looks perfectly fine. But sometimes, the SQL Server decides not to follow this order in order to optimize your query.
In your case, the SQL Server might be simplyfing/transforming your query into another and performing the convert function, before applying the where isnumeric filtering.
If we made your query a little more complex (but still giving the same results), the SQL Server is executing the code correctly this time:
;with isnum AS (
SELECT result
FROM #temp
WHERE ISNUMERIC(result) = 1
GROUP BY result
HAVING MAX(result) = result
)
SELECT
result,
ISNUMERIC(result)
FROM isnum
WHERE CONVERT(INT,result) > 1;
In your case (and this is what I am doing in such situations when different types are stored in one column), you can simply use TRY_CONVERT function:
;with isnum AS (
SELECT result
FROM #temp
WHERE ISNUMERIC(result) = 1)
SELECT
result,
ISNUMERIC(result)
FROM isnum
WHERE TRY_CONVERT(INT, result) > 1

Milion Record Sql Order By and count rows

I have a database with millions of records.
The table is structured as:
Table name : record
Filed1: Name (varchar)(Primary key)
Field2: Record(int/bigint)
example:
Name | Record
Darrin | 256
Aaron | 3
Daryl | 12
...
I need to know what position does the user with the name 'namex' in sorted records.
Currently i implement this solution:
...
$name=namex;
$query= mysqli_query($mysqli,"SELECT Name FROM record ORDER BY Record DESC");
$x=0;
$rank=0;
if ($query->num_rows > 0) {
// output data of each row
while($row = $query->fetch_assoc()) {
if($row["Name"]==$name){
$rank=$x+1;
echo "Rank : $rank<br>";
break;
}
$x++;
}
}
...
With it and 1 million records in the database, the answer comes in about 4 second.
I tried to put a table index on the field Record but have remained the same performance.
how can I reduce the execution times?
Since I don't know what DBMS you are using for (in tags you are using both mysql and sql-server...), you can you create a view (for SQL server have to be a indexed view) or for mysql implement/emulate a kind of materialized view (has a better performance). The view is good to get a better perfomance through some DBMS. For MySQL may have no difference.
In the view show up the rank position as the query below (mysql example):
CREATE VIEW ranked_record AS
SELECT
record.Name,
#curRank := #curRank + 1 AS rank
FROM
record,
(SELECT #curRank := 0) r
ORDER BY Record DESC;
or SQL server:
CREATE VIEW ranked_record AS
SELECT
record.Name,
row_number() over(ORDER BY record)
FROM
record;
And just run your query:
SELECT name , rank FROM ranked_record WHERE name LIKE 'some name'
Update:
After John comments, I've realized about the error from views using variables. It isn't possible due the "feature/bug" of/from MySQL
Due this, you can choose about use this as subquery in FROM clause:
SELECT
name,
rank
FROM (
SELECT
record.Name,
#curRank := #curRank + 1 AS rank
FROM
record,
(SELECT #curRank := 0) r
) AS ranked_record
WHERE
name LIKE 'some name';
OR create a function to count the rank inside the view (like this example):
CREATE FUNCTION `func_inc_var_session`() RETURNS int(11)
begin
SET #var := IFNULL(#var,0) + 1;
return #var;
end;
Then, create your view as before, just using the function instead of the variable:
CREATE VIEW ranked_record AS
SELECT
record.Name,
func_inc_var_session() as rank
FROM
record
ORDER BY Record DESC;
We can write this in sql query instead of fetching the records and looping it in PHP.
select row = row_number() over( order by Record ) , * from record where name like 'namex'
I dont really understand why are you looping every record knowing that the column "Name" its your PK (unique values).
$name="Darrin";
$query= mysqli_query($db_connection, "SELECT COUNT(1) as rank FROM record WHERE Name = '".mysqli_real_escape_string($name)."' ORDER BY Record DESC");
$row = mysqli_fetch_row($query);
if ($query->num_rows > 0) {
echo "Rank : $row["rank"]<br>";
}

merging SQL statements and how can it affect processing time

Let's assume I have the following tables:
items table
item_id|view_count
item_views table
view_id|item_id|ip_address|last_view
What I would like to do is:
If last view of item with given item_id by given ip_address was 1+ hour ago I would like to increment view_count of item in items table. And as a result get the view count of item. How I will do it normally:
q = SELECT count(*) FROM item_views WHERE item_id='item_id' AND ip_address='some_ip' AND last_view < current_time-60*60
if(q==1) then q = UPDATE items SET view_count = view_count+1 WHERE item_id='item_id'
//and finally get view_count of item
q = SELECT view_count FROM items WHERE item_id='item_id'
Here I used 3 SQL queries. How can I merge it into one SQL query? And how can it affect the processing time? Will it be faster or slower than previous method?
I don't think your logic is correct for what you describe that you want. The query:
SELECT count(*)
FROM item_views
WHERE item_id='item_id' AND
ip_address='some_ip' AND
last_view < current_time-60*60
is counting the number of views longer ago than your time frame. I think you want:
last_view > current_time-60*60
and then have if q = 0 on the next line.
MySQL is pretty good with the performance of not exists, so the following should work well:
update items
set view_count = view_count+1
WHERE item_id='item_id' and
not exists (select 1
from item_views
where item_id='item_id' AND
ip_address='some_ip' AND
last_view > current_time-60*60
)
It will work much better with an index on item_views(item_id, ip_address, last_view) and an index on item(item_id).
In MySQL scripting, you could then write:
. . .
set view_count = (#q := view_count+1)
. . .
This would also give you the variable you are looking for.
update target
set target.view_count = target.view_count + 1
from items target
inner join (
select item_id
from item_views
where item_id = 'item_id'
and ip_address = 'some_ip'
and last_view < current_time - 60*60
) ref
on ref.item_id = target.item_id;
You can only combine the update statement with the condition using a join as in the above example; but you'll still need a separate select statement.
It may be slower on very large set and/or unindexed table.

MySQL get interval records

Are there any solutions on MySQL script to filter the results with specific interval number.
For example, if I have 100,000 records in database and I'd like to get only the record number 1000, 2000, 3000, etc. (step by 1000).
I could do this on server side script by getting the entire results (e.g. 100,000) and use syntax like:
for($i=0, $i <= 100,000, $i = $i+1000) $filterResult[] = $record[$i];
However, as you may see, it would pull stress to the system as 100,000 records will need to generated first.
Are there any solutions that could complete from database script? Please note that, primary key may not start with 1 - 100,000 as the results based on some condition in where clause.
Your help would be really appreciated.
You can do:
SELECT *
FROM tbl
WHERE id % 1000 = 0
But it seems like you don't want to rely on the primary key value, but rather the row ranking of a result set.
In that case, you can do:
SELECT *
FROM (
SELECT *, #rn:=#rn+1 AS rank
FROM tbl
CROSS JOIN (SELECT #rn:=0) var_init
WHERE column1 = value AND
column2 = value
) a
WHERE a.rank % 1000 = 0
Where column1 = value AND column2 = value is just a placeholder for whatever filtration you're doing in your query.