PostgreSQL cannot call json_object_keys on a scalar - json

I have a PostgreSQL table with a JSON column, and I'm trying to get a list of all the distinct keys in that column. I created a query to do this:
SELECT DISTINCT json_object_keys(j) FROM t;
Where t is the table and j is the JSON column. This worked on a small set of data correctly, it would list all the keys that exist in j, without repeating them. However, after adding a lot more data, it doesn't work anymore, giving the error:
ERROR: cannot call json_object_keys on a scalar
I'm not sure exactly why this is happening. By just limiting the query to certain rows one at a time, I found one that causes the error. In that row, j is null. However, calling SELECT json_object_keys(null); does not cause this error, while calling SELECT json_object_keys(j) FROM t WHERE id=12; does, and j in this row is just null. I'm not really sure where to go from here.
So I guess my question is what could be causing this, and how can I either work around it or prevent it from happening?
Running PostgreSQL 9.3.9
Edit: Ok, I may have posted this a little preemptively. I figured out that j in the problematic row isn't null, it's 'null'::json, which wasn't clear from just selecting the row. This does cause the scalar error, so now I just have to figure out a way to select the rows where j isn't 'null'::json.

I tried this query, to filter out the 'null'::json values with this query:
SELECT DISTINCT json_object_keys(j) from t WHERE j <> 'null'::json;
However, apparently there is no json <> json operator, so I had to cast it to text and compare.
SELECT DISTINCT json_object_keys(j) from t WHERE j::TEXT <> 'null';
This works! I'm not a Postgres expert though, so this may not be the most effecient way of doing this check.

Related

MySQL Stored Procedure with Parameters for Recursive CTE

I'm working on a MySQL Way of printing an "affiliate tree" and got the thing working with Common Table Expression. I'm using the following code right now:
WITH RECURSIVE recUsers AS
(
SELECT ID, username, sponsorID, 1 AS depth, username AS path
FROM users
WHERE id = 1
UNION ALL
SELECT c.ID, c.username, c.sponsorID, sc.depth + 1, CONCAT(sc.path, ' > ', c.username)
FROM recUsers AS sc
JOIN users AS c ON sc.ID = c.sponsorID
)
SELECT * FROM recUsers;
This selects the tree underneath the user with the id 1.
Now what I'd need to get is a way to pass that id as a parameter, so I don't need to define everything from the beginning every time I want to get the result.. So my idea is to put everything in a stored prodecure and pass the id in as a parameter.. However, so far I didn't get it working and always getting various errors that are very self speaking...
Basically what I've tried was
DELIMITER //
CREATE PROCEDURE getAffiliateTree(IN userid INT())
BEGIN
---my code here, the userid 1 replaced with userid
END//
DELIMITER;
However, this doesn't seem to work.. How can I get this done?
Two things I would suggest:
Use INT, not INT(). The optional length argument to integer types is deprecated in MySQL 8.0 (which I know you're using, because you're using CTE syntax). Even if you did use the length argument, using an empty argument is not legal syntax.
Make sure that the userid input parameter name is distinct from all of the columns in the tables you reference. That is, if the table has a column named userid (any capitalization), then change the name of your input parameter. Otherwise you may make ambiguous expressions like:
... WHERE userid = userid
Even though you intend one of these to be the column and the other to be the parameter, the SQL parser has no way of knowing that. It ends up treating both as the column name, so it's trivially true on all rows of the table.
Actually, a third thing I would suggest: when you ask questions, "it doesn't seem to work" isn't clear enough. Did it produce an error? If so, what was the full error message? Did it produce no error, but didn't give you the result you wanted? If so, show a mocked-up example of what you expected, and what the query produced that didn't match. It helps to be as clear as you can when you post questions, so readers don't have to guess what trouble you need help with.

Statement in trigger is not "picking up" the condition in its Where clause

so I'm currently working on a MySQL trigger. I'm trying to assign values to two variables when a new record is inserted. Below are the queries:
SET mssgDocNo = (SELECT Document_ID FROM CORE_MSSG WHERE Message_ID = new.MSSG_ID);
SET mssgRegime = (SELECT CONCAT (Regime_Type, Regime_Code) FROM T_DOC WHERE CD_Message_ID = new.MSSG_ID);;
For some reason, the second SQL query is not picking up the 'new.MSSG_ID' condition while the first query in same trigger recognizes it. I really can't figure out what seems to be the problem.
When I replace the 'new.MSSG_ID' with a hard-coded value from the database in the second query it seems to work. I doubt the 'new.MSSG_ID' is the problem because it works perfectly fine in the first query.
I've tried pretty much anything I could think of. Would appreciate the help.
I would write these more simply as:
SELECT mssgDocNo := Document_ID
FROM CORE_MSSG
WHERE Message_ID = new.MSSG_ID;
SELECT mssgRegime := CONCAT(Regime_Type, Regime_Code)
FROM T_DOC
WHERE CD_Message_ID = new.MSSG_ID;
The SET is not necessary.
I did make one other change that might make it work. I removed the space after CONCAT. Some versions of MySQL have trouble parsing spaces after function calls.

SQLAlchemy db.text().bindparams() without clobbering

I just discovered that if you use the same name in bindparams twice in the same query, the second value clobbers the first one. For a contrived example:
db.session.query(MyTable).filter(
db.or_(
db.text("my_table.field = :value").bindparams(value=value1),
db.text("my_table.field = :value").bindparams(value=value2),
)
)
Here you would only get things with value2. value1 would not appear in the query.
Is there a general purpose way to fix this?
Btw, db.text() bits in my real query access nested jsonb properties, so please don't answer telling me to just use the column objects in this query in place of db.text().

Why is my query wrong?

before i use alias for table i get the error:
: Integrity constraint violation: 1052 Column 'id' in field list is ambiguous
Then i used aliases and i get this error:
unknown index a
I am trying to get a list of category name ( dependant to a translation) and the associated category id which is unique. Since i need to put them in a select, i see that i should use the lists.
$categorie= DB::table('cat as a')
->join('campo_cat as c','c.id_cat','=','a.id')
->join('campo as d','d.id','=','c.id_campo')
->join('cat_nome as nome','nome.id_cat','=','a.id')
->join('lingua','nome.id_lingua','=','lingua.id')
->where('lingua.lingua','=','it-IT')
->groupby('nome.nome')
->lists('nome.nome','a.id');
The best way to debug your query is to look at the raw query Laravel generates and trying to run this raw query in your favorite SQL tool (Navicat, MySQL cli tool...), so you can dump it to log using:
DB::listen(function($sql, $bindings, $time) {
Log::info($sql);
Log::info($bindings);
});
Doing that with yours I could see at least one problem:
->where('lingua.lingua','=','it-IT')
Must be changed to
->where('lingua.lingua','=',"'it-IT'")
As #jmail said, you didn't really describe the problem very well, just what you ended up doing to get around (part of) it. However, if I read your question right you're saying that originally you did it without all the aliases you got the 'ambiguous' error.
So let me explain that first: this would happen, because there are many parts of that query that use id rather than a qualified table`.`id.
if you think about it, without aliases you query looks a bit like this: SELECT * FROM `cat` JOIN `campo_cat` ON `id_cat` = `id` JOIN `campo` ON `id` = `id_campo`; and suddenly, MySQL doesn't know to which table all these id columns refer. So to get around that all you need to do is namespace your fields (i.e. use ... JOIN `campo` ON `campo`.`id` = `campo_cat`.`id_campo`...). In your case you've gone one step further and aliased your tables. This certianly makes the query a little simpler, though you don't need to actually do it.
So on to your next issue - this will be a Laravel error. And presumably happening because your key column from lists($valueColumn, $keyColumn) isn't found in the results. This is because you're referring to the cat.id column (okay in your aliased case a.id) in part of the code that's no longer in MySQL - the lists() method is actually run in PHP after Laravel gets the results from the database. As such, there's no such column called a.id. It's likely it'll be called id, but because you don't request it specifically, you may find that the ambiguous issue is back. My suggestion would be to select it specifically and alias the column. Try something like the below:
$categories = DB::table('cat as a')
->join('campo_cat as c','c.id_cat','=','a.id')
->join('campo as d','d.id','=','c.id_campo')
->join('cat_nome as nome','nome.id_cat','=','a.id')
->join('lingua','nome.id_lingua','=','lingua.id')
->where('lingua.lingua','=','it-IT')
->groupby('nome.nome')
->select('nome.nome as nome_nome','a.id as a_id') // here we alias `.id as a_id
->lists('nome_nome','a_id'); // here we refer to the actual columns
It may not work perfectly (I don't use ->select() so don't know whether you pass an array or multiple parameters, also you may need DB::raw() wrapping each one in order to do the aliasing) but hopefully you get my meaning and can get it working.

nested "select " query in mysql

hi i am executing nested "select" query in mysql .
the query is
SELECT `btitle` FROM `backlog` WHERE `bid` in (SELECT `abacklog_id` FROM `asprint` WHERE `aid`=184 )
I am not getting expected answer by the above query. If I execute:
SELECT abacklog_id FROM asprint WHERE aid=184
separately
I will get abacklog_id as 42,43,44,45;
So if again I execute:
SELECT `btitle` FROM `backlog` WHERE `bid` in(42,43,44,45)
I will get btitle as scrum1 scrum2 scrum3 msoffice
But if I combine those queries I will get only scrum1 remaining 3 atitle will not get.
You Can Try As Like Following...
SELECT `age_backlog`.`ab_title` FROM `age_backlog` LEFT JOIN `age_sprint` ON `age_backlog`.`ab_id` = `age_sprint`.`as_backlog_id` WHERE `age_sprint`.`as_id` = 184
By using this query you will get result with loop . You will be able to get all result with same by place with comma separated by using IMPLODE function ..
May it will be helpful for you... If you get any error , Please inform me...
What you did is to store comma separated values in age_sprint.as_backlog_id, right?
Your query actually becomes
SELECT `ab_title` FROM `age_backlog` WHERE `ab_id` IN ('42,43,44,45')
Note the ' in the IN() function. You don't get separate numbers, you get one string.
Now, when you do
SELECT CAST('42,43,44,45' AS SIGNED)
which basically is the implicit cast MySQL does, the result is 42. That's why you just get scrum1 as result.
You can search for dozens of answers to this problem here on SO.
You should never ever store comma separated values in a database. It violates the first normal form. In most cases databases are in third normal form or BCNF or even higher. Lower normal forms are just used in some special cases to get the most performance, usually for reporting issues. Not for actually working with data. You want 1 row for every as_backlog_id.
Again, your primary goal should be to get a better database design, not to write some crazy functions to get each comma separated number out of the field.