Fetch all table name and row count for specific table with Rails? - mysql

How can i fetch all the table name and row count for the specific table from the specific database ?
Result
Table Name , Row Count , Table Size(MB)
---------------------------------------
table_1 , 10 , 2.45
table_2 , 20 , 4.00

ActiveRecord::Base.connection.tables.each do |table|
h = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
puts "#{h['Name']} has #{h['Rows']} rows with size: #{h['Data_length']}"
end

The question is tagged mysql but you can do it in a DB-agnostic manner via ORM.
class DatabaseReport
def entry_counts
table_model_names.map do |model_name|
entity = model_name.constantize rescue nil
next if entity.nil?
{ entity.to_s => entity.count }
end.compact
end
private
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize)
end
end
Note that this will skip tables for which you don't have an object mapping such as meta tables like ar_internal_metadata or schema_migrations. It also cannot infer scoped models (but could be extended to do so). E.g. with Delayed::Job I do this:
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize) + ["Delayed::Job"]
end

I came up with my own version which is also db agnostic.
As it uses the descendants directly it also handles any tables where the table_name is different to the model name.
The rescue nil exists for cases when you have the class that inherits from ActiveRecord but for some reason don't have a table associated with it. It does give data for STI classes and the parent class.
my_models = ActiveRecord::Base.descendants
results = my_models.inject({}) do |result, model|
result[model.name] = model.count rescue nil
result
end

#temp_table = []
ActiveRecord::Base.connection.tables.each do |table|
count = ActiveRecord::Base.connection.execute("SELECT COUNT(*) as count FROM #{table}").fetch_hash['count']
size = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
#temp_table << {:table_name => table,
:records => count.to_i,
:size_of_table => ((BigDecimal(size['Data_length']) + BigDecimal(size['Index_length']))/1024/1024).round(2)
}
end
end

Related

Yii2 mysql how to insert a record into a table where column value already exists and should be unique?

I have a table, say 'mytable' that use a "rank" column that is unique. After having created some record where rank is successively rec A(rank=0), rec B (rank=1), rec C (rank=2), rec D (rank=3), rec E (rank=4).
I need to insert a new record that will take an existing rank, say 1, and modify the rank value of the following records accordingly.
The result being : rec A(rank=0), new rec (rank=1), rec B (rank=2), rec C (rank=3), rec D (rank=4), rec E (rank=5).
How can I do this ? Can this be solved with mysql only or should I write some important bunch of code in PHP (Yii2) ?
Assuming no rank is skipped you need to shift existing ranks before saving the new record. To do that you can use beforeSave() method of your ActiveRecord like this:
class MyModel extends \yii\db\ActiveRecord
{
public function beforeSave($insert)
{
if (!parent::beforeSave($insert)) {
return false;
}
if ($insert) { //only if we are saving new record
{
$query = self::find()
->where(['rank' => $this->rank]);
if ($query->exists()) { //check if the rank is already present in DB
//we will create the query directly because yii2
// doesn't support order by for update
$command = static::getDb()->createCommand(
"UPDATE " . static::tableName() .
" SET rank = rank + 1 WHERE rank >= :rank ORDER BY rank DESC",
[':rank' => $this->rank]
);
$command->execute();
}
}
return true;
}
// ... other content of your model ...
}
MySQL allows use of ORDER BY in UPDATE query, that will help us deal with fact that doing UPDATE on table is not atomic and the UNIQUE constraint is checked after each row is updated.
It would be more problematic if there are skipped ranks. In that case you will need to shift ranks only until you hit first skipped rank.
Another option might be creating an before insert trigger on the table that would do the rank shifting.
Note:
It might be a good idea to also implement afterDelete method to shift the ranks in oposite direction when some record is removed to avoid skipped ranks.
Resources:
\yii\db\BaseActiveRecord::beforeSave()
\yii\db\ActiveRecord::updateAllCounters() - replaced with direct update
MySQL triggers
MySQL UPDATE syntax

Ordering a queryset by occurrences

I have a django model:
class Field:
choice = models.CharField(choices=choices)
value = models.CharField(max_length=255)
In my database I have some cases where there are 3 "fields" with the same choice, and some cases where there is 1 field of that choice
How can I order the queryset so it returns, sorted by choice, but with all ones in a set of 3 at the start?
For example
[1,1,1,3,3,3,4,4,4,2,5] where 1,2,3,4,5 are possible choices?
This is the best I can do using django's ORM. Basically, just like in SQL, you have to construct a custom order_by statement. In our case, we'll place it in the SELECT and then order by it:
1) Get a list of choices sorted by frequency: [1, 3, 4, 2, 5]
freq_list = (
Field.objects.values_list('choice', flat=True)
.annotate(c=Count('id')).order_by('-c', 'choice')
)
2) Add indexes with enumerate: [(0,1), (1,3), (2,4), (3,2), (4,5)]
enum_list = list(enumerate(freq_list))
3) Create a list of cases: ['CASE', 'WHEN choice=1 THEN 0', ..., 'END']
case_list = ['CASE']
case_list += ["WHEN choice={1} THEN {0}".format(*tup) for tup in enum_list]
case_list += ['END']
4) Combine the case list into one string: 'CASE WHEN choice=1 THEN 0 ...'
case_statement = ' '.join(case_list)
5) Finally, use the case statement to select an extra field 'o' which will be corresponding order, then just order by this field
Field.objects.extra(select={'o': case_statement}).order_by('o')
To simplify all this, you can put the above code into a Model Manager:
class FieldManager(models.Manager):
def get_query_set(self):
freq_list = (
Field.objects.values_list('choice', flat=True)
.annotate(c=Count('id')).order_by('-c', 'choice')
)
enum_list = list(enumerate(freq_list))
case_list = ['CASE']
case_list += ["WHEN choice={1} THEN {0}".format(*tup) for tup in enum_list]
case_list += ['END']
case_statement = ' '.join(case_list)
ordered = Field.objects.extra(select={'o': case_statement}).order_by('o')
return ordered
class Field(models.Model):
...
freq_sorted = FieldManager()
Now you can query:
Field.freq_sorted.all()
Which will get you a Field QuerySet sorted by frequency of choices
You should make a function and detect which is repeated to select unique, then calling from mysql as a function over mysql

Convert this code into active record/sql query

I have the following code and would like to convert the request into a mysql query. Right now I achieve the desired result using a manual .select (array method) on the data. This should be possibile with a single query (correct me if I am wrong).
Current code:
def self.active_companies(zip_code = nil)
if !zip_code
query = Company.locatable.not_deleted
else
query = Company.locatable.not_deleted.where("zip_code = ?", zip_code)
end
query.select do |company|
company.company_active?
end
end
# Check if the company can be considered as active
def company_active?(min_orders = 5, last_order_days_ago = 15)
if orders.count >= min_orders &&
orders.last.created_at >= last_order_days_ago.days.ago &&
active
return true
else
return false
end
end
Explanation:
I want to find out which companies are active. We have a company model and an orders model.
Data:
Company:
active
orders (associated orders)
Orders:
created_at
I don't know if it is possible to make the company_active? predicate a single SQL query, but I can offer an alternative:
If you do:
query = Company.locatable.not_deleted.includes(:orders)
All of the relevant orders will be loaded into the memory for future processing.
This will eliminate all the queries except for 2:
One to get the companies, and one to get all their associated orders.

How to access the mysql table field schema description column?

How to obtain or query the description column of the table schema?
Currently:
si_table_name = params[:rid]
#si_field_names = Array.new
si_cols = ActiveRecord::Base.connection.columns(si_table_name, "#{name} Columns")
si_cols.each do |c|
#si_field_names << "#{c.name}:#{c.type}" <---------------
end
Goal: (this example doesn't work... looking for the correct way to query this)
si_table_name = params[:rid]
#si_field_names = Array.new
si_cols = ActiveRecord::Base.connection.columns(si_table_name, "#{name} Columns")
si_cols.each do |c|
#si_field_names << "#{c.name}:#{c.type}:#{c.description}" <---------------
end
Not sure what you mean by 'description'.
In any case, the table metadata can be queried using the information schema.
See
http://dev.mysql.com/doc/refman/5.6/en/columns-table.html
and in particular the table information_schema.columns, column COLUMN_COMMENT.

Rails 3. Checking for true values in SQL

I need to check if the column exam has a value of true. So I set this up but it doesn't work...
#exam_shipments = Shipment.where("exam <> NULL AND exam <> 0 AND customer_id = ?", current_admin_user.customer_id)
# This one gives me error "SQLite3::SQLException: no such column: true:"
#exam_shipments = Shipment.where("exam = true AND customer_id = ?", current_admin_user.customer_id)
#exam_shipments = Shipment.where("exam = 1 AND customer_id = ?", current_admin_user.customer_id)
You should really just stick to AR syntax:
#exam_shipments = Shipment.where(:exam => true, :customer_id => current_admin_user.customer_id)
Assuming :exam is a boolean field on your Shipment model. ActiveRecord takes care of converting your query to the proper syntax for the given database. So the less inline SQL you write, the more database-agnostic and portable your code will be.
Why do you need do execute SQL?
It's much easier just to do
#exam_shipments = Shipment.find_by_id(current_admin_user.customer_id).exam?