Creating a self-reflexive cross join - mysql

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.

Related

Using the result of an outer query in a subquery or similar

I have a devices table that contains a device_id and an account_id.
Every time a new device is created in the device table, a new table with the name 'd_' . $device_id is created.
The idea was to store the flood of data created by a device in a separate table. To prevent an excessive growth of a single table.
This design scheme is now blowing up in my face.
devices table
id, device_id, account_id
d_$device_id table (The table is composed of d_ and the device_id from the devices table!)
timestamp, value1, value2, value3
Now I would like to generate the following query with this schema to retrieve all devices belonging to an account, but additionally also the newest line from the 'd_' . $device_id table.
My approach is as follows. The problem is that the table of the subquery must be variable, for clarification: 'd_' . $s1.device_id
SELECT * FROM `devices` s1, (SELECT * FROM `d_` . $s1.device_id ORDER BY timestamp DESC LIMIT 1) s2 WHERE s1.account_id = 2;
I'm afraid my design idea doesn't work that way, but maybe someone here has a solution for that.
You can query for the table then continue if it returns a value
DECLARE #deviceTable nvarchar(50)
DECLARE #query nvarchar(max);
SELECT #deviceTable = [TABLE_NAME]
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''+$s1.device_id+''
IF #deviceTable IS NOT NULL OR LEN(#deviceTable) > 0
BEGIN
SET #query = 'SELECT * FROM [DEVICES] s1, (SELECT * FROM '+#deviceTable+'+ ORDER BY timestamp DESC LIMIT 1) s2 WHERE s1.account_id = 2;'
EXEC sp_executesql #query
END
EDIT:
PREPARE query FROM 'SELECT * FROM [DEVICES] s1, (SELECT * FROM ? ORDER BY timestamp DESC LIMIT 1) s2 WHERE s1.account_id = 2;';
Set #deviceTable = SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = ''+#somedeviceidhere+''
EXECUTE query using #deviceTable;
DEALLOCATE PREPARE query;

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

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.

How to use query string inside IN statement in MySQL stored procedure

friends I have a stored procedure. which taking an input. But the input is a Query string. When I'm executing that string in IN statement I'm not getting anything.
My Stored Procedure is:
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255)
IN location VARCHAR(255),
IN classification VARCHAR(255))
BEGIN
SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(SELECT DISTINCT serviceprovider_cl AS serviceProviderId FROM db.serviceprovider_classification t1
INNER JOIN
db.locationid t2 ON t1.serviceprovider_cl=t2.serviceprovider_locationId
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_location
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t1.serviceproviderclassification_classification IN (classification)
AND
t2.location_serviceLocation IN (location)
AND
t3.category_serviceProviderCategory IN (category)
);
END
In category, classification and location. I'm getting another query in String. So to execute that string or How to convert it into query or how to use string as Query?
Thanks
for this you can use something called Prepared Statements, you can find more about that here...
So here is an SQL Fiddle where you can see how prepared statement works...
As you can see in this simple stored procedure it is not complicated that much. Basically there is three step to do this.
First create string which will be used in prepared statement. You do this to connect your query and query you will get as a string (IN category VARCHAR(255)) into one statement.
In my Fiddle:
SET #myString =
CONCAT('SELECT * FROM t2 WHERE t1_id IN (', category, ')');
That is the hardest part. Than you should perapare statement from that string
PREPARE statement FROM #myString;
End than execute the statement
EXECUTE statement;
When you call your procedure you pass your string which will be part of statement:
CALL SimpleProcedure('SELECT id FROM t1 WHERE val1 = "myVal2"');
And that's the logic you should apply on your problem.
That should look like this:
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255))
BEGIN
SET #myString =
CONCAT('SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(', category,
' INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t3.category_serviceProviderCategory IN (', category, '))');
PREPARE statement FROM #myString;
EXECUTE statement;
END
EDIT: note that between ' and INNER JOIN there is one blank space because CONCAT, without that, would connect last word from 'category' query and inner join and that will cause you problem and your query wont work!
GL!
P.S. Also i notice that you mix both syntax when JOIN table (old comma separated JOIN and the new way) which is not look nice, it would be good to correct that and use new INNER JOIN syntax like you do in your sub query...
New EDIT (based on question edit)
CREATE DEFINER=`root`#`localhost` PROCEDURE `SampleProcedure`(IN category VARCHAR(255)
IN location VARCHAR(255),
IN classification VARCHAR(255))
BEGIN
SET #myString =
CONCAT('SELECT u1.firstname , u1.lastname, u1.avatar , s1.address ,c1.cityName
FROM user u1,serviceprovider s1, city c1
WHERE s1.userId=u1.id
AND c1.cityId=s1.city
AND s1.serviceProviderId
IN
(SELECT DISTINCT serviceprovider_cl AS serviceProviderId FROM db.serviceprovider_classification t1
INNER JOIN
db.locationid t2 ON t1.serviceprovider_cl=t2.serviceprovider_locationId
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_location
INNER JOIN
db.serviceprovider_category t3 ON t2.serviceprovider_locationId=t3.serviceprovider_category
WHERE
t1.serviceproviderclassification_classification IN (', classification, ')
AND
t2.location_serviceLocation IN (', location, ')
AND
t3.category_serviceProviderCategory IN (', category, '))');
PREPARE statement FROM #myString;
EXECUTE statement;
END
Subquery in IN statement is equal to JOIN with subquery's table. Why you need "exotic" syntax instead of simple join.

if condition inside a sql query

following is a part of my stored proceedure im using it to extract data from my db.
query
BEGIN
SET #sqlstring = CONCAT("SELECT b.ID, c.name, c.accountID,, b.total_logs, a.time_start, a.time_end ,COUNT(a.id) AS number_of_users
FROM ",logtable," a INNER JOIN users b on a.ID = b.ID INNER JOIN accounts c on b.accountID = c.accountID
GROUP BY ID;");
PREPARE stmt FROM #sqlstring;
EXECUTE stmt;
END
At times in the db, the logtable(table is passed in a variable like logtable_1, logtable_2 .... ) can be non existent, currently when the perticuler table is missing it crashes and throws an error because a.time_start, a.time_end cannot have values without the log table.
but what i want is just to assign NULL on values a.time_start, a.time_end without throwing an error,
So can any body tell is there a way i could modify this code like
BEGIN
if logtable exists
\\ the query
else
\\ the query
END
Find existence of the table by querying information_schema.tables. If it returns a count equals to 1 then you can proceed executing your query on the table. Otherwise go with your Else block.
Sample:
declare table_exists int default 0;
select count(1) into table_exists
from information_schema.tables
where table_schema='your_table_schema_name'
and table_name = 'your_table_name';
if table_exists then
-- do something
else
-- do something else
end if;

List all stored procedures with schema name

Can anyone advise on a way to list all stored procedures along with their schema names in a database? Thanks!
SELECT [schema] = OBJECT_SCHEMA_NAME([object_id]),
name
FROM sys.procedures;
or
SELECT [schema] = SCHEMA_NAME([schema_id]),
name
FROM sys.procedures;
For a specific database, you can just change the context to that database first, or change Marc's query slightly (my queries are no good in this case because they rely on functions that are context-sensitive):
SELECT
SchemaName = s.name,
ProcedureName = pr.name
FROM
databasename.sys.procedures pr
INNER JOIN
databasename.sys.schemas s ON pr.schema_id = s.schema_id;
If you want to do this for all databases:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'
UNION ALL SELECT db = N''' + name + ''',
s.name COLLATE Latin1_General_CI_AI,
o.name COLLATE Latin1_General_CI_AI
FROM ' + QUOTENAME(name) + '.sys.procedures AS o
INNER JOIN ' + QUOTENAME(name) + '.sys.schemas AS s
ON o.[schema_id] = s.[schema_id]'
FROM sys.databases
-- WHERE ... -- probably don't need system databases at least
SELECT #sql = STUFF(#sql, 1, 18, '')
-- you may have to adjust ^^ 18 due to copy/paste, cr/lf, tabs etc
+ ' ORDER BY by db, s.name, o.name';
EXEC sp_executesql #sql;
The collate clauses are necessary in case you have databases with different collations.
Try this:
SELECT
SchemaName = s.name,
ProcedureName = pr.name
FROM
sys.procedures pr
INNER JOIN
sys.schemas s ON pr.schema_id = s.schema_id
This should list all stored procedures and their schema name as a result set.
Both views - sys.procedures and sys.schemas - have quite a few more attributes - check them out, if you need them, include them in your query.
If you want to search for procs with required schema name you can use this query:
SELECT
SchemaName = s.name,
ProcedureName = pr.name
FROM
sys.procedures pr
INNER JOIN
sys.schemas s
ON pr.schema_id = s.schema_id
WHERE s.name = 'YOUR_SCHEMA_NAME'
ORDER BY SchemaName;
this may help You..
SELECT * FROM sys.procedures;
You can use Script Generator to get them. In the left pan right click on the database for which you want to get Stored Procedures, Tasks->Generate Scripts Click Next and choose Select Specific Database Objects and select Stored Procedures and click on next, there you can customize as you need and generate the scripts.
Try this:
execute [sys].[sp_stored_procedures]
Or try this and also get all parameters:
execute [sys].[sp_sproc_columns]
Ok...you'll have to loop through all DB catalog names with this, but...
SELECT name,crdate FROM dbo.sysobjects WHERE (type = 'P') order by name
SELECT [schema] = OBJECT_SCHEMA_NAME([object_id]),name FROM sys.procedures;
select OBJECT_SCHEMA_NAME([object_id]) as 'SchemaName',name as 'SP Name ' ,
create_date,modify_date FROM sys.procedures order by OBJECT_SCHEMA_NAME([object_id]), name