MySQL: How do you concat a column name with AS? - mysql

I am trying to get it so that I can use a variable in the column name but it doesn't seem to be working.
select
v.name as concat(#person,'\'s Vendor')
,sum(t.amount) as 'Total'
from
total t
inner join vendor v
on v.d = t.d
where
t.who = #person
and v.name is not null
and t.sub_cat not like 'Payment'
and year(date) = (year(sysdate())-1)
and amount > 0
group by
v.name
order by
sum(amount)
desc limit 20;
The above throws an error but below works.
select
v.name as 'John\'s Vendor'
,sum(t.amount) as 'Total'
from
total t
inner join vendor v
on v.d = t.d
where
t.who = #person
and v.name is not null
and t.sub_cat not like 'Payment'
and year(date) = (year(sysdate())-1)
and amount > 0
group by
v.name
order by
sum(amount)
desc limit 20;
Basically, I want to be able to add the person variable and some more text as the column name to better identify the purpose of the data, in this case, who the data is about. Also, I would like to use this as a stored procedure:
create procedure GetTotals(IN person varchar(10));
delimiter //
begin
above working code
end //
delimiter ;

You can use dynamic sql for such queires
SET #sql = CONCAT("select
v.name as '",#person,
"\\'s Vendor'
,sum(t.amount) as 'Total'
from
total t
inner join vendor v
on v.d = t.d
where
t.who = #person
and v.name is not null
and t.sub_cat not like 'Payment'
and year(date) = (year(sysdate())-1)
and amount > 0
group by
v.name
order by
sum(amount)
desc limit 20;");
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Keep in mind thsi can be used for sql injection, so a white list for the name is in order

Why not just add the parameter as its own column with a fixed column name. Your return process will be responsible for presenting and can just use that extra column as the "who's" it for purposes. Ex:
select
v.name,
sum(t.amount) as 'Total',
concat(#person,'\'s Vendor') as ForWho
from
Yes, every row will return a result of ex: "John's Vendor" in the column ForWho, but at least you wont have to format that in the return set, you have the column.

Identifiers, including aliases, must be fixed at the time the query is parsed. You can't make an alias that depends on an expression.
You could make a dynamic SQL query by formatting an SQL statement using your #person variable:
SET #sql = CONCAT(
'select v.name as `', concat(#person,'\'s Vendor'), '`,',
...
' where t.who = ? ',
...
);
PREPARE stmt FROM #sql;
EXECUTE stmt USING #person;
DEALLOCATE PREPARE stmt;
The value of #person can be set as the parameter where the ? placeholder appears in the prepared query syntax. But the usage in the column alias cannot be parameterized. So you just have to be careful that the value of #person is safe to use in that position.
You should delimit the alias with back-ticks like any other identifier, to protect from syntax errors if #person contains whitespace or punctuation characters.

Related

iterating over arrays in MYSQL

Is there a way to pass an array into a MySQL query and return the results as another array ?(apart from using cursors which would be an overkill for my use case)
For a single id, my query looks like this.
SET #userId = '04b452cd59dcc656'
Select user_account_number from userstore where u_id = #userId ;
Instead of sending each id at a time, I am trying to send a list and return a list
SET #userId = ('04b452cd59dcc656','eqwe52cddasfsd656');
<query returning the list of account numbers>
Also - I think this would be efficient over just sending one id at a time. Thoughts ?
You can use IN:
select user_account_number
from userstore
where u_id in ('04b452cd59dcc656', 'eqwe52cddasfsd656') ;
Using variables is trickier. If you know a maximum number, you can do:
select user_account_number
from userstore
where u_id in (#id1, #id2);
Not satisfying, but it does the job. Similarly unsatisfying is FIND_IN_SET():
set #ids = '04b452cd59dcc656,eqwe52cddasfsd656';
select user_account_number
from userstore
where find_in_set(u_id, #ids) > 0;
Alas, this won't use an index.
Finally there is dynamic SQL:
set #sql = concat('select user_account_number from userstore where u_id in (''',
replace(ids, ',', ''','''),
''')'
);
prepare s from #sql;
execute s;

MySQL UDF/Stored function not recognizing table name passed as an argument

The following is the stored function:
DELIMITER //
CREATE FUNCTION `calcMedian`(
`tbl` VARCHAR(64),
`clm` VARCHAR(64)
) RETURNS decimal(14,4)
BEGIN
SELECT AVG(middle_values) AS 'median'
INTO medRslt
FROM (
SELECT t1.clm AS 'middle_values'
FROM
(
SELECT #row:=#row+1 as `row`, table_column_name
FROM tbl, (SELECT #row:=0) AS r
ORDER BY clm
) AS t1,
(
SELECT COUNT(*) as 'count'
FROM tbl
) AS t2
WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;
RETURN medRslt;
END//
DELIMITER ;
I then proceed to execute the following query:
USE ap2;
SELECT vendor_id, calcMedian('invoices', 'invoice_total')
FROM invoices i
WHERE vendor_id = 97
GROUP BY vendor_id;
I get the error message:
SQL Error (1146): Table 'ap2.tbl' doesn't exist *
I understand that the following may be better off as stored procedure/prepared statement rather than function. I just want to take things one step at a time now.
Also I made a different function to simply output the value stored in the variable 'tbl', and it displayed the correct table name (invoices in this case).
Identifiers in a SQL statement cannot be provided as values. Identifiers (table names, column names, function names, etc.) must be specified in the SQL text.
To get the value of the tbl variable (procedure argument) used as a table name within a SQL statement in the procedure, you can use dynamic SQL.
Set a variable to the SQL text, incorporate the string value, and then execute the string as a SQL statement. As an example:
SET #sql = CONCAT( 'SELECT AVG(middle_values) AS `median`'
, ' INTO medRslt'
, ' ... '
, tbl
, ' ... '
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Be aware that incorporating string values into the SQL text makes the procedure subject to SQL Injection vulnerabilities.
If I had to do this, I would reduce the potential for SQL Injection by verifying that tbl does not contain a backtick character, and enclose/escape the identifier in backticks, e.g.
CONCAT( ' ...' , '`' , tbl , '`' , ' ... ' );
^^^ ^^^

Mysql with parameters not working

I have a mysql stored procedure like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `accounts_summary_by`(
IN **created_by** int(10)
)
BEGIN
SET group_concat_max_len=2048;
SET #sql = NULL;
SELECT
GROUP_CONCAT(
DISTINCT CONCAT(
'MAX(IF(fiscal_year = ''',
fiscal_year,
''', amount, 0)) AS ',
CONCAT("'",fiscal_year,"'")
)
) INTO #sql
FROM
net_savings;
SET #sql = CONCAT('SELECT accounts.id,
accounts.account,
accounts.region,
accounts.cluster,
accounts.industry, ,'
,#sql,
'FROM net_savings join accounts
on accounts.id = net_savings.account_id
Where accounts.user_id = **created_by**
GROUP BY accounts.account,net_savings.account_id
ORDER BY accounts.id');
PREPARE statement FROM #sql;
EXECUTE statement;
DEALLOCATE PREPARE statement;
END
But upon calling the procedure like these:
CALL accounts_summary_by(2)
2 is a user_id reference to another table called users.
It gave me an error. Please help as I can't find any fixed to my problem.
0 72 23:41:12 CALL `buckets`.`accounts_summary_by`(1) Error Code: 1054. Unknown column 'created_by' in 'where clause' 0.000 sec
MySQL's internal programming language is not php, it is not going to resolve variables within a text, so you need to concat it properly to the middle of the prepared statement:
SET #sql = CONCAT('SELECT accounts.id,
accounts.account,
accounts.region,
accounts.cluster,
accounts.industry,'
,#sql,
'FROM net_savings join accounts
on accounts.id = net_savings.account_id
Where accounts.user_id ='
,created_by
,'GROUP BY accounts.account,net_savings.account_id
ORDER BY accounts.id');
Since created_by is a parameter of the procedure, you do not need to preposition it with #.

Can you use an mysql #variable in WHERE IN (SELECT #var)?

Can you use an mysql #variable in WHERE IN clause ?
I tried this:
SET #inlist =
(
SELECT GROUP_CONCAT(product_id) FROM products WHERE `status` = "new"
);
SELECT #inlist; -- shows comma separated values;
SELECT * from products where product_id IN (SELECT #inlist);
will only return the first product in the list.
I know there is a default 1k limit on GROUP_CONCAT.
You can do it with dynamic SQL:
PREPARE stmt FROM CONCAT('SELECT * from products where product_id IN (', #inlist, ')');
EXECUTE stmt;
If you need to do this frequently, you could put it in a stored procedure, and then just do:
CALL yourProc(#inlist);
try this with FIND_IN_SET
SELECT * from products where FIND_IN_SET(product_id , #inlist );

Creating a self-reflexive cross join

Hi I'm looking to see if there is alternative syntax for the following self-reflexive cross-join. The objective is a sort of row-filler for a table - dates should have entries for every cdn. I am using MySQL
select
d.labelDate,
n.cdn,
networks.sites
from (
select
distinct labelDate
from
cdn_trend
) as d
cross join (
select
distinct cdn
from cdn_trend
) as n
left join cdn_trend as networks
on networks.labelDate = d.labelDate
and networks.cdn = n.cdn
order by
labelDate,
cdn
I've tried recasting the cross-join using simple aliases but that gives me column errors in the join. Is it possible to do so or should I consider using views instead?
As a cross join should simply return the Cartesian product of two tables it should be the same as simply selecting both without a join. However, the following raises an "unknown column d.labelDate in on clause" exception
select distinct d.labelDate, n.cdn, networks.sites
from
cdn_trend as d,
cdn_trend as n
left join cdn_trend as networks ON
(n.labelDate = networks.labelDate
and d.cdn = networks.cdn)
order by labelDate, cdn
Error Code: 1054. Unknown column 'd.cdn' in 'on clause'
Because the length of dand n are relatively small the size of the query is fast enough.
I think you were close in your original intent... For every date, you want the results of every network node status. If you list multiple tables in the WHERE clause without a join condition, it by default will create a Cartesian... From that, join to your detail table...
select
d.labelDate,
n.cdn,
networks.sites
from
( select d.LabelDate, n.cdn
from
( select distinct labelDate
from cdn_trend ) as d,
( select distinct cdn
from cdn_trend ) as n ) as CrossResults
LEFT JOIN cdn_trend as networks
on CrossResults.labelDate = networks.labelDate
and CrossResults.cdn = networks.cdn
order by
networks.labelDate,
networks.cdn
Reading the comments and the extra info you need a pivot with y - lableDate and x - cdn and values - sites assuming the values for cdn are (a,b,c) and that sites is a number you can try this:
SELECT
labelDate,
SUM(IF(cdn = 'a',sites,0)) as cdn_a,
SUM(IF(cdn = 'b',sites,0)) as cdn_b,
SUM(IF(cdn = 'c',sites,0)) as cdn_c
FROM
cdn_trend
GROUP BY
labelDate
And the output should be something like this (i used the sample data from you) :
labelDate cdn_a cdn_b cdn_c
2013-04 NULL 5 4
2013-05 6 NULL NULL
....
After some playing around this is the best I could come up with. It seems that parametrising the table name would be possible but would involve yet another layer of statement generation that I fortunately don't need for this project.
-- --------------------------------------------------------------------------------
-- Routine DDL
-- Note: comments before and after the routine body will not be stored by the server
-- --------------------------------------------------------------------------------
DELIMITER $$
CREATE DEFINER="root"#"localhost" PROCEDURE "cdn_pivot"(
IN slice varchar(64),
IN start date,
IN stop date)
BEGIN
SET ##group_concat_max_len = 32000;
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
' sum(IF(cdn = ''',
cdn,
''', sites,NULL)) "'
,cdn, '"'
)
) INTO #sql
FROM cdns ORDER BY sites;
SET #stmt = CONCAT('SELECT labelDate, ',
#sql,
' from cdns
WHERE slice = ''',
slice,
''' AND ( labelDate between''',
start,
''' AND ''',
stop,
'''
)
GROUP BY labelDate');
prepare stmt from #stmt;
execute stmt;
deallocate prepare stmt;
SET ##group_concat_max_len = 1024;
END
This can then simply be called e.,g.
call cdn_pivot('Top100', '2013-01-01', 2013-02-01')
Given the problems associated with testing this code and keeping it with any client-side code it's very tempting to generate the dynamic part of the head on the client and, at least for this kind of use case, the performance penalty of the additional query shouldn't be too high. The key thing is obviously understanding how to generate the columns dynamically.